/* -*- 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 "nsIScriptContext.h" #include "nsIScriptError.h" #include "nsIScriptGlobalObject.h" #include "nsIViewSourceChannel.h" #include "nsNetUtil.h" #include "xpcpublic.h" using namespace mozilla; static LazyLogModule gCharsetMenuLog("Chardetng"); #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(TaskCategory::Network, 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 (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_NS_HTML5_TREE_OP_EXECUTOR_FLUSH printf("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize); if (sAppendBatchExaminations != 0) { printf("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(TaskCategory::Network, 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(TaskCategory::Network, 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_NS_HTML5_TREE_OP_EXECUTOR_FLUSH uint32_t mStartTime; #endif public: explicit nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor) : mExecutor(aExecutor) #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH , mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow())) #endif { mExecutor->mRunFlushLoopOnStack = true; } ~nsHtml5FlushLoopGuard() { #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH uint32_t timeOffTheEventLoop = PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime; if (timeOffTheEventLoop > nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) { nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = timeOffTheEventLoop; } printf("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); // Always check the clock in nsContentSink right after a script StopDeflecting(); if (nsContentSink::DidProcessATokenImpl() == NS_ERROR_HTMLPARSER_INTERRUPTED) { #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH printf("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); } 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 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) { 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; } if (sele->GetScriptDeferred() || sele->GetScriptAsync()) { DebugOnly<bool> block = sele->AttemptToExecute(); NS_ASSERTION(!block, "Defer or async script tried to block."); return; } MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing, "Tried to run script while flushing."); mReadingFromStage = false; sele->SetCreatorParser(GetParser()); // 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; } 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& aIntegrity, dom::ReferrerPolicy aReferrerPolicy, bool aScriptFromHead, bool aAsync, bool aDefer, bool aNoModule, 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, aIntegrity, aScriptFromHead, aAsync, aDefer, aNoModule, aLinkPreload, GetPreloadReferrerPolicy(aReferrerPolicy), 0); } void nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL, const nsAString& aCharset, const nsAString& aCrossOrigin, const nsAString& aMedia, const nsAString& aReferrerPolicy, const nsAString& aIntegrity, bool aLinkPreload) { 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), aIntegrity, aLinkPreload ? css::StylePreloadKind::FromLinkRelPreloadElement : css::StylePreloadKind::FromParser, 0); } void nsHtml5TreeOpExecutor::PreloadImage( const nsAString& aURL, const nsAString& aCrossOrigin, const nsAString& aMedia, const nsAString& aSrcset, const nsAString& aSizes, const nsAString& aImageReferrerPolicy, bool aLinkPreload, const TimeStamp& aInitTimestamp) { 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, aInitTimestamp); } } // 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) { nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia); if (!uri) { return; } mDocument->Preloads().PreloadFont(uri, aCrossOrigin, aReferrerPolicy, 0); } void nsHtml5TreeOpExecutor::PreloadFetch(const nsAString& aURL, const nsAString& aCrossOrigin, const nsAString& aMedia, const nsAString& aReferrerPolicy) { nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia); if (!uri) { return; } mDocument->Preloads().PreloadFetch(uri, aCrossOrigin, aReferrerPolicy, 0); } 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_NS_HTML5_TREE_OP_EXECUTOR_FLUSH 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