Bug 1465388 - Resume about:blank parser upon unblocking the document r=hsivonen
authorRob Wu <rob@robwu.nl>
Tue, 28 Aug 2018 05:28:21 +0000
changeset 488670 126368a5c3ec8880f748a8bb91844048f67922b6
parent 488669 b173e620243ebbe9f0cecb19cff82c065f5f8ce1
child 488671 f9d93a20e6d6c5c5fca0fa3309d96b3f2328714b
push id9734
push usershindli@mozilla.com
push dateThu, 30 Aug 2018 12:18:07 +0000
treeherdermozilla-beta@71c71ab3afae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershsivonen
bugs1465388, 1407501
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1465388 - Resume about:blank parser upon unblocking the document r=hsivonen When `document.blockParsing()` is called, the nsIParser is suspended until the document is unblocked. For about:blank documents, this is a nsParser. When a document is unblocked, nsParser::ContinueInterruptedParsingAsync is invoked, which delegates its implementation to nsIContentSink, which is a nsHTMLContentSink for about:blank documents. Due to a missing implementation of nsHTMLContentSink::ContinueInterruptedParsingAsync, the parser was never resumed, causing bug 1465388 and bug 1407501. This patch fixes the problem, by implementing the required method (and using a load blocker to ensure that the (about:blank) document does not finish before the parser finishes). This patch is tested through extension tests: Currently document_start stylesheets always activate the parser blocker, and document_start scripts trigger the parser blocker when the script has not been preloaded yet (e.g. at the first run). Before this patch, the test failed due to the assertion failure as reported in the linked bugs. After this patch, the tests pass. Differential Revision: https://phabricator.services.mozilla.com/D4352
dom/base/nsDocument.cpp
dom/html/nsHTMLContentSink.cpp
toolkit/components/extensions/test/xpcshell/test_ext_contentscript_about_blank_start.js
toolkit/components/extensions/test/xpcshell/xpcshell-content.ini
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -10033,16 +10033,17 @@ public:
                                         const BlockParsingOptions& aOptions)
     : mPromise(aPromise)
   {
     nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
     if (parser && (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
       parser->BlockParser();
       mParser = do_GetWeakReference(parser);
       mDocument = aDocument;
+      mDocument->BlockOnload();
     }
   }
 
   void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
     MaybeUnblockParser();
 
@@ -10071,16 +10072,17 @@ private:
   void MaybeUnblockParser() {
     nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
     if (parser) {
       MOZ_DIAGNOSTIC_ASSERT(mDocument);
       nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
       if (parser == docParser) {
         parser->UnblockParser();
         parser->ContinueInterruptedParsingAsync();
+        mDocument->UnblockOnload(false);
       }
     }
     mParser = nullptr;
     mDocument = nullptr;
   }
 
   nsWeakPtr mParser;
   RefPtr<Promise> mPromise;
--- a/dom/html/nsHTMLContentSink.cpp
+++ b/dom/html/nsHTMLContentSink.cpp
@@ -126,16 +126,17 @@ public:
   NS_IMETHOD DidBuildModel(bool aTerminated) override;
   NS_IMETHOD WillInterrupt(void) override;
   NS_IMETHOD WillResume(void) override;
   NS_IMETHOD SetParser(nsParserBase* aParser) override;
   virtual void FlushPendingNotifications(FlushType aType) override;
   virtual void SetDocumentCharset(NotNull<const Encoding*> aEncoding) override;
   virtual nsISupports *GetTarget() override;
   virtual bool IsScriptExecuting() override;
+  virtual void ContinueInterruptedParsingAsync() override;
 
   // nsIHTMLContentSink
   NS_IMETHOD OpenContainer(ElementType aNodeType) override;
   NS_IMETHOD CloseContainer(ElementType aTag) override;
 
 protected:
   virtual ~HTMLContentSink();
 
@@ -170,16 +171,19 @@ protected:
   void CloseHeadContext();
 
   // nsContentSink overrides
   void UpdateChildCounts() override;
 
   void NotifyInsert(nsIContent* aContent,
                     nsIContent* aChildContent);
   void NotifyRootInsertion();
+
+private:
+  void ContinueInterruptedParsingIfEnabled();
 };
 
 class SinkContext
 {
 public:
   explicit SinkContext(HTMLContentSink* aSink);
   ~SinkContext();
 
@@ -1042,8 +1046,28 @@ HTMLContentSink::GetTarget()
   return mDocument;
 }
 
 bool
 HTMLContentSink::IsScriptExecuting()
 {
   return IsScriptExecutingImpl();
 }
+
+void
+HTMLContentSink::ContinueInterruptedParsingIfEnabled()
+{
+  if (mParser->IsParserEnabled()) {
+    static_cast<nsIParser*>(mParser.get())->ContinueInterruptedParsing();
+  }
+}
+
+void
+HTMLContentSink::ContinueInterruptedParsingAsync()
+{
+  nsCOMPtr<nsIRunnable> ev =
+    NewRunnableMethod("HTMLContentSink::ContinueInterruptedParsingIfEnabled",
+                      this,
+                      &HTMLContentSink::ContinueInterruptedParsingIfEnabled);
+
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(mHTMLDocument);
+  doc->Dispatch(mozilla::TaskCategory::Other, ev.forget());
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_about_blank_start.js
@@ -0,0 +1,69 @@
+"use strict";
+
+const server = createHttpServer({hosts: ["example.com"]});
+
+server.registerPathHandler("/blank-iframe.html", (request, response) => {
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+  response.write("<iframe></iframe>");
+});
+
+add_task(async function content_script_at_document_start() {
+  let extensionData = {
+    manifest: {
+      content_scripts: [{
+        "matches": ["<all_urls>"],
+        "js": ["start.js"],
+        "run_at": "document_start",
+        "match_about_blank": true,
+      }],
+    },
+
+    files: {
+      "start.js": function() {
+        browser.test.sendMessage("content-script-done");
+      },
+    },
+  };
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  await extension.startup();
+  let contentPage = await ExtensionTestUtils.loadContentPage(`about:blank`);
+  await extension.awaitMessage("content-script-done");
+  await contentPage.close();
+  await extension.unload();
+});
+
+add_task(async function content_style_at_document_start() {
+  let extensionData = {
+    manifest: {
+      content_scripts: [{
+        "matches": ["<all_urls>"],
+        "css": ["start.css"],
+        "run_at": "document_start",
+        "match_about_blank": true,
+      }, {
+        "matches": ["<all_urls>"],
+        "js": ["end.js"],
+        "run_at": "document_end",
+        "match_about_blank": true,
+      }],
+    },
+
+    files: {
+      "start.css": "body { background: red; }",
+      "end.js": function() {
+        let style = window.getComputedStyle(document.body);
+        browser.test.assertEq("rgb(255, 0, 0)", style.backgroundColor, "document_start style should have been applied");
+        browser.test.sendMessage("content-script-done");
+      },
+    },
+  };
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  await extension.startup();
+  let contentPage = await ExtensionTestUtils.loadContentPage(`about:blank`);
+  await extension.awaitMessage("content-script-done");
+  await contentPage.close();
+  await extension.unload();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini
@@ -1,13 +1,13 @@
 [test_ext_i18n.js]
 skip-if = os == "android" || (os == "win" && debug) || (os == "linux")
 [test_ext_i18n_css.js]
 [test_ext_contentscript.js]
+[test_ext_contentscript_about_blank_start.js]
 [test_ext_contentscript_scriptCreated.js]
-skip-if = debug # Bug 1407501
 [test_ext_contentscript_triggeringPrincipal.js]
 skip-if = (os == "android" && debug) || (os == "win" && debug) # Windows: Bug 1438796
 [test_ext_contentscript_xrays.js]
 [test_ext_contentScripts_register.js]
 [test_ext_contexts_gc.js]
 [test_ext_adoption_with_xrays.js]
 [test_ext_shadowdom.js]