From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- parser/html/nsHtml5TreeOpExecutor.cpp | 1405 +++++++++++++++++++++++++++++++++ 1 file changed, 1405 insertions(+) create mode 100644 parser/html/nsHtml5TreeOpExecutor.cpp (limited to 'parser/html/nsHtml5TreeOpExecutor.cpp') diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp new file mode 100644 index 0000000000..b74246fdca --- /dev/null +++ b/parser/html/nsHtml5TreeOpExecutor.cpp @@ -0,0 +1,1405 @@ +/* -*- 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; + +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 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 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 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* gBackgroundFlushList = nullptr; +StaticRefPtr 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 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 channel; + nsresult rv = GetParser()->GetChannel(getter_AddRefs(channel)); + if (NS_SUCCEEDED(rv) && channel) { + nsCOMPtr 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 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 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 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(); + } + 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 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 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 parserKungFuDeathGrip(mParser); + RefPtr 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 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 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 kungFuDeathGrip(this); + RefPtr parserKungFuDeathGrip(mParser); + Unused << parserKungFuDeathGrip; // Intentionally not used within function + RefPtr 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 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 + //