Commit 618a64cb authored by Rob Wu's avatar Rob Wu
Browse files

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

--HG--
extra : moz-landing-system : lando
parent 1345af50
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -10038,6 +10038,7 @@ public:
      parser->BlockParser();
      mParser = do_GetWeakReference(parser);
      mDocument = aDocument;
      mDocument->BlockOnload();
    }
  }

@@ -10076,6 +10077,7 @@ private:
      if (parser == docParser) {
        parser->UnblockParser();
        parser->ContinueInterruptedParsingAsync();
        mDocument->UnblockOnload(false);
      }
    }
    mParser = nullptr;
+24 −0
Original line number Diff line number Diff line
@@ -131,6 +131,7 @@ public:
  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;
@@ -175,6 +176,9 @@ protected:
  void NotifyInsert(nsIContent* aContent,
                    nsIContent* aChildContent);
  void NotifyRootInsertion();

private:
  void ContinueInterruptedParsingIfEnabled();
};

class SinkContext
@@ -1047,3 +1051,23 @@ 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());
}
+69 −0
Original line number Diff line number Diff line
"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();
});
+1 −1
Original line number Diff line number Diff line
@@ -2,8 +2,8 @@
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]