Bug 1333990: Part 2d - Add a utility to block HTML parsing until sandbox scripts are ready. r?hsivonen,billm
In order to asynchronously load content scripts that need to run very early in
the page load cycle, before any ordinary page scripts, we need to be able to
block parsing from the document-element-inserted listener. Since the script
loader operates by returning promises, blocking on promise resolution is the
simplest way to achieve this.
MozReview-Commit-ID: CTWlyrP6dqG
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -71,16 +71,18 @@
#include "nsITabChild.h"
#include "nsRange.h"
#include "nsIDOMText.h"
#include "nsIDOMComment.h"
#include "mozilla/dom/DocumentType.h"
#include "mozilla/dom/NodeIterator.h"
#include "mozilla/dom/TreeWalker.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
#include "nsIServiceManager.h"
#include "mozilla/dom/workers/ServiceWorkerManager.h"
#include "imgLoader.h"
#include "nsCanvasFrame.h"
#include "nsContentCID.h"
#include "nsError.h"
@@ -2755,16 +2757,23 @@ nsDocument::InitCSP(nsIChannel* aChannel
// stop! ERROR page!
aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
}
}
ApplySettingsFromCSP(false);
return NS_OK;
}
+already_AddRefed<nsIParser>
+nsDocument::CreatorParserOrNull()
+{
+ nsCOMPtr<nsIParser> parser = mParser;
+ return parser.forget();
+}
+
void
nsDocument::StopDocumentLoad()
{
if (mParser) {
mParserAborted = true;
mParser->Terminate();
}
}
@@ -10502,16 +10511,88 @@ nsIDocument::ObsoleteSheet(const nsAStri
return;
}
res = CSSLoader()->ObsoleteSheet(uri);
if (NS_FAILED(res)) {
rv.Throw(res);
}
}
+class UnblockParsingPromiseHandler final : public PromiseNativeHandler
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit UnblockParsingPromiseHandler(nsIDocument* aDocument, Promise* aPromise)
+ : mDocument(aDocument)
+ , mPromise(aPromise)
+ {
+ nsCOMPtr<nsIParser> parser = mDocument->CreatorParserOrNull();
+ if (parser) {
+ parser->BlockParser();
+ } else {
+ mDocument = nullptr;
+ }
+ }
+
+ void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ MaybeUnblockParser();
+
+ mPromise->MaybeResolve(aCx, aValue);
+ }
+
+ void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ MaybeUnblockParser();
+
+ mPromise->MaybeReject(aCx, aValue);
+ }
+
+protected:
+ virtual ~UnblockParsingPromiseHandler()
+ {
+ MaybeUnblockParser();
+ }
+
+private:
+ void MaybeUnblockParser() {
+ if (mDocument) {
+ nsCOMPtr<nsIParser> parser = mDocument->CreatorParserOrNull();
+ if (parser) {
+ parser->UnblockParser();
+ parser->ContinueInterruptedParsingAsync();
+ }
+ mDocument = nullptr;
+ }
+ }
+
+ RefPtr<nsIDocument> mDocument;
+ RefPtr<Promise> mPromise;
+};
+
+NS_IMPL_ISUPPORTS0(UnblockParsingPromiseHandler)
+
+already_AddRefed<Promise>
+nsIDocument::BlockParsing(OwningNonNull<Promise> aPromise,
+ ErrorResult& aRv)
+{
+ RefPtr<Promise> resultPromise = Promise::Create(aPromise->GetParentObject(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<PromiseNativeHandler> promiseHandler = new UnblockParsingPromiseHandler(this, resultPromise);
+ aPromise->AppendNativeHandler(promiseHandler);
+
+ return resultPromise.forget();
+}
+
already_AddRefed<nsIURI>
nsIDocument::GetMozDocumentURIIfNotForErrorPages()
{
if (mFailedChannel) {
nsCOMPtr<nsIURI> failedURI;
if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
return failedURI.forget();
}
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -531,16 +531,18 @@ public:
virtual void SetDocumentURI(nsIURI* aURI) override;
virtual void SetChromeXHRDocURI(nsIURI* aURI) override;
virtual void SetChromeXHRDocBaseURI(nsIURI* aURI) override;
virtual void ApplySettingsFromCSP(bool aSpeculative) override;
+ virtual already_AddRefed<nsIParser> CreatorParserOrNull() override;
+
/**
* Set the principal responsible for this document.
*/
virtual void SetPrincipal(nsIPrincipal *aPrincipal) override;
/**
* Get the Content-Type of this document.
*/
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -10,16 +10,17 @@
#include "nsAutoPtr.h" // for member
#include "nsCOMArray.h" // for member
#include "nsCompatibility.h" // for member
#include "nsCOMPtr.h" // for member
#include "nsGkAtoms.h" // for static class members
#include "nsIDocumentObserver.h" // for typedef (nsUpdateType)
#include "nsILoadGroup.h" // for member (in nsCOMPtr)
#include "nsINode.h" // for base class
+#include "nsIParser.h"
#include "nsIScriptGlobalObject.h" // for member (in nsCOMPtr)
#include "nsIServiceManager.h"
#include "nsIUUIDGenerator.h"
#include "nsPIDOMWindow.h" // for use in inline functions
#include "nsPropertyTable.h" // for member
#include "nsDataHashtable.h" // for member
#include "nsURIHashKey.h" // for member
#include "mozilla/net/ReferrerPolicy.h" // for member
@@ -353,16 +354,18 @@ public:
*/
virtual void SetChromeXHRDocBaseURI(nsIURI* aURI) = 0;
/**
* Set referrer policy and upgrade-insecure-requests flags
*/
virtual void ApplySettingsFromCSP(bool aSpeculative) = 0;
+ virtual already_AddRefed<nsIParser> CreatorParserOrNull() = 0;
+
/**
* Return the referrer policy of the document. Return "default" if there's no
* valid meta referrer tag found in the document.
*/
ReferrerPolicyEnum GetReferrerPolicy() const
{
return mReferrerPolicy;
}
@@ -2764,16 +2767,19 @@ public:
{
return mStyleSheetChangeEventsEnabled;
}
void ObsoleteSheet(nsIURI *aSheetURI, mozilla::ErrorResult& rv);
void ObsoleteSheet(const nsAString& aSheetURI, mozilla::ErrorResult& rv);
+ already_AddRefed<mozilla::dom::Promise> BlockParsing(mozilla::OwningNonNull<mozilla::dom::Promise> aPromise,
+ mozilla::ErrorResult& aRv);
+
already_AddRefed<nsIURI> GetMozDocumentURIIfNotForErrorPages();
// ParentNode
nsIHTMLCollection* Children();
uint32_t ChildElementCount();
virtual nsHTMLDocument* AsHTMLDocument() { return nullptr; }
virtual mozilla::dom::SVGDocument* AsSVGDocument() { return nullptr; }
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -374,16 +374,20 @@ partial interface Document {
void obsoleteSheet(DOMString sheetURI);
[ChromeOnly] readonly attribute nsIDocShell? docShell;
[ChromeOnly] readonly attribute DOMString contentLanguage;
[ChromeOnly] readonly attribute nsILoadGroup? documentLoadGroup;
+ // Blocks the initial document parser until the given promise is settled.
+ [ChromeOnly, Throws]
+ Promise<any> blockParsing(Promise<any> promise);
+
// like documentURI, except that for error pages, it returns the URI we were
// trying to load when we hit an error, rather than the error page's own URI.
[ChromeOnly] readonly attribute URI? mozDocumentURIIfNotForErrorPages;
};
// Extension to give chrome JS the ability to determine when a document was
// created to satisfy an iframe with srcdoc attribute.
partial interface Document {