Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(277)

Side by Side Diff: content/browser/download/mhtml_generation_browsertest.cc

Issue 2540483002: Allow Offline MHTML save operations to succeed even with missing frames.
Patch Set: Address code review comments. Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <stdint.h> 5 #include <stdint.h>
6 #include <memory> 6 #include <memory>
7 7
8 #include "base/bind.h" 8 #include "base/bind.h"
9 #include "base/callback.h" 9 #include "base/callback.h"
10 #include "base/files/file_path.h" 10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h" 11 #include "base/files/file_util.h"
12 #include "base/files/scoped_temp_dir.h" 12 #include "base/files/scoped_temp_dir.h"
13 #include "base/macros.h" 13 #include "base/macros.h"
14 #include "base/run_loop.h" 14 #include "base/run_loop.h"
15 #include "base/strings/utf_string_conversions.h" 15 #include "base/strings/utf_string_conversions.h"
16 #include "base/test/histogram_tester.h" 16 #include "base/test/histogram_tester.h"
17 #include "base/threading/thread_restrictions.h" 17 #include "base/threading/thread_restrictions.h"
18 #include "content/browser/frame_host/frame_tree_node.h"
18 #include "content/browser/renderer_host/render_process_host_impl.h" 19 #include "content/browser/renderer_host/render_process_host_impl.h"
20 #include "content/browser/web_contents/web_contents_impl.h"
19 #include "content/common/frame_messages.h" 21 #include "content/common/frame_messages.h"
20 #include "content/public/browser/render_process_host.h" 22 #include "content/public/browser/render_process_host.h"
21 #include "content/public/browser/web_contents.h"
22 #include "content/public/common/mhtml_generation_params.h" 23 #include "content/public/common/mhtml_generation_params.h"
23 #include "content/public/test/browser_test_utils.h" 24 #include "content/public/test/browser_test_utils.h"
24 #include "content/public/test/content_browser_test.h" 25 #include "content/public/test/content_browser_test.h"
25 #include "content/public/test/content_browser_test_utils.h" 26 #include "content/public/test/content_browser_test_utils.h"
26 #include "content/public/test/test_utils.h" 27 #include "content/public/test/test_utils.h"
27 #include "content/shell/browser/shell.h" 28 #include "content/shell/browser/shell.h"
28 #include "net/base/filename_util.h" 29 #include "net/base/filename_util.h"
29 #include "net/dns/mock_host_resolver.h" 30 #include "net/dns/mock_host_resolver.h"
30 #include "net/test/embedded_test_server/embedded_test_server.h" 31 #include "net/test/embedded_test_server/embedded_test_server.h"
31 #include "testing/gmock/include/gmock/gmock.h" 32 #include "testing/gmock/include/gmock/gmock.h"
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
100 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 101 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
101 ASSERT_TRUE(embedded_test_server()->Start()); 102 ASSERT_TRUE(embedded_test_server()->Start());
102 ContentBrowserTest::SetUp(); 103 ContentBrowserTest::SetUp();
103 } 104 }
104 105
105 void GenerateMHTML(const base::FilePath& path, const GURL& url) { 106 void GenerateMHTML(const base::FilePath& path, const GURL& url) {
106 GenerateMHTML(MHTMLGenerationParams(path), url); 107 GenerateMHTML(MHTMLGenerationParams(path), url);
107 } 108 }
108 109
109 void GenerateMHTML(const MHTMLGenerationParams& params, const GURL& url) { 110 void GenerateMHTML(const MHTMLGenerationParams& params, const GURL& url) {
110 NavigateToURL(shell(), url); 111 ASSERT_TRUE(NavigateToURL(shell(), url));
111 GenerateMHTMLForCurrentPage(params); 112 GenerateMHTMLForCurrentPage(params);
112 } 113 }
113 114
114 void GenerateMHTMLForCurrentPage(const MHTMLGenerationParams& params) { 115 void GenerateMHTMLForCurrentPage(const MHTMLGenerationParams& params) {
115 base::RunLoop run_loop; 116 base::RunLoop run_loop;
116 histogram_tester_.reset(new base::HistogramTester()); 117 histogram_tester_.reset(new base::HistogramTester());
117 118
118 shell()->web_contents()->GenerateMHTML( 119 shell()->web_contents()->GenerateMHTML(
119 params, base::Bind(&MHTMLGenerationTest::MHTMLGenerated, 120 params, base::Bind(&MHTMLGenerationTest::MHTMLGenerated,
120 base::Unretained(this), 121 base::Unretained(this),
(...skipping 16 matching lines...) Expand all
137 const GURL& url, 138 const GURL& url,
138 const MHTMLGenerationParams params, 139 const MHTMLGenerationParams params,
139 int expected_number_of_frames, 140 int expected_number_of_frames,
140 const std::vector<std::string>& expected_substrings, 141 const std::vector<std::string>& expected_substrings,
141 const std::vector<std::string>& forbidden_substrings_in_saved_page, 142 const std::vector<std::string>& forbidden_substrings_in_saved_page,
142 bool skip_verification_of_original_page = false) { 143 bool skip_verification_of_original_page = false) {
143 // Navigate to the test page and verify if test expectations 144 // Navigate to the test page and verify if test expectations
144 // are met (this is mostly a sanity check - a failure to meet 145 // are met (this is mostly a sanity check - a failure to meet
145 // expectations would probably mean that there is a test bug 146 // expectations would probably mean that there is a test bug
146 // (i.e. that we got called with wrong expected_foo argument). 147 // (i.e. that we got called with wrong expected_foo argument).
147 NavigateToURL(shell(), url); 148 ASSERT_TRUE(NavigateToURL(shell(), url));
148 if (!skip_verification_of_original_page) { 149 if (!skip_verification_of_original_page) {
149 AssertExpectationsAboutCurrentTab(expected_number_of_frames, 150 AssertExpectationsAboutCurrentTab(expected_number_of_frames,
150 expected_substrings, 151 expected_substrings,
151 std::vector<std::string>()); 152 std::vector<std::string>());
152 } 153 }
153 154
154 GenerateMHTML(params, url); 155 GenerateMHTML(params, url);
155 ASSERT_FALSE(HasFailure()); 156 ASSERT_FALSE(HasFailure());
156 157
157 // Stop the test server (to make sure the locally saved page 158 // Stop the test server (to make sure the locally saved page
158 // is self-contained / won't try to open original resources). 159 // is self-contained / won't try to open original resources).
159 ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete()); 160 ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
160 161
161 // Open the saved page and verify if test expectations are 162 // Open the saved page and verify if test expectations are
162 // met (i.e. if the same expectations are met for "after" 163 // met (i.e. if the same expectations are met for "after"
163 // [saved version of the page] as for the "before" 164 // [saved version of the page] as for the "before"
164 // [the original version of the page]. 165 // [the original version of the page].
165 NavigateToURL(shell(), net::FilePathToFileURL(params.file_path)); 166 ASSERT_TRUE(
167 NavigateToURL(shell(), net::FilePathToFileURL(params.file_path)));
166 AssertExpectationsAboutCurrentTab(expected_number_of_frames, 168 AssertExpectationsAboutCurrentTab(expected_number_of_frames,
167 expected_substrings, 169 expected_substrings,
168 forbidden_substrings_in_saved_page); 170 forbidden_substrings_in_saved_page);
169 } 171 }
170 172
171 void AssertExpectationsAboutCurrentTab( 173 void AssertExpectationsAboutCurrentTab(
172 int expected_number_of_frames, 174 int expected_number_of_frames,
173 const std::vector<std::string>& expected_substrings, 175 const std::vector<std::string>& expected_substrings,
174 const std::vector<std::string>& forbidden_substrings) { 176 const std::vector<std::string>& forbidden_substrings) {
175 int actual_number_of_frames = 177 int actual_number_of_frames =
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
215 217
216 // Tests that generating a MHTML does create contents. 218 // Tests that generating a MHTML does create contents.
217 // Note that the actual content of the file is not tested, the purpose of this 219 // Note that the actual content of the file is not tested, the purpose of this
218 // test is to ensure we were successful in creating the MHTML data from the 220 // test is to ensure we were successful in creating the MHTML data from the
219 // renderer. 221 // renderer.
220 IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTML) { 222 IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTML) {
221 base::FilePath path(temp_dir_.GetPath()); 223 base::FilePath path(temp_dir_.GetPath());
222 path = path.Append(FILE_PATH_LITERAL("test.mht")); 224 path = path.Append(FILE_PATH_LITERAL("test.mht"));
223 225
224 GenerateMHTML(path, embedded_test_server()->GetURL("/simple_page.html")); 226 GenerateMHTML(path, embedded_test_server()->GetURL("/simple_page.html"));
227
228 // Checks that no other test failure was detected so far.
225 ASSERT_FALSE(HasFailure()); 229 ASSERT_FALSE(HasFailure());
226 230
227 // Make sure the actual generated file has some contents. 231 // Make sure the actual generated file has some contents.
228 EXPECT_GT(file_size(), 0); // Verify the size reported by the callback. 232 EXPECT_GT(file_size(), 0); // Verify the size reported by the callback.
229 EXPECT_GT(ReadFileSizeFromDisk(path), 100); // Verify the actual file size. 233 EXPECT_GT(ReadFileSizeFromDisk(path), 100); // Verify the actual file size.
230 234
231 { 235 {
232 base::ThreadRestrictions::ScopedAllowIO allow_io_for_content_verification; 236 base::ThreadRestrictions::ScopedAllowIO allow_io_for_content_verification;
233 std::string mhtml; 237 std::string mhtml;
234 ASSERT_TRUE(base::ReadFileToString(path, &mhtml)); 238 ASSERT_TRUE(base::ReadFileToString(path, &mhtml));
235 EXPECT_THAT(mhtml, 239 EXPECT_THAT(mhtml,
236 HasSubstr("Content-Transfer-Encoding: quoted-printable")); 240 HasSubstr("Content-Transfer-Encoding: quoted-printable"));
237 } 241 }
238 242
239 // Checks that the final status reported to UMA is correct. 243 // Checks that the final status reported to UMA is correct.
240 histogram_tester()->ExpectUniqueSample( 244 histogram_tester()->ExpectUniqueSample(
241 "PageSerialization.MhtmlGeneration.FinalSaveStatus", 245 "PageSerialization.MhtmlGeneration.FinalSaveStatus",
242 static_cast<int>(MhtmlSaveStatus::SUCCESS), 1); 246 static_cast<int>(MhtmlSaveStatus::SUCCESS), 1);
243 } 247 }
244 248
249 // Removes all children of the root tree node once the first MHTML serialization
250 // response (regarding the main frame) is received from the renderer but before
251 // that message is actually handled by the browser UI thread.
252 class ChildFrameRemoverFilter : public BrowserMessageFilter {
253 public:
254 ChildFrameRemoverFilter(WebContentsImpl* web_contents)
255 : BrowserMessageFilter(FrameMsgStart), web_contents_(web_contents) {
256 DCHECK(web_contents_);
257 }
258
259 protected:
260 ~ChildFrameRemoverFilter() override {}
261
262 private:
263 bool OnMessageReceived(const IPC::Message& message) override {
264 DCHECK_CURRENTLY_ON(BrowserThread::IO);
265 if (message.type() == FrameHostMsg_SerializeAsMHTMLResponse::ID) {
266 if (!already_received_response_) {
267 already_received_response_ = true;
268 BrowserThread::PostTask(
269 BrowserThread::UI, FROM_HERE,
270 base::Bind(&ChildFrameRemoverFilter::RemoveAllChildFrames,
271 base::Unretained(this)));
272 } else {
273 ADD_FAILURE()
274 << "Should not receive another MHTML serialization response";
275 }
276 }
277 return false;
278 }
279
280 void RemoveAllChildFrames() {
281 DCHECK_CURRENTLY_ON(BrowserThread::UI);
282 int tree_node_count = 1;
283 for (auto tree_node : web_contents_->GetFrameTree()->Nodes())
284 tree_node_count += tree_node->child_count();
285 // Note: at least 2 children nodes are needed to test all code paths.
286 ASSERT_GE(tree_node_count, 3);
287 FrameTreeNode* root = web_contents_->GetFrameTree()->root();
288 while (root->child_count())
289 root->RemoveChild(root->child_at(0));
290 ASSERT_EQ(0u, root->child_count());
291 }
292
293 WebContentsImpl* web_contents_;
294 bool already_received_response_ = false;
295
296 DISALLOW_COPY_AND_ASSIGN(ChildFrameRemoverFilter);
297 };
298
299 // Tests that if child frames are removed while saving a MHTML page the
300 // operation fails by default.
301 IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, FailDueToMissingIframe) {
302 ASSERT_TRUE(NavigateToURL(
303 shell(), embedded_test_server()->GetURL("/site_per_process_main.html")));
304
305 scoped_refptr<BrowserMessageFilter> filter = new ChildFrameRemoverFilter(
306 static_cast<WebContentsImpl*>(shell()->web_contents()));
307 shell()->web_contents()->GetRenderProcessHost()->AddFilter(filter.get());
308
309 base::FilePath path(temp_dir_.GetPath());
310 path = path.Append(FILE_PATH_LITERAL("test.mht"));
311
312 MHTMLGenerationParams params(path);
313
314 // Verifies the default is to not ignore missing iframes.
315 ASSERT_FALSE(params.ignore_missing_frames);
316
317 GenerateMHTMLForCurrentPage(params);
318
319 // Checks that no other test failure was detected so far.
320 ASSERT_FALSE(HasFailure());
321
322 // Verify the size reported by the callback is negative (an error happened).
323 EXPECT_LT(file_size(), 0);
324
325 // Checks that the final status reported to UMA is correct.
326 histogram_tester()->ExpectUniqueSample(
327 "PageSerialization.MhtmlGeneration.FinalSaveStatus",
328 static_cast<int>(MhtmlSaveStatus::FRAME_NO_LONGER_EXISTS), 1);
329 }
330
331 // Tests that a MHTML save operation does not fail when frames are removed if
332 // the caller configures it to ignore missing frames.
333 IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, SucceedIgnoringMissingIframe) {
334 ASSERT_TRUE(NavigateToURL(
335 shell(), embedded_test_server()->GetURL("/site_per_process_main.html")));
336
337 scoped_refptr<BrowserMessageFilter> filter = new ChildFrameRemoverFilter(
338 static_cast<WebContentsImpl*>(shell()->web_contents()));
339 shell()->web_contents()->GetRenderProcessHost()->AddFilter(filter.get());
340
341 base::FilePath path(temp_dir_.GetPath());
342 path = path.Append(FILE_PATH_LITERAL("test.mht"));
343
344 MHTMLGenerationParams params(path);
345
346 // Enables the ignoring of missing iframes.
347 params.ignore_missing_frames = true;
348
349 GenerateMHTMLForCurrentPage(params);
350
351 // Checks that no other test failure was detected so far.
352 ASSERT_FALSE(HasFailure());
353
354 // Verify the size reported by the callback is positive.
355 EXPECT_GT(file_size(), 0);
356
357 // Checks that the final status reported to UMA is correct.
358 histogram_tester()->ExpectUniqueSample(
359 "PageSerialization.MhtmlGeneration.FinalSaveStatus",
360 static_cast<int>(MhtmlSaveStatus::SUCCESS), 1);
361 }
362
245 class GenerateMHTMLAndExitRendererMessageFilter : public BrowserMessageFilter { 363 class GenerateMHTMLAndExitRendererMessageFilter : public BrowserMessageFilter {
246 public: 364 public:
247 GenerateMHTMLAndExitRendererMessageFilter( 365 GenerateMHTMLAndExitRendererMessageFilter(
248 RenderProcessHostImpl* render_process_host) 366 RenderProcessHostImpl* render_process_host)
249 : BrowserMessageFilter(FrameMsgStart), 367 : BrowserMessageFilter(FrameMsgStart),
250 render_process_host_(render_process_host) {} 368 render_process_host_(render_process_host) {}
251 369
252 protected: 370 protected:
253 ~GenerateMHTMLAndExitRendererMessageFilter() override {} 371 ~GenerateMHTMLAndExitRendererMessageFilter() override {}
254 372
255 private: 373 private:
256 bool OnMessageReceived(const IPC::Message& message) override { 374 bool OnMessageReceived(const IPC::Message& message) override {
375 DCHECK_CURRENTLY_ON(BrowserThread::IO);
257 if (message.type() == FrameHostMsg_SerializeAsMHTMLResponse::ID) { 376 if (message.type() == FrameHostMsg_SerializeAsMHTMLResponse::ID) {
258 // After |return false| below, this IPC message will be handled by the 377 // After |return false| below, this IPC message will be handled by the
259 // product code as illustrated below. (1), (2), (3) depict points in time 378 // product code as illustrated below. (1), (2), (3) depict points in time
260 // when product code runs on UI and FILE threads. (X), (Y), (Z) depict 379 // when product code runs on UI and FILE threads. (X), (Y), (Z) depict
261 // when we want test-injected tasks to run - for the repro, (Z) has to 380 // when we want test-injected tasks to run - for the repro, (Z) has to
262 // happen between (1) and (3). (Y?) and (Z?) depict when test tasks can 381 // happen between (1) and (3). (Y?) and (Z?) depict when test tasks can
263 // theoretically happen and ruin the repro. 382 // theoretically happen and ruin the repro.
264 // 383 //
265 // IO thread UI thread FILE thread 384 // IO thread UI thread FILE thread
266 // --------- --------- ----------- 385 // --------- --------- -----------
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
310 BrowserThread::PostTask( 429 BrowserThread::PostTask(
311 BrowserThread::UI, FROM_HERE, base::Bind( 430 BrowserThread::UI, FROM_HERE, base::Bind(
312 &GenerateMHTMLAndExitRendererMessageFilter::TaskX, 431 &GenerateMHTMLAndExitRendererMessageFilter::TaskX,
313 base::Unretained(this))); 432 base::Unretained(this)));
314 } 433 }
315 434
316 return false; 435 return false;
317 }; 436 };
318 437
319 void TaskX() { 438 void TaskX() {
439 DCHECK_CURRENTLY_ON(BrowserThread::UI);
320 BrowserThread::PostTask( 440 BrowserThread::PostTask(
321 BrowserThread::FILE, FROM_HERE, base::Bind( 441 BrowserThread::FILE, FROM_HERE, base::Bind(
322 &GenerateMHTMLAndExitRendererMessageFilter::TaskY, 442 &GenerateMHTMLAndExitRendererMessageFilter::TaskY,
323 base::Unretained(this))); 443 base::Unretained(this)));
324 } 444 }
325 445
326 void TaskY() { 446 void TaskY() {
447 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
327 BrowserThread::PostTask( 448 BrowserThread::PostTask(
328 BrowserThread::UI, FROM_HERE, base::Bind( 449 BrowserThread::UI, FROM_HERE, base::Bind(
329 &GenerateMHTMLAndExitRendererMessageFilter::TaskZ, 450 &GenerateMHTMLAndExitRendererMessageFilter::TaskZ,
330 base::Unretained(this))); 451 base::Unretained(this)));
331 } 452 }
332 453
333 void TaskZ() { 454 void TaskZ() {
455 DCHECK_CURRENTLY_ON(BrowserThread::UI);
334 render_process_host_->FastShutdownIfPossible(); 456 render_process_host_->FastShutdownIfPossible();
335 } 457 }
336 458
337 RenderProcessHostImpl* render_process_host_; 459 RenderProcessHostImpl* render_process_host_;
338 460
339 DISALLOW_COPY_AND_ASSIGN(GenerateMHTMLAndExitRendererMessageFilter); 461 DISALLOW_COPY_AND_ASSIGN(GenerateMHTMLAndExitRendererMessageFilter);
340 }; 462 };
341 463
342 // Regression test for the crash/race from https://crbug.com/612098. 464 // Regression test for the crash/race from https://crbug.com/612098.
343 IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTMLAndExitRenderer) { 465 IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTMLAndExitRenderer) {
344 NavigateToURL(shell(), embedded_test_server()->GetURL("/simple_page.html")); 466 ASSERT_TRUE(NavigateToURL(
467 shell(), embedded_test_server()->GetURL("/simple_page.html")));
345 468
346 RenderProcessHostImpl* render_process_host = 469 RenderProcessHostImpl* render_process_host =
347 static_cast<RenderProcessHostImpl*>( 470 static_cast<RenderProcessHostImpl*>(
348 shell()->web_contents()->GetRenderProcessHost()); 471 shell()->web_contents()->GetRenderProcessHost());
349 scoped_refptr<BrowserMessageFilter> filter = 472 scoped_refptr<BrowserMessageFilter> filter =
350 new GenerateMHTMLAndExitRendererMessageFilter(render_process_host); 473 new GenerateMHTMLAndExitRendererMessageFilter(render_process_host);
351 render_process_host->AddFilter(filter.get()); 474 render_process_host->AddFilter(filter.get());
352 475
353 base::FilePath path(temp_dir_.GetPath()); 476 base::FilePath path(temp_dir_.GetPath());
354 path = path.Append(FILE_PATH_LITERAL("test.mht")); 477 path = path.Append(FILE_PATH_LITERAL("test.mht"));
(...skipping 289 matching lines...) Expand 10 before | Expand all | Expand 10 after
644 767
645 // Make sure that URLs of both frames are present 768 // Make sure that URLs of both frames are present
646 // (note that these are single-line regexes). 769 // (note that these are single-line regexes).
647 EXPECT_THAT( 770 EXPECT_THAT(
648 mhtml, 771 mhtml,
649 ContainsRegex("Content-Location:.*/frame_tree/page_with_one_frame.html")); 772 ContainsRegex("Content-Location:.*/frame_tree/page_with_one_frame.html"));
650 EXPECT_THAT(mhtml, ContainsRegex("Content-Location:.*/title1.html")); 773 EXPECT_THAT(mhtml, ContainsRegex("Content-Location:.*/title1.html"));
651 } 774 }
652 775
653 } // namespace content 776 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698