/* -*- 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