1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/bind.h"
6 #include "base/files/scoped_temp_dir.h"
7 #include "base/run_loop.h"
8 #include "base/threading/sequenced_task_runner_handle.h"
9 #include "content/browser/download/download_manager_impl.h"
10 #include "content/browser/download/save_package.h"
11 #include "content/public/browser/browser_context.h"
12 #include "content/public/browser/download_manager.h"
13 #include "content/public/browser/web_contents.h"
14 #include "content/public/test/content_browser_test.h"
15 #include "content/public/test/content_browser_test_utils.h"
16 #include "content/public/test/download_test_observer.h"
17 #include "content/shell/browser/shell.h"
18 #include "content/shell/browser/shell_download_manager_delegate.h"
19 #include "net/test/embedded_test_server/embedded_test_server.h"
20 
21 namespace content {
22 
23 namespace {
24 
25 const char kTestFile[] = "/simple_page.html";
26 
27 class TestShellDownloadManagerDelegate : public ShellDownloadManagerDelegate {
28  public:
TestShellDownloadManagerDelegate(SavePageType save_page_type)29   explicit TestShellDownloadManagerDelegate(SavePageType save_page_type)
30       : save_page_type_(save_page_type) {}
31 
ChooseSavePath(WebContents * web_contents,const base::FilePath & suggested_path,const base::FilePath::StringType & default_extension,bool can_save_as_complete,SavePackagePathPickedCallback callback)32   void ChooseSavePath(WebContents* web_contents,
33                       const base::FilePath& suggested_path,
34                       const base::FilePath::StringType& default_extension,
35                       bool can_save_as_complete,
36                       SavePackagePathPickedCallback callback) override {
37     std::move(callback).Run(suggested_path, save_page_type_,
38                             SavePackageDownloadCreatedCallback());
39   }
40 
GetSaveDir(BrowserContext * context,base::FilePath * website_save_dir,base::FilePath * download_save_dir)41   void GetSaveDir(BrowserContext* context,
42                   base::FilePath* website_save_dir,
43                   base::FilePath* download_save_dir) override {
44     *website_save_dir = download_dir_;
45     *download_save_dir = download_dir_;
46   }
47 
ShouldCompleteDownload(download::DownloadItem * download,base::OnceClosure closure)48   bool ShouldCompleteDownload(download::DownloadItem* download,
49                               base::OnceClosure closure) override {
50     return true;
51   }
52 
53   base::FilePath download_dir_;
54   SavePageType save_page_type_;
55 };
56 
57 class DownloadicidalObserver : public DownloadManager::Observer {
58  public:
DownloadicidalObserver(bool remove_download,base::OnceClosure after_closure)59   DownloadicidalObserver(bool remove_download, base::OnceClosure after_closure)
60       : remove_download_(remove_download),
61         after_closure_(std::move(after_closure)) {}
OnDownloadCreated(DownloadManager * manager,download::DownloadItem * item)62   void OnDownloadCreated(DownloadManager* manager,
63                          download::DownloadItem* item) override {
64     base::SequencedTaskRunnerHandle::Get()->PostTask(
65         FROM_HERE, base::BindOnce(
66                        [](bool remove_download, base::OnceClosure closure,
67                           download::DownloadItem* item) {
68                          remove_download ? item->Remove() : item->Cancel(true);
69                          std::move(closure).Run();
70                        },
71                        remove_download_, std::move(after_closure_), item));
72   }
73 
74  private:
75   bool remove_download_;
76   base::OnceClosure after_closure_;
77 };
78 
79 class DownloadCancelObserver : public DownloadManager::Observer {
80  public:
DownloadCancelObserver(base::OnceClosure canceled_closure,std::string * mime_type_out)81   explicit DownloadCancelObserver(base::OnceClosure canceled_closure,
82                                   std::string* mime_type_out)
83       : canceled_closure_(std::move(canceled_closure)),
84         mime_type_out_(mime_type_out) {}
85   DownloadCancelObserver(const DownloadCancelObserver&) = delete;
86   DownloadCancelObserver& operator=(const DownloadCancelObserver&) = delete;
87 
OnDownloadCreated(DownloadManager * manager,download::DownloadItem * item)88   void OnDownloadCreated(DownloadManager* manager,
89                          download::DownloadItem* item) override {
90     *mime_type_out_ = item->GetMimeType();
91     DCHECK(!item_cancel_observer_);
92     item_cancel_observer_ = std::make_unique<DownloadItemCancelObserver>(
93         item, std::move(canceled_closure_));
94   }
95 
96  private:
97   class DownloadItemCancelObserver : public download::DownloadItem::Observer {
98    public:
DownloadItemCancelObserver(download::DownloadItem * item,base::OnceClosure canceled_closure)99     DownloadItemCancelObserver(download::DownloadItem* item,
100                                base::OnceClosure canceled_closure)
101         : item_(item), canceled_closure_(std::move(canceled_closure)) {
102       item_->AddObserver(this);
103     }
104     DownloadItemCancelObserver(const DownloadItemCancelObserver&) = delete;
105     DownloadItemCancelObserver& operator=(const DownloadItemCancelObserver&) =
106         delete;
107 
~DownloadItemCancelObserver()108     ~DownloadItemCancelObserver() override {
109       if (item_)
110         item_->RemoveObserver(this);
111     }
112 
113    private:
OnDownloadUpdated(download::DownloadItem * item)114     void OnDownloadUpdated(download::DownloadItem* item) override {
115       DCHECK_EQ(item_, item);
116       if (item_->GetState() == download::DownloadItem::CANCELLED)
117         std::move(canceled_closure_).Run();
118     }
119 
OnDownloadDestroyed(download::DownloadItem * item)120     void OnDownloadDestroyed(download::DownloadItem* item) override {
121       DCHECK_EQ(item_, item);
122       item_->RemoveObserver(this);
123       item_ = nullptr;
124     }
125 
126     download::DownloadItem* item_;
127     base::OnceClosure canceled_closure_;
128   };
129 
130   std::unique_ptr<DownloadItemCancelObserver> item_cancel_observer_;
131   base::OnceClosure canceled_closure_;
132   std::string* mime_type_out_;
133 };
134 
135 }  // namespace
136 
137 class SavePackageBrowserTest : public ContentBrowserTest {
138  protected:
SetUp()139   void SetUp() override {
140     ASSERT_TRUE(save_dir_.CreateUniqueTempDir());
141     ContentBrowserTest::SetUp();
142   }
143 
144   // Returns full paths of destination file and directory.
GetDestinationPaths(const std::string & prefix,base::FilePath * full_file_name,base::FilePath * dir)145   void GetDestinationPaths(const std::string& prefix,
146                            base::FilePath* full_file_name,
147                            base::FilePath* dir) {
148     *full_file_name = save_dir_.GetPath().AppendASCII(prefix + ".htm");
149     *dir = save_dir_.GetPath().AppendASCII(prefix + "_files");
150   }
151 
152   // Start a SavePackage download and then cancels it. If |remove_download| is
153   // true, the download item will be removed while page is being saved.
154   // Otherwise, the download item will be canceled.
RunAndCancelSavePackageDownload(SavePageType save_page_type,bool remove_download)155   void RunAndCancelSavePackageDownload(SavePageType save_page_type,
156                                        bool remove_download) {
157     ASSERT_TRUE(embedded_test_server()->Start());
158     GURL url = embedded_test_server()->GetURL("/page_with_iframe.html");
159     EXPECT_TRUE(NavigateToURL(shell(), url));
160     auto* download_manager =
161         static_cast<DownloadManagerImpl*>(BrowserContext::GetDownloadManager(
162             shell()->web_contents()->GetBrowserContext()));
163     auto delegate =
164         std::make_unique<TestShellDownloadManagerDelegate>(save_page_type);
165     delegate->download_dir_ = save_dir_.GetPath();
166     auto* old_delegate = download_manager->GetDelegate();
167     download_manager->SetDelegate(delegate.get());
168 
169     {
170       base::RunLoop run_loop;
171       DownloadicidalObserver download_item_killer(remove_download,
172                                                   run_loop.QuitClosure());
173       download_manager->AddObserver(&download_item_killer);
174 
175       scoped_refptr<SavePackage> save_package(
176           new SavePackage(shell()->web_contents()));
177       save_package->GetSaveInfo();
178       run_loop.Run();
179       download_manager->RemoveObserver(&download_item_killer);
180       EXPECT_TRUE(save_package->canceled());
181     }
182 
183     // Run a second download to completion so that any pending tasks will get
184     // flushed out. If the previous SavePackage operation didn't cleanup after
185     // itself, then there could be stray tasks that invoke the now defunct
186     // download item.
187     {
188       base::RunLoop run_loop;
189       SavePackageFinishedObserver finished_observer(download_manager,
190                                                     run_loop.QuitClosure());
191       shell()->web_contents()->OnSavePage();
192       run_loop.Run();
193     }
194     download_manager->SetDelegate(old_delegate);
195   }
196 
197   // Temporary directory we will save pages to.
198   base::ScopedTempDir save_dir_;
199 };
200 
201 // Create a SavePackage and delete it without calling Init.
202 // SavePackage dtor has various asserts/checks that should not fire.
IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest,ImplicitCancel)203 IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest, ImplicitCancel) {
204   ASSERT_TRUE(embedded_test_server()->Start());
205   GURL url = embedded_test_server()->GetURL(kTestFile);
206   EXPECT_TRUE(NavigateToURL(shell(), url));
207   base::FilePath full_file_name, dir;
208   GetDestinationPaths("a", &full_file_name, &dir);
209   scoped_refptr<SavePackage> save_package(new SavePackage(
210       shell()->web_contents(), SAVE_PAGE_TYPE_AS_ONLY_HTML, full_file_name,
211       dir));
212 }
213 
214 // Create a SavePackage, call Cancel, then delete it.
215 // SavePackage dtor has various asserts/checks that should not fire.
IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest,ExplicitCancel)216 IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest, ExplicitCancel) {
217   ASSERT_TRUE(embedded_test_server()->Start());
218   GURL url = embedded_test_server()->GetURL(kTestFile);
219   EXPECT_TRUE(NavigateToURL(shell(), url));
220   base::FilePath full_file_name, dir;
221   GetDestinationPaths("a", &full_file_name, &dir);
222   scoped_refptr<SavePackage> save_package(new SavePackage(
223       shell()->web_contents(), SAVE_PAGE_TYPE_AS_ONLY_HTML, full_file_name,
224       dir));
225   save_package->Cancel(true);
226 }
227 
IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest,DownloadItemDestroyed)228 IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest, DownloadItemDestroyed) {
229   RunAndCancelSavePackageDownload(SAVE_PAGE_TYPE_AS_COMPLETE_HTML, true);
230 }
231 
IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest,DownloadItemCanceled)232 IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest, DownloadItemCanceled) {
233   RunAndCancelSavePackageDownload(SAVE_PAGE_TYPE_AS_MHTML, false);
234 }
235 
236 // Currently, SavePageAsWebBundle feature is not implemented yet.
237 // WebContentsImpl::GenerateWebBundle() will call the passed callback with 0
238 // file size and WebBundlerError::kNotImplemented via WebBundler in the utility
239 // process which means it cancels all SavePageAsWebBundle requests. So this test
240 // checks that the request is successfully canceled.
241 // TODO(crbug.com/1040752): Implement WebBundler and update this test.
IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest,SaveAsWebBundleCanceled)242 IN_PROC_BROWSER_TEST_F(SavePackageBrowserTest, SaveAsWebBundleCanceled) {
243   ASSERT_TRUE(embedded_test_server()->Start());
244   GURL url = embedded_test_server()->GetURL("/page_with_iframe.html");
245   EXPECT_TRUE(NavigateToURL(shell(), url));
246   auto* download_manager =
247       static_cast<DownloadManagerImpl*>(BrowserContext::GetDownloadManager(
248           shell()->web_contents()->GetBrowserContext()));
249   auto delegate = std::make_unique<TestShellDownloadManagerDelegate>(
250       SAVE_PAGE_TYPE_AS_WEB_BUNDLE);
251   delegate->download_dir_ = save_dir_.GetPath();
252   auto* old_delegate = download_manager->GetDelegate();
253   download_manager->SetDelegate(delegate.get());
254 
255   {
256     base::RunLoop run_loop;
257     std::string mime_type;
258     DownloadCancelObserver observer(run_loop.QuitClosure(), &mime_type);
259     download_manager->AddObserver(&observer);
260     scoped_refptr<SavePackage> save_package(
261         new SavePackage(shell()->web_contents()));
262     save_package->GetSaveInfo();
263     run_loop.Run();
264     download_manager->RemoveObserver(&observer);
265     EXPECT_TRUE(save_package->canceled());
266     EXPECT_EQ("application/webbundle", mime_type);
267   }
268 
269   download_manager->SetDelegate(old_delegate);
270 }
271 
272 }  // namespace content
273