forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1422 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1422 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set sw=2 ts=2 et tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "mozilla/DebugOnly.h"
 | |
| #include "mozilla/Likely.h"
 | |
| #include "mozilla/dom/BrowsingContext.h"
 | |
| #include "mozilla/dom/MediaList.h"
 | |
| #include "mozilla/dom/ScriptLoader.h"
 | |
| #include "mozilla/dom/nsCSPContext.h"
 | |
| #include "mozilla/dom/nsCSPService.h"
 | |
| 
 | |
| #include "mozAutoDocUpdate.h"
 | |
| #include "mozilla/IdleTaskRunner.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "mozilla/ProfilerLabels.h"
 | |
| #include "mozilla/ProfilerMarkers.h"
 | |
| #include "mozilla/StaticPrefs_content.h"
 | |
| #include "mozilla/StaticPrefs_security.h"
 | |
| #include "mozilla/StaticPrefs_view_source.h"
 | |
| #include "mozilla/Telemetry.h"
 | |
| #include "mozilla/css/Loader.h"
 | |
| #include "mozilla/fallible.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsDocShell.h"
 | |
| #include "nsError.h"
 | |
| #include "nsHTMLDocument.h"
 | |
| #include "nsHtml5AutoPauseUpdate.h"
 | |
| #include "nsHtml5Parser.h"
 | |
| #include "nsHtml5StreamParser.h"
 | |
| #include "nsHtml5Tokenizer.h"
 | |
| #include "nsHtml5TreeBuilder.h"
 | |
| #include "nsHtml5TreeOpExecutor.h"
 | |
| #include "nsIContentSecurityPolicy.h"
 | |
| #include "nsIDocShell.h"
 | |
| #include "nsIDocShellTreeItem.h"
 | |
| #include "nsINestedURI.h"
 | |
| #include "nsIHttpChannel.h"
 | |
| #include "nsIScriptContext.h"
 | |
| #include "nsIScriptError.h"
 | |
| #include "nsIScriptGlobalObject.h"
 | |
| #include "nsIViewSourceChannel.h"
 | |
| #include "nsNetUtil.h"
 | |
| #include "xpcpublic.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| 
 | |
| #ifdef DEBUG
 | |
| static LazyLogModule gHtml5TreeOpExecutorLog("Html5TreeOpExecutor");
 | |
| #endif  // DEBUG
 | |
| static LazyLogModule gCharsetMenuLog("Chardetng");
 | |
| 
 | |
| #define LOG(args) MOZ_LOG(gHtml5TreeOpExecutorLog, LogLevel::Debug, args)
 | |
| #define LOGCHARDETNG(args) MOZ_LOG(gCharsetMenuLog, LogLevel::Debug, args)
 | |
| 
 | |
| NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor,
 | |
|                                              nsHtml5DocumentBuilder,
 | |
|                                              nsIContentSink)
 | |
| 
 | |
| class nsHtml5ExecutorReflusher : public Runnable {
 | |
|  private:
 | |
|   RefPtr<nsHtml5TreeOpExecutor> mExecutor;
 | |
| 
 | |
|  public:
 | |
|   explicit nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
 | |
|       : Runnable("nsHtml5ExecutorReflusher"), mExecutor(aExecutor) {}
 | |
|   NS_IMETHOD Run() override {
 | |
|     dom::Document* doc = mExecutor->GetDocument();
 | |
|     if (XRE_IsContentProcess() &&
 | |
|         nsContentUtils::
 | |
|             HighPriorityEventPendingForTopLevelDocumentBeforeContentfulPaint(
 | |
|                 doc)) {
 | |
|       // Possible early paint pending, reuse the runnable and try to
 | |
|       // call RunFlushLoop later.
 | |
|       nsCOMPtr<nsIRunnable> flusher = this;
 | |
|       if (NS_SUCCEEDED(doc->Dispatch(flusher.forget()))) {
 | |
|         PROFILER_MARKER_UNTYPED("HighPrio blocking parser flushing(2)", DOM);
 | |
|         return NS_OK;
 | |
|       }
 | |
|     }
 | |
|     mExecutor->RunFlushLoop();
 | |
|     return NS_OK;
 | |
|   }
 | |
| };
 | |
| 
 | |
| class MOZ_RAII nsHtml5AutoFlush final {
 | |
|  private:
 | |
|   RefPtr<nsHtml5TreeOpExecutor> mExecutor;
 | |
|   size_t mOpsToRemove;
 | |
| 
 | |
|  public:
 | |
|   explicit nsHtml5AutoFlush(nsHtml5TreeOpExecutor* aExecutor)
 | |
|       : mExecutor(aExecutor), mOpsToRemove(aExecutor->OpQueueLength()) {
 | |
|     mExecutor->BeginFlush();
 | |
|     mExecutor->BeginDocUpdate();
 | |
|   }
 | |
|   ~nsHtml5AutoFlush() {
 | |
|     if (mExecutor->IsInDocUpdate()) {
 | |
|       mExecutor->EndDocUpdate();
 | |
|     } else {
 | |
|       // We aren't in an update if nsHtml5AutoPauseUpdate
 | |
|       // caused something to terminate the parser.
 | |
|       MOZ_RELEASE_ASSERT(
 | |
|           mExecutor->IsComplete(),
 | |
|           "How do we have mParser but the doc update isn't open?");
 | |
|     }
 | |
|     mExecutor->EndFlush();
 | |
|     mExecutor->RemoveFromStartOfOpQueue(mOpsToRemove);
 | |
|   }
 | |
|   void SetNumberOfOpsToRemove(size_t aOpsToRemove) {
 | |
|     MOZ_ASSERT(aOpsToRemove < mOpsToRemove,
 | |
|                "Requested partial clearing of op queue but the number to clear "
 | |
|                "wasn't less than the length of the queue.");
 | |
|     mOpsToRemove = aOpsToRemove;
 | |
|   }
 | |
| };
 | |
| 
 | |
| static LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr;
 | |
| StaticRefPtr<IdleTaskRunner> gBackgroundFlushRunner;
 | |
| 
 | |
| nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
 | |
|     : nsHtml5DocumentBuilder(false),
 | |
|       mSuppressEOF(false),
 | |
|       mReadingFromStage(false),
 | |
|       mStreamParser(nullptr),
 | |
|       mPreloadedURLs(23),  // Mean # of preloadable resources per page on dmoz
 | |
|       mStarted(false),
 | |
|       mRunFlushLoopOnStack(false),
 | |
|       mCallContinueInterruptedParsingIfEnabled(false),
 | |
|       mAlreadyComplainedAboutCharset(false),
 | |
|       mAlreadyComplainedAboutDeepTree(false) {}
 | |
| 
 | |
| nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor() {
 | |
|   if (gBackgroundFlushList && isInList()) {
 | |
|     ClearOpQueue();
 | |
|     removeFrom(*gBackgroundFlushList);
 | |
|     if (gBackgroundFlushList->isEmpty()) {
 | |
|       delete gBackgroundFlushList;
 | |
|       gBackgroundFlushList = nullptr;
 | |
|       if (gBackgroundFlushRunner) {
 | |
|         gBackgroundFlushRunner->Cancel();
 | |
|         gBackgroundFlushRunner = nullptr;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   MOZ_ASSERT(NS_FAILED(mBroken) || mOpQueue.IsEmpty(),
 | |
|              "Somehow there's stuff in the op queue.");
 | |
| }
 | |
| 
 | |
| // nsIContentSink
 | |
| NS_IMETHODIMP
 | |
| nsHtml5TreeOpExecutor::WillParse() {
 | |
|   MOZ_ASSERT_UNREACHABLE("No one should call this");
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| }
 | |
| 
 | |
| nsresult nsHtml5TreeOpExecutor::WillBuildModel() {
 | |
|   mDocument->AddObserver(this);
 | |
|   WillBuildModelImpl();
 | |
|   GetDocument()->BeginLoad();
 | |
|   if (mDocShell && !GetDocument()->GetWindow() && !IsExternalViewSource()) {
 | |
|     // Not loading as data but script global object not ready
 | |
|     return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // This is called when the tree construction has ended
 | |
| NS_IMETHODIMP
 | |
| nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated) {
 | |
|   if (mRunsToCompletion) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   MOZ_RELEASE_ASSERT(!IsInDocUpdate(),
 | |
|                      "DidBuildModel from inside a doc update.");
 | |
| 
 | |
|   RefPtr<nsHtml5TreeOpExecutor> pin(this);
 | |
|   auto queueClearer = MakeScopeExit([&] {
 | |
|     if (aTerminated && (mFlushState == eNotFlushing)) {
 | |
|       ClearOpQueue();  // clear in order to be able to assert in destructor
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   // This comes from nsXMLContentSink and nsHTMLContentSink
 | |
|   // If this parser has been marked as broken, treat the end of parse as
 | |
|   // forced termination.
 | |
|   DidBuildModelImpl(aTerminated || NS_FAILED(IsBroken()));
 | |
| 
 | |
|   bool destroying = true;
 | |
|   if (mDocShell) {
 | |
|     mDocShell->IsBeingDestroyed(&destroying);
 | |
|   }
 | |
| 
 | |
|   if (!destroying) {
 | |
|     mDocument->OnParsingCompleted();
 | |
| 
 | |
|     if (!mLayoutStarted) {
 | |
|       // We never saw the body, and layout never got started. Force
 | |
|       // layout *now*, to get an initial reflow.
 | |
| 
 | |
|       // NOTE: only force the layout if we are NOT destroying the
 | |
|       // docshell. If we are destroying it, then starting layout will
 | |
|       // likely cause us to crash, or at best waste a lot of time as we
 | |
|       // are just going to tear it down anyway.
 | |
|       nsContentSink::StartLayout(false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ScrollToRef();
 | |
|   mDocument->RemoveObserver(this);
 | |
|   if (!mParser) {
 | |
|     // DidBuildModelImpl may cause mParser to be nulled out
 | |
|     // Return early to avoid unblocking the onload event too many times.
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // We may not have called BeginLoad() if loading is terminated before
 | |
|   // OnStartRequest call.
 | |
|   if (mStarted) {
 | |
|     mDocument->EndLoad();
 | |
| 
 | |
|     // Gather telemetry only for top-level content navigations in order to
 | |
|     // avoid noise from ad iframes.
 | |
|     bool topLevel = false;
 | |
|     if (mozilla::dom::BrowsingContext* bc = mDocument->GetBrowsingContext()) {
 | |
|       topLevel = bc->IsTopContent();
 | |
|     }
 | |
| 
 | |
|     // Gather telemetry only for text/html and text/plain (excluding CSS, JS,
 | |
|     // etc. being viewed as text.)
 | |
|     nsAutoString contentType;
 | |
|     mDocument->GetContentType(contentType);
 | |
|     bool htmlOrPlain = contentType.EqualsLiteral(u"text/html") ||
 | |
|                        contentType.EqualsLiteral(u"text/plain");
 | |
| 
 | |
|     // Gather telemetry only for HTTP status code 200 in order to exclude
 | |
|     // error pages.
 | |
|     bool httpOk = false;
 | |
|     nsCOMPtr<nsIChannel> channel;
 | |
|     nsresult rv = GetParser()->GetChannel(getter_AddRefs(channel));
 | |
|     if (NS_SUCCEEDED(rv) && channel) {
 | |
|       nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
 | |
|       if (httpChannel) {
 | |
|         uint32_t httpStatus;
 | |
|         rv = httpChannel->GetResponseStatus(&httpStatus);
 | |
|         if (NS_SUCCEEDED(rv) && httpStatus == 200) {
 | |
|           httpOk = true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Gather chardetng telemetry
 | |
|     MOZ_ASSERT(mDocument->IsHTMLDocument());
 | |
|     if (httpOk && htmlOrPlain && topLevel && !aTerminated &&
 | |
|         !mDocument->AsHTMLDocument()->IsViewSource()) {
 | |
|       // We deliberately measure only normally-completed (non-aborted) loads
 | |
|       // that are not View Source loads. This seems like a better place for
 | |
|       // checking normal completion than anything in nsHtml5StreamParser.
 | |
|       bool plain = mDocument->AsHTMLDocument()->IsPlainText();
 | |
|       int32_t charsetSource = mDocument->GetDocumentCharacterSetSource();
 | |
|       switch (charsetSource) {
 | |
|         case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8:
 | |
|           if (plain) {
 | |
|             LOGCHARDETNG(("TEXT::UtfInitial"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::UtfInitial);
 | |
|           } else {
 | |
|             LOGCHARDETNG(("HTML::UtfInitial"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::UtfInitial);
 | |
|           }
 | |
|           break;
 | |
|         case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic:
 | |
|           if (plain) {
 | |
|             LOGCHARDETNG(("TEXT::GenericInitial"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
 | |
|                     GenericInitial);
 | |
|           } else {
 | |
|             LOGCHARDETNG(("HTML::GenericInitial"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
 | |
|                     GenericInitial);
 | |
|           }
 | |
|           break;
 | |
|         case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content:
 | |
|           if (plain) {
 | |
|             LOGCHARDETNG(("TEXT::ContentInitial"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
 | |
|                     ContentInitial);
 | |
|           } else {
 | |
|             LOGCHARDETNG(("HTML::ContentInitial"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
 | |
|                     ContentInitial);
 | |
|           }
 | |
|           break;
 | |
|         case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
 | |
|           if (plain) {
 | |
|             LOGCHARDETNG(("TEXT::TldInitial"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::TldInitial);
 | |
|           } else {
 | |
|             LOGCHARDETNG(("HTML::TldInitial"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::TldInitial);
 | |
|           }
 | |
|           break;
 | |
|         case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII:
 | |
|           if (plain) {
 | |
|             LOGCHARDETNG(("TEXT::UtfFinal"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::UtfFinal);
 | |
|           } else {
 | |
|             LOGCHARDETNG(("HTML::UtfFinal"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::UtfFinal);
 | |
|           }
 | |
|           break;
 | |
|         case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic:
 | |
|           if (plain) {
 | |
|             LOGCHARDETNG(("TEXT::GenericFinal"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
 | |
|                     GenericFinal);
 | |
|           } else {
 | |
|             LOGCHARDETNG(("HTML::GenericFinal"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
 | |
|                     GenericFinal);
 | |
|           }
 | |
|           break;
 | |
|         case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII:
 | |
|           if (plain) {
 | |
|             LOGCHARDETNG(("TEXT::GenericFinalA"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
 | |
|                     GenericFinalA);
 | |
|           } else {
 | |
|             LOGCHARDETNG(("HTML::GenericFinalA"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
 | |
|                     GenericFinalA);
 | |
|           }
 | |
|           break;
 | |
|         case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content:
 | |
|           if (plain) {
 | |
|             LOGCHARDETNG(("TEXT::ContentFinal"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
 | |
|                     ContentFinal);
 | |
|           } else {
 | |
|             LOGCHARDETNG(("HTML::ContentFinal"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
 | |
|                     ContentFinal);
 | |
|           }
 | |
|           break;
 | |
|         case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII:
 | |
|           if (plain) {
 | |
|             LOGCHARDETNG(("TEXT::ContentFinalA"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::
 | |
|                     ContentFinalA);
 | |
|           } else {
 | |
|             LOGCHARDETNG(("HTML::ContentFinalA"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::
 | |
|                     ContentFinalA);
 | |
|           }
 | |
|           break;
 | |
|         case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
 | |
|           if (plain) {
 | |
|             LOGCHARDETNG(("TEXT::TldFinal"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::TldFinal);
 | |
|           } else {
 | |
|             LOGCHARDETNG(("HTML::TldFinal"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::TldFinal);
 | |
|           }
 | |
|           break;
 | |
|         case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII:
 | |
|           if (plain) {
 | |
|             LOGCHARDETNG(("TEXT::TldFinalA"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_TEXT::TldFinalA);
 | |
|           } else {
 | |
|             LOGCHARDETNG(("HTML::TldFinalA"));
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_ENCODING_DETECTION_OUTCOME_HTML::TldFinalA);
 | |
|           }
 | |
|           break;
 | |
|         default:
 | |
|           // Chardetng didn't run automatically or the input was all ASCII.
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Dropping the stream parser changes the parser's apparent
 | |
|   // script-createdness, which is why the stream parser must not be dropped
 | |
|   // before this executor's nsHtml5Parser has been made unreachable from its
 | |
|   // nsHTMLDocument. (mDocument->EndLoad() above drops the parser from the
 | |
|   // document.)
 | |
|   GetParser()->DropStreamParser();
 | |
|   DropParserAndPerfHint();
 | |
| #ifdef GATHER_DOCWRITE_STATISTICS
 | |
|   printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites);
 | |
|   printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites);
 | |
|   printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites);
 | |
| #endif
 | |
| #ifdef DEBUG
 | |
|   LOG(("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize));
 | |
|   if (sAppendBatchExaminations != 0) {
 | |
|     LOG(("AVERAGE SLOTS EXAMINED: %d\n",
 | |
|          sAppendBatchSlotsExamined / sAppendBatchExaminations));
 | |
|   }
 | |
| #endif
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsHtml5TreeOpExecutor::WillInterrupt() {
 | |
|   MOZ_ASSERT_UNREACHABLE("Don't call. For interface compat only.");
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::WillResume() {
 | |
|   MOZ_ASSERT_UNREACHABLE("Don't call. For interface compat only.");
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser) {
 | |
|   mParser = aParser;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::InitialTranslationCompleted() {
 | |
|   nsContentSink::StartLayout(false);
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::FlushPendingNotifications(FlushType aType) {
 | |
|   if (aType >= FlushType::EnsurePresShellInitAndFrames) {
 | |
|     // Bug 577508 / 253951
 | |
|     nsContentSink::StartLayout(true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsISupports* nsHtml5TreeOpExecutor::GetTarget() {
 | |
|   return ToSupports(mDocument);
 | |
| }
 | |
| 
 | |
| nsresult nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason) {
 | |
|   MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
 | |
|   mBroken = aReason;
 | |
|   if (mStreamParser) {
 | |
|     mStreamParser->Terminate();
 | |
|   }
 | |
|   // We are under memory pressure, but let's hope the following allocation
 | |
|   // works out so that we get to terminate and clean up the parser from
 | |
|   // a safer point.
 | |
|   if (mParser && mDocument) {  // can mParser ever be null here?
 | |
|     nsCOMPtr<nsIRunnable> terminator = NewRunnableMethod(
 | |
|         "nsHtml5Parser::Terminate", GetParser(), &nsHtml5Parser::Terminate);
 | |
|     if (NS_FAILED(mDocument->Dispatch(terminator.forget()))) {
 | |
|       NS_WARNING("failed to dispatch executor flush event");
 | |
|     }
 | |
|   }
 | |
|   return aReason;
 | |
| }
 | |
| 
 | |
| static bool BackgroundFlushCallback(TimeStamp /*aDeadline*/) {
 | |
|   RefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst();
 | |
|   if (ex) {
 | |
|     ex->RunFlushLoop();
 | |
|   }
 | |
|   if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) {
 | |
|     delete gBackgroundFlushList;
 | |
|     gBackgroundFlushList = nullptr;
 | |
|     gBackgroundFlushRunner->Cancel();
 | |
|     gBackgroundFlushRunner = nullptr;
 | |
|     return true;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync() {
 | |
|   if (mDocument && !mDocument->IsInBackgroundWindow()) {
 | |
|     nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);
 | |
|     if (NS_FAILED(mDocument->Dispatch(flusher.forget()))) {
 | |
|       NS_WARNING("failed to dispatch executor flush event");
 | |
|     }
 | |
|   } else {
 | |
|     if (!gBackgroundFlushList) {
 | |
|       gBackgroundFlushList = new LinkedList<nsHtml5TreeOpExecutor>();
 | |
|     }
 | |
|     if (!isInList()) {
 | |
|       gBackgroundFlushList->insertBack(this);
 | |
|     }
 | |
|     if (gBackgroundFlushRunner) {
 | |
|       return;
 | |
|     }
 | |
|     // Now we set up a repetitive idle scheduler for flushing background list.
 | |
|     gBackgroundFlushRunner = IdleTaskRunner::Create(
 | |
|         &BackgroundFlushCallback,
 | |
|         "nsHtml5TreeOpExecutor::BackgroundFlushCallback",
 | |
|         0,  // Start looking for idle time immediately.
 | |
|         TimeDuration::FromMilliseconds(250),  // The hard deadline.
 | |
|         TimeDuration::FromMicroseconds(
 | |
|             StaticPrefs::content_sink_interactive_parse_time()),  // Required
 | |
|                                                                   // budget.
 | |
|         true,                                                     // repeating
 | |
|         [] { return false; });  // MayStopProcessing
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::FlushSpeculativeLoads() {
 | |
|   nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
 | |
|   mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
 | |
|   nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
 | |
|   nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
 | |
|   for (nsHtml5SpeculativeLoad* iter = start; iter < end; ++iter) {
 | |
|     if (MOZ_UNLIKELY(!mParser)) {
 | |
|       // An extension terminated the parser from a HTTP observer.
 | |
|       return;
 | |
|     }
 | |
|     iter->Perform(this);
 | |
|   }
 | |
| }
 | |
| 
 | |
| class nsHtml5FlushLoopGuard {
 | |
|  private:
 | |
|   RefPtr<nsHtml5TreeOpExecutor> mExecutor;
 | |
| #ifdef DEBUG
 | |
|   uint32_t mStartTime;
 | |
| #endif
 | |
|  public:
 | |
|   explicit nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor)
 | |
|       : mExecutor(aExecutor)
 | |
| #ifdef DEBUG
 | |
|         ,
 | |
|         mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow()))
 | |
| #endif
 | |
|   {
 | |
|     mExecutor->mRunFlushLoopOnStack = true;
 | |
|   }
 | |
|   ~nsHtml5FlushLoopGuard() {
 | |
| #ifdef DEBUG
 | |
|     uint32_t timeOffTheEventLoop =
 | |
|         PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime;
 | |
|     if (timeOffTheEventLoop >
 | |
|         nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) {
 | |
|       nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = timeOffTheEventLoop;
 | |
|     }
 | |
|     LOG(("Longest time off the event loop: %d\n",
 | |
|          nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop));
 | |
| #endif
 | |
| 
 | |
|     mExecutor->mRunFlushLoopOnStack = false;
 | |
|   }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * The purpose of the loop here is to avoid returning to the main event loop
 | |
|  */
 | |
| void nsHtml5TreeOpExecutor::RunFlushLoop() {
 | |
|   AUTO_PROFILER_LABEL("nsHtml5TreeOpExecutor::RunFlushLoop", OTHER);
 | |
| 
 | |
|   if (mRunFlushLoopOnStack) {
 | |
|     // There's already a RunFlushLoop() on the call stack.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsHtml5FlushLoopGuard guard(this);  // this is also the self-kungfu!
 | |
| 
 | |
|   RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
 | |
|   RefPtr<nsHtml5StreamParser> streamParserGrip;
 | |
|   if (mParser) {
 | |
|     streamParserGrip = GetParser()->GetStreamParser();
 | |
|   }
 | |
|   Unused << streamParserGrip;  // Intentionally not used within function
 | |
| 
 | |
|   // Remember the entry time
 | |
|   (void)nsContentSink::WillParseImpl();
 | |
| 
 | |
|   for (;;) {
 | |
|     if (!mParser) {
 | |
|       // Parse has terminated.
 | |
|       ClearOpQueue();  // clear in order to be able to assert in destructor
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (NS_FAILED(IsBroken())) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (!parserKungFuDeathGrip->IsParserEnabled()) {
 | |
|       // The parser is blocked.
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (mFlushState != eNotFlushing) {
 | |
|       // XXX Can this happen? In case it can, let's avoid crashing.
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // If there are scripts executing, then the content sink is jumping the gun
 | |
|     // (probably due to a synchronous XMLHttpRequest) and will re-enable us
 | |
|     // later, see bug 460706.
 | |
|     if (IsScriptExecuting()) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (mReadingFromStage) {
 | |
|       nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
 | |
|       MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
 | |
|                          "mOpQueue modified during flush.");
 | |
|       if (!mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue,
 | |
|                                                speculativeLoadQueue)) {
 | |
|         MarkAsBroken(nsresult::NS_ERROR_OUT_OF_MEMORY);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // Make sure speculative loads never start after the corresponding
 | |
|       // normal loads for the same URLs.
 | |
|       nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
 | |
|       nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
 | |
|       for (nsHtml5SpeculativeLoad* iter = start; iter < end; ++iter) {
 | |
|         iter->Perform(this);
 | |
|         if (MOZ_UNLIKELY(!mParser)) {
 | |
|           // An extension terminated the parser from a HTTP observer.
 | |
|           ClearOpQueue();  // clear in order to be able to assert in destructor
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       FlushSpeculativeLoads();  // Make sure speculative loads never start after
 | |
|                                 // the corresponding normal loads for the same
 | |
|                                 // URLs.
 | |
|       if (MOZ_UNLIKELY(!mParser)) {
 | |
|         // An extension terminated the parser from a HTTP observer.
 | |
|         ClearOpQueue();  // clear in order to be able to assert in destructor
 | |
|         return;
 | |
|       }
 | |
|       // Now parse content left in the document.write() buffer queue if any.
 | |
|       // This may generate tree ops on its own or dequeue a speculation.
 | |
|       nsresult rv = GetParser()->ParseUntilBlocked();
 | |
| 
 | |
|       // ParseUntilBlocked flushes operations from the stage to the OpQueue.
 | |
|       // Those operations may have accompanying speculative operations.
 | |
|       // If so, we have to flush those speculative loads so that we maintain
 | |
|       // the invariant that no speculative load starts after the corresponding
 | |
|       // normal load for the same URL. See
 | |
|       // https://bugzilla.mozilla.org/show_bug.cgi?id=1513292#c80
 | |
|       // for a more detailed explanation of why this is necessary.
 | |
|       FlushSpeculativeLoads();
 | |
| 
 | |
|       if (NS_FAILED(rv)) {
 | |
|         MarkAsBroken(rv);
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (mOpQueue.IsEmpty()) {
 | |
|       // Avoid bothering the rest of the engine with a doc update if there's
 | |
|       // nothing to do.
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     nsIContent* scriptElement = nullptr;
 | |
|     bool interrupted = false;
 | |
|     bool streamEnded = false;
 | |
| 
 | |
|     {
 | |
|       // autoFlush clears mOpQueue in its destructor unless
 | |
|       // SetNumberOfOpsToRemove is called first, in which case only
 | |
|       // some ops from the start of the queue are cleared.
 | |
|       nsHtml5AutoFlush autoFlush(this);
 | |
| 
 | |
|       nsHtml5TreeOperation* first = mOpQueue.Elements();
 | |
|       nsHtml5TreeOperation* last = first + mOpQueue.Length() - 1;
 | |
|       for (nsHtml5TreeOperation* iter = first;; ++iter) {
 | |
|         if (MOZ_UNLIKELY(!mParser)) {
 | |
|           // The previous tree op caused a call to nsIParser::Terminate().
 | |
|           return;
 | |
|         }
 | |
|         MOZ_ASSERT(IsInDocUpdate(),
 | |
|                    "Tried to perform tree op outside update batch.");
 | |
|         nsresult rv =
 | |
|             iter->Perform(this, &scriptElement, &interrupted, &streamEnded);
 | |
|         if (NS_FAILED(rv)) {
 | |
|           MarkAsBroken(rv);
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         // Be sure not to check the deadline if the last op was just performed.
 | |
|         if (MOZ_UNLIKELY(iter == last)) {
 | |
|           break;
 | |
|         } else if (MOZ_UNLIKELY(interrupted) ||
 | |
|                    MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
 | |
|                                 NS_ERROR_HTMLPARSER_INTERRUPTED)) {
 | |
|           autoFlush.SetNumberOfOpsToRemove((iter - first) + 1);
 | |
| 
 | |
|           nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (MOZ_UNLIKELY(!mParser)) {
 | |
|         // The parse ended during an update pause.
 | |
|         return;
 | |
|       }
 | |
|       if (streamEnded) {
 | |
|         GetParser()->PermanentlyUndefineInsertionPoint();
 | |
|       }
 | |
|     }  // end autoFlush
 | |
| 
 | |
|     if (MOZ_UNLIKELY(!mParser)) {
 | |
|       // Ending the doc update caused a call to nsIParser::Terminate().
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (streamEnded) {
 | |
|       DidBuildModel(false);
 | |
| #ifdef DEBUG
 | |
|       if (scriptElement) {
 | |
|         nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(scriptElement);
 | |
|         if (!sele) {
 | |
|           MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled,
 | |
|                      "Node didn't QI to script, but SVG wasn't disabled.");
 | |
|         }
 | |
|         MOZ_ASSERT(sele->IsMalformed(), "Script wasn't marked as malformed.");
 | |
|       }
 | |
| #endif
 | |
|     } else if (scriptElement) {
 | |
|       // must be tail call when mFlushState is eNotFlushing
 | |
|       RunScript(scriptElement, true);
 | |
| 
 | |
|       // Always check the clock in nsContentSink right after a script
 | |
|       StopDeflecting();
 | |
|       if (nsContentSink::DidProcessATokenImpl() ==
 | |
|           NS_ERROR_HTMLPARSER_INTERRUPTED) {
 | |
| #ifdef DEBUG
 | |
|         LOG(("REFLUSH SCHEDULED (after script): %d\n",
 | |
|              ++sTimesFlushLoopInterrupted));
 | |
| #endif
 | |
|         nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult nsHtml5TreeOpExecutor::FlushDocumentWrite() {
 | |
|   nsresult rv = IsBroken();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   FlushSpeculativeLoads();  // Make sure speculative loads never start after the
 | |
|                             // corresponding normal loads for the same URLs.
 | |
| 
 | |
|   if (MOZ_UNLIKELY(!mParser)) {
 | |
|     // The parse has ended.
 | |
|     ClearOpQueue();  // clear in order to be able to assert in destructor
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (mFlushState != eNotFlushing) {
 | |
|     // XXX Can this happen? In case it can, let's avoid crashing.
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // avoid crashing near EOF
 | |
|   RefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
 | |
|   RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
 | |
|   Unused << parserKungFuDeathGrip;  // Intentionally not used within function
 | |
|   RefPtr<nsHtml5StreamParser> streamParserGrip;
 | |
|   if (mParser) {
 | |
|     streamParserGrip = GetParser()->GetStreamParser();
 | |
|   }
 | |
|   Unused << streamParserGrip;  // Intentionally not used within function
 | |
| 
 | |
|   MOZ_RELEASE_ASSERT(!mReadingFromStage,
 | |
|                      "Got doc write flush when reading from stage");
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   mStage.AssertEmpty();
 | |
| #endif
 | |
| 
 | |
|   nsIContent* scriptElement = nullptr;
 | |
|   bool interrupted = false;
 | |
|   bool streamEnded = false;
 | |
| 
 | |
|   {
 | |
|     // autoFlush clears mOpQueue in its destructor.
 | |
|     nsHtml5AutoFlush autoFlush(this);
 | |
| 
 | |
|     nsHtml5TreeOperation* start = mOpQueue.Elements();
 | |
|     nsHtml5TreeOperation* end = start + mOpQueue.Length();
 | |
|     for (nsHtml5TreeOperation* iter = start; iter < end; ++iter) {
 | |
|       if (MOZ_UNLIKELY(!mParser)) {
 | |
|         // The previous tree op caused a call to nsIParser::Terminate().
 | |
|         return rv;
 | |
|       }
 | |
|       NS_ASSERTION(IsInDocUpdate(),
 | |
|                    "Tried to perform tree op outside update batch.");
 | |
|       rv = iter->Perform(this, &scriptElement, &interrupted, &streamEnded);
 | |
|       if (NS_FAILED(rv)) {
 | |
|         MarkAsBroken(rv);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (MOZ_UNLIKELY(!mParser)) {
 | |
|       // The parse ended during an update pause.
 | |
|       return rv;
 | |
|     }
 | |
|     if (streamEnded) {
 | |
|       // This should be redundant but let's do it just in case.
 | |
|       GetParser()->PermanentlyUndefineInsertionPoint();
 | |
|     }
 | |
|   }  // autoFlush
 | |
| 
 | |
|   if (MOZ_UNLIKELY(!mParser)) {
 | |
|     // Ending the doc update caused a call to nsIParser::Terminate().
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (streamEnded) {
 | |
|     DidBuildModel(false);
 | |
| #ifdef DEBUG
 | |
|     if (scriptElement) {
 | |
|       nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(scriptElement);
 | |
|       if (!sele) {
 | |
|         MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled,
 | |
|                    "Node didn't QI to script, but SVG wasn't disabled.");
 | |
|       }
 | |
|       MOZ_ASSERT(sele->IsMalformed(), "Script wasn't marked as malformed.");
 | |
|     }
 | |
| #endif
 | |
|   } else if (scriptElement) {
 | |
|     // must be tail call when mFlushState is eNotFlushing
 | |
|     RunScript(scriptElement, true);
 | |
|   }
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::CommitToInternalEncoding() {
 | |
|   if (MOZ_UNLIKELY(!mParser || !mStreamParser)) {
 | |
|     // An extension terminated the parser from a HTTP observer.
 | |
|     ClearOpQueue();  // clear in order to be able to assert in destructor
 | |
|     return;
 | |
|   }
 | |
|   mStreamParser->ContinueAfterScriptsOrEncodingCommitment(nullptr, nullptr,
 | |
|                                                           false);
 | |
| }
 | |
| 
 | |
| [[nodiscard]] bool nsHtml5TreeOpExecutor::TakeOpsFromStage() {
 | |
|   return mStage.MoveOpsTo(mOpQueue);
 | |
| }
 | |
| 
 | |
| // copied from HTML content sink
 | |
| bool nsHtml5TreeOpExecutor::IsScriptEnabled() {
 | |
|   // Note that if we have no document or no docshell or no global or whatnot we
 | |
|   // want to claim script _is_ enabled, so we don't parse the contents of
 | |
|   // <noscript> tags!
 | |
|   if (!mDocument || !mDocShell) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return mDocument->IsScriptEnabled();
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::StartLayout(bool* aInterrupted) {
 | |
|   if (mLayoutStarted || !mDocument) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsHtml5AutoPauseUpdate autoPause(this);
 | |
| 
 | |
|   if (MOZ_UNLIKELY(!mParser)) {
 | |
|     // got terminate
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsContentSink::StartLayout(false);
 | |
| 
 | |
|   if (mParser) {
 | |
|     *aInterrupted = !GetParser()->IsParserEnabled();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::PauseDocUpdate(bool* aInterrupted) {
 | |
|   // Pausing the document update allows JS to run, and potentially block
 | |
|   // further parsing.
 | |
|   nsHtml5AutoPauseUpdate autoPause(this);
 | |
| 
 | |
|   if (MOZ_LIKELY(mParser)) {
 | |
|     *aInterrupted = !GetParser()->IsParserEnabled();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * The reason why this code is here and not in the tree builder even in the
 | |
|  * main-thread case is to allow the control to return from the tokenizer
 | |
|  * before scripts run. This way, the tokenizer is not invoked re-entrantly
 | |
|  * although the parser is.
 | |
|  *
 | |
|  * The reason why this is called with `aMayDocumentWriteOrBlock=true` as a
 | |
|  * tail call when `mFlushState` is set to `eNotFlushing` is to allow re-entry
 | |
|  * to `Flush()` but only after the current `Flush()` has cleared the op queue
 | |
|  * and is otherwise done cleaning up after itself.
 | |
|  */
 | |
| void nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement,
 | |
|                                       bool aMayDocumentWriteOrBlock) {
 | |
|   if (mRunsToCompletion) {
 | |
|     // We are in createContextualFragment() or in the upcoming document.parse().
 | |
|     // Do nothing. Let's not even mark scripts malformed here, because that
 | |
|     // could cause serialization weirdness later.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(mParser, "Trying to run script with a terminated parser.");
 | |
|   MOZ_ASSERT(aScriptElement, "No script to run");
 | |
|   nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement);
 | |
|   if (!sele) {
 | |
|     MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled,
 | |
|                "Node didn't QI to script, but SVG wasn't disabled.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   sele->SetCreatorParser(GetParser());
 | |
| 
 | |
|   if (!aMayDocumentWriteOrBlock) {
 | |
|     MOZ_ASSERT(sele->GetScriptDeferred() || sele->GetScriptAsync() ||
 | |
|                sele->GetScriptIsModule() || sele->GetScriptIsImportMap() ||
 | |
|                aScriptElement->AsElement()->HasAttr(nsGkAtoms::nomodule));
 | |
|     DebugOnly<bool> block = sele->AttemptToExecute();
 | |
|     MOZ_ASSERT(!block,
 | |
|                "Defer, async, module, importmap, or nomodule tried to block.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_RELEASE_ASSERT(
 | |
|       mFlushState == eNotFlushing,
 | |
|       "Tried to run a potentially-blocking script while flushing.");
 | |
| 
 | |
|   mReadingFromStage = false;
 | |
| 
 | |
|   // Copied from nsXMLContentSink
 | |
|   // Now tell the script that it's ready to go. This may execute the script
 | |
|   // or return true, or neither if the script doesn't need executing.
 | |
|   bool block = sele->AttemptToExecute();
 | |
| 
 | |
|   // If the act of insertion evaluated the script, we're fine.
 | |
|   // Else, block the parser till the script has loaded.
 | |
|   if (block) {
 | |
|     if (mParser) {
 | |
|       GetParser()->BlockParser();
 | |
|     }
 | |
|   } else {
 | |
|     // mParser may have been nulled out by now, but the flusher deals
 | |
| 
 | |
|     // If this event isn't needed, it doesn't do anything. It is sometimes
 | |
|     // necessary for the parse to continue after complex situations.
 | |
|     nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::Start() {
 | |
|   MOZ_ASSERT(!mStarted, "Tried to start when already started.");
 | |
|   mStarted = true;
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::UpdateCharsetSource(
 | |
|     nsCharsetSource aCharsetSource) {
 | |
|   if (mDocument) {
 | |
|     mDocument->SetDocumentCharacterSetSource(aCharsetSource);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::SetDocumentCharsetAndSource(
 | |
|     NotNull<const Encoding*> aEncoding, nsCharsetSource aCharsetSource) {
 | |
|   if (mDocument) {
 | |
|     mDocument->SetDocumentCharacterSetSource(aCharsetSource);
 | |
|     mDocument->SetDocumentCharacterSet(aEncoding);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(
 | |
|     NotNull<const Encoding*> aEncoding, int32_t aSource, uint32_t aLineNumber) {
 | |
|   nsHtml5AutoPauseUpdate autoPause(this);
 | |
|   if (MOZ_UNLIKELY(!mParser)) {
 | |
|     // got terminate
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mDocShell) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsDocShell> docShell = static_cast<nsDocShell*>(mDocShell.get());
 | |
| 
 | |
|   if (NS_SUCCEEDED(docShell->CharsetChangeStopDocumentLoad())) {
 | |
|     docShell->CharsetChangeReloadDocument(aEncoding, aSource);
 | |
|   }
 | |
|   // if the charset switch was accepted, mDocShell has called Terminate() on the
 | |
|   // parser by now
 | |
|   if (!mParser) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   GetParser()->ContinueAfterFailedCharsetSwitch();
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId,
 | |
|                                                       bool aError,
 | |
|                                                       uint32_t aLineNumber) {
 | |
|   // Encoding errors don't count towards already complaining
 | |
|   if (!(!strcmp(aMsgId, "EncError") || !strcmp(aMsgId, "EncErrorFrame") ||
 | |
|         !strcmp(aMsgId, "EncErrorFramePlain"))) {
 | |
|     if (mAlreadyComplainedAboutCharset) {
 | |
|       return;
 | |
|     }
 | |
|     mAlreadyComplainedAboutCharset = true;
 | |
|   }
 | |
|   nsContentUtils::ReportToConsole(
 | |
|       aError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag,
 | |
|       "HTML parser"_ns, mDocument, nsContentUtils::eHTMLPARSER_PROPERTIES,
 | |
|       aMsgId, nsTArray<nsString>(), nullptr, u""_ns, aLineNumber);
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset(
 | |
|     Document* aDoc, bool aUnrecognized) {
 | |
|   NS_ASSERTION(!mAlreadyComplainedAboutCharset,
 | |
|                "How come we already managed to complain?");
 | |
|   mAlreadyComplainedAboutCharset = true;
 | |
|   nsContentUtils::ReportToConsole(
 | |
|       nsIScriptError::errorFlag, "HTML parser"_ns, aDoc,
 | |
|       nsContentUtils::eHTMLPARSER_PROPERTIES,
 | |
|       aUnrecognized ? "EncProtocolUnsupported" : "EncProtocolReplacement");
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::MaybeComplainAboutDeepTree(uint32_t aLineNumber) {
 | |
|   if (mAlreadyComplainedAboutDeepTree) {
 | |
|     return;
 | |
|   }
 | |
|   mAlreadyComplainedAboutDeepTree = true;
 | |
|   nsContentUtils::ReportToConsole(
 | |
|       nsIScriptError::errorFlag, "HTML parser"_ns, mDocument,
 | |
|       nsContentUtils::eHTMLPARSER_PROPERTIES, "errDeepTree",
 | |
|       nsTArray<nsString>(), nullptr, u""_ns, aLineNumber);
 | |
| }
 | |
| 
 | |
| nsHtml5Parser* nsHtml5TreeOpExecutor::GetParser() {
 | |
|   MOZ_ASSERT(!mRunsToCompletion);
 | |
|   return static_cast<nsHtml5Parser*>(mParser.get());
 | |
| }
 | |
| 
 | |
| [[nodiscard]] bool nsHtml5TreeOpExecutor::MoveOpsFrom(
 | |
|     nsTArray<nsHtml5TreeOperation>& aOpQueue) {
 | |
|   MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
 | |
|                      "Ops added to mOpQueue during tree op execution.");
 | |
|   return !!mOpQueue.AppendElements(std::move(aOpQueue), mozilla::fallible_t());
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::ClearOpQueue() {
 | |
|   MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
 | |
|                      "mOpQueue cleared during tree op execution.");
 | |
|   mOpQueue.Clear();
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::RemoveFromStartOfOpQueue(
 | |
|     size_t aNumberOfOpsToRemove) {
 | |
|   MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
 | |
|                      "Ops removed from mOpQueue during tree op execution.");
 | |
|   mOpQueue.RemoveElementsAt(0, aNumberOfOpsToRemove);
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::InitializeDocWriteParserState(
 | |
|     nsAHtml5TreeBuilderState* aState, int32_t aLine) {
 | |
|   GetParser()->InitializeDocWriteParserState(aState, aLine);
 | |
| }
 | |
| 
 | |
| nsIURI* nsHtml5TreeOpExecutor::GetViewSourceBaseURI() {
 | |
|   if (!mViewSourceBaseURI) {
 | |
|     // We query the channel for the baseURI because in certain situations it
 | |
|     // cannot otherwise be determined. If this process fails, fall back to the
 | |
|     // standard method.
 | |
|     nsCOMPtr<nsIViewSourceChannel> vsc =
 | |
|         do_QueryInterface(mDocument->GetChannel());
 | |
|     if (vsc) {
 | |
|       nsresult rv = vsc->GetBaseURI(getter_AddRefs(mViewSourceBaseURI));
 | |
|       if (NS_SUCCEEDED(rv) && mViewSourceBaseURI) {
 | |
|         return mViewSourceBaseURI;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsIURI> orig = mDocument->GetOriginalURI();
 | |
|     if (orig->SchemeIs("view-source")) {
 | |
|       nsCOMPtr<nsINestedURI> nested = do_QueryInterface(orig);
 | |
|       NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!");
 | |
|       nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI));
 | |
|     } else {
 | |
|       // Fail gracefully if the base URL isn't a view-source: URL.
 | |
|       // Not sure if this can ever happen.
 | |
|       mViewSourceBaseURI = orig;
 | |
|     }
 | |
|   }
 | |
|   return mViewSourceBaseURI;
 | |
| }
 | |
| 
 | |
| bool nsHtml5TreeOpExecutor::IsExternalViewSource() {
 | |
|   if (!StaticPrefs::view_source_editor_external()) {
 | |
|     return false;
 | |
|   }
 | |
|   if (mDocumentURI) {
 | |
|     return mDocumentURI->SchemeIs("view-source");
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // Speculative loading
 | |
| 
 | |
| nsIURI* nsHtml5TreeOpExecutor::BaseURIForPreload() {
 | |
|   // The URL of the document without <base>
 | |
|   nsIURI* documentURI = mDocument->GetDocumentURI();
 | |
|   // The URL of the document with non-speculative <base>
 | |
|   nsIURI* documentBaseURI = mDocument->GetDocBaseURI();
 | |
| 
 | |
|   // If the two above are different, use documentBaseURI. If they are the same,
 | |
|   // the document object isn't aware of a <base>, so attempt to use the
 | |
|   // mSpeculationBaseURI or, failing, that, documentURI.
 | |
|   return (documentURI == documentBaseURI)
 | |
|              ? (mSpeculationBaseURI ? mSpeculationBaseURI.get() : documentURI)
 | |
|              : documentBaseURI;
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIURI>
 | |
| nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYetAndMediaApplies(
 | |
|     const nsAString& aURL, const nsAString& aMedia) {
 | |
|   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
 | |
|   if (!uri) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (!MediaApplies(aMedia)) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return uri.forget();
 | |
| }
 | |
| 
 | |
| bool nsHtml5TreeOpExecutor::MediaApplies(const nsAString& aMedia) {
 | |
|   using dom::MediaList;
 | |
| 
 | |
|   if (aMedia.IsEmpty()) {
 | |
|     return true;
 | |
|   }
 | |
|   RefPtr<MediaList> media = MediaList::Create(NS_ConvertUTF16toUTF8(aMedia));
 | |
|   return media->Matches(*mDocument);
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIURI> nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(
 | |
|     const nsAString& aURL) {
 | |
|   if (aURL.IsEmpty()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsIURI* base = BaseURIForPreload();
 | |
|   auto encoding = mDocument->GetDocumentCharacterSet();
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, encoding, base);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     NS_WARNING("Failed to create a URI");
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (ShouldPreloadURI(uri)) {
 | |
|     return uri.forget();
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| bool nsHtml5TreeOpExecutor::ShouldPreloadURI(nsIURI* aURI) {
 | |
|   nsAutoCString spec;
 | |
|   nsresult rv = aURI->GetSpec(spec);
 | |
|   NS_ENSURE_SUCCESS(rv, false);
 | |
|   return mPreloadedURLs.EnsureInserted(spec);
 | |
| }
 | |
| 
 | |
| dom::ReferrerPolicy nsHtml5TreeOpExecutor::GetPreloadReferrerPolicy(
 | |
|     const nsAString& aReferrerPolicy) {
 | |
|   dom::ReferrerPolicy referrerPolicy =
 | |
|       dom::ReferrerInfo::ReferrerPolicyAttributeFromString(aReferrerPolicy);
 | |
|   return GetPreloadReferrerPolicy(referrerPolicy);
 | |
| }
 | |
| 
 | |
| dom::ReferrerPolicy nsHtml5TreeOpExecutor::GetPreloadReferrerPolicy(
 | |
|     ReferrerPolicy aReferrerPolicy) {
 | |
|   if (aReferrerPolicy != dom::ReferrerPolicy::_empty) {
 | |
|     return aReferrerPolicy;
 | |
|   }
 | |
| 
 | |
|   return mDocument->GetPreloadReferrerInfo()->ReferrerPolicy();
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::PreloadScript(
 | |
|     const nsAString& aURL, const nsAString& aCharset, const nsAString& aType,
 | |
|     const nsAString& aCrossOrigin, const nsAString& aMedia,
 | |
|     const nsAString& aNonce, const nsAString& aFetchPriority,
 | |
|     const nsAString& aIntegrity, dom::ReferrerPolicy aReferrerPolicy,
 | |
|     bool aScriptFromHead, bool aAsync, bool aDefer, bool aLinkPreload) {
 | |
|   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
 | |
|   if (!uri) {
 | |
|     return;
 | |
|   }
 | |
|   auto key = PreloadHashKey::CreateAsScript(uri, aCrossOrigin, aType);
 | |
|   if (mDocument->Preloads().PreloadExists(key)) {
 | |
|     return;
 | |
|   }
 | |
|   mDocument->ScriptLoader()->PreloadURI(
 | |
|       uri, aCharset, aType, aCrossOrigin, aNonce, aFetchPriority, aIntegrity,
 | |
|       aScriptFromHead, aAsync, aDefer, aLinkPreload,
 | |
|       GetPreloadReferrerPolicy(aReferrerPolicy), 0);
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::PreloadStyle(
 | |
|     const nsAString& aURL, const nsAString& aCharset,
 | |
|     const nsAString& aCrossOrigin, const nsAString& aMedia,
 | |
|     const nsAString& aReferrerPolicy, const nsAString& aNonce,
 | |
|     const nsAString& aIntegrity, bool aLinkPreload,
 | |
|     const nsAString& aFetchPriority) {
 | |
|   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
 | |
|   if (!uri) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aLinkPreload) {
 | |
|     auto hashKey = PreloadHashKey::CreateAsStyle(
 | |
|         uri, mDocument->NodePrincipal(),
 | |
|         dom::Element::StringToCORSMode(aCrossOrigin),
 | |
|         css::eAuthorSheetFeatures);
 | |
|     if (mDocument->Preloads().PreloadExists(hashKey)) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mDocument->PreloadStyle(
 | |
|       uri, Encoding::ForLabel(aCharset), aCrossOrigin,
 | |
|       GetPreloadReferrerPolicy(aReferrerPolicy), aNonce, aIntegrity,
 | |
|       aLinkPreload ? css::StylePreloadKind::FromLinkRelPreloadElement
 | |
|                    : css::StylePreloadKind::FromParser,
 | |
|       0, aFetchPriority);
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::PreloadImage(
 | |
|     const nsAString& aURL, const nsAString& aCrossOrigin,
 | |
|     const nsAString& aMedia, const nsAString& aSrcset, const nsAString& aSizes,
 | |
|     const nsAString& aImageReferrerPolicy, bool aLinkPreload,
 | |
|     const nsAString& aFetchPriority) {
 | |
|   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, aFetchPriority);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // These calls inform the document of picture state and seen sources, such that
 | |
| // it can use them to inform ResolvePreLoadImage as necessary
 | |
| void nsHtml5TreeOpExecutor::PreloadPictureSource(const nsAString& aSrcset,
 | |
|                                                  const nsAString& aSizes,
 | |
|                                                  const nsAString& aType,
 | |
|                                                  const nsAString& aMedia) {
 | |
|   mDocument->PreloadPictureImageSource(aSrcset, aSizes, aType, aMedia);
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::PreloadFont(const nsAString& aURL,
 | |
|                                         const nsAString& aCrossOrigin,
 | |
|                                         const nsAString& aMedia,
 | |
|                                         const nsAString& aReferrerPolicy,
 | |
|                                         const nsAString& aFetchPriority) {
 | |
|   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
 | |
|   if (!uri) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mDocument->Preloads().PreloadFont(uri, aCrossOrigin, aReferrerPolicy, 0,
 | |
|                                     aFetchPriority);
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::PreloadFetch(const nsAString& aURL,
 | |
|                                          const nsAString& aCrossOrigin,
 | |
|                                          const nsAString& aMedia,
 | |
|                                          const nsAString& aReferrerPolicy,
 | |
|                                          const nsAString& aFetchPriority) {
 | |
|   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
 | |
|   if (!uri) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mDocument->Preloads().PreloadFetch(uri, aCrossOrigin, aReferrerPolicy, 0,
 | |
|                                      aFetchPriority);
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::PreloadOpenPicture() {
 | |
|   mDocument->PreloadPictureOpened();
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::PreloadEndPicture() {
 | |
|   mDocument->PreloadPictureClosed();
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::AddBase(const nsAString& aURL) {
 | |
|   auto encoding = mDocument->GetDocumentCharacterSet();
 | |
|   nsresult rv = NS_NewURI(getter_AddRefs(mViewSourceBaseURI), aURL, encoding,
 | |
|                           GetViewSourceBaseURI());
 | |
|   if (NS_FAILED(rv)) {
 | |
|     mViewSourceBaseURI = nullptr;
 | |
|   }
 | |
| }
 | |
| void nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL) {
 | |
|   if (mSpeculationBaseURI) {
 | |
|     // the first one wins
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto encoding = mDocument->GetDocumentCharacterSet();
 | |
|   nsCOMPtr<nsIURI> newBaseURI;
 | |
|   DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(newBaseURI), aURL, encoding,
 | |
|                                      mDocument->GetDocumentURI());
 | |
|   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to create a URI");
 | |
|   if (!newBaseURI) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // See
 | |
|   // https://html.spec.whatwg.org/multipage/semantics.html#set-the-frozen-base-url
 | |
|   // data: and javascript: base URLs are not allowed.
 | |
|   if (newBaseURI->SchemeIs("data") || newBaseURI->SchemeIs("javascript")) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Check the document's CSP usually delivered via the CSP header.
 | |
|   if (nsCOMPtr<nsIContentSecurityPolicy> csp = mDocument->GetCsp()) {
 | |
|     // base-uri should not fallback to the default-src and preloads should not
 | |
|     // trigger violation reports.
 | |
|     bool cspPermitsBaseURI = true;
 | |
|     nsresult rv = csp->Permits(
 | |
|         nullptr, nullptr, newBaseURI,
 | |
|         nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true /* aSpecific */,
 | |
|         false /* aSendViolationReports */, &cspPermitsBaseURI);
 | |
|     if (NS_FAILED(rv) || !cspPermitsBaseURI) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Also check the CSP discovered from the <meta> tag during speculative
 | |
|   // parsing.
 | |
|   if (nsCOMPtr<nsIContentSecurityPolicy> csp = mDocument->GetPreloadCsp()) {
 | |
|     bool cspPermitsBaseURI = true;
 | |
|     nsresult rv = csp->Permits(
 | |
|         nullptr, nullptr, newBaseURI,
 | |
|         nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true /* aSpecific */,
 | |
|         false /* aSendViolationReports */, &cspPermitsBaseURI);
 | |
|     if (NS_FAILED(rv) || !cspPermitsBaseURI) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mSpeculationBaseURI = newBaseURI;
 | |
|   mDocument->Preloads().SetSpeculationBase(mSpeculationBaseURI);
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta(
 | |
|     const nsAString& aMetaReferrer) {
 | |
|   mDocument->UpdateReferrerInfoFromMeta(aMetaReferrer, true);
 | |
| }
 | |
| 
 | |
| void nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP) {
 | |
|   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 | |
| 
 | |
|   nsresult rv = NS_OK;
 | |
|   nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = mDocument->GetPreloadCsp();
 | |
|   if (!preloadCsp) {
 | |
|     RefPtr<nsCSPContext> csp = new nsCSPContext();
 | |
|     csp->SuppressParserLogMessages();
 | |
|     preloadCsp = csp;
 | |
|     rv = preloadCsp->SetRequestContextWithDocument(mDocument);
 | |
|     NS_ENSURE_SUCCESS_VOID(rv);
 | |
|   }
 | |
| 
 | |
|   // Please note that multiple meta CSPs need to be joined together.
 | |
|   rv = preloadCsp->AppendPolicy(
 | |
|       aCSP,
 | |
|       false,  // csp via meta tag can not be report only
 | |
|       true);  // delivered through the meta tag
 | |
|   NS_ENSURE_SUCCESS_VOID(rv);
 | |
| 
 | |
|   nsPIDOMWindowInner* inner = mDocument->GetInnerWindow();
 | |
|   if (inner) {
 | |
|     inner->SetPreloadCsp(preloadCsp);
 | |
|   }
 | |
|   mDocument->ApplySettingsFromCSP(true);
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG
 | |
| uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
 | |
| uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
 | |
| uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
 | |
| uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0;
 | |
| uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0;
 | |
| #endif
 | 
