summaryrefslogtreecommitdiffstats
path: root/parser/html/nsHtml5TreeOpExecutor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'parser/html/nsHtml5TreeOpExecutor.cpp')
-rw-r--r--parser/html/nsHtml5TreeOpExecutor.cpp1414
1 files changed, 1414 insertions, 0 deletions
diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp
new file mode 100644
index 0000000000..3cb38dcf28
--- /dev/null
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -0,0 +1,1414 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Likely.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/MediaList.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsCSPService.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/IdleTaskRunner.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/StaticPrefs_content.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/StaticPrefs_view_source.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/fallible.h"
+#include "nsContentUtils.h"
+#include "nsDocShell.h"
+#include "nsError.h"
+#include "nsHTMLDocument.h"
+#include "nsHtml5AutoPauseUpdate.h"
+#include "nsHtml5Parser.h"
+#include "nsHtml5StreamParser.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5TreeOpExecutor.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsINestedURI.h"
+#include "nsIHttpChannel.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptError.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIViewSourceChannel.h"
+#include "nsNetUtil.h"
+#include "xpcpublic.h"
+
+using namespace mozilla;
+
+#ifdef DEBUG
+static LazyLogModule gHtml5TreeOpExecutorLog("Html5TreeOpExecutor");
+#endif // DEBUG
+static LazyLogModule gCharsetMenuLog("Chardetng");
+
+#define LOG(args) MOZ_LOG(gHtml5TreeOpExecutorLog, LogLevel::Debug, args)
+#define LOGCHARDETNG(args) MOZ_LOG(gCharsetMenuLog, LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor,
+ nsHtml5DocumentBuilder,
+ nsIContentSink)
+
+class nsHtml5ExecutorReflusher : public Runnable {
+ private:
+ RefPtr<nsHtml5TreeOpExecutor> mExecutor;
+
+ public:
+ explicit nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
+ : Runnable("nsHtml5ExecutorReflusher"), mExecutor(aExecutor) {}
+ NS_IMETHOD Run() override {
+ dom::Document* doc = mExecutor->GetDocument();
+ if (XRE_IsContentProcess() &&
+ nsContentUtils::
+ HighPriorityEventPendingForTopLevelDocumentBeforeContentfulPaint(
+ doc)) {
+ // Possible early paint pending, reuse the runnable and try to
+ // call RunFlushLoop later.
+ nsCOMPtr<nsIRunnable> flusher = this;
+ if (NS_SUCCEEDED(doc->Dispatch(flusher.forget()))) {
+ PROFILER_MARKER_UNTYPED("HighPrio blocking parser flushing(2)", DOM);
+ return NS_OK;
+ }
+ }
+ mExecutor->RunFlushLoop();
+ return NS_OK;
+ }
+};
+
+class MOZ_RAII nsHtml5AutoFlush final {
+ private:
+ RefPtr<nsHtml5TreeOpExecutor> mExecutor;
+ size_t mOpsToRemove;
+
+ public:
+ explicit nsHtml5AutoFlush(nsHtml5TreeOpExecutor* aExecutor)
+ : mExecutor(aExecutor), mOpsToRemove(aExecutor->OpQueueLength()) {
+ mExecutor->BeginFlush();
+ mExecutor->BeginDocUpdate();
+ }
+ ~nsHtml5AutoFlush() {
+ if (mExecutor->IsInDocUpdate()) {
+ mExecutor->EndDocUpdate();
+ } else {
+ // We aren't in an update if nsHtml5AutoPauseUpdate
+ // caused something to terminate the parser.
+ MOZ_RELEASE_ASSERT(
+ mExecutor->IsComplete(),
+ "How do we have mParser but the doc update isn't open?");
+ }
+ mExecutor->EndFlush();
+ mExecutor->RemoveFromStartOfOpQueue(mOpsToRemove);
+ }
+ void SetNumberOfOpsToRemove(size_t aOpsToRemove) {
+ MOZ_ASSERT(aOpsToRemove < mOpsToRemove,
+ "Requested partial clearing of op queue but the number to clear "
+ "wasn't less than the length of the queue.");
+ mOpsToRemove = aOpsToRemove;
+ }
+};
+
+static LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr;
+StaticRefPtr<IdleTaskRunner> gBackgroundFlushRunner;
+
+nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
+ : nsHtml5DocumentBuilder(false),
+ mSuppressEOF(false),
+ mReadingFromStage(false),
+ mStreamParser(nullptr),
+ mPreloadedURLs(23), // Mean # of preloadable resources per page on dmoz
+ mStarted(false),
+ mRunFlushLoopOnStack(false),
+ mCallContinueInterruptedParsingIfEnabled(false),
+ mAlreadyComplainedAboutCharset(false),
+ mAlreadyComplainedAboutDeepTree(false) {}
+
+nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor() {
+ if (gBackgroundFlushList && isInList()) {
+ ClearOpQueue();
+ removeFrom(*gBackgroundFlushList);
+ if (gBackgroundFlushList->isEmpty()) {
+ delete gBackgroundFlushList;
+ gBackgroundFlushList = nullptr;
+ if (gBackgroundFlushRunner) {
+ gBackgroundFlushRunner->Cancel();
+ gBackgroundFlushRunner = nullptr;
+ }
+ }
+ }
+ MOZ_ASSERT(NS_FAILED(mBroken) || mOpQueue.IsEmpty(),
+ "Somehow there's stuff in the op queue.");
+}
+
+// nsIContentSink
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::WillParse() {
+ MOZ_ASSERT_UNREACHABLE("No one should call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsHtml5TreeOpExecutor::WillBuildModel() {
+ mDocument->AddObserver(this);
+ WillBuildModelImpl();
+ GetDocument()->BeginLoad();
+ if (mDocShell && !GetDocument()->GetWindow() && !IsExternalViewSource()) {
+ // Not loading as data but script global object not ready
+ return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+ return NS_OK;
+}
+
+// This is called when the tree construction has ended
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated) {
+ if (mRunsToCompletion) {
+ return NS_OK;
+ }
+
+ MOZ_RELEASE_ASSERT(!IsInDocUpdate(),
+ "DidBuildModel from inside a doc update.");
+
+ RefPtr<nsHtml5TreeOpExecutor> pin(this);
+ auto queueClearer = MakeScopeExit([&] {
+ if (aTerminated && (mFlushState == eNotFlushing)) {
+ ClearOpQueue(); // clear in order to be able to assert in destructor
+ }
+ });
+
+ // This comes from nsXMLContentSink and nsHTMLContentSink
+ // If this parser has been marked as broken, treat the end of parse as
+ // forced termination.
+ DidBuildModelImpl(aTerminated || NS_FAILED(IsBroken()));
+
+ bool destroying = true;
+ if (mDocShell) {
+ mDocShell->IsBeingDestroyed(&destroying);
+ }
+
+ if (!destroying) {
+ mDocument->OnParsingCompleted();
+
+ if (!mLayoutStarted) {
+ // We never saw the body, and layout never got started. Force
+ // layout *now*, to get an initial reflow.
+
+ // NOTE: only force the layout if we are NOT destroying the
+ // docshell. If we are destroying it, then starting layout will
+ // likely cause us to crash, or at best waste a lot of time as we
+ // are just going to tear it down anyway.
+ nsContentSink::StartLayout(false);
+ }
+ }
+
+ ScrollToRef();
+ mDocument->RemoveObserver(this);
+ if (!mParser) {
+ // DidBuildModelImpl may cause mParser to be nulled out
+ // Return early to avoid unblocking the onload event too many times.
+ return NS_OK;
+ }
+
+ // We may not have called BeginLoad() if loading is terminated before
+ // OnStartRequest call.
+ if (mStarted) {
+ mDocument->EndLoad();
+
+ // Gather telemetry only for top-level content navigations in order to
+ // avoid noise from ad iframes.
+ bool topLevel = false;
+ if (mozilla::dom::BrowsingContext* bc = mDocument->GetBrowsingContext()) {
+ topLevel = bc->IsTopContent();
+ }
+
+ // Gather telemetry only for text/html and text/plain (excluding CSS, JS,
+ // etc. being viewed as text.)
+ nsAutoString contentType;
+ mDocument->GetContentType(contentType);
+ bool htmlOrPlain = contentType.EqualsLiteral(u"text/html") ||
+ contentType.EqualsLiteral(u"text/plain");
+
+ // Gather telemetry only for HTTP status code 200 in order to exclude
+ // error pages.
+ bool httpOk = false;
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = GetParser()->GetChannel(getter_AddRefs(channel));
+ if (NS_SUCCEEDED(rv) && channel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
+ if (httpChannel) {
+ uint32_t httpStatus;
+ rv = httpChannel->GetResponseStatus(&httpStatus);
+ if (NS_SUCCEEDED(rv) && httpStatus == 200) {
+ httpOk = true;
+ }
+ }
+ }
+
+ // Gather chardetng telemetry
+ MOZ_ASSERT(mDocument->IsHTMLDocument());
+ if (httpOk && htmlOrPlain && topLevel && !aTerminated &&
+ !mDocument->AsHTMLDocument()->IsViewSource()) {
+ // We deliberately measure only normally-completed (non-aborted) loads
+ // that are not View Source loads. This seems like a better place for
+ // checking normal completion than anything in nsHtml5StreamParser.
+ bool plain = mDocument->AsHTMLDocument()->IsPlainText();
+ int32_t charsetSource = mDocument->GetDocumentCharacterSetSource();
+ switch (charsetSource) {
+ case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8:
+ if (plain) {
+ LOGCHARDETNG(("TEXT::UtfInitial"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::UtfInitial);
+ } else {
+ LOGCHARDETNG(("HTML::UtfInitial"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::UtfInitial);
+ }
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic:
+ if (plain) {
+ LOGCHARDETNG(("TEXT::GenericInitial"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
+ GenericInitial);
+ } else {
+ LOGCHARDETNG(("HTML::GenericInitial"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
+ GenericInitial);
+ }
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content:
+ if (plain) {
+ LOGCHARDETNG(("TEXT::ContentInitial"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
+ ContentInitial);
+ } else {
+ LOGCHARDETNG(("HTML::ContentInitial"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
+ ContentInitial);
+ }
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ if (plain) {
+ LOGCHARDETNG(("TEXT::TldInitial"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::TldInitial);
+ } else {
+ LOGCHARDETNG(("HTML::TldInitial"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::TldInitial);
+ }
+ break;
+ case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII:
+ if (plain) {
+ LOGCHARDETNG(("TEXT::UtfFinal"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::UtfFinal);
+ } else {
+ LOGCHARDETNG(("HTML::UtfFinal"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::UtfFinal);
+ }
+ break;
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic:
+ if (plain) {
+ LOGCHARDETNG(("TEXT::GenericFinal"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
+ GenericFinal);
+ } else {
+ LOGCHARDETNG(("HTML::GenericFinal"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
+ GenericFinal);
+ }
+ break;
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII:
+ if (plain) {
+ LOGCHARDETNG(("TEXT::GenericFinalA"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
+ GenericFinalA);
+ } else {
+ LOGCHARDETNG(("HTML::GenericFinalA"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
+ GenericFinalA);
+ }
+ break;
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content:
+ if (plain) {
+ LOGCHARDETNG(("TEXT::ContentFinal"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
+ ContentFinal);
+ } else {
+ LOGCHARDETNG(("HTML::ContentFinal"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
+ ContentFinal);
+ }
+ break;
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII:
+ if (plain) {
+ LOGCHARDETNG(("TEXT::ContentFinalA"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
+ ContentFinalA);
+ } else {
+ LOGCHARDETNG(("HTML::ContentFinalA"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
+ ContentFinalA);
+ }
+ break;
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ if (plain) {
+ LOGCHARDETNG(("TEXT::TldFinal"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::TldFinal);
+ } else {
+ LOGCHARDETNG(("HTML::TldFinal"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::TldFinal);
+ }
+ break;
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII:
+ if (plain) {
+ LOGCHARDETNG(("TEXT::TldFinalA"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::TldFinalA);
+ } else {
+ LOGCHARDETNG(("HTML::TldFinalA"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::TldFinalA);
+ }
+ break;
+ default:
+ // Chardetng didn't run automatically or the input was all ASCII.
+ break;
+ }
+ }
+ }
+
+ // Dropping the stream parser changes the parser's apparent
+ // script-createdness, which is why the stream parser must not be dropped
+ // before this executor's nsHtml5Parser has been made unreachable from its
+ // nsHTMLDocument. (mDocument->EndLoad() above drops the parser from the
+ // document.)
+ GetParser()->DropStreamParser();
+ DropParserAndPerfHint();
+#ifdef GATHER_DOCWRITE_STATISTICS
+ printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites);
+ printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites);
+ printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites);
+#endif
+#ifdef DEBUG
+ LOG(("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize));
+ if (sAppendBatchExaminations != 0) {
+ LOG(("AVERAGE SLOTS EXAMINED: %d\n",
+ sAppendBatchSlotsExamined / sAppendBatchExaminations));
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::WillInterrupt() {
+ MOZ_ASSERT_UNREACHABLE("Don't call. For interface compat only.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void nsHtml5TreeOpExecutor::WillResume() {
+ MOZ_ASSERT_UNREACHABLE("Don't call. For interface compat only.");
+}
+
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser) {
+ mParser = aParser;
+ return NS_OK;
+}
+
+void nsHtml5TreeOpExecutor::InitialTranslationCompleted() {
+ nsContentSink::StartLayout(false);
+}
+
+void nsHtml5TreeOpExecutor::FlushPendingNotifications(FlushType aType) {
+ if (aType >= FlushType::EnsurePresShellInitAndFrames) {
+ // Bug 577508 / 253951
+ nsContentSink::StartLayout(true);
+ }
+}
+
+nsISupports* nsHtml5TreeOpExecutor::GetTarget() {
+ return ToSupports(mDocument);
+}
+
+nsresult nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason) {
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+ mBroken = aReason;
+ if (mStreamParser) {
+ mStreamParser->Terminate();
+ }
+ // We are under memory pressure, but let's hope the following allocation
+ // works out so that we get to terminate and clean up the parser from
+ // a safer point.
+ if (mParser && mDocument) { // can mParser ever be null here?
+ nsCOMPtr<nsIRunnable> terminator = NewRunnableMethod(
+ "nsHtml5Parser::Terminate", GetParser(), &nsHtml5Parser::Terminate);
+ if (NS_FAILED(mDocument->Dispatch(terminator.forget()))) {
+ NS_WARNING("failed to dispatch executor flush event");
+ }
+ }
+ return aReason;
+}
+
+static bool BackgroundFlushCallback(TimeStamp /*aDeadline*/) {
+ RefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst();
+ if (ex) {
+ ex->RunFlushLoop();
+ }
+ if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) {
+ delete gBackgroundFlushList;
+ gBackgroundFlushList = nullptr;
+ gBackgroundFlushRunner->Cancel();
+ gBackgroundFlushRunner = nullptr;
+ return true;
+ }
+ return true;
+}
+
+void nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync() {
+ if (mDocument && !mDocument->IsInBackgroundWindow()) {
+ nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);
+ if (NS_FAILED(mDocument->Dispatch(flusher.forget()))) {
+ NS_WARNING("failed to dispatch executor flush event");
+ }
+ } else {
+ if (!gBackgroundFlushList) {
+ gBackgroundFlushList = new LinkedList<nsHtml5TreeOpExecutor>();
+ }
+ if (!isInList()) {
+ gBackgroundFlushList->insertBack(this);
+ }
+ if (gBackgroundFlushRunner) {
+ return;
+ }
+ // Now we set up a repetitive idle scheduler for flushing background list.
+ gBackgroundFlushRunner = IdleTaskRunner::Create(
+ &BackgroundFlushCallback,
+ "nsHtml5TreeOpExecutor::BackgroundFlushCallback",
+ 0, // Start looking for idle time immediately.
+ TimeDuration::FromMilliseconds(250), // The hard deadline.
+ TimeDuration::FromMicroseconds(
+ StaticPrefs::content_sink_interactive_parse_time()), // Required
+ // budget.
+ true, // repeating
+ [] { return false; }); // MayStopProcessing
+ }
+}
+
+void nsHtml5TreeOpExecutor::FlushSpeculativeLoads() {
+ nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
+ mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
+ nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
+ nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
+ for (nsHtml5SpeculativeLoad* iter = start; iter < end; ++iter) {
+ if (MOZ_UNLIKELY(!mParser)) {
+ // An extension terminated the parser from a HTTP observer.
+ return;
+ }
+ iter->Perform(this);
+ }
+}
+
+class nsHtml5FlushLoopGuard {
+ private:
+ RefPtr<nsHtml5TreeOpExecutor> mExecutor;
+#ifdef DEBUG
+ uint32_t mStartTime;
+#endif
+ public:
+ explicit nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor)
+ : mExecutor(aExecutor)
+#ifdef DEBUG
+ ,
+ mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow()))
+#endif
+ {
+ mExecutor->mRunFlushLoopOnStack = true;
+ }
+ ~nsHtml5FlushLoopGuard() {
+#ifdef DEBUG
+ uint32_t timeOffTheEventLoop =
+ PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime;
+ if (timeOffTheEventLoop >
+ nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) {
+ nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = timeOffTheEventLoop;
+ }
+ LOG(("Longest time off the event loop: %d\n",
+ nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop));
+#endif
+
+ mExecutor->mRunFlushLoopOnStack = false;
+ }
+};
+
+/**
+ * The purpose of the loop here is to avoid returning to the main event loop
+ */
+void nsHtml5TreeOpExecutor::RunFlushLoop() {
+ AUTO_PROFILER_LABEL("nsHtml5TreeOpExecutor::RunFlushLoop", OTHER);
+
+ if (mRunFlushLoopOnStack) {
+ // There's already a RunFlushLoop() on the call stack.
+ return;
+ }
+
+ nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu!
+
+ RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
+ RefPtr<nsHtml5StreamParser> streamParserGrip;
+ if (mParser) {
+ streamParserGrip = GetParser()->GetStreamParser();
+ }
+ Unused << streamParserGrip; // Intentionally not used within function
+
+ // Remember the entry time
+ (void)nsContentSink::WillParseImpl();
+
+ for (;;) {
+ if (!mParser) {
+ // Parse has terminated.
+ ClearOpQueue(); // clear in order to be able to assert in destructor
+ return;
+ }
+
+ if (NS_FAILED(IsBroken())) {
+ return;
+ }
+
+ if (!parserKungFuDeathGrip->IsParserEnabled()) {
+ // The parser is blocked.
+ return;
+ }
+
+ if (mFlushState != eNotFlushing) {
+ // XXX Can this happen? In case it can, let's avoid crashing.
+ return;
+ }
+
+ // If there are scripts executing, then the content sink is jumping the gun
+ // (probably due to a synchronous XMLHttpRequest) and will re-enable us
+ // later, see bug 460706.
+ if (IsScriptExecuting()) {
+ return;
+ }
+
+ if (mReadingFromStage) {
+ nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
+ MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
+ "mOpQueue modified during flush.");
+ if (!mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue,
+ speculativeLoadQueue)) {
+ MarkAsBroken(nsresult::NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ // Make sure speculative loads never start after the corresponding
+ // normal loads for the same URLs.
+ nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
+ nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
+ for (nsHtml5SpeculativeLoad* iter = start; iter < end; ++iter) {
+ iter->Perform(this);
+ if (MOZ_UNLIKELY(!mParser)) {
+ // An extension terminated the parser from a HTTP observer.
+ ClearOpQueue(); // clear in order to be able to assert in destructor
+ return;
+ }
+ }
+ } else {
+ FlushSpeculativeLoads(); // Make sure speculative loads never start after
+ // the corresponding normal loads for the same
+ // URLs.
+ if (MOZ_UNLIKELY(!mParser)) {
+ // An extension terminated the parser from a HTTP observer.
+ ClearOpQueue(); // clear in order to be able to assert in destructor
+ return;
+ }
+ // Now parse content left in the document.write() buffer queue if any.
+ // This may generate tree ops on its own or dequeue a speculation.
+ nsresult rv = GetParser()->ParseUntilBlocked();
+
+ // ParseUntilBlocked flushes operations from the stage to the OpQueue.
+ // Those operations may have accompanying speculative operations.
+ // If so, we have to flush those speculative loads so that we maintain
+ // the invariant that no speculative load starts after the corresponding
+ // normal load for the same URL. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1513292#c80
+ // for a more detailed explanation of why this is necessary.
+ FlushSpeculativeLoads();
+
+ if (NS_FAILED(rv)) {
+ MarkAsBroken(rv);
+ return;
+ }
+ }
+
+ if (mOpQueue.IsEmpty()) {
+ // Avoid bothering the rest of the engine with a doc update if there's
+ // nothing to do.
+ return;
+ }
+
+ nsIContent* scriptElement = nullptr;
+ bool interrupted = false;
+ bool streamEnded = false;
+
+ {
+ // autoFlush clears mOpQueue in its destructor unless
+ // SetNumberOfOpsToRemove is called first, in which case only
+ // some ops from the start of the queue are cleared.
+ nsHtml5AutoFlush autoFlush(this);
+
+ nsHtml5TreeOperation* first = mOpQueue.Elements();
+ nsHtml5TreeOperation* last = first + mOpQueue.Length() - 1;
+ for (nsHtml5TreeOperation* iter = first;; ++iter) {
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The previous tree op caused a call to nsIParser::Terminate().
+ return;
+ }
+ MOZ_ASSERT(IsInDocUpdate(),
+ "Tried to perform tree op outside update batch.");
+ nsresult rv =
+ iter->Perform(this, &scriptElement, &interrupted, &streamEnded);
+ if (NS_FAILED(rv)) {
+ MarkAsBroken(rv);
+ break;
+ }
+
+ // Be sure not to check the deadline if the last op was just performed.
+ if (MOZ_UNLIKELY(iter == last)) {
+ break;
+ } else if (MOZ_UNLIKELY(interrupted) ||
+ MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
+ NS_ERROR_HTMLPARSER_INTERRUPTED)) {
+ autoFlush.SetNumberOfOpsToRemove((iter - first) + 1);
+
+ nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
+ return;
+ }
+ }
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The parse ended during an update pause.
+ return;
+ }
+ if (streamEnded) {
+ GetParser()->PermanentlyUndefineInsertionPoint();
+ }
+ } // end autoFlush
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // Ending the doc update caused a call to nsIParser::Terminate().
+ return;
+ }
+
+ if (streamEnded) {
+ DidBuildModel(false);
+#ifdef DEBUG
+ if (scriptElement) {
+ nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(scriptElement);
+ if (!sele) {
+ MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled,
+ "Node didn't QI to script, but SVG wasn't disabled.");
+ }
+ MOZ_ASSERT(sele->IsMalformed(), "Script wasn't marked as malformed.");
+ }
+#endif
+ } else if (scriptElement) {
+ // must be tail call when mFlushState is eNotFlushing
+ RunScript(scriptElement, true);
+
+ // Always check the clock in nsContentSink right after a script
+ StopDeflecting();
+ if (nsContentSink::DidProcessATokenImpl() ==
+ NS_ERROR_HTMLPARSER_INTERRUPTED) {
+#ifdef DEBUG
+ LOG(("REFLUSH SCHEDULED (after script): %d\n",
+ ++sTimesFlushLoopInterrupted));
+#endif
+ nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
+ return;
+ }
+ }
+ }
+}
+
+nsresult nsHtml5TreeOpExecutor::FlushDocumentWrite() {
+ nsresult rv = IsBroken();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ FlushSpeculativeLoads(); // Make sure speculative loads never start after the
+ // corresponding normal loads for the same URLs.
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The parse has ended.
+ ClearOpQueue(); // clear in order to be able to assert in destructor
+ return rv;
+ }
+
+ if (mFlushState != eNotFlushing) {
+ // XXX Can this happen? In case it can, let's avoid crashing.
+ return rv;
+ }
+
+ // avoid crashing near EOF
+ RefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
+ RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
+ Unused << parserKungFuDeathGrip; // Intentionally not used within function
+ RefPtr<nsHtml5StreamParser> streamParserGrip;
+ if (mParser) {
+ streamParserGrip = GetParser()->GetStreamParser();
+ }
+ Unused << streamParserGrip; // Intentionally not used within function
+
+ MOZ_RELEASE_ASSERT(!mReadingFromStage,
+ "Got doc write flush when reading from stage");
+
+#ifdef DEBUG
+ mStage.AssertEmpty();
+#endif
+
+ nsIContent* scriptElement = nullptr;
+ bool interrupted = false;
+ bool streamEnded = false;
+
+ {
+ // autoFlush clears mOpQueue in its destructor.
+ nsHtml5AutoFlush autoFlush(this);
+
+ nsHtml5TreeOperation* start = mOpQueue.Elements();
+ nsHtml5TreeOperation* end = start + mOpQueue.Length();
+ for (nsHtml5TreeOperation* iter = start; iter < end; ++iter) {
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The previous tree op caused a call to nsIParser::Terminate().
+ return rv;
+ }
+ NS_ASSERTION(IsInDocUpdate(),
+ "Tried to perform tree op outside update batch.");
+ rv = iter->Perform(this, &scriptElement, &interrupted, &streamEnded);
+ if (NS_FAILED(rv)) {
+ MarkAsBroken(rv);
+ break;
+ }
+ }
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The parse ended during an update pause.
+ return rv;
+ }
+ if (streamEnded) {
+ // This should be redundant but let's do it just in case.
+ GetParser()->PermanentlyUndefineInsertionPoint();
+ }
+ } // autoFlush
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // Ending the doc update caused a call to nsIParser::Terminate().
+ return rv;
+ }
+
+ if (streamEnded) {
+ DidBuildModel(false);
+#ifdef DEBUG
+ if (scriptElement) {
+ nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(scriptElement);
+ if (!sele) {
+ MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled,
+ "Node didn't QI to script, but SVG wasn't disabled.");
+ }
+ MOZ_ASSERT(sele->IsMalformed(), "Script wasn't marked as malformed.");
+ }
+#endif
+ } else if (scriptElement) {
+ // must be tail call when mFlushState is eNotFlushing
+ RunScript(scriptElement, true);
+ }
+ return rv;
+}
+
+void nsHtml5TreeOpExecutor::CommitToInternalEncoding() {
+ if (MOZ_UNLIKELY(!mParser || !mStreamParser)) {
+ // An extension terminated the parser from a HTTP observer.
+ ClearOpQueue(); // clear in order to be able to assert in destructor
+ return;
+ }
+ mStreamParser->ContinueAfterScriptsOrEncodingCommitment(nullptr, nullptr,
+ false);
+}
+
+[[nodiscard]] bool nsHtml5TreeOpExecutor::TakeOpsFromStage() {
+ return mStage.MoveOpsTo(mOpQueue);
+}
+
+// copied from HTML content sink
+bool nsHtml5TreeOpExecutor::IsScriptEnabled() {
+ // Note that if we have no document or no docshell or no global or whatnot we
+ // want to claim script _is_ enabled, so we don't parse the contents of
+ // <noscript> tags!
+ if (!mDocument || !mDocShell) {
+ return true;
+ }
+
+ return mDocument->IsScriptEnabled();
+}
+
+void nsHtml5TreeOpExecutor::StartLayout(bool* aInterrupted) {
+ if (mLayoutStarted || !mDocument) {
+ return;
+ }
+
+ nsHtml5AutoPauseUpdate autoPause(this);
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // got terminate
+ return;
+ }
+
+ nsContentSink::StartLayout(false);
+
+ if (mParser) {
+ *aInterrupted = !GetParser()->IsParserEnabled();
+ }
+}
+
+void nsHtml5TreeOpExecutor::PauseDocUpdate(bool* aInterrupted) {
+ // Pausing the document update allows JS to run, and potentially block
+ // further parsing.
+ nsHtml5AutoPauseUpdate autoPause(this);
+
+ if (MOZ_LIKELY(mParser)) {
+ *aInterrupted = !GetParser()->IsParserEnabled();
+ }
+}
+
+/**
+ * The reason why this code is here and not in the tree builder even in the
+ * main-thread case is to allow the control to return from the tokenizer
+ * before scripts run. This way, the tokenizer is not invoked re-entrantly
+ * although the parser is.
+ *
+ * The reason why this is called with `aMayDocumentWriteOrBlock=true` as a
+ * tail call when `mFlushState` is set to `eNotFlushing` is to allow re-entry
+ * to `Flush()` but only after the current `Flush()` has cleared the op queue
+ * and is otherwise done cleaning up after itself.
+ */
+void nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement,
+ bool aMayDocumentWriteOrBlock) {
+ if (mRunsToCompletion) {
+ // We are in createContextualFragment() or in the upcoming document.parse().
+ // Do nothing. Let's not even mark scripts malformed here, because that
+ // could cause serialization weirdness later.
+ return;
+ }
+
+ MOZ_ASSERT(mParser, "Trying to run script with a terminated parser.");
+ MOZ_ASSERT(aScriptElement, "No script to run");
+ nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement);
+ if (!sele) {
+ MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled,
+ "Node didn't QI to script, but SVG wasn't disabled.");
+ return;
+ }
+
+ sele->SetCreatorParser(GetParser());
+
+ if (!aMayDocumentWriteOrBlock) {
+ MOZ_ASSERT(sele->GetScriptDeferred() || sele->GetScriptAsync() ||
+ sele->GetScriptIsModule() || sele->GetScriptIsImportMap() ||
+ aScriptElement->AsElement()->HasAttr(nsGkAtoms::nomodule));
+ DebugOnly<bool> block = sele->AttemptToExecute();
+ MOZ_ASSERT(!block,
+ "Defer, async, module, importmap, or nomodule tried to block.");
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(
+ mFlushState == eNotFlushing,
+ "Tried to run a potentially-blocking script while flushing.");
+
+ mReadingFromStage = false;
+
+ // Copied from nsXMLContentSink
+ // Now tell the script that it's ready to go. This may execute the script
+ // or return true, or neither if the script doesn't need executing.
+ bool block = sele->AttemptToExecute();
+
+ // If the act of insertion evaluated the script, we're fine.
+ // Else, block the parser till the script has loaded.
+ if (block) {
+ if (mParser) {
+ GetParser()->BlockParser();
+ }
+ } else {
+ // mParser may have been nulled out by now, but the flusher deals
+
+ // If this event isn't needed, it doesn't do anything. It is sometimes
+ // necessary for the parse to continue after complex situations.
+ nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
+ }
+}
+
+void nsHtml5TreeOpExecutor::Start() {
+ MOZ_ASSERT(!mStarted, "Tried to start when already started.");
+ mStarted = true;
+}
+
+void nsHtml5TreeOpExecutor::UpdateCharsetSource(
+ nsCharsetSource aCharsetSource) {
+ if (mDocument) {
+ mDocument->SetDocumentCharacterSetSource(aCharsetSource);
+ }
+}
+
+void nsHtml5TreeOpExecutor::SetDocumentCharsetAndSource(
+ NotNull<const Encoding*> aEncoding, nsCharsetSource aCharsetSource) {
+ if (mDocument) {
+ mDocument->SetDocumentCharacterSetSource(aCharsetSource);
+ mDocument->SetDocumentCharacterSet(aEncoding);
+ }
+}
+
+void nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(
+ NotNull<const Encoding*> aEncoding, int32_t aSource, uint32_t aLineNumber) {
+ nsHtml5AutoPauseUpdate autoPause(this);
+ if (MOZ_UNLIKELY(!mParser)) {
+ // got terminate
+ return;
+ }
+
+ if (!mDocShell) {
+ return;
+ }
+
+ RefPtr<nsDocShell> docShell = static_cast<nsDocShell*>(mDocShell.get());
+
+ if (NS_SUCCEEDED(docShell->CharsetChangeStopDocumentLoad())) {
+ docShell->CharsetChangeReloadDocument(aEncoding, aSource);
+ }
+ // if the charset switch was accepted, mDocShell has called Terminate() on the
+ // parser by now
+ if (!mParser) {
+ return;
+ }
+
+ GetParser()->ContinueAfterFailedCharsetSwitch();
+}
+
+void nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId,
+ bool aError,
+ uint32_t aLineNumber) {
+ // Encoding errors don't count towards already complaining
+ if (!(!strcmp(aMsgId, "EncError") || !strcmp(aMsgId, "EncErrorFrame") ||
+ !strcmp(aMsgId, "EncErrorFramePlain"))) {
+ if (mAlreadyComplainedAboutCharset) {
+ return;
+ }
+ mAlreadyComplainedAboutCharset = true;
+ }
+ nsContentUtils::ReportToConsole(
+ aError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag,
+ "HTML parser"_ns, mDocument, nsContentUtils::eHTMLPARSER_PROPERTIES,
+ aMsgId, nsTArray<nsString>(), nullptr, u""_ns, aLineNumber);
+}
+
+void nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset(
+ Document* aDoc, bool aUnrecognized) {
+ NS_ASSERTION(!mAlreadyComplainedAboutCharset,
+ "How come we already managed to complain?");
+ mAlreadyComplainedAboutCharset = true;
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::errorFlag, "HTML parser"_ns, aDoc,
+ nsContentUtils::eHTMLPARSER_PROPERTIES,
+ aUnrecognized ? "EncProtocolUnsupported" : "EncProtocolReplacement");
+}
+
+void nsHtml5TreeOpExecutor::MaybeComplainAboutDeepTree(uint32_t aLineNumber) {
+ if (mAlreadyComplainedAboutDeepTree) {
+ return;
+ }
+ mAlreadyComplainedAboutDeepTree = true;
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::errorFlag, "HTML parser"_ns, mDocument,
+ nsContentUtils::eHTMLPARSER_PROPERTIES, "errDeepTree",
+ nsTArray<nsString>(), nullptr, u""_ns, aLineNumber);
+}
+
+nsHtml5Parser* nsHtml5TreeOpExecutor::GetParser() {
+ MOZ_ASSERT(!mRunsToCompletion);
+ return static_cast<nsHtml5Parser*>(mParser.get());
+}
+
+[[nodiscard]] bool nsHtml5TreeOpExecutor::MoveOpsFrom(
+ nsTArray<nsHtml5TreeOperation>& aOpQueue) {
+ MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
+ "Ops added to mOpQueue during tree op execution.");
+ return !!mOpQueue.AppendElements(std::move(aOpQueue), mozilla::fallible_t());
+}
+
+void nsHtml5TreeOpExecutor::ClearOpQueue() {
+ MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
+ "mOpQueue cleared during tree op execution.");
+ mOpQueue.Clear();
+}
+
+void nsHtml5TreeOpExecutor::RemoveFromStartOfOpQueue(
+ size_t aNumberOfOpsToRemove) {
+ MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
+ "Ops removed from mOpQueue during tree op execution.");
+ mOpQueue.RemoveElementsAt(0, aNumberOfOpsToRemove);
+}
+
+void nsHtml5TreeOpExecutor::InitializeDocWriteParserState(
+ nsAHtml5TreeBuilderState* aState, int32_t aLine) {
+ GetParser()->InitializeDocWriteParserState(aState, aLine);
+}
+
+nsIURI* nsHtml5TreeOpExecutor::GetViewSourceBaseURI() {
+ if (!mViewSourceBaseURI) {
+ // We query the channel for the baseURI because in certain situations it
+ // cannot otherwise be determined. If this process fails, fall back to the
+ // standard method.
+ nsCOMPtr<nsIViewSourceChannel> vsc =
+ do_QueryInterface(mDocument->GetChannel());
+ if (vsc) {
+ nsresult rv = vsc->GetBaseURI(getter_AddRefs(mViewSourceBaseURI));
+ if (NS_SUCCEEDED(rv) && mViewSourceBaseURI) {
+ return mViewSourceBaseURI;
+ }
+ }
+
+ nsCOMPtr<nsIURI> orig = mDocument->GetOriginalURI();
+ if (orig->SchemeIs("view-source")) {
+ nsCOMPtr<nsINestedURI> nested = do_QueryInterface(orig);
+ NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!");
+ nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI));
+ } else {
+ // Fail gracefully if the base URL isn't a view-source: URL.
+ // Not sure if this can ever happen.
+ mViewSourceBaseURI = orig;
+ }
+ }
+ return mViewSourceBaseURI;
+}
+
+bool nsHtml5TreeOpExecutor::IsExternalViewSource() {
+ if (!StaticPrefs::view_source_editor_external()) {
+ return false;
+ }
+ if (mDocumentURI) {
+ return mDocumentURI->SchemeIs("view-source");
+ }
+ return false;
+}
+
+// Speculative loading
+
+nsIURI* nsHtml5TreeOpExecutor::BaseURIForPreload() {
+ // The URL of the document without <base>
+ nsIURI* documentURI = mDocument->GetDocumentURI();
+ // The URL of the document with non-speculative <base>
+ nsIURI* documentBaseURI = mDocument->GetDocBaseURI();
+
+ // If the two above are different, use documentBaseURI. If they are the same,
+ // the document object isn't aware of a <base>, so attempt to use the
+ // mSpeculationBaseURI or, failing, that, documentURI.
+ return (documentURI == documentBaseURI)
+ ? (mSpeculationBaseURI ? mSpeculationBaseURI.get() : documentURI)
+ : documentBaseURI;
+}
+
+already_AddRefed<nsIURI>
+nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYetAndMediaApplies(
+ const nsAString& aURL, const nsAString& aMedia) {
+ nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
+ if (!uri) {
+ return nullptr;
+ }
+
+ if (!MediaApplies(aMedia)) {
+ return nullptr;
+ }
+ return uri.forget();
+}
+
+bool nsHtml5TreeOpExecutor::MediaApplies(const nsAString& aMedia) {
+ using dom::MediaList;
+
+ if (aMedia.IsEmpty()) {
+ return true;
+ }
+ RefPtr<MediaList> media = MediaList::Create(NS_ConvertUTF16toUTF8(aMedia));
+ return media->Matches(*mDocument);
+}
+
+already_AddRefed<nsIURI> nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(
+ const nsAString& aURL) {
+ if (aURL.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsIURI* base = BaseURIForPreload();
+ auto encoding = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, encoding, base);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create a URI");
+ return nullptr;
+ }
+
+ if (ShouldPreloadURI(uri)) {
+ return uri.forget();
+ }
+
+ return nullptr;
+}
+
+bool nsHtml5TreeOpExecutor::ShouldPreloadURI(nsIURI* aURI) {
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, false);
+ return mPreloadedURLs.EnsureInserted(spec);
+}
+
+dom::ReferrerPolicy nsHtml5TreeOpExecutor::GetPreloadReferrerPolicy(
+ const nsAString& aReferrerPolicy) {
+ dom::ReferrerPolicy referrerPolicy =
+ dom::ReferrerInfo::ReferrerPolicyAttributeFromString(aReferrerPolicy);
+ return GetPreloadReferrerPolicy(referrerPolicy);
+}
+
+dom::ReferrerPolicy nsHtml5TreeOpExecutor::GetPreloadReferrerPolicy(
+ ReferrerPolicy aReferrerPolicy) {
+ if (aReferrerPolicy != dom::ReferrerPolicy::_empty) {
+ return aReferrerPolicy;
+ }
+
+ return mDocument->GetPreloadReferrerInfo()->ReferrerPolicy();
+}
+
+void nsHtml5TreeOpExecutor::PreloadScript(
+ const nsAString& aURL, const nsAString& aCharset, const nsAString& aType,
+ const nsAString& aCrossOrigin, const nsAString& aMedia,
+ const nsAString& aNonce, const nsAString& aFetchPriority,
+ const nsAString& aIntegrity, dom::ReferrerPolicy aReferrerPolicy,
+ bool aScriptFromHead, bool aAsync, bool aDefer, bool aLinkPreload) {
+ nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
+ if (!uri) {
+ return;
+ }
+ auto key = PreloadHashKey::CreateAsScript(uri, aCrossOrigin, aType);
+ if (mDocument->Preloads().PreloadExists(key)) {
+ return;
+ }
+ mDocument->ScriptLoader()->PreloadURI(
+ uri, aCharset, aType, aCrossOrigin, aNonce, aFetchPriority, aIntegrity,
+ aScriptFromHead, aAsync, aDefer, aLinkPreload,
+ GetPreloadReferrerPolicy(aReferrerPolicy), 0);
+}
+
+void nsHtml5TreeOpExecutor::PreloadStyle(
+ const nsAString& aURL, const nsAString& aCharset,
+ const nsAString& aCrossOrigin, const nsAString& aMedia,
+ const nsAString& aReferrerPolicy, const nsAString& aNonce,
+ const nsAString& aIntegrity, bool aLinkPreload,
+ const nsAString& aFetchPriority) {
+ nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
+ if (!uri) {
+ return;
+ }
+
+ if (aLinkPreload) {
+ auto hashKey = PreloadHashKey::CreateAsStyle(
+ uri, mDocument->NodePrincipal(),
+ dom::Element::StringToCORSMode(aCrossOrigin),
+ css::eAuthorSheetFeatures);
+ if (mDocument->Preloads().PreloadExists(hashKey)) {
+ return;
+ }
+ }
+
+ mDocument->PreloadStyle(
+ uri, Encoding::ForLabel(aCharset), aCrossOrigin,
+ GetPreloadReferrerPolicy(aReferrerPolicy), aNonce, aIntegrity,
+ aLinkPreload ? css::StylePreloadKind::FromLinkRelPreloadElement
+ : css::StylePreloadKind::FromParser,
+ 0, aFetchPriority);
+}
+
+void nsHtml5TreeOpExecutor::PreloadImage(
+ const nsAString& aURL, const nsAString& aCrossOrigin,
+ const nsAString& aMedia, const nsAString& aSrcset, const nsAString& aSizes,
+ const nsAString& aImageReferrerPolicy, bool aLinkPreload) {
+ nsCOMPtr<nsIURI> baseURI = BaseURIForPreload();
+ bool isImgSet = false;
+ nsCOMPtr<nsIURI> uri =
+ mDocument->ResolvePreloadImage(baseURI, aURL, aSrcset, aSizes, &isImgSet);
+ if (uri && ShouldPreloadURI(uri) && MediaApplies(aMedia)) {
+ // use document wide referrer policy
+ mDocument->MaybePreLoadImage(uri, aCrossOrigin,
+ GetPreloadReferrerPolicy(aImageReferrerPolicy),
+ isImgSet, aLinkPreload);
+ }
+}
+
+// These calls inform the document of picture state and seen sources, such that
+// it can use them to inform ResolvePreLoadImage as necessary
+void nsHtml5TreeOpExecutor::PreloadPictureSource(const nsAString& aSrcset,
+ const nsAString& aSizes,
+ const nsAString& aType,
+ const nsAString& aMedia) {
+ mDocument->PreloadPictureImageSource(aSrcset, aSizes, aType, aMedia);
+}
+
+void nsHtml5TreeOpExecutor::PreloadFont(const nsAString& aURL,
+ const nsAString& aCrossOrigin,
+ const nsAString& aMedia,
+ const nsAString& aReferrerPolicy,
+ const nsAString& aFetchPriority) {
+ nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
+ if (!uri) {
+ return;
+ }
+
+ mDocument->Preloads().PreloadFont(uri, aCrossOrigin, aReferrerPolicy, 0,
+ aFetchPriority);
+}
+
+void nsHtml5TreeOpExecutor::PreloadFetch(const nsAString& aURL,
+ const nsAString& aCrossOrigin,
+ const nsAString& aMedia,
+ const nsAString& aReferrerPolicy,
+ const nsAString& aFetchPriority) {
+ nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
+ if (!uri) {
+ return;
+ }
+
+ mDocument->Preloads().PreloadFetch(uri, aCrossOrigin, aReferrerPolicy, 0,
+ aFetchPriority);
+}
+
+void nsHtml5TreeOpExecutor::PreloadOpenPicture() {
+ mDocument->PreloadPictureOpened();
+}
+
+void nsHtml5TreeOpExecutor::PreloadEndPicture() {
+ mDocument->PreloadPictureClosed();
+}
+
+void nsHtml5TreeOpExecutor::AddBase(const nsAString& aURL) {
+ auto encoding = mDocument->GetDocumentCharacterSet();
+ nsresult rv = NS_NewURI(getter_AddRefs(mViewSourceBaseURI), aURL, encoding,
+ GetViewSourceBaseURI());
+ if (NS_FAILED(rv)) {
+ mViewSourceBaseURI = nullptr;
+ }
+}
+void nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL) {
+ if (mSpeculationBaseURI) {
+ // the first one wins
+ return;
+ }
+
+ auto encoding = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> newBaseURI;
+ DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(newBaseURI), aURL, encoding,
+ mDocument->GetDocumentURI());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to create a URI");
+ if (!newBaseURI) {
+ return;
+ }
+
+ // Check the document's CSP usually delivered via the CSP header.
+ if (nsCOMPtr<nsIContentSecurityPolicy> csp = mDocument->GetCsp()) {
+ // base-uri should not fallback to the default-src and preloads should not
+ // trigger violation reports.
+ bool cspPermitsBaseURI = true;
+ nsresult rv = csp->Permits(
+ nullptr, nullptr, newBaseURI,
+ nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true /* aSpecific */,
+ false /* aSendViolationReports */, &cspPermitsBaseURI);
+ if (NS_FAILED(rv) || !cspPermitsBaseURI) {
+ return;
+ }
+ }
+
+ // Also check the CSP discovered from the <meta> tag during speculative
+ // parsing.
+ if (nsCOMPtr<nsIContentSecurityPolicy> csp = mDocument->GetPreloadCsp()) {
+ bool cspPermitsBaseURI = true;
+ nsresult rv = csp->Permits(
+ nullptr, nullptr, newBaseURI,
+ nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true /* aSpecific */,
+ false /* aSendViolationReports */, &cspPermitsBaseURI);
+ if (NS_FAILED(rv) || !cspPermitsBaseURI) {
+ return;
+ }
+ }
+
+ mSpeculationBaseURI = newBaseURI;
+ mDocument->Preloads().SetSpeculationBase(mSpeculationBaseURI);
+}
+
+void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta(
+ const nsAString& aMetaReferrer) {
+ mDocument->UpdateReferrerInfoFromMeta(aMetaReferrer, true);
+}
+
+void nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP) {
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = mDocument->GetPreloadCsp();
+ if (!preloadCsp) {
+ RefPtr<nsCSPContext> csp = new nsCSPContext();
+ csp->SuppressParserLogMessages();
+ preloadCsp = csp;
+ rv = preloadCsp->SetRequestContextWithDocument(mDocument);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+
+ // Please note that multiple meta CSPs need to be joined together.
+ rv = preloadCsp->AppendPolicy(
+ aCSP,
+ false, // csp via meta tag can not be report only
+ true); // delivered through the meta tag
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsPIDOMWindowInner* inner = mDocument->GetInnerWindow();
+ if (inner) {
+ inner->SetPreloadCsp(preloadCsp);
+ }
+ mDocument->ApplySettingsFromCSP(true);
+}
+
+#ifdef DEBUG
+uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
+uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
+uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
+uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0;
+uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0;
+#endif