Index: content/browser/download/mhtml_generation_browsertest.cc |
diff --git a/content/browser/download/mhtml_generation_browsertest.cc b/content/browser/download/mhtml_generation_browsertest.cc |
index 0605115bccf62a4075a60d3dd2ceeeabe5cf8a0d..8a8052efd3d4cba81795b59e0ef338543d48779f 100644 |
--- a/content/browser/download/mhtml_generation_browsertest.cc |
+++ b/content/browser/download/mhtml_generation_browsertest.cc |
@@ -12,6 +12,9 @@ |
#include "base/macros.h" |
#include "base/run_loop.h" |
#include "base/strings/utf_string_conversions.h" |
+#include "content/browser/renderer_host/render_process_host_impl.h" |
+#include "content/common/frame_messages.h" |
+#include "content/public/browser/render_process_host.h" |
#include "content/public/browser/web_contents.h" |
#include "content/public/common/mhtml_generation_params.h" |
#include "content/public/test/browser_test_utils.h" |
@@ -102,7 +105,10 @@ class MHTMLGenerationTest : public ContentBrowserTest { |
void GenerateMHTML(const MHTMLGenerationParams& params, const GURL& url) { |
NavigateToURL(shell(), url); |
+ GenerateMHTMLForCurrentPage(params); |
+ } |
+ void GenerateMHTMLForCurrentPage(const MHTMLGenerationParams& params) { |
base::RunLoop run_loop; |
shell()->web_contents()->GenerateMHTML( |
@@ -220,6 +226,121 @@ IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTML) { |
HasSubstr("Content-Transfer-Encoding: quoted-printable")); |
} |
+class GenerateMHTMLAndExitRendererMessageFilter : public BrowserMessageFilter { |
+ public: |
+ GenerateMHTMLAndExitRendererMessageFilter( |
+ RenderProcessHostImpl* render_process_host) |
+ : BrowserMessageFilter(FrameMsgStart), |
+ render_process_host_(render_process_host) {} |
+ |
+ protected: |
+ ~GenerateMHTMLAndExitRendererMessageFilter() override {} |
+ |
+ private: |
+ bool OnMessageReceived(const IPC::Message& message) override { |
+ if (message.type() == FrameHostMsg_SerializeAsMHTMLResponse::ID) { |
+ // After |return false| below, this IPC message will be handled by the |
+ // product code as illustrated below. (1), (2), (3) depict points in time |
+ // when product code runs on UI and FILE threads. (X), (Y), (Z) depict |
+ // when we want test-injected tasks to run - for the repro, (Z) has to |
+ // happen between (1) and (3). (Y?) and (Z?) depict when test tasks can |
+ // theoretically happen and ruin the repro. |
+ // |
+ // IO thread UI thread FILE thread |
+ // --------- --------- ----------- |
+ // | | | |
+ // WE ARE HERE | | |
+ // | | | |
+ // after |return false| | | |
+ // +--------------->+ | |
+ // | | | |
+ // | (X) | |
+ // | | | |
+ // | | (Y?) |
+ // | (Z?) | |
+ // | | | |
+ // (1) | MHTMLGenerationManager | |
+ // | ::OnSerializeAsMHTMLResponse | |
+ // | +-------------------->+ |
+ // | | | |
+ // | | (Y) |
+ // | | | |
+ // (2) | | MHTMLGenerationManager::Job |
+ // | | ::CloseFileOnFileThread |
+ // | | | |
+ // | (Z) | |
+ // | test needs to inject | |
+ // | fast renderer shutdown | |
+ // | HERE - between (1) and (3) | |
+ // | | | |
+ // | | | |
+ // | +<--------------------+ |
+ // | | | |
+ // (3) | MHTMLGenerationManager | |
+ // | ::OnFileClosed | |
+ // | | | |
+ // |
+ // We hope that (Z) happens between (1) and (3) by doing the following: |
+ // - From here post TaskX to UI thread. (X) is guaranteed to happen |
+ // before timepoint (1) (because posting of (1) happens after |
+ // |return false| / before we post TaskX below). |
+ // - From (X) post TaskY to FILE thread. Because this posting is done |
+ // before (1), we can guarantee that (Y) will happen before (2). |
+ // - From (Y) post TaskZ to UI thread. Because this posting is done |
+ // before (2), we can guarantee that (Z) will happen before (3). |
+ // - We cannot really guarantee that (Y) and (Z) happen *after* (1) - i.e. |
+ // execution at (Y?) and (Z?) instead is possible. In practice, |
+ // bouncing off of UI and FILE thread does mean (Z) happens after (1). |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, base::Bind( |
+ &GenerateMHTMLAndExitRendererMessageFilter::TaskX, |
+ base::Unretained(this))); |
+ } |
+ |
+ return false; |
+ }; |
+ |
+ void TaskX() { |
+ BrowserThread::PostTask( |
+ BrowserThread::FILE, FROM_HERE, base::Bind( |
+ &GenerateMHTMLAndExitRendererMessageFilter::TaskY, |
+ base::Unretained(this))); |
+ } |
+ |
+ void TaskY() { |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, base::Bind( |
+ &GenerateMHTMLAndExitRendererMessageFilter::TaskZ, |
+ base::Unretained(this))); |
+ } |
+ |
+ void TaskZ() { |
+ render_process_host_->FastShutdownIfPossible(); |
+ } |
+ |
+ RenderProcessHostImpl* render_process_host_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(GenerateMHTMLAndExitRendererMessageFilter); |
+}; |
+ |
+// Regression test for the crash/race from https://crbug.com/612098. |
+IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTMLAndExitRenderer) { |
+ NavigateToURL(shell(), embedded_test_server()->GetURL("/simple_page.html")); |
+ |
+ RenderProcessHostImpl* render_process_host = |
+ static_cast<RenderProcessHostImpl*>( |
+ shell()->web_contents()->GetRenderProcessHost()); |
+ scoped_refptr<BrowserMessageFilter> filter = |
+ new GenerateMHTMLAndExitRendererMessageFilter(render_process_host); |
+ render_process_host->AddFilter(filter.get()); |
+ |
+ base::FilePath path(temp_dir_.path()); |
+ path = path.Append(FILE_PATH_LITERAL("test.mht")); |
+ GenerateMHTMLForCurrentPage(MHTMLGenerationParams(path)); |
+ |
+ EXPECT_GT(ReadFileSizeFromDisk(path), 100); // Verify the actual file size. |
+} |
+ |
IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, InvalidPath) { |
base::FilePath path(FILE_PATH_LITERAL("/invalid/file/path")); |