/* -*- 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 "nsError.h" #include "nsHtml5TreeOpExecutor.h" #include "nsScriptLoader.h" #include "nsIMarkupDocumentViewer.h" #include "nsIContentViewer.h" #include "nsIDocShellTreeItem.h" #include "nsIStyleSheetLinkingElement.h" #include "nsStyleLinkElement.h" #include "nsIDocShell.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptGlobalObjectOwner.h" #include "nsIScriptSecurityManager.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 "mozilla/Util.h" // DebugOnly #include "sampler.h" #include "nsIScriptError.h" #include "nsIScriptContext.h" #include "mozilla/Preferences.h" #include "nsIHTMLDocument.h" #include "mozilla/Likely.h" using namespace mozilla; NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5TreeOpExecutor) NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor) NS_INTERFACE_TABLE_INHERITED1(nsHtml5TreeOpExecutor, nsIContentSink) NS_INTERFACE_TABLE_TAIL_INHERITING(nsContentSink) NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_OF_NSCOMPTR(mOwnedElements) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mOwnedElements) NS_IMPL_CYCLE_COLLECTION_UNLINK_END class nsHtml5ExecutorReflusher : public nsRunnable { private: nsRefPtr mExecutor; public: nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor) : mExecutor(aExecutor) {} NS_IMETHODIMP Run() { mExecutor->RunFlushLoop(); return NS_OK; } }; static mozilla::LinkedList* gBackgroundFlushList = nullptr; static nsITimer* gFlushTimer = nullptr; nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor(bool aRunsToCompletion) { mRunsToCompletion = aRunsToCompletion; mPreloadedURLs.Init(23); // Mean # of preloadable resources per page on dmoz // zeroing operator new for everything else } nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor() { if (gBackgroundFlushList && isInList()) { mOpQueue.Clear(); remove(); 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) { if (mDocShell && !GetDocument()->GetScriptGlobalObject() && !IsExternalViewSource()) { // Not loading as data but script global object not ready return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR); } mDocument->AddObserver(this); WillBuildModelImpl(); GetDocument()->BeginLoad(); return NS_OK; } // This is called when the tree construction has ended NS_IMETHODIMP nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated) { NS_PRECONDITION(mStarted, "Bad life cycle."); 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; } 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); } } void nsHtml5TreeOpExecutor::SetDocumentCharsetAndSource(nsACString& aCharset, int32_t aCharsetSource) { if (mDocument) { mDocument->SetDocumentCharacterSetSource(aCharsetSource); mDocument->SetDocumentCharacterSet(aCharset); } if (mDocShell) { // the following logic to get muCV is copied from // nsHTMLDocument::StartDocumentLoad // We need to call muCV->SetPrevDocCharacterSet here in case // the charset is detected by parser DetectMetaTag nsCOMPtr mucv; nsCOMPtr cv; mDocShell->GetContentViewer(getter_AddRefs(cv)); if (cv) { mucv = do_QueryInterface(cv); } else { // in this block of code, if we get an error result, we return // it but if we get a null pointer, that's perfectly legal for // parent and parentContentViewer nsCOMPtr docShellAsItem = do_QueryInterface(mDocShell); if (!docShellAsItem) { return; } nsCOMPtr parentAsItem; docShellAsItem->GetSameTypeParent(getter_AddRefs(parentAsItem)); nsCOMPtr parent(do_QueryInterface(parentAsItem)); if (parent) { nsCOMPtr parentContentViewer; nsresult rv = parent->GetContentViewer(getter_AddRefs(parentContentViewer)); if (NS_SUCCEEDED(rv) && parentContentViewer) { mucv = do_QueryInterface(parentContentViewer); } } } if (mucv) { mucv->SetPrevDocCharacterSet(aCharset); } } } nsISupports* nsHtml5TreeOpExecutor::GetTarget() { return mDocument; } // nsContentSink overrides void nsHtml5TreeOpExecutor::UpdateChildCounts() { // No-op } nsresult nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!mRunsToCompletion, "Fragment parsers can't be broken!"); 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? nsCOMPtr terminator = NS_NewRunnableMethod(GetParser(), &nsHtml5Parser::Terminate); if (NS_FAILED(NS_DispatchToMainThread(terminator))) { NS_WARNING("failed to dispatch executor flush event"); } } return aReason; } nsresult nsHtml5TreeOpExecutor::FlushTags() { return NS_OK; } void FlushTimerCallback(nsITimer* aTimer, void* aClosure) { nsRefPtr 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 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(); } if (!isInList()) { gBackgroundFlushList->insertBack(this); } if (!gFlushTimer) { nsCOMPtr 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->InitWithFuncCallback(FlushTimerCallback, nullptr, 50, nsITimer::TYPE_REPEATING_SLACK); } } } void nsHtml5TreeOpExecutor::UpdateStyleSheet(nsIContent* aElement) { // Break out of the doc update created by Flush() to zap a runnable // waiting to call UpdateStyleSheet without the right observer EndDocUpdate(); if (MOZ_UNLIKELY(!mParser)) { // EndDocUpdate ran stuff that called nsIParser::Terminate() return; } nsCOMPtr ssle(do_QueryInterface(aElement)); NS_ASSERTION(ssle, "Node didn't QI to style."); ssle->SetEnableUpdates(true); bool willNotify; bool isAlternate; nsresult rv = ssle->UpdateStyleSheet(mRunsToCompletion ? nullptr : this, &willNotify, &isAlternate); if (NS_SUCCEEDED(rv) && willNotify && !isAlternate && !mRunsToCompletion) { ++mPendingSheetCount; mScriptLoader->AddExecuteBlocker(); } if (aElement->IsHTML(nsGkAtoms::link)) { // look for nsAutoString relVal; aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, relVal); if (!relVal.IsEmpty()) { uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(relVal); bool hasPrefetch = linkTypes & PREFETCH; if (hasPrefetch || (linkTypes & NEXT)) { nsAutoString hrefVal; aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal); if (!hrefVal.IsEmpty()) { PrefetchHref(hrefVal, aElement, hasPrefetch); } } if (linkTypes & DNS_PREFETCH) { nsAutoString hrefVal; aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal); if (!hrefVal.IsEmpty()) { PrefetchDNS(hrefVal); } } } } // Re-open update BeginDocUpdate(); } void nsHtml5TreeOpExecutor::FlushSpeculativeLoads() { nsTArray speculativeLoadQueue; mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue); const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements(); const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); for (nsHtml5SpeculativeLoad* iter = const_cast(start); iter < end; ++iter) { if (MOZ_UNLIKELY(!mParser)) { // An extension terminated the parser from a HTTP observer. return; } iter->Perform(this); } } class nsHtml5FlushLoopGuard { private: nsRefPtr mExecutor; #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH uint32_t mStartTime; #endif public: 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() { SAMPLE_LABEL("html5", "RunFlushLoop"); if (mRunFlushLoopOnStack) { // There's already a RunFlushLoop() on the call stack. return; } nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu! nsCOMPtr 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 (!mParser->IsParserEnabled()) { // The parser is blocked. return; } if (mFlushState != eNotFlushing) { // XXX Can this happen? In case it can, let's avoid crashing. return; } // If there are scripts executing, then the content sink is jumping the gun // (probably due to a synchronous XMLHttpRequest) and will re-enable us // later, see bug 460706. if (IsScriptExecuting()) { return; } if (mReadingFromStage) { nsTArray speculativeLoadQueue; 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(); nsRefPtr streamKungFuDeathGrip = GetParser()->GetStreamParser(); // Now parse content left in the document.write() buffer queue if any. // This may generate tree ops on its own or dequeue a speculation. GetParser()->ParseUntilBlocked(); } 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(); mElementsSeenInThisAppendBatch.SetCapacity(numberOfOpsToFlush * 2); const nsHtml5TreeOperation* first = mOpQueue.Elements(); const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1; for (nsHtml5TreeOperation* iter = const_cast(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."); iter->Perform(this, &scriptElement); // 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; } } } } void nsHtml5TreeOpExecutor::FlushDocumentWrite() { 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; } if (mFlushState != eNotFlushing) { // XXX Can this happen? In case it can, let's avoid crashing. return; } mFlushState = eInFlush; // avoid crashing near EOF nsRefPtr kungFuDeathGrip(this); nsRefPtr parserKungFuDeathGrip(mParser); 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(); mElementsSeenInThisAppendBatch.SetCapacity(numberOfOpsToFlush * 2); const nsHtml5TreeOperation* start = mOpQueue.Elements(); const nsHtml5TreeOperation* end = start + numberOfOpsToFlush; for (nsHtml5TreeOperation* iter = const_cast(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."); iter->Perform(this, &scriptElement); } mOpQueue.Clear(); EndDocUpdate(); mFlushState = eNotFlushing; if (MOZ_UNLIKELY(!mParser)) { // Ending the doc update caused a call to nsIParser::Terminate(). return; } if (scriptElement) { // must be tail call when mFlushState is eNotFlushing RunScript(scriptElement); } } // copied from HTML content sink bool nsHtml5TreeOpExecutor::IsScriptEnabled() { if (!mDocument || !mDocShell) return true; nsCOMPtr globalObject = mDocument->GetScriptGlobalObject(); // Getting context is tricky if the document hasn't had its // GlobalObject set yet if (!globalObject) { nsCOMPtr owner = do_GetInterface(mDocShell); NS_ENSURE_TRUE(owner, true); globalObject = owner->GetScriptGlobalObject(); NS_ENSURE_TRUE(globalObject, true); } nsIScriptContext *scriptContext = globalObject->GetContext(); NS_ENSURE_TRUE(scriptContext, true); JSContext* cx = scriptContext->GetNativeContext(); NS_ENSURE_TRUE(cx, true); bool enabled = true; nsContentUtils::GetSecurityManager()-> CanExecuteScripts(cx, mDocument->NodePrincipal(), &enabled); return enabled; } void nsHtml5TreeOpExecutor::SetDocumentMode(nsHtml5DocumentMode m) { nsCompatibility mode = eCompatibility_NavQuirks; switch (m) { case STANDARDS_MODE: mode = eCompatibility_FullStandards; break; case ALMOST_STANDARDS_MODE: mode = eCompatibility_AlmostStandards; break; case QUIRKS_MODE: mode = eCompatibility_NavQuirks; break; } nsCOMPtr htmlDocument = do_QueryInterface(mDocument); NS_ASSERTION(htmlDocument, "Document didn't QI into HTML document."); htmlDocument->SetCompatibilityMode(mode); } 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 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 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(); } } nsresult nsHtml5TreeOpExecutor::Init(nsIDocument* aDoc, nsIURI* aURI, nsISupports* aContainer, nsIChannel* aChannel) { return nsContentSink::Init(aDoc, aURI, aContainer, aChannel); } 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 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 treeItem = do_QueryInterface(mDocShell); nsCOMPtr parent; treeItem->GetSameTypeParent(getter_AddRefs(parent)); if (parent) { return; } } mAlreadyComplainedAboutCharset = true; nsContentUtils::ReportToConsole(aError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag, "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, "HTML parser", aDoc, nsContentUtils::eHTMLPARSER_PROPERTIES, "EncProtocolUnsupported"); } nsHtml5Parser* nsHtml5TreeOpExecutor::GetParser() { MOZ_ASSERT(!mRunsToCompletion); return static_cast(mParser.get()); } void nsHtml5TreeOpExecutor::Reset() { MOZ_ASSERT(mRunsToCompletion); DropHeldElements(); mOpQueue.Clear(); mStarted = false; mFlushState = eNotFlushing; mRunFlushLoopOnStack = false; MOZ_ASSERT(!mReadingFromStage); MOZ_ASSERT(NS_SUCCEEDED(mBroken)); } void nsHtml5TreeOpExecutor::DropHeldElements() { mScriptLoader = nullptr; mDocument = nullptr; mNodeInfoManager = nullptr; mCSSLoader = nullptr; mDocumentURI = nullptr; mDocShell = nullptr; mOwnedElements.Clear(); } void nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray& aOpQueue) { NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution."); if (mOpQueue.IsEmpty()) { mOpQueue.SwapElements(aOpQueue); return; } mOpQueue.MoveElementsFrom(aOpQueue); } void nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine) { GetParser()->InitializeDocWriteParserState(aState, aLine); } nsIURI* nsHtml5TreeOpExecutor::GetViewSourceBaseURI() { if (!mViewSourceBaseURI) { nsCOMPtr orig = mDocument->GetOriginalURI(); bool isViewSource; orig->SchemeIs("view-source", &isViewSource); if (isViewSource) { nsCOMPtr 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 already_AddRefed nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(const nsAString& aURL) { if (aURL.IsEmpty()) { return nullptr; } // The URL of the document without nsIURI* documentURI = mDocument->GetDocumentURI(); // The URL of the document with non-speculative 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 , so attempt to use the // mSpeculationBaseURI or, failing, that, documentURI. nsIURI* base = (documentURI == documentBaseURI) ? (mSpeculationBaseURI ? mSpeculationBaseURI.get() : documentURI) : documentBaseURI; const nsCString& charset = mDocument->GetDocumentCharacterSet(); nsCOMPtr 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; } nsAutoCString spec; uri->GetSpec(spec); if (mPreloadedURLs.Contains(spec)) { return nullptr; } mPreloadedURLs.PutEntry(spec); return uri.forget(); } void nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL, const nsAString& aCharset, const nsAString& aType, const nsAString& aCrossOrigin) { nsCOMPtr uri = ConvertIfNotPreloadedYet(aURL); if (!uri) { return; } mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin); } void nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL, const nsAString& aCharset, const nsAString& aCrossOrigin) { nsCOMPtr uri = ConvertIfNotPreloadedYet(aURL); if (!uri) { return; } mDocument->PreloadStyle(uri, aCharset, aCrossOrigin); } void nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL, const nsAString& aCrossOrigin) { nsCOMPtr uri = ConvertIfNotPreloadedYet(aURL); if (!uri) { return; } mDocument->MaybePreLoadImage(uri, aCrossOrigin); } void nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL) { if (mSpeculationBaseURI) { // the first one wins return; } const nsCString& charset = mDocument->GetDocumentCharacterSet(); DebugOnly rv = NS_NewURI(getter_AddRefs(mSpeculationBaseURI), aURL, charset.get(), mDocument->GetDocumentURI()); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to create a URI"); } #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;