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