diff options
Diffstat (limited to 'parser/html/nsHtml5TreeOpExecutor.cpp')
-rw-r--r-- | parser/html/nsHtml5TreeOpExecutor.cpp | 1414 |
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 |