tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsHtml5TreeOpExecutor.cpp (48671B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set sw=2 ts=2 et tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/DebugOnly.h"
      8 #include "mozilla/Likely.h"
      9 #include "mozilla/dom/BrowsingContext.h"
     10 #include "mozilla/dom/MediaList.h"
     11 #include "mozilla/dom/ScriptLoader.h"
     12 #include "mozilla/dom/nsCSPContext.h"
     13 #include "mozilla/dom/nsCSPService.h"
     14 #include "mozilla/dom/PolicyContainer.h"
     15 
     16 #include "imgLoader.h"
     17 #include "mozAutoDocUpdate.h"
     18 #include "mozilla/IdleTaskRunner.h"
     19 #include "mozilla/Preferences.h"
     20 #include "mozilla/ProfilerLabels.h"
     21 #include "mozilla/ProfilerMarkers.h"
     22 #include "mozilla/StaticPrefs_content.h"
     23 #include "mozilla/StaticPrefs_security.h"
     24 #include "mozilla/StaticPrefs_view_source.h"
     25 #include "mozilla/css/Loader.h"
     26 #include "mozilla/fallible.h"
     27 #include "nsContentUtils.h"
     28 #include "nsDocShell.h"
     29 #include "nsError.h"
     30 #include "nsHTMLDocument.h"
     31 #include "nsHtml5AutoPauseUpdate.h"
     32 #include "nsHtml5Parser.h"
     33 #include "nsHtml5StreamParser.h"
     34 #include "nsHtml5Tokenizer.h"
     35 #include "nsHtml5TreeBuilder.h"
     36 #include "nsHtml5TreeOpExecutor.h"
     37 #include "nsIContentSecurityPolicy.h"
     38 #include "nsIDocShell.h"
     39 #include "nsIDocShellTreeItem.h"
     40 #include "nsINestedURI.h"
     41 #include "nsIHttpChannel.h"
     42 #include "nsIScriptContext.h"
     43 #include "nsIScriptError.h"
     44 #include "nsIScriptGlobalObject.h"
     45 #include "nsIViewSourceChannel.h"
     46 #include "nsNetUtil.h"
     47 #include "xpcpublic.h"
     48 
     49 using namespace mozilla;
     50 
     51 #ifdef DEBUG
     52 static LazyLogModule gHtml5TreeOpExecutorLog("Html5TreeOpExecutor");
     53 #endif  // DEBUG
     54 static LazyLogModule gCharsetMenuLog("Chardetng");
     55 
     56 #define LOG(args) MOZ_LOG(gHtml5TreeOpExecutorLog, LogLevel::Debug, args)
     57 #define LOGCHARDETNG(args) MOZ_LOG(gCharsetMenuLog, LogLevel::Debug, args)
     58 
     59 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor,
     60                                             nsHtml5DocumentBuilder,
     61                                             nsIContentSink)
     62 
     63 class nsHtml5ExecutorReflusher : public Runnable {
     64 private:
     65  RefPtr<nsHtml5TreeOpExecutor> mExecutor;
     66 
     67 public:
     68  explicit nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
     69      : Runnable("nsHtml5ExecutorReflusher"), mExecutor(aExecutor) {}
     70  NS_IMETHOD Run() override {
     71    dom::Document* doc = mExecutor->GetDocument();
     72    if (XRE_IsContentProcess() &&
     73        nsContentUtils::
     74            HighPriorityEventPendingForTopLevelDocumentBeforeContentfulPaint(
     75                doc)) {
     76      // Possible early paint pending, reuse the runnable and try to
     77      // call RunFlushLoop later.
     78      nsCOMPtr<nsIRunnable> flusher = this;
     79      if (NS_SUCCEEDED(doc->Dispatch(flusher.forget()))) {
     80        PROFILER_MARKER_UNTYPED("HighPrio blocking parser flushing(2)", DOM);
     81        return NS_OK;
     82      }
     83    }
     84    mExecutor->RunFlushLoop();
     85    return NS_OK;
     86  }
     87 };
     88 
     89 class MOZ_RAII nsHtml5AutoFlush final {
     90 private:
     91  RefPtr<nsHtml5TreeOpExecutor> mExecutor;
     92  size_t mOpsToRemove;
     93 
     94 public:
     95  explicit nsHtml5AutoFlush(nsHtml5TreeOpExecutor* aExecutor)
     96      : mExecutor(aExecutor), mOpsToRemove(aExecutor->OpQueueLength()) {
     97    mExecutor->BeginFlush();
     98    mExecutor->BeginDocUpdate();
     99  }
    100  ~nsHtml5AutoFlush() {
    101    if (mExecutor->IsInDocUpdate()) {
    102      mExecutor->EndDocUpdate();
    103    } else {
    104      // We aren't in an update if nsHtml5AutoPauseUpdate
    105      // caused something to terminate the parser.
    106      MOZ_RELEASE_ASSERT(
    107          mExecutor->IsComplete(),
    108          "How do we have mParser but the doc update isn't open?");
    109    }
    110    mExecutor->EndFlush();
    111    if (mExecutor->IsComplete()) {
    112      // `mExecutor->EndDocUpdate()` caused a call to `nsIParser::Terminate`,
    113      // so now we should clear the whole op queue in order to be able to
    114      // assert in the destructor of `nsHtml5TreeOpExecutor`.
    115      mOpsToRemove = mExecutor->OpQueueLength();
    116    }
    117    mExecutor->RemoveFromStartOfOpQueue(mOpsToRemove);
    118    // We might have missed a speculative load flush due to sync XHR
    119    mExecutor->FlushSpeculativeLoads();
    120  }
    121  void SetNumberOfOpsToRemove(size_t aOpsToRemove) {
    122    MOZ_ASSERT(aOpsToRemove < mOpsToRemove,
    123               "Requested partial clearing of op queue but the number to clear "
    124               "wasn't less than the length of the queue.");
    125    mOpsToRemove = aOpsToRemove;
    126  }
    127 };
    128 
    129 static LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr;
    130 StaticRefPtr<IdleTaskRunner> gBackgroundFlushRunner;
    131 
    132 nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
    133    : nsHtml5DocumentBuilder(false),
    134      mSuppressEOF(false),
    135      mReadingFromStage(false),
    136      mStreamParser(nullptr),
    137      mPreloadedURLs(23),  // Mean # of preloadable resources per page on dmoz
    138      mStarted(false),
    139      mRunFlushLoopOnStack(false),
    140      mCallContinueInterruptedParsingIfEnabled(false),
    141      mAlreadyComplainedAboutCharset(false),
    142      mAlreadyComplainedAboutDeepTree(false) {}
    143 
    144 nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor() {
    145  if (gBackgroundFlushList && isInList()) {
    146    ClearOpQueue();
    147    removeFrom(*gBackgroundFlushList);
    148    if (gBackgroundFlushList->isEmpty()) {
    149      delete gBackgroundFlushList;
    150      gBackgroundFlushList = nullptr;
    151      if (gBackgroundFlushRunner) {
    152        gBackgroundFlushRunner->Cancel();
    153        gBackgroundFlushRunner = nullptr;
    154      }
    155    }
    156  }
    157  MOZ_ASSERT(NS_FAILED(mBroken) || mOpQueue.IsEmpty(),
    158             "Somehow there's stuff in the op queue.");
    159 }
    160 
    161 // nsIContentSink
    162 NS_IMETHODIMP
    163 nsHtml5TreeOpExecutor::WillParse() {
    164  MOZ_ASSERT_UNREACHABLE("No one should call this");
    165  return NS_ERROR_NOT_IMPLEMENTED;
    166 }
    167 
    168 NS_IMETHODIMP
    169 nsHtml5TreeOpExecutor::WillBuildModel() {
    170  mDocument->AddObserver(this);
    171  WillBuildModelImpl();
    172  GetDocument()->BeginLoad();
    173  if (mDocShell && !GetDocument()->GetWindow() && !IsExternalViewSource()) {
    174    // Not loading as data but script global object not ready
    175    return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR);
    176  }
    177  return NS_OK;
    178 }
    179 
    180 // This is called when the tree construction has ended
    181 NS_IMETHODIMP
    182 nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated) {
    183  if (mRunsToCompletion) {
    184    return NS_OK;
    185  }
    186 
    187  MOZ_RELEASE_ASSERT(!IsInDocUpdate(),
    188                     "DidBuildModel from inside a doc update.");
    189 
    190  RefPtr<nsHtml5TreeOpExecutor> pin(this);
    191  auto queueClearer = MakeScopeExit([&] {
    192    if (aTerminated && (mFlushState == eNotFlushing)) {
    193      ClearOpQueue();  // clear in order to be able to assert in destructor
    194    }
    195  });
    196 
    197  // This comes from nsXMLContentSink and the old (now removed)
    198  // nsHTMLContentSink. If this parser has been marked as broken, treat the end
    199  // of parse as forced termination.
    200  DidBuildModelImpl(aTerminated || NS_FAILED(IsBroken()));
    201 
    202  bool destroying = true;
    203  if (mDocShell) {
    204    mDocShell->IsBeingDestroyed(&destroying);
    205  }
    206 
    207  if (!destroying) {
    208    mDocument->OnParsingCompleted();
    209 
    210    if (!mLayoutStarted) {
    211      // We never saw the body, and layout never got started. Force
    212      // layout *now*, to get an initial reflow.
    213 
    214      // NOTE: only force the layout if we are NOT destroying the
    215      // docshell. If we are destroying it, then starting layout will
    216      // likely cause us to crash, or at best waste a lot of time as we
    217      // are just going to tear it down anyway.
    218      nsContentSink::StartLayout(false);
    219    }
    220  }
    221 
    222  ScrollToRef();
    223  mDocument->RemoveObserver(this);
    224  if (!mParser) {
    225    // DidBuildModelImpl may cause mParser to be nulled out
    226    // Return early to avoid unblocking the onload event too many times.
    227    return NS_OK;
    228  }
    229 
    230  // We may not have called BeginLoad() if loading is terminated before
    231  // OnStartRequest call.
    232  if (mStarted) {
    233    mDocument->EndLoad();
    234 
    235    // Log outcome only for top-level content navigations in order to
    236    // avoid noise from ad iframes.
    237    bool topLevel = false;
    238    if (mozilla::dom::BrowsingContext* bc = mDocument->GetBrowsingContext()) {
    239      topLevel = bc->IsTopContent();
    240    }
    241 
    242    // Log outcome only for text/html and text/plain (excluding CSS, JS,
    243    // etc. being viewed as text.)
    244    nsAutoString contentType;
    245    mDocument->GetContentType(contentType);
    246    bool htmlOrPlain = contentType.EqualsLiteral(u"text/html") ||
    247                       contentType.EqualsLiteral(u"text/plain");
    248 
    249    // Log outcome only for HTTP status code 200 in order to exclude
    250    // error pages.
    251    bool httpOk = false;
    252    nsCOMPtr<nsIChannel> channel;
    253    nsresult rv = GetParser()->GetChannel(getter_AddRefs(channel));
    254    if (NS_SUCCEEDED(rv) && channel) {
    255      nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
    256      if (httpChannel) {
    257        uint32_t httpStatus;
    258        rv = httpChannel->GetResponseStatus(&httpStatus);
    259        if (NS_SUCCEEDED(rv) && httpStatus == 200) {
    260          httpOk = true;
    261        }
    262      }
    263    }
    264 
    265    // Log chardetng outcome
    266    MOZ_ASSERT(mDocument->IsHTMLDocument());
    267    if (httpOk && htmlOrPlain && topLevel && !aTerminated &&
    268        !mDocument->AsHTMLDocument()->IsViewSource()) {
    269      // We deliberately measure only normally-completed (non-aborted) loads
    270      // that are not View Source loads. This seems like a better place for
    271      // checking normal completion than anything in nsHtml5StreamParser.
    272      bool plain = mDocument->AsHTMLDocument()->IsPlainText();
    273      int32_t charsetSource = mDocument->GetDocumentCharacterSetSource();
    274      switch (charsetSource) {
    275        case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8:
    276          if (plain) {
    277            LOGCHARDETNG(("TEXT::UtfInitial"));
    278          } else {
    279            LOGCHARDETNG(("HTML::UtfInitial"));
    280          }
    281          break;
    282        case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic:
    283          if (plain) {
    284            LOGCHARDETNG(("TEXT::GenericInitial"));
    285          } else {
    286            LOGCHARDETNG(("HTML::GenericInitial"));
    287          }
    288          break;
    289        case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content:
    290          if (plain) {
    291            LOGCHARDETNG(("TEXT::ContentInitial"));
    292          } else {
    293            LOGCHARDETNG(("HTML::ContentInitial"));
    294          }
    295          break;
    296        case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
    297          if (plain) {
    298            LOGCHARDETNG(("TEXT::TldInitial"));
    299          } else {
    300            LOGCHARDETNG(("HTML::TldInitial"));
    301          }
    302          break;
    303        case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII:
    304          if (plain) {
    305            LOGCHARDETNG(("TEXT::UtfFinal"));
    306          } else {
    307            LOGCHARDETNG(("HTML::UtfFinal"));
    308          }
    309          break;
    310        case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic:
    311          if (plain) {
    312            LOGCHARDETNG(("TEXT::GenericFinal"));
    313          } else {
    314            LOGCHARDETNG(("HTML::GenericFinal"));
    315          }
    316          break;
    317        case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII:
    318          if (plain) {
    319            LOGCHARDETNG(("TEXT::GenericFinalA"));
    320          } else {
    321            LOGCHARDETNG(("HTML::GenericFinalA"));
    322          }
    323          break;
    324        case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content:
    325          if (plain) {
    326            LOGCHARDETNG(("TEXT::ContentFinal"));
    327          } else {
    328            LOGCHARDETNG(("HTML::ContentFinal"));
    329          }
    330          break;
    331        case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII:
    332          if (plain) {
    333            LOGCHARDETNG(("TEXT::ContentFinalA"));
    334          } else {
    335            LOGCHARDETNG(("HTML::ContentFinalA"));
    336          }
    337          break;
    338        case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
    339          if (plain) {
    340            LOGCHARDETNG(("TEXT::TldFinal"));
    341          } else {
    342            LOGCHARDETNG(("HTML::TldFinal"));
    343          }
    344          break;
    345        case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII:
    346          if (plain) {
    347            LOGCHARDETNG(("TEXT::TldFinalA"));
    348          } else {
    349            LOGCHARDETNG(("HTML::TldFinalA"));
    350          }
    351          break;
    352        default:
    353          // Chardetng didn't run automatically or the input was all ASCII.
    354          break;
    355      }
    356    }
    357  }
    358 
    359  // Dropping the stream parser changes the parser's apparent
    360  // script-createdness, which is why the stream parser must not be dropped
    361  // before this executor's nsHtml5Parser has been made unreachable from its
    362  // nsHTMLDocument. (mDocument->EndLoad() above drops the parser from the
    363  // document.)
    364  GetParser()->DropStreamParser();
    365  DropParserAndPerfHint();
    366 #ifdef GATHER_DOCWRITE_STATISTICS
    367  printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites);
    368  printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites);
    369  printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites);
    370 #endif
    371 #ifdef DEBUG
    372  LOG(("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize));
    373  if (sAppendBatchExaminations != 0) {
    374    LOG(("AVERAGE SLOTS EXAMINED: %d\n",
    375         sAppendBatchSlotsExamined / sAppendBatchExaminations));
    376  }
    377 #endif
    378  return NS_OK;
    379 }
    380 
    381 NS_IMETHODIMP
    382 nsHtml5TreeOpExecutor::WillInterrupt() {
    383  MOZ_ASSERT_UNREACHABLE("Don't call. For interface compat only.");
    384  return NS_ERROR_NOT_IMPLEMENTED;
    385 }
    386 
    387 void nsHtml5TreeOpExecutor::WillResume() {
    388  MOZ_ASSERT_UNREACHABLE("Don't call. For interface compat only.");
    389 }
    390 
    391 NS_IMETHODIMP
    392 nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser) {
    393  mParser = aParser;
    394  return NS_OK;
    395 }
    396 
    397 void nsHtml5TreeOpExecutor::InitialTranslationCompleted() {
    398  nsContentSink::StartLayout(false);
    399 }
    400 
    401 void nsHtml5TreeOpExecutor::FlushPendingNotifications(FlushType aType) {
    402  if (aType >= FlushType::EnsurePresShellInitAndFrames) {
    403    // Bug 577508 / 253951
    404    nsContentSink::StartLayout(true);
    405  }
    406 }
    407 
    408 nsISupports* nsHtml5TreeOpExecutor::GetTarget() {
    409  return ToSupports(mDocument);
    410 }
    411 
    412 nsresult nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason) {
    413  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
    414  mBroken = aReason;
    415  if (mStreamParser) {
    416    mStreamParser->Terminate();
    417  }
    418  // We are under memory pressure, but let's hope the following allocation
    419  // works out so that we get to terminate and clean up the parser from
    420  // a safer point.
    421  if (mParser && mDocument) {  // can mParser ever be null here?
    422    nsCOMPtr<nsIRunnable> terminator = NewRunnableMethod(
    423        "nsHtml5Parser::Terminate", GetParser(), &nsHtml5Parser::Terminate);
    424    if (NS_FAILED(mDocument->Dispatch(terminator.forget()))) {
    425      NS_WARNING("failed to dispatch executor flush event");
    426    }
    427  }
    428  return aReason;
    429 }
    430 
    431 static bool BackgroundFlushCallback(TimeStamp /*aDeadline*/) {
    432  RefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst();
    433  if (ex) {
    434    ex->RunFlushLoop();
    435  }
    436  if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) {
    437    delete gBackgroundFlushList;
    438    gBackgroundFlushList = nullptr;
    439    gBackgroundFlushRunner->Cancel();
    440    gBackgroundFlushRunner = nullptr;
    441    return true;
    442  }
    443  return true;
    444 }
    445 
    446 void nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync() {
    447  if (mDocument && !mDocument->IsInBackgroundWindow()) {
    448    nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);
    449    if (NS_FAILED(mDocument->Dispatch(flusher.forget()))) {
    450      NS_WARNING("failed to dispatch executor flush event");
    451    }
    452  } else {
    453    if (!gBackgroundFlushList) {
    454      gBackgroundFlushList = new LinkedList<nsHtml5TreeOpExecutor>();
    455    }
    456    if (!isInList()) {
    457      gBackgroundFlushList->insertBack(this);
    458    }
    459    if (gBackgroundFlushRunner) {
    460      return;
    461    }
    462    // Now we set up a repetitive idle scheduler for flushing background list.
    463    gBackgroundFlushRunner = IdleTaskRunner::Create(
    464        &BackgroundFlushCallback,
    465        "nsHtml5TreeOpExecutor::BackgroundFlushCallback"_ns,
    466        0,  // Start looking for idle time immediately.
    467        TimeDuration::FromMilliseconds(250),  // The hard deadline.
    468        TimeDuration::FromMicroseconds(
    469            StaticPrefs::content_sink_interactive_parse_time()),  // Required
    470                                                                  // budget.
    471        true,                                                     // repeating
    472        [] { return false; });  // MayStopProcessing
    473  }
    474 }
    475 
    476 void nsHtml5TreeOpExecutor::FlushSpeculativeLoads() {
    477  nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
    478  mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
    479  nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
    480  nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
    481  for (nsHtml5SpeculativeLoad* iter = start; iter < end; ++iter) {
    482    if (MOZ_UNLIKELY(!mParser)) {
    483      // An extension terminated the parser from a HTTP observer.
    484      return;
    485    }
    486    iter->Perform(this);
    487  }
    488 }
    489 
    490 class nsHtml5FlushLoopGuard {
    491 private:
    492  RefPtr<nsHtml5TreeOpExecutor> mExecutor;
    493 #ifdef DEBUG
    494  uint32_t mStartTime;
    495 #endif
    496 public:
    497  explicit nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor)
    498      : mExecutor(aExecutor)
    499 #ifdef DEBUG
    500        ,
    501        mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow()))
    502 #endif
    503  {
    504    mExecutor->mRunFlushLoopOnStack = true;
    505  }
    506  ~nsHtml5FlushLoopGuard() {
    507 #ifdef DEBUG
    508    uint32_t timeOffTheEventLoop =
    509        PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime;
    510    if (timeOffTheEventLoop >
    511        nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) {
    512      nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = timeOffTheEventLoop;
    513    }
    514    LOG(("Longest time off the event loop: %d\n",
    515         nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop));
    516 #endif
    517 
    518    mExecutor->mRunFlushLoopOnStack = false;
    519  }
    520 };
    521 
    522 /**
    523 * The purpose of the loop here is to avoid returning to the main event loop
    524 */
    525 void nsHtml5TreeOpExecutor::RunFlushLoop() {
    526  AUTO_PROFILER_LABEL("nsHtml5TreeOpExecutor::RunFlushLoop", OTHER);
    527 
    528  if (mRunFlushLoopOnStack) {
    529    // There's already a RunFlushLoop() on the call stack.
    530    // The instance of RunFlushLoop() already on the stack is
    531    // expected to ensure that we don't stall.
    532    return;
    533  }
    534 
    535  nsHtml5FlushLoopGuard guard(this);  // this is also the self-kungfu!
    536 
    537  RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
    538  RefPtr<nsHtml5StreamParser> streamParserGrip;
    539  if (mParser) {
    540    streamParserGrip = GetParser()->GetStreamParser();
    541  }
    542  (void)streamParserGrip;  // Intentionally not used within function
    543 
    544  // Remember the entry time
    545  (void)nsContentSink::WillParseImpl();
    546 
    547  for (;;) {
    548    if (!mParser) {
    549      // Parse has terminated.
    550      ClearOpQueue();  // clear in order to be able to assert in destructor
    551      return;
    552    }
    553 
    554    if (NS_FAILED(IsBroken())) {
    555      return;
    556    }
    557 
    558    if (!parserKungFuDeathGrip->IsParserEnabled()) {
    559      // The parser is blocked.
    560      // Whatever blocked the parser is responsible for ensuring that
    561      // we don't stall.
    562      return;
    563    }
    564 
    565    if (mFlushState != eNotFlushing) {
    566      // It's not clear whether this case can ever happen. It's not
    567      // supposed to, but in case this can happen, let's return
    568      // early to avoid an even worse state. In case this happens,
    569      // there in no obvious other mechanism to prevent stalling,
    570      // so let's call `ContinueInterruptedParsingAsync()` to
    571      // make sure we don't stall.
    572      nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
    573      return;
    574    }
    575 
    576    // If there are scripts executing, this is probably due to a synchronous
    577    // XMLHttpRequest, see bug 460706 and 1938290.
    578    if (IsScriptExecuting()) {
    579      ContinueParsingDocumentAfterCurrentScript();
    580      return;
    581    }
    582 
    583    if (mReadingFromStage) {
    584      nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
    585      MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
    586                         "mOpQueue modified during flush.");
    587      if (!mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue,
    588                                               speculativeLoadQueue)) {
    589        MarkAsBroken(nsresult::NS_ERROR_OUT_OF_MEMORY);
    590        return;
    591      }
    592 
    593      // Make sure speculative loads never start after the corresponding
    594      // normal loads for the same URLs.
    595      nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
    596      nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
    597      for (nsHtml5SpeculativeLoad* iter = start; iter < end; ++iter) {
    598        iter->Perform(this);
    599        if (MOZ_UNLIKELY(!mParser)) {
    600          // An extension terminated the parser from a HTTP observer.
    601          ClearOpQueue();  // clear in order to be able to assert in destructor
    602          return;
    603        }
    604      }
    605    } else {
    606      FlushSpeculativeLoads();  // Make sure speculative loads never start after
    607                                // the corresponding normal loads for the same
    608                                // URLs.
    609      if (MOZ_UNLIKELY(!mParser)) {
    610        // An extension terminated the parser from a HTTP observer.
    611        ClearOpQueue();  // clear in order to be able to assert in destructor
    612        return;
    613      }
    614      // Now parse content left in the document.write() buffer queue if any.
    615      // This may generate tree ops on its own or dequeue a speculation.
    616      nsresult rv = GetParser()->ParseUntilBlocked();
    617 
    618      // ParseUntilBlocked flushes operations from the stage to the OpQueue.
    619      // Those operations may have accompanying speculative operations.
    620      // If so, we have to flush those speculative loads so that we maintain
    621      // the invariant that no speculative load starts after the corresponding
    622      // normal load for the same URL. See
    623      // https://bugzilla.mozilla.org/show_bug.cgi?id=1513292#c80
    624      // for a more detailed explanation of why this is necessary.
    625      FlushSpeculativeLoads();
    626 
    627      if (NS_FAILED(rv)) {
    628        MarkAsBroken(rv);
    629        return;
    630      }
    631    }
    632 
    633    if (mOpQueue.IsEmpty()) {
    634      // Avoid bothering the rest of the engine with a doc update if there's
    635      // nothing to do.
    636      return;
    637    }
    638 
    639    nsIContent* scriptElement = nullptr;
    640    bool interrupted = false;
    641    bool streamEnded = false;
    642 
    643    {
    644      // autoFlush clears mOpQueue in its destructor unless
    645      // SetNumberOfOpsToRemove is called first, in which case only
    646      // some ops from the start of the queue are cleared.
    647      nsHtml5AutoFlush autoFlush(this);
    648      // Profiler marker deliberately not counting layout and script
    649      // execution.
    650      AUTO_PROFILER_MARKER_UNTYPED(
    651          "HTMLParserTreeOps", DOM,
    652          MarkerOptions(MarkerInnerWindowIdFromDocShell(mDocShell)));
    653 
    654      nsHtml5TreeOperation* first = mOpQueue.Elements();
    655      nsHtml5TreeOperation* last = first + mOpQueue.Length() - 1;
    656      for (nsHtml5TreeOperation* iter = first;; ++iter) {
    657        if (MOZ_UNLIKELY(!mParser)) {
    658          // The previous tree op caused a call to nsIParser::Terminate().
    659          return;
    660        }
    661        MOZ_ASSERT(IsInDocUpdate(),
    662                   "Tried to perform tree op outside update batch.");
    663        nsresult rv =
    664            iter->Perform(this, &scriptElement, &interrupted, &streamEnded);
    665        if (NS_FAILED(rv)) {
    666          MarkAsBroken(rv);
    667          break;
    668        }
    669 
    670        // Be sure not to check the deadline if the last op was just performed.
    671        if (MOZ_UNLIKELY(iter == last)) {
    672          break;
    673        } else if (MOZ_UNLIKELY(interrupted) ||
    674                   MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
    675                                NS_ERROR_HTMLPARSER_INTERRUPTED)) {
    676          autoFlush.SetNumberOfOpsToRemove((iter - first) + 1);
    677 
    678          nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
    679          if (!interrupted) {
    680            PROFILER_MARKER_UNTYPED("HTMLParserTreeOpsYieldedOnDeadline", DOM,
    681                                    MarkerInnerWindowIdFromDocShell(mDocShell));
    682          }
    683          return;
    684        }
    685      }
    686 
    687      if (MOZ_UNLIKELY(!mParser)) {
    688        // The parse ended during an update pause.
    689        return;
    690      }
    691      if (streamEnded) {
    692        GetParser()->PermanentlyUndefineInsertionPoint();
    693      }
    694    }  // end autoFlush
    695 
    696    if (MOZ_UNLIKELY(!mParser)) {
    697      // Ending the doc update caused a call to nsIParser::Terminate().
    698      return;
    699    }
    700 
    701    if (streamEnded) {
    702      DidBuildModel(false);
    703 #ifdef DEBUG
    704      if (scriptElement) {
    705        nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(scriptElement);
    706        if (!sele) {
    707          MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled,
    708                     "Node didn't QI to script, but SVG wasn't disabled.");
    709        }
    710        MOZ_ASSERT(sele->IsMalformed(), "Script wasn't marked as malformed.");
    711      }
    712 #endif
    713    } else if (scriptElement) {
    714      // must be tail call when mFlushState is eNotFlushing
    715      RunScript(scriptElement, true);
    716 
    717      // Always check the clock in nsContentSink right after a script
    718      StopDeflecting();
    719      if (nsContentSink::DidProcessATokenImpl() ==
    720          NS_ERROR_HTMLPARSER_INTERRUPTED) {
    721 #ifdef DEBUG
    722        LOG(("REFLUSH SCHEDULED (after script): %d\n",
    723             ++sTimesFlushLoopInterrupted));
    724 #endif
    725        nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
    726        return;
    727      }
    728    }
    729  }
    730 }
    731 
    732 nsresult nsHtml5TreeOpExecutor::FlushDocumentWrite() {
    733  nsresult rv = IsBroken();
    734  NS_ENSURE_SUCCESS(rv, rv);
    735 
    736  FlushSpeculativeLoads();  // Make sure speculative loads never start after the
    737                            // corresponding normal loads for the same URLs.
    738 
    739  if (MOZ_UNLIKELY(!mParser)) {
    740    // The parse has ended.
    741    ClearOpQueue();  // clear in order to be able to assert in destructor
    742    return rv;
    743  }
    744 
    745  if (mFlushState != eNotFlushing) {
    746    // XXX Can this happen? In case it can, let's avoid crashing.
    747    return rv;
    748  }
    749 
    750  // avoid crashing near EOF
    751  RefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
    752  RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
    753  (void)parserKungFuDeathGrip;  // Intentionally not used within function
    754  RefPtr<nsHtml5StreamParser> streamParserGrip;
    755  if (mParser) {
    756    streamParserGrip = GetParser()->GetStreamParser();
    757  }
    758  (void)streamParserGrip;  // Intentionally not used within function
    759 
    760  MOZ_RELEASE_ASSERT(!mReadingFromStage,
    761                     "Got doc write flush when reading from stage");
    762 
    763 #ifdef DEBUG
    764  mStage.AssertEmpty();
    765 #endif
    766 
    767  nsIContent* scriptElement = nullptr;
    768  bool interrupted = false;
    769  bool streamEnded = false;
    770 
    771  {
    772    // autoFlush clears mOpQueue in its destructor.
    773    nsHtml5AutoFlush autoFlush(this);
    774    // Profiler marker deliberately not counting layout and script
    775    // execution.
    776    AUTO_PROFILER_MARKER_TEXT(
    777        "HTMLParserTreeOps", DOM,
    778        MarkerOptions(MarkerInnerWindowIdFromDocShell(mDocShell)),
    779        "document.write"_ns);
    780 
    781    nsHtml5TreeOperation* start = mOpQueue.Elements();
    782    nsHtml5TreeOperation* end = start + mOpQueue.Length();
    783    for (nsHtml5TreeOperation* iter = start; iter < end; ++iter) {
    784      if (MOZ_UNLIKELY(!mParser)) {
    785        // The previous tree op caused a call to nsIParser::Terminate().
    786        return rv;
    787      }
    788      NS_ASSERTION(IsInDocUpdate(),
    789                   "Tried to perform tree op outside update batch.");
    790      rv = iter->Perform(this, &scriptElement, &interrupted, &streamEnded);
    791      if (NS_FAILED(rv)) {
    792        MarkAsBroken(rv);
    793        break;
    794      }
    795    }
    796 
    797    if (MOZ_UNLIKELY(!mParser)) {
    798      // The parse ended during an update pause.
    799      return rv;
    800    }
    801    if (streamEnded) {
    802      // This should be redundant but let's do it just in case.
    803      GetParser()->PermanentlyUndefineInsertionPoint();
    804    }
    805  }  // autoFlush
    806 
    807  if (MOZ_UNLIKELY(!mParser)) {
    808    // Ending the doc update caused a call to nsIParser::Terminate().
    809    return rv;
    810  }
    811 
    812  if (streamEnded) {
    813    DidBuildModel(false);
    814 #ifdef DEBUG
    815    if (scriptElement) {
    816      nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(scriptElement);
    817      if (!sele) {
    818        MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled,
    819                   "Node didn't QI to script, but SVG wasn't disabled.");
    820      }
    821      MOZ_ASSERT(sele->IsMalformed(), "Script wasn't marked as malformed.");
    822    }
    823 #endif
    824  } else if (scriptElement) {
    825    // must be tail call when mFlushState is eNotFlushing
    826    RunScript(scriptElement, true);
    827  }
    828  return rv;
    829 }
    830 
    831 void nsHtml5TreeOpExecutor::CommitToInternalEncoding() {
    832  if (MOZ_UNLIKELY(!mParser || !mStreamParser)) {
    833    // An extension terminated the parser from a HTTP observer.
    834    ClearOpQueue();  // clear in order to be able to assert in destructor
    835    return;
    836  }
    837  mStreamParser->ContinueAfterScriptsOrEncodingCommitment(nullptr, nullptr,
    838                                                          false);
    839  ContinueInterruptedParsingAsync();
    840 }
    841 
    842 [[nodiscard]] bool nsHtml5TreeOpExecutor::TakeOpsFromStage() {
    843  return mStage.MoveOpsTo(mOpQueue);
    844 }
    845 
    846 // copied from HTML content sink
    847 bool nsHtml5TreeOpExecutor::IsScriptEnabled() {
    848  // Note that if we have no document or no docshell or no global or whatnot we
    849  // want to claim script _is_ enabled, so we don't parse the contents of
    850  // <noscript> tags!
    851  if (!mDocument || !mDocShell) {
    852    return true;
    853  }
    854 
    855  return mDocument->IsScriptEnabled();
    856 }
    857 
    858 void nsHtml5TreeOpExecutor::StartLayout(bool* aInterrupted) {
    859  if (mLayoutStarted || !mDocument) {
    860    return;
    861  }
    862 
    863  nsHtml5AutoPauseUpdate autoPause(this);
    864 
    865  if (MOZ_UNLIKELY(!mParser)) {
    866    // got terminate
    867    return;
    868  }
    869 
    870  nsContentSink::StartLayout(false);
    871 
    872  if (mParser) {
    873    *aInterrupted = !GetParser()->IsParserEnabled();
    874  }
    875 }
    876 
    877 void nsHtml5TreeOpExecutor::PauseDocUpdate(bool* aInterrupted) {
    878  // Pausing the document update allows JS to run, and potentially block
    879  // further parsing.
    880  nsHtml5AutoPauseUpdate autoPause(this);
    881 
    882  if (MOZ_LIKELY(mParser)) {
    883    *aInterrupted = !GetParser()->IsParserEnabled();
    884  }
    885 }
    886 
    887 /**
    888 * The reason why this code is here and not in the tree builder even in the
    889 * main-thread case is to allow the control to return from the tokenizer
    890 * before scripts run. This way, the tokenizer is not invoked re-entrantly
    891 * although the parser is.
    892 *
    893 * The reason why this is called with `aMayDocumentWriteOrBlock=true` as a
    894 * tail call when `mFlushState` is set to `eNotFlushing` is to allow re-entry
    895 * to `Flush()` but only after the current `Flush()` has cleared the op queue
    896 * and is otherwise done cleaning up after itself.
    897 */
    898 void nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement,
    899                                      bool aMayDocumentWriteOrBlock) {
    900  if (mRunsToCompletion) {
    901    // We are in createContextualFragment() or in the upcoming document.parse().
    902    // Do nothing. Let's not even mark scripts malformed here, because that
    903    // could cause serialization weirdness later.
    904    return;
    905  }
    906 
    907  MOZ_ASSERT(mParser, "Trying to run script with a terminated parser.");
    908  MOZ_ASSERT(aScriptElement, "No script to run");
    909  nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement);
    910  if (!sele) {
    911    MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled,
    912               "Node didn't QI to script, but SVG wasn't disabled.");
    913    return;
    914  }
    915 
    916  sele->SetCreatorParser(GetParser());
    917 
    918  if (!aMayDocumentWriteOrBlock) {
    919    MOZ_ASSERT(sele->GetScriptDeferred() || sele->GetScriptAsync() ||
    920               sele->GetScriptIsModule() || sele->GetScriptIsImportMap() ||
    921               aScriptElement->AsElement()->HasAttr(nsGkAtoms::nomodule));
    922    sele->AttemptToExecute(nullptr /* aParser */);
    923    return;
    924  }
    925 
    926  MOZ_RELEASE_ASSERT(
    927      mFlushState == eNotFlushing,
    928      "Tried to run a potentially-blocking script while flushing.");
    929 
    930  mReadingFromStage = false;
    931 
    932  // Copied from nsXMLContentSink
    933  // Now tell the script that it's ready to go. This may execute the script
    934  // or return true, or neither if the script doesn't need executing.
    935  bool block = sele->AttemptToExecute(GetParser());
    936 
    937  // If the act of insertion evaluated the script, we're fine.
    938  if (!block) {
    939    // mParser may have been nulled out by now, but the flusher deals
    940 
    941    // If this event isn't needed, it doesn't do anything. It is sometimes
    942    // necessary for the parse to continue after complex situations.
    943    nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
    944  }
    945 }
    946 
    947 void nsHtml5TreeOpExecutor::Start() {
    948  MOZ_ASSERT(!mStarted, "Tried to start when already started.");
    949  mStarted = true;
    950 }
    951 
    952 void nsHtml5TreeOpExecutor::UpdateCharsetSource(
    953    nsCharsetSource aCharsetSource) {
    954  if (mDocument) {
    955    mDocument->SetDocumentCharacterSetSource(aCharsetSource);
    956  }
    957 }
    958 
    959 void nsHtml5TreeOpExecutor::SetDocumentCharsetAndSource(
    960    NotNull<const Encoding*> aEncoding, nsCharsetSource aCharsetSource) {
    961  if (mDocument) {
    962    mDocument->SetDocumentCharacterSetSource(aCharsetSource);
    963    mDocument->SetDocumentCharacterSet(aEncoding);
    964  }
    965 }
    966 
    967 void nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(
    968    NotNull<const Encoding*> aEncoding, int32_t aSource, uint32_t aLineNumber) {
    969  nsHtml5AutoPauseUpdate autoPause(this);
    970  if (MOZ_UNLIKELY(!mParser)) {
    971    // got terminate
    972    return;
    973  }
    974 
    975  if (!mDocShell) {
    976    return;
    977  }
    978 
    979  RefPtr<nsDocShell> docShell = static_cast<nsDocShell*>(mDocShell.get());
    980 
    981  if (NS_SUCCEEDED(docShell->CharsetChangeStopDocumentLoad())) {
    982    docShell->CharsetChangeReloadDocument(aEncoding, aSource);
    983  }
    984  // if the charset switch was accepted, mDocShell has called Terminate() on the
    985  // parser by now
    986  if (!mParser) {
    987    return;
    988  }
    989 
    990  GetParser()->ContinueAfterFailedCharsetSwitch();
    991 }
    992 
    993 void nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId,
    994                                                      bool aError,
    995                                                      uint32_t aLineNumber) {
    996  // Encoding errors don't count towards already complaining
    997  if (!(!strcmp(aMsgId, "EncError") || !strcmp(aMsgId, "EncErrorFrame") ||
    998        !strcmp(aMsgId, "EncErrorFramePlain"))) {
    999    if (mAlreadyComplainedAboutCharset) {
   1000      return;
   1001    }
   1002    mAlreadyComplainedAboutCharset = true;
   1003  }
   1004  nsContentUtils::ReportToConsole(
   1005      aError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag,
   1006      "HTML parser"_ns, mDocument, nsContentUtils::eHTMLPARSER_PROPERTIES,
   1007      aMsgId, nsTArray<nsString>(),
   1008      SourceLocation{mDocument->GetDocumentURI(), aLineNumber});
   1009 }
   1010 
   1011 void nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset(
   1012    Document* aDoc, bool aUnrecognized) {
   1013  NS_ASSERTION(!mAlreadyComplainedAboutCharset,
   1014               "How come we already managed to complain?");
   1015  mAlreadyComplainedAboutCharset = true;
   1016  nsContentUtils::ReportToConsole(
   1017      nsIScriptError::errorFlag, "HTML parser"_ns, aDoc,
   1018      nsContentUtils::eHTMLPARSER_PROPERTIES,
   1019      aUnrecognized ? "EncProtocolUnsupported" : "EncProtocolReplacement");
   1020 }
   1021 
   1022 void nsHtml5TreeOpExecutor::MaybeComplainAboutDeepTree(uint32_t aLineNumber) {
   1023  if (mAlreadyComplainedAboutDeepTree) {
   1024    return;
   1025  }
   1026  mAlreadyComplainedAboutDeepTree = true;
   1027  nsContentUtils::ReportToConsole(
   1028      nsIScriptError::errorFlag, "HTML parser"_ns, mDocument,
   1029      nsContentUtils::eHTMLPARSER_PROPERTIES, "errDeepTree",
   1030      nsTArray<nsString>(),
   1031      SourceLocation{mDocument->GetDocumentURI(), aLineNumber});
   1032 }
   1033 
   1034 nsHtml5Parser* nsHtml5TreeOpExecutor::GetParser() {
   1035  MOZ_ASSERT(!mRunsToCompletion);
   1036  return static_cast<nsHtml5Parser*>(mParser.get());
   1037 }
   1038 
   1039 [[nodiscard]] bool nsHtml5TreeOpExecutor::MoveOpsFrom(
   1040    nsTArray<nsHtml5TreeOperation>& aOpQueue) {
   1041  MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
   1042                     "Ops added to mOpQueue during tree op execution.");
   1043  return !!mOpQueue.AppendElements(std::move(aOpQueue), mozilla::fallible_t());
   1044 }
   1045 
   1046 void nsHtml5TreeOpExecutor::ClearOpQueue() {
   1047  MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
   1048                     "mOpQueue cleared during tree op execution.");
   1049  mOpQueue.Clear();
   1050 }
   1051 
   1052 void nsHtml5TreeOpExecutor::RemoveFromStartOfOpQueue(
   1053    size_t aNumberOfOpsToRemove) {
   1054  MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
   1055                     "Ops removed from mOpQueue during tree op execution.");
   1056  mOpQueue.RemoveElementsAt(0, aNumberOfOpsToRemove);
   1057 }
   1058 
   1059 void nsHtml5TreeOpExecutor::InitializeDocWriteParserState(
   1060    nsAHtml5TreeBuilderState* aState, int32_t aLine) {
   1061  GetParser()->InitializeDocWriteParserState(aState, aLine);
   1062 }
   1063 
   1064 nsIURI* nsHtml5TreeOpExecutor::GetViewSourceBaseURI() {
   1065  if (!mViewSourceBaseURI) {
   1066    // We query the channel for the baseURI because in certain situations it
   1067    // cannot otherwise be determined. If this process fails, fall back to the
   1068    // standard method.
   1069    nsCOMPtr<nsIViewSourceChannel> vsc =
   1070        do_QueryInterface(mDocument->GetChannel());
   1071    if (vsc) {
   1072      nsresult rv = vsc->GetBaseURI(getter_AddRefs(mViewSourceBaseURI));
   1073      if (NS_SUCCEEDED(rv) && mViewSourceBaseURI) {
   1074        return mViewSourceBaseURI;
   1075      }
   1076    }
   1077 
   1078    nsCOMPtr<nsIURI> orig = mDocument->GetOriginalURI();
   1079    if (orig->SchemeIs("view-source")) {
   1080      nsCOMPtr<nsINestedURI> nested = do_QueryInterface(orig);
   1081      NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!");
   1082      nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI));
   1083    } else {
   1084      // Fail gracefully if the base URL isn't a view-source: URL.
   1085      // Not sure if this can ever happen.
   1086      mViewSourceBaseURI = orig;
   1087    }
   1088  }
   1089  return mViewSourceBaseURI;
   1090 }
   1091 
   1092 bool nsHtml5TreeOpExecutor::IsExternalViewSource() {
   1093  if (!StaticPrefs::view_source_editor_external()) {
   1094    return false;
   1095  }
   1096  if (mDocumentURI) {
   1097    return mDocumentURI->SchemeIs("view-source");
   1098  }
   1099  return false;
   1100 }
   1101 
   1102 // Speculative loading
   1103 
   1104 nsIURI* nsHtml5TreeOpExecutor::BaseURIForPreload() {
   1105  // The URL of the document without <base>
   1106  nsIURI* documentURI = mDocument->GetDocumentURI();
   1107  // The URL of the document with non-speculative <base>
   1108  nsIURI* documentBaseURI = mDocument->GetDocBaseURI();
   1109 
   1110  // If the two above are different, use documentBaseURI. If they are the same,
   1111  // the document object isn't aware of a <base>, so attempt to use the
   1112  // mSpeculationBaseURI or, failing, that, documentURI.
   1113  return (documentURI == documentBaseURI)
   1114             ? (mSpeculationBaseURI ? mSpeculationBaseURI.get() : documentURI)
   1115             : documentBaseURI;
   1116 }
   1117 
   1118 already_AddRefed<nsIURI>
   1119 nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYetAndMediaApplies(
   1120    const nsAString& aURL, const nsAString& aMedia) {
   1121  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
   1122  if (!uri) {
   1123    return nullptr;
   1124  }
   1125 
   1126  if (!MediaApplies(aMedia)) {
   1127    return nullptr;
   1128  }
   1129  return uri.forget();
   1130 }
   1131 
   1132 bool nsHtml5TreeOpExecutor::MediaApplies(const nsAString& aMedia) {
   1133  using dom::MediaList;
   1134 
   1135  if (aMedia.IsEmpty()) {
   1136    return true;
   1137  }
   1138  RefPtr<MediaList> media = MediaList::Create(NS_ConvertUTF16toUTF8(aMedia));
   1139  return media->Matches(*mDocument);
   1140 }
   1141 
   1142 already_AddRefed<nsIURI> nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(
   1143    const nsAString& aURL) {
   1144  if (aURL.IsEmpty()) {
   1145    return nullptr;
   1146  }
   1147 
   1148  nsIURI* base = BaseURIForPreload();
   1149  auto encoding = mDocument->GetDocumentCharacterSet();
   1150  nsCOMPtr<nsIURI> uri;
   1151  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, encoding, base);
   1152  if (NS_FAILED(rv)) {
   1153    NS_WARNING("Failed to create a URI");
   1154    return nullptr;
   1155  }
   1156 
   1157  if (ShouldPreloadURI(uri)) {
   1158    return uri.forget();
   1159  }
   1160 
   1161  return nullptr;
   1162 }
   1163 
   1164 bool nsHtml5TreeOpExecutor::ShouldPreloadURI(nsIURI* aURI) {
   1165  nsAutoCString spec;
   1166  nsresult rv = aURI->GetSpec(spec);
   1167  NS_ENSURE_SUCCESS(rv, false);
   1168  return mPreloadedURLs.EnsureInserted(spec);
   1169 }
   1170 
   1171 bool nsHtml5TreeOpExecutor::ImageTypeSupports(const nsAString& aType) {
   1172  if (aType.IsEmpty()) {
   1173    return true;
   1174  }
   1175  return imgLoader::SupportImageWithMimeType(
   1176      NS_ConvertUTF16toUTF8(aType), AcceptedMimeTypes::IMAGES_AND_DOCUMENTS);
   1177 }
   1178 
   1179 dom::ReferrerPolicy nsHtml5TreeOpExecutor::GetPreloadReferrerPolicy(
   1180    const nsAString& aReferrerPolicy) {
   1181  dom::ReferrerPolicy referrerPolicy =
   1182      dom::ReferrerInfo::ReferrerPolicyAttributeFromString(aReferrerPolicy);
   1183  return GetPreloadReferrerPolicy(referrerPolicy);
   1184 }
   1185 
   1186 dom::ReferrerPolicy nsHtml5TreeOpExecutor::GetPreloadReferrerPolicy(
   1187    ReferrerPolicy aReferrerPolicy) {
   1188  if (aReferrerPolicy != dom::ReferrerPolicy::_empty) {
   1189    return aReferrerPolicy;
   1190  }
   1191 
   1192  return mDocument->GetPreloadReferrerInfo()->ReferrerPolicy();
   1193 }
   1194 
   1195 void nsHtml5TreeOpExecutor::PreloadScript(
   1196    const nsAString& aURL, const nsAString& aCharset, const nsAString& aType,
   1197    const nsAString& aCrossOrigin, const nsAString& aMedia,
   1198    const nsAString& aNonce, const nsAString& aFetchPriority,
   1199    const nsAString& aIntegrity, dom::ReferrerPolicy aReferrerPolicy,
   1200    bool aScriptFromHead, bool aAsync, bool aDefer, bool aLinkPreload) {
   1201  dom::ScriptLoader* loader = mDocument->GetScriptLoader();
   1202  if (!loader) {
   1203    return;
   1204  }
   1205  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
   1206  if (!uri) {
   1207    return;
   1208  }
   1209  auto key = PreloadHashKey::CreateAsScript(uri, aCrossOrigin, aType);
   1210  if (mDocument->Preloads().PreloadExists(key)) {
   1211    return;
   1212  }
   1213  loader->PreloadURI(uri, aCharset, aType, aCrossOrigin, aNonce, aFetchPriority,
   1214                     aIntegrity, aScriptFromHead, aAsync, aDefer, aLinkPreload,
   1215                     GetPreloadReferrerPolicy(aReferrerPolicy), 0);
   1216 }
   1217 
   1218 void nsHtml5TreeOpExecutor::PreloadStyle(
   1219    const nsAString& aURL, const nsAString& aCharset,
   1220    const nsAString& aCrossOrigin, const nsAString& aMedia,
   1221    const nsAString& aReferrerPolicy, const nsAString& aNonce,
   1222    const nsAString& aIntegrity, bool aLinkPreload,
   1223    const nsAString& aFetchPriority) {
   1224  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
   1225  if (!uri) {
   1226    return;
   1227  }
   1228 
   1229  if (aLinkPreload) {
   1230    auto hashKey = PreloadHashKey::CreateAsStyle(
   1231        uri, mDocument->NodePrincipal(),
   1232        dom::Element::StringToCORSMode(aCrossOrigin),
   1233        css::eAuthorSheetFeatures);
   1234    if (mDocument->Preloads().PreloadExists(hashKey)) {
   1235      return;
   1236    }
   1237  }
   1238 
   1239  mDocument->PreloadStyle(
   1240      uri, Encoding::ForLabel(aCharset), aCrossOrigin,
   1241      GetPreloadReferrerPolicy(aReferrerPolicy), aNonce, aIntegrity,
   1242      aLinkPreload ? css::StylePreloadKind::FromLinkRelPreloadElement
   1243                   : css::StylePreloadKind::FromParser,
   1244      0, aFetchPriority);
   1245 }
   1246 
   1247 void nsHtml5TreeOpExecutor::PreloadImage(
   1248    const nsAString& aURL, const nsAString& aCrossOrigin,
   1249    const nsAString& aMedia, const nsAString& aSrcset, const nsAString& aSizes,
   1250    const nsAString& aImageReferrerPolicy, bool aLinkPreload,
   1251    const nsAString& aFetchPriority, const nsAString& aType) {
   1252  nsCOMPtr<nsIURI> baseURI = BaseURIForPreload();
   1253  bool isImgSet = false;
   1254  nsCOMPtr<nsIURI> uri =
   1255      mDocument->ResolvePreloadImage(baseURI, aURL, aSrcset, aSizes, &isImgSet);
   1256  if (uri && ShouldPreloadURI(uri) && MediaApplies(aMedia) &&
   1257      ImageTypeSupports(aType)) {
   1258    // use document wide referrer policy
   1259    mDocument->MaybePreLoadImage(uri, aCrossOrigin,
   1260                                 GetPreloadReferrerPolicy(aImageReferrerPolicy),
   1261                                 isImgSet, aLinkPreload, aFetchPriority);
   1262  }
   1263 }
   1264 
   1265 // These calls inform the document of picture state and seen sources, such that
   1266 // it can use them to inform ResolvePreLoadImage as necessary
   1267 void nsHtml5TreeOpExecutor::PreloadPictureSource(const nsAString& aSrcset,
   1268                                                 const nsAString& aSizes,
   1269                                                 const nsAString& aType,
   1270                                                 const nsAString& aMedia) {
   1271  mDocument->PreloadPictureImageSource(aSrcset, aSizes, aType, aMedia);
   1272 }
   1273 
   1274 void nsHtml5TreeOpExecutor::PreloadFont(const nsAString& aURL,
   1275                                        const nsAString& aCrossOrigin,
   1276                                        const nsAString& aMedia,
   1277                                        const nsAString& aReferrerPolicy,
   1278                                        const nsAString& aFetchPriority) {
   1279  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
   1280  if (!uri) {
   1281    return;
   1282  }
   1283 
   1284  mDocument->Preloads().PreloadFont(uri, aCrossOrigin, aReferrerPolicy, 0,
   1285                                    aFetchPriority);
   1286 }
   1287 
   1288 void nsHtml5TreeOpExecutor::PreloadFetch(const nsAString& aURL,
   1289                                         const nsAString& aCrossOrigin,
   1290                                         const nsAString& aMedia,
   1291                                         const nsAString& aReferrerPolicy,
   1292                                         const nsAString& aFetchPriority) {
   1293  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
   1294  if (!uri) {
   1295    return;
   1296  }
   1297 
   1298  mDocument->Preloads().PreloadFetch(uri, aCrossOrigin, aReferrerPolicy, 0,
   1299                                     aFetchPriority);
   1300 }
   1301 
   1302 void nsHtml5TreeOpExecutor::PreloadOpenPicture() {
   1303  mDocument->PreloadPictureOpened();
   1304 }
   1305 
   1306 void nsHtml5TreeOpExecutor::PreloadEndPicture() {
   1307  mDocument->PreloadPictureClosed();
   1308 }
   1309 
   1310 void nsHtml5TreeOpExecutor::AddBase(const nsAString& aURL) {
   1311  auto encoding = mDocument->GetDocumentCharacterSet();
   1312  nsresult rv = NS_NewURI(getter_AddRefs(mViewSourceBaseURI), aURL, encoding,
   1313                          GetViewSourceBaseURI());
   1314  if (NS_FAILED(rv)) {
   1315    mViewSourceBaseURI = nullptr;
   1316  }
   1317 }
   1318 void nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL) {
   1319  if (mSpeculationBaseURI) {
   1320    // the first one wins
   1321    return;
   1322  }
   1323 
   1324  auto encoding = mDocument->GetDocumentCharacterSet();
   1325  nsCOMPtr<nsIURI> newBaseURI;
   1326  DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(newBaseURI), aURL, encoding,
   1327                                     mDocument->GetDocumentURI());
   1328  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to create a URI");
   1329  if (!newBaseURI) {
   1330    return;
   1331  }
   1332 
   1333  // See
   1334  // https://html.spec.whatwg.org/multipage/semantics.html#set-the-frozen-base-url
   1335  // data: and javascript: base URLs are not allowed.
   1336  if (newBaseURI->SchemeIs("data") || newBaseURI->SchemeIs("javascript")) {
   1337    return;
   1338  }
   1339 
   1340  // Check the document's CSP usually delivered via the CSP header.
   1341  if (nsCOMPtr<nsIContentSecurityPolicy> csp =
   1342          PolicyContainer::GetCSP(mDocument->GetPolicyContainer())) {
   1343    // base-uri should not fallback to the default-src and preloads should not
   1344    // trigger violation reports.
   1345    bool cspPermitsBaseURI = true;
   1346    nsresult rv = csp->Permits(
   1347        nullptr, nullptr, newBaseURI,
   1348        nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true /* aSpecific */,
   1349        false /* aSendViolationReports */, &cspPermitsBaseURI);
   1350    if (NS_FAILED(rv) || !cspPermitsBaseURI) {
   1351      return;
   1352    }
   1353  }
   1354 
   1355  // Also check the CSP discovered from the <meta> tag during speculative
   1356  // parsing.
   1357  if (nsCOMPtr<nsIContentSecurityPolicy> csp = mDocument->GetPreloadCsp()) {
   1358    bool cspPermitsBaseURI = true;
   1359    nsresult rv = csp->Permits(
   1360        nullptr, nullptr, newBaseURI,
   1361        nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true /* aSpecific */,
   1362        false /* aSendViolationReports */, &cspPermitsBaseURI);
   1363    if (NS_FAILED(rv) || !cspPermitsBaseURI) {
   1364      return;
   1365    }
   1366  }
   1367 
   1368  mSpeculationBaseURI = newBaseURI;
   1369  mDocument->Preloads().SetSpeculationBase(mSpeculationBaseURI);
   1370 }
   1371 
   1372 void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta(
   1373    const nsAString& aMetaReferrer) {
   1374  mDocument->UpdateReferrerInfoFromMeta(aMetaReferrer, true);
   1375 }
   1376 
   1377 void nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP) {
   1378  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   1379 
   1380  nsresult rv = NS_OK;
   1381  nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = mDocument->GetPreloadCsp();
   1382  if (!preloadCsp) {
   1383    RefPtr<nsCSPContext> csp = new nsCSPContext();
   1384    csp->SuppressParserLogMessages();
   1385    preloadCsp = csp;
   1386    rv = preloadCsp->SetRequestContextWithDocument(mDocument);
   1387    NS_ENSURE_SUCCESS_VOID(rv);
   1388  }
   1389 
   1390  // Please note that multiple meta CSPs need to be joined together.
   1391  rv = preloadCsp->AppendPolicy(
   1392      aCSP,
   1393      false,  // csp via meta tag can not be report only
   1394      true);  // delivered through the meta tag
   1395  NS_ENSURE_SUCCESS_VOID(rv);
   1396 
   1397  nsPIDOMWindowInner* inner = mDocument->GetInnerWindow();
   1398  if (inner) {
   1399    inner->SetPreloadCsp(preloadCsp);
   1400  }
   1401  mDocument->ApplySettingsFromCSP(true);
   1402 }
   1403 
   1404 #ifdef DEBUG
   1405 uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
   1406 uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
   1407 uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
   1408 uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0;
   1409 uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0;
   1410 #endif