summaryrefslogtreecommitdiffstats
path: root/parser/html/nsHtml5TreeOpExecutor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'parser/html/nsHtml5TreeOpExecutor.cpp')
-rw-r--r--parser/html/nsHtml5TreeOpExecutor.cpp1085
1 files changed, 1085 insertions, 0 deletions
diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp
new file mode 100644
index 000000000..b0eabb13d
--- /dev/null
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -0,0 +1,1085 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=2 et tw=79: */
+/* 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/nsCSPService.h"
+
+#include "nsError.h"
+#include "nsHtml5TreeOpExecutor.h"
+#include "nsScriptLoader.h"
+#include "nsIContentViewer.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShell.h"
+#include "nsIDOMDocument.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIWebShellServices.h"
+#include "nsContentUtils.h"
+#include "mozAutoDocUpdate.h"
+#include "nsNetUtil.h"
+#include "nsHtml5Parser.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5StreamParser.h"
+#include "mozilla/css/Loader.h"
+#include "GeckoProfiler.h"
+#include "nsIScriptError.h"
+#include "nsIScriptContext.h"
+#include "mozilla/Preferences.h"
+#include "nsIHTMLDocument.h"
+#include "nsIViewSourceChannel.h"
+#include "xpcpublic.h"
+
+using namespace mozilla;
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor)
+ NS_INTERFACE_TABLE_INHERITED(nsHtml5TreeOpExecutor,
+ nsIContentSink)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsHtml5DocumentBuilder)
+
+NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
+
+NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
+
+class nsHtml5ExecutorReflusher : public Runnable
+{
+ private:
+ RefPtr<nsHtml5TreeOpExecutor> mExecutor;
+ public:
+ explicit nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
+ : mExecutor(aExecutor)
+ {}
+ NS_IMETHOD Run() override
+ {
+ mExecutor->RunFlushLoop();
+ return NS_OK;
+ }
+};
+
+static mozilla::LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr;
+static nsITimer* gFlushTimer = nullptr;
+
+nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
+ : nsHtml5DocumentBuilder(false)
+ , mPreloadedURLs(23) // Mean # of preloadable resources per page on dmoz
+ , mSpeculationReferrerPolicy(mozilla::net::RP_Default)
+{
+ // zeroing operator new for everything else
+}
+
+nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
+{
+ if (gBackgroundFlushList && isInList()) {
+ mOpQueue.Clear();
+ removeFrom(*gBackgroundFlushList);
+ if (gBackgroundFlushList->isEmpty()) {
+ delete gBackgroundFlushList;
+ gBackgroundFlushList = nullptr;
+ if (gFlushTimer) {
+ gFlushTimer->Cancel();
+ NS_RELEASE(gFlushTimer);
+ }
+ }
+ }
+ NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue.");
+}
+
+// nsIContentSink
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::WillParse()
+{
+ NS_NOTREACHED("No one should call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::WillBuildModel(nsDTDMode aDTDMode)
+{
+ 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 (!aTerminated) {
+ // This is needed to avoid unblocking loads too many times on one hand
+ // and on the other hand to avoid destroying the frame constructor from
+ // within an update batch. See bug 537683.
+ EndDocUpdate();
+
+ // If the above caused a call to nsIParser::Terminate(), let that call
+ // win.
+ if (!mParser) {
+ return NS_OK;
+ }
+ }
+
+ if (mRunsToCompletion) {
+ return NS_OK;
+ }
+
+ GetParser()->DropStreamParser();
+
+ // 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()));
+
+ 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.
+ bool destroying = true;
+ if (mDocShell) {
+ mDocShell->IsBeingDestroyed(&destroying);
+ }
+
+ if (!destroying) {
+ 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();
+ }
+ DropParserAndPerfHint();
+#ifdef GATHER_DOCWRITE_STATISTICS
+ printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites);
+ printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites);
+ printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites);
+#endif
+#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ printf("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize);
+ if (sAppendBatchExaminations != 0) {
+ printf("AVERAGE SLOTS EXAMINED: %d\n", sAppendBatchSlotsExamined / sAppendBatchExaminations);
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::WillInterrupt()
+{
+ NS_NOTREACHED("Don't call. For interface compat only.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::WillResume()
+{
+ NS_NOTREACHED("Don't call. For interface compat only.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser)
+{
+ mParser = aParser;
+ return NS_OK;
+}
+
+void
+nsHtml5TreeOpExecutor::FlushPendingNotifications(mozFlushType aType)
+{
+ if (aType >= Flush_InterruptibleLayout) {
+ // Bug 577508 / 253951
+ nsContentSink::StartLayout(true);
+ }
+}
+
+nsISupports*
+nsHtml5TreeOpExecutor::GetTarget()
+{
+ return mDocument;
+}
+
+nsresult
+nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason)
+{
+ NS_ASSERTION(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) { // can mParser ever be null here?
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_DispatchToMainThread(NewRunnableMethod(GetParser(), &nsHtml5Parser::Terminate)));
+ }
+ return aReason;
+}
+
+void
+FlushTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ RefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst();
+ if (ex) {
+ ex->RunFlushLoop();
+ }
+ if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) {
+ delete gBackgroundFlushList;
+ gBackgroundFlushList = nullptr;
+ gFlushTimer->Cancel();
+ NS_RELEASE(gFlushTimer);
+ }
+}
+
+void
+nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync()
+{
+ if (!mDocument || !mDocument->IsInBackgroundWindow()) {
+ nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);
+ if (NS_FAILED(NS_DispatchToMainThread(flusher))) {
+ NS_WARNING("failed to dispatch executor flush event");
+ }
+ } else {
+ if (!gBackgroundFlushList) {
+ gBackgroundFlushList = new mozilla::LinkedList<nsHtml5TreeOpExecutor>();
+ }
+ if (!isInList()) {
+ gBackgroundFlushList->insertBack(this);
+ }
+ if (!gFlushTimer) {
+ nsCOMPtr<nsITimer> t = do_CreateInstance("@mozilla.org/timer;1");
+ t.swap(gFlushTimer);
+ // The timer value 50 should not hopefully slow down background pages too
+ // much, yet lets event loop to process enough between ticks.
+ // See bug 734015.
+ gFlushTimer->InitWithNamedFuncCallback(FlushTimerCallback, nullptr,
+ 50, nsITimer::TYPE_REPEATING_SLACK,
+ "FlushTimerCallback");
+ }
+ }
+}
+
+void
+nsHtml5TreeOpExecutor::FlushSpeculativeLoads()
+{
+ nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
+ mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
+ const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
+ const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
+ for (nsHtml5SpeculativeLoad* iter = const_cast<nsHtml5SpeculativeLoad*>(start);
+ iter < end;
+ ++iter) {
+ if (MOZ_UNLIKELY(!mParser)) {
+ // An extension terminated the parser from a HTTP observer.
+ return;
+ }
+ iter->Perform(this);
+ }
+}
+
+class nsHtml5FlushLoopGuard
+{
+ private:
+ RefPtr<nsHtml5TreeOpExecutor> mExecutor;
+ #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ uint32_t mStartTime;
+ #endif
+ public:
+ explicit nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor)
+ : mExecutor(aExecutor)
+ #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ , mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow()))
+ #endif
+ {
+ mExecutor->mRunFlushLoopOnStack = true;
+ }
+ ~nsHtml5FlushLoopGuard()
+ {
+ #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ uint32_t timeOffTheEventLoop =
+ PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime;
+ if (timeOffTheEventLoop >
+ nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) {
+ nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop =
+ timeOffTheEventLoop;
+ }
+ printf("Longest time off the event loop: %d\n",
+ nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop);
+ #endif
+
+ mExecutor->mRunFlushLoopOnStack = false;
+ }
+};
+
+/**
+ * The purpose of the loop here is to avoid returning to the main event loop
+ */
+void
+nsHtml5TreeOpExecutor::RunFlushLoop()
+{
+ PROFILER_LABEL("nsHtml5TreeOpExecutor", "RunFlushLoop",
+ js::ProfileEntry::Category::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);
+
+ // Remember the entry time
+ (void) nsContentSink::WillParseImpl();
+
+ for (;;) {
+ if (!mParser) {
+ // Parse has terminated.
+ mOpQueue.Clear(); // 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;
+ mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, speculativeLoadQueue);
+ // Make sure speculative loads never start after the corresponding
+ // normal loads for the same URLs.
+ const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
+ const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
+ for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start;
+ iter < end;
+ ++iter) {
+ iter->Perform(this);
+ if (MOZ_UNLIKELY(!mParser)) {
+ // An extension terminated the parser from a HTTP observer.
+ mOpQueue.Clear(); // 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.
+ mOpQueue.Clear(); // clear in order to be able to assert in destructor
+ return;
+ }
+ // Not sure if this grip is still needed, but previously, the code
+ // gripped before calling ParseUntilBlocked();
+ RefPtr<nsHtml5StreamParser> streamKungFuDeathGrip =
+ GetParser()->GetStreamParser();
+ mozilla::Unused << streamKungFuDeathGrip; // Not used within function
+ // 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();
+ 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;
+ }
+
+ mFlushState = eInFlush;
+
+ nsIContent* scriptElement = nullptr;
+
+ BeginDocUpdate();
+
+ uint32_t numberOfOpsToFlush = mOpQueue.Length();
+
+ const nsHtml5TreeOperation* first = mOpQueue.Elements();
+ const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1;
+ for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(first);;) {
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The previous tree op caused a call to nsIParser::Terminate().
+ break;
+ }
+ NS_ASSERTION(mFlushState == eInDocUpdate,
+ "Tried to perform tree op outside update batch.");
+ nsresult rv = iter->Perform(this, &scriptElement);
+ 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(nsContentSink::DidProcessATokenImpl() ==
+ NS_ERROR_HTMLPARSER_INTERRUPTED)) {
+ mOpQueue.RemoveElementsAt(0, (iter - first) + 1);
+
+ EndDocUpdate();
+
+ mFlushState = eNotFlushing;
+
+ #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ printf("REFLUSH SCHEDULED (executing ops): %d\n",
+ ++sTimesFlushLoopInterrupted);
+ #endif
+ nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
+ return;
+ }
+ ++iter;
+ }
+
+ mOpQueue.Clear();
+
+ EndDocUpdate();
+
+ mFlushState = eNotFlushing;
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The parse ended already.
+ return;
+ }
+
+ if (scriptElement) {
+ // must be tail call when mFlushState is eNotFlushing
+ RunScript(scriptElement);
+
+ // Always check the clock in nsContentSink right after a script
+ StopDeflecting();
+ if (nsContentSink::DidProcessATokenImpl() ==
+ NS_ERROR_HTMLPARSER_INTERRUPTED) {
+ #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ printf("REFLUSH SCHEDULED (after script): %d\n",
+ ++sTimesFlushLoopInterrupted);
+ #endif
+ nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
+ return;
+ }
+ }
+ }
+}
+
+nsresult
+nsHtml5TreeOpExecutor::FlushDocumentWrite()
+{
+ nsresult rv = IsBroken();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ FlushSpeculativeLoads(); // Make sure speculative loads never start after the
+ // corresponding normal loads for the same URLs.
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The parse has ended.
+ mOpQueue.Clear(); // 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;
+ }
+
+ mFlushState = eInFlush;
+
+ // avoid crashing near EOF
+ RefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
+ RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
+ mozilla::Unused << parserKungFuDeathGrip; // Intentionally not used within function
+
+ NS_ASSERTION(!mReadingFromStage,
+ "Got doc write flush when reading from stage");
+
+#ifdef DEBUG
+ mStage.AssertEmpty();
+#endif
+
+ nsIContent* scriptElement = nullptr;
+
+ BeginDocUpdate();
+
+ uint32_t numberOfOpsToFlush = mOpQueue.Length();
+
+ const nsHtml5TreeOperation* start = mOpQueue.Elements();
+ const nsHtml5TreeOperation* end = start + numberOfOpsToFlush;
+ for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(start);
+ iter < end;
+ ++iter) {
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The previous tree op caused a call to nsIParser::Terminate().
+ break;
+ }
+ NS_ASSERTION(mFlushState == eInDocUpdate,
+ "Tried to perform tree op outside update batch.");
+ rv = iter->Perform(this, &scriptElement);
+ if (NS_FAILED(rv)) {
+ MarkAsBroken(rv);
+ break;
+ }
+ }
+
+ mOpQueue.Clear();
+
+ EndDocUpdate();
+
+ mFlushState = eNotFlushing;
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // Ending the doc update caused a call to nsIParser::Terminate().
+ return rv;
+ }
+
+ if (scriptElement) {
+ // must be tail call when mFlushState is eNotFlushing
+ RunScript(scriptElement);
+ }
+ return rv;
+}
+
+// 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;
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(mDocument->GetInnerWindow());
+ // Getting context is tricky if the document hasn't had its
+ // GlobalObject set yet
+ if (!globalObject) {
+ globalObject = mDocShell->GetScriptGlobalObject();
+ }
+ NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true);
+ return xpc::Scriptability::Get(globalObject->GetGlobalJSObject()).Allowed();
+}
+
+void
+nsHtml5TreeOpExecutor::StartLayout() {
+ if (mLayoutStarted || !mDocument) {
+ return;
+ }
+
+ EndDocUpdate();
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // got terminate
+ return;
+ }
+
+ nsContentSink::StartLayout(false);
+
+ BeginDocUpdate();
+}
+
+/**
+ * The reason why this code is here and not in the tree builder even in the
+ * main-thread case is to allow the control to return from the tokenizer
+ * before scripts run. This way, the tokenizer is not invoked re-entrantly
+ * although the parser is.
+ *
+ * The reason why this is called as a tail call when mFlushState is set to
+ * eNotFlushing is to allow re-entry to Flush() but only after the current
+ * Flush() has cleared the op queue and is otherwise done cleaning up after
+ * itself.
+ */
+void
+nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement)
+{
+ if (mRunsToCompletion) {
+ // We are in createContextualFragment() or in the upcoming document.parse().
+ // Do nothing. Let's not even mark scripts malformed here, because that
+ // could cause serialization weirdness later.
+ return;
+ }
+
+ NS_ASSERTION(aScriptElement, "No script to run");
+ nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement);
+
+ if (!mParser) {
+ NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed.");
+ // We got here not because of an end tag but because the tree builder
+ // popped an incomplete script element on EOF. Returning here to avoid
+ // calling back into mParser anymore.
+ return;
+ }
+
+ if (sele->GetScriptDeferred() || sele->GetScriptAsync()) {
+ DebugOnly<bool> block = sele->AttemptToExecute();
+ NS_ASSERTION(!block, "Defer or async script tried to block.");
+ return;
+ }
+
+ NS_ASSERTION(mFlushState == eNotFlushing, "Tried to run script when flushing.");
+
+ mReadingFromStage = false;
+
+ sele->SetCreatorParser(GetParser());
+
+ // Copied from nsXMLContentSink
+ // Now tell the script that it's ready to go. This may execute the script
+ // or return true, or neither if the script doesn't need executing.
+ bool block = sele->AttemptToExecute();
+
+ // If the act of insertion evaluated the script, we're fine.
+ // Else, block the parser till the script has loaded.
+ if (block) {
+ if (mParser) {
+ GetParser()->BlockParser();
+ }
+ } else {
+ // mParser may have been nulled out by now, but the flusher deals
+
+ // If this event isn't needed, it doesn't do anything. It is sometimes
+ // necessary for the parse to continue after complex situations.
+ nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
+ }
+}
+
+void
+nsHtml5TreeOpExecutor::Start()
+{
+ NS_PRECONDITION(!mStarted, "Tried to start when already started.");
+ mStarted = true;
+}
+
+void
+nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const char* aEncoding,
+ int32_t aSource,
+ uint32_t aLineNumber)
+{
+ EndDocUpdate();
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // got terminate
+ return;
+ }
+
+ nsCOMPtr<nsIWebShellServices> wss = do_QueryInterface(mDocShell);
+ if (!wss) {
+ return;
+ }
+
+ // ask the webshellservice to load the URL
+ if (NS_SUCCEEDED(wss->StopDocumentLoad())) {
+ wss->ReloadDocument(aEncoding, aSource);
+ }
+ // if the charset switch was accepted, wss has called Terminate() on the
+ // parser by now
+
+ if (!mParser) {
+ // success
+ if (aSource == kCharsetFromMetaTag) {
+ MaybeComplainAboutCharset("EncLateMetaReload", false, aLineNumber);
+ }
+ return;
+ }
+
+ if (aSource == kCharsetFromMetaTag) {
+ MaybeComplainAboutCharset("EncLateMetaTooLate", true, aLineNumber);
+ }
+
+ GetParser()->ContinueAfterFailedCharsetSwitch();
+
+ BeginDocUpdate();
+}
+
+void
+nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId,
+ bool aError,
+ uint32_t aLineNumber)
+{
+ if (mAlreadyComplainedAboutCharset) {
+ return;
+ }
+ // The EncNoDeclaration case for advertising iframes is so common that it
+ // would result is way too many errors. The iframe case doesn't matter
+ // when the ad is an image or a Flash animation anyway. When the ad is
+ // textual, a misrendered ad probably isn't a huge loss for users.
+ // Let's suppress the message in this case.
+ // This means that errors about other different-origin iframes in mashups
+ // are lost as well, but generally, the site author isn't in control of
+ // the embedded different-origin pages anyway and can't fix problems even
+ // if alerted about them.
+ if (!strcmp(aMsgId, "EncNoDeclaration") && mDocShell) {
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ mDocShell->GetSameTypeParent(getter_AddRefs(parent));
+ if (parent) {
+ return;
+ }
+ }
+ mAlreadyComplainedAboutCharset = true;
+ nsContentUtils::ReportToConsole(aError ? nsIScriptError::errorFlag
+ : nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("HTML parser"),
+ mDocument,
+ nsContentUtils::eHTMLPARSER_PROPERTIES,
+ aMsgId,
+ nullptr,
+ 0,
+ nullptr,
+ EmptyString(),
+ aLineNumber);
+}
+
+void
+nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset(nsIDocument* aDoc)
+{
+ NS_ASSERTION(!mAlreadyComplainedAboutCharset,
+ "How come we already managed to complain?");
+ mAlreadyComplainedAboutCharset = true;
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("HTML parser"),
+ aDoc,
+ nsContentUtils::eHTMLPARSER_PROPERTIES,
+ "EncProtocolUnsupported");
+}
+
+nsHtml5Parser*
+nsHtml5TreeOpExecutor::GetParser()
+{
+ MOZ_ASSERT(!mRunsToCompletion);
+ return static_cast<nsHtml5Parser*>(mParser.get());
+}
+
+void
+nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
+{
+ NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution.");
+ mOpQueue.AppendElements(Move(aOpQueue));
+}
+
+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();
+ bool isViewSource;
+ orig->SchemeIs("view-source", &isViewSource);
+ if (isViewSource) {
+ 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;
+}
+
+//static
+void
+nsHtml5TreeOpExecutor::InitializeStatics()
+{
+ mozilla::Preferences::AddBoolVarCache(&sExternalViewSource,
+ "view_source.editor.external");
+}
+
+bool
+nsHtml5TreeOpExecutor::IsExternalViewSource()
+{
+ if (!sExternalViewSource) {
+ return false;
+ }
+ bool isViewSource = false;
+ if (mDocumentURI) {
+ mDocumentURI->SchemeIs("view-source", &isViewSource);
+ }
+ return isViewSource;
+}
+
+// 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::ConvertIfNotPreloadedYet(const nsAString& aURL)
+{
+ if (aURL.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsIURI* base = BaseURIForPreload();
+ const nsCString& charset = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), 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);
+ if (mPreloadedURLs.Contains(spec)) {
+ return false;
+ }
+ mPreloadedURLs.PutEntry(spec);
+ return true;
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
+ const nsAString& aCharset,
+ const nsAString& aType,
+ const nsAString& aCrossOrigin,
+ const nsAString& aIntegrity,
+ bool aScriptFromHead)
+{
+ nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
+ if (!uri) {
+ return;
+ }
+ mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin,
+ aIntegrity, aScriptFromHead,
+ mSpeculationReferrerPolicy);
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
+ const nsAString& aCharset,
+ const nsAString& aCrossOrigin,
+ const nsAString& aIntegrity)
+{
+ nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
+ if (!uri) {
+ return;
+ }
+ mDocument->PreloadStyle(uri, aCharset, aCrossOrigin,
+ mSpeculationReferrerPolicy, aIntegrity);
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
+ const nsAString& aCrossOrigin,
+ const nsAString& aSrcset,
+ const nsAString& aSizes,
+ const nsAString& aImageReferrerPolicy)
+{
+ nsCOMPtr<nsIURI> baseURI = BaseURIForPreload();
+ nsCOMPtr<nsIURI> uri = mDocument->ResolvePreloadImage(baseURI, aURL, aSrcset,
+ aSizes);
+ if (uri && ShouldPreloadURI(uri)) {
+ // use document wide referrer policy
+ mozilla::net::ReferrerPolicy referrerPolicy = mSpeculationReferrerPolicy;
+ // if enabled in preferences, use the referrer attribute from the image, if provided
+ bool referrerAttributeEnabled = Preferences::GetBool("network.http.enablePerElementReferrer", true);
+ if (referrerAttributeEnabled) {
+ mozilla::net::ReferrerPolicy imageReferrerPolicy =
+ mozilla::net::AttributeReferrerPolicyFromString(aImageReferrerPolicy);
+ if (imageReferrerPolicy != mozilla::net::RP_Unset) {
+ referrerPolicy = imageReferrerPolicy;
+ }
+ }
+
+ mDocument->MaybePreLoadImage(uri, aCrossOrigin, referrerPolicy);
+ }
+}
+
+// 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::PreloadOpenPicture()
+{
+ mDocument->PreloadPictureOpened();
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadEndPicture()
+{
+ mDocument->PreloadPictureClosed();
+}
+
+void
+nsHtml5TreeOpExecutor::AddBase(const nsAString& aURL)
+{
+ const nsCString& charset = mDocument->GetDocumentCharacterSet();
+ nsresult rv = NS_NewURI(getter_AddRefs(mViewSourceBaseURI), aURL,
+ charset.get(), GetViewSourceBaseURI());
+ if (NS_FAILED(rv)) {
+ mViewSourceBaseURI = nullptr;
+ }
+}
+void
+nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL)
+{
+ if (mSpeculationBaseURI) {
+ // the first one wins
+ return;
+ }
+ const nsCString& charset = mDocument->GetDocumentCharacterSet();
+ DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(mSpeculationBaseURI), aURL,
+ charset.get(), mDocument->GetDocumentURI());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to create a URI");
+}
+
+void
+nsHtml5TreeOpExecutor::SetSpeculationReferrerPolicy(const nsAString& aReferrerPolicy)
+{
+ // Specs says:
+ // - Let value be the result of stripping leading and trailing whitespace from
+ // the value of element's content attribute.
+ // - If value is not the empty string, then:
+ if (aReferrerPolicy.IsEmpty()) {
+ return;
+ }
+
+ ReferrerPolicy policy = mozilla::net::ReferrerPolicyFromString(aReferrerPolicy);
+ // Specs says:
+ // - If policy is not the empty string, then set element's node document's
+ // referrer policy to policy
+ if (policy != mozilla::net::RP_Unset) {
+ SetSpeculationReferrerPolicy(policy);
+ }
+}
+
+void
+nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP)
+{
+ if (!CSPService::sCSPEnabled) {
+ return;
+ }
+
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ nsIPrincipal* principal = mDocument->NodePrincipal();
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCsp;
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
+ nsresult rv = principal->EnsurePreloadCSP(domDoc, getter_AddRefs(preloadCsp));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // please note that meta CSPs and CSPs delivered through a header 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);
+
+ // Record "speculated" referrer policy for preloads
+ bool hasReferrerPolicy = false;
+ uint32_t referrerPolicy = mozilla::net::RP_Default;
+ rv = preloadCsp->GetReferrerPolicy(&referrerPolicy, &hasReferrerPolicy);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ if (hasReferrerPolicy) {
+ SetSpeculationReferrerPolicy(static_cast<ReferrerPolicy>(referrerPolicy));
+ }
+
+ mDocument->ApplySettingsFromCSP(true);
+}
+
+void
+nsHtml5TreeOpExecutor::SetSpeculationReferrerPolicy(ReferrerPolicy aReferrerPolicy)
+{
+ // Record "speculated" referrer policy locally and thread through the
+ // speculation phase. The actual referrer policy will be set by
+ // HTMLMetaElement::BindToTree().
+ mSpeculationReferrerPolicy = aReferrerPolicy;
+}
+
+#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
+uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
+uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
+uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0;
+uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0;
+#endif
+bool nsHtml5TreeOpExecutor::sExternalViewSource = false;