michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=2 et tw=79: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/Likely.h" michael@0: michael@0: #include "nsError.h" michael@0: #include "nsHtml5TreeOpExecutor.h" michael@0: #include "nsScriptLoader.h" michael@0: #include "nsIMarkupDocumentViewer.h" michael@0: #include "nsIContentViewer.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIWebShellServices.h" michael@0: #include "nsContentUtils.h" michael@0: #include "mozAutoDocUpdate.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsHtml5Parser.h" michael@0: #include "nsHtml5Tokenizer.h" michael@0: #include "nsHtml5TreeBuilder.h" michael@0: #include "nsHtml5StreamParser.h" michael@0: #include "mozilla/css/Loader.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsIHTMLDocument.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor) michael@0: NS_INTERFACE_TABLE_INHERITED(nsHtml5TreeOpExecutor, michael@0: nsIContentSink) michael@0: NS_INTERFACE_TABLE_TAIL_INHERITING(nsHtml5DocumentBuilder) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) michael@0: michael@0: NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) michael@0: michael@0: class nsHtml5ExecutorReflusher : public nsRunnable michael@0: { michael@0: private: michael@0: nsRefPtr mExecutor; michael@0: public: michael@0: nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor) michael@0: : mExecutor(aExecutor) michael@0: {} michael@0: NS_IMETHODIMP Run() michael@0: { michael@0: mExecutor->RunFlushLoop(); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: static mozilla::LinkedList* gBackgroundFlushList = nullptr; michael@0: static nsITimer* gFlushTimer = nullptr; michael@0: michael@0: nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor() michael@0: : nsHtml5DocumentBuilder(false) michael@0: , mPreloadedURLs(23) // Mean # of preloadable resources per page on dmoz michael@0: { michael@0: // zeroing operator new for everything else michael@0: } michael@0: michael@0: nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor() michael@0: { michael@0: if (gBackgroundFlushList && isInList()) { michael@0: mOpQueue.Clear(); michael@0: removeFrom(*gBackgroundFlushList); michael@0: if (gBackgroundFlushList->isEmpty()) { michael@0: delete gBackgroundFlushList; michael@0: gBackgroundFlushList = nullptr; michael@0: if (gFlushTimer) { michael@0: gFlushTimer->Cancel(); michael@0: NS_RELEASE(gFlushTimer); michael@0: } michael@0: } michael@0: } michael@0: NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue."); michael@0: } michael@0: michael@0: // nsIContentSink michael@0: NS_IMETHODIMP michael@0: nsHtml5TreeOpExecutor::WillParse() michael@0: { michael@0: NS_NOTREACHED("No one should call this"); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5TreeOpExecutor::WillBuildModel(nsDTDMode aDTDMode) michael@0: { michael@0: mDocument->AddObserver(this); michael@0: WillBuildModelImpl(); michael@0: GetDocument()->BeginLoad(); michael@0: if (mDocShell && !GetDocument()->GetWindow() && michael@0: !IsExternalViewSource()) { michael@0: // Not loading as data but script global object not ready michael@0: return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // This is called when the tree construction has ended michael@0: NS_IMETHODIMP michael@0: nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated) michael@0: { michael@0: if (!aTerminated) { michael@0: // This is needed to avoid unblocking loads too many times on one hand michael@0: // and on the other hand to avoid destroying the frame constructor from michael@0: // within an update batch. See bug 537683. michael@0: EndDocUpdate(); michael@0: michael@0: // If the above caused a call to nsIParser::Terminate(), let that call michael@0: // win. michael@0: if (!mParser) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: if (mRunsToCompletion) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: GetParser()->DropStreamParser(); michael@0: michael@0: // This comes from nsXMLContentSink and nsHTMLContentSink michael@0: // If this parser has been marked as broken, treat the end of parse as michael@0: // forced termination. michael@0: DidBuildModelImpl(aTerminated || NS_FAILED(IsBroken())); michael@0: michael@0: if (!mLayoutStarted) { michael@0: // We never saw the body, and layout never got started. Force michael@0: // layout *now*, to get an initial reflow. michael@0: michael@0: // NOTE: only force the layout if we are NOT destroying the michael@0: // docshell. If we are destroying it, then starting layout will michael@0: // likely cause us to crash, or at best waste a lot of time as we michael@0: // are just going to tear it down anyway. michael@0: bool destroying = true; michael@0: if (mDocShell) { michael@0: mDocShell->IsBeingDestroyed(&destroying); michael@0: } michael@0: michael@0: if (!destroying) { michael@0: nsContentSink::StartLayout(false); michael@0: } michael@0: } michael@0: michael@0: ScrollToRef(); michael@0: mDocument->RemoveObserver(this); michael@0: if (!mParser) { michael@0: // DidBuildModelImpl may cause mParser to be nulled out michael@0: // Return early to avoid unblocking the onload event too many times. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We may not have called BeginLoad() if loading is terminated before michael@0: // OnStartRequest call. michael@0: if (mStarted) { michael@0: mDocument->EndLoad(); michael@0: } michael@0: DropParserAndPerfHint(); michael@0: #ifdef GATHER_DOCWRITE_STATISTICS michael@0: printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites); michael@0: printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites); michael@0: printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites); michael@0: #endif michael@0: #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH michael@0: printf("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize); michael@0: if (sAppendBatchExaminations != 0) { michael@0: printf("AVERAGE SLOTS EXAMINED: %d\n", sAppendBatchSlotsExamined / sAppendBatchExaminations); michael@0: } michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5TreeOpExecutor::WillInterrupt() michael@0: { michael@0: NS_NOTREACHED("Don't call. For interface compat only."); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5TreeOpExecutor::WillResume() michael@0: { michael@0: NS_NOTREACHED("Don't call. For interface compat only."); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser) michael@0: { michael@0: mParser = aParser; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::FlushPendingNotifications(mozFlushType aType) michael@0: { michael@0: if (aType >= Flush_InterruptibleLayout) { michael@0: // Bug 577508 / 253951 michael@0: nsContentSink::StartLayout(true); michael@0: } michael@0: } michael@0: michael@0: nsISupports* michael@0: nsHtml5TreeOpExecutor::GetTarget() michael@0: { michael@0: return mDocument; michael@0: } michael@0: michael@0: nsresult michael@0: nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: mBroken = aReason; michael@0: if (mStreamParser) { michael@0: mStreamParser->Terminate(); michael@0: } michael@0: // We are under memory pressure, but let's hope the following allocation michael@0: // works out so that we get to terminate and clean up the parser from michael@0: // a safer point. michael@0: if (mParser) { // can mParser ever be null here? michael@0: nsCOMPtr terminator = michael@0: NS_NewRunnableMethod(GetParser(), &nsHtml5Parser::Terminate); michael@0: if (NS_FAILED(NS_DispatchToMainThread(terminator))) { michael@0: NS_WARNING("failed to dispatch executor flush event"); michael@0: } michael@0: } michael@0: return aReason; michael@0: } michael@0: michael@0: void michael@0: FlushTimerCallback(nsITimer* aTimer, void* aClosure) michael@0: { michael@0: nsRefPtr ex = gBackgroundFlushList->popFirst(); michael@0: if (ex) { michael@0: ex->RunFlushLoop(); michael@0: } michael@0: if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) { michael@0: delete gBackgroundFlushList; michael@0: gBackgroundFlushList = nullptr; michael@0: gFlushTimer->Cancel(); michael@0: NS_RELEASE(gFlushTimer); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync() michael@0: { michael@0: if (!mDocument || !mDocument->IsInBackgroundWindow()) { michael@0: nsCOMPtr flusher = new nsHtml5ExecutorReflusher(this); michael@0: if (NS_FAILED(NS_DispatchToMainThread(flusher))) { michael@0: NS_WARNING("failed to dispatch executor flush event"); michael@0: } michael@0: } else { michael@0: if (!gBackgroundFlushList) { michael@0: gBackgroundFlushList = new mozilla::LinkedList(); michael@0: } michael@0: if (!isInList()) { michael@0: gBackgroundFlushList->insertBack(this); michael@0: } michael@0: if (!gFlushTimer) { michael@0: nsCOMPtr t = do_CreateInstance("@mozilla.org/timer;1"); michael@0: t.swap(gFlushTimer); michael@0: // The timer value 50 should not hopefully slow down background pages too michael@0: // much, yet lets event loop to process enough between ticks. michael@0: // See bug 734015. michael@0: gFlushTimer->InitWithFuncCallback(FlushTimerCallback, nullptr, michael@0: 50, nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::FlushSpeculativeLoads() michael@0: { michael@0: nsTArray speculativeLoadQueue; michael@0: mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue); michael@0: const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements(); michael@0: const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); michael@0: for (nsHtml5SpeculativeLoad* iter = const_cast(start); michael@0: iter < end; michael@0: ++iter) { michael@0: if (MOZ_UNLIKELY(!mParser)) { michael@0: // An extension terminated the parser from a HTTP observer. michael@0: return; michael@0: } michael@0: iter->Perform(this); michael@0: } michael@0: } michael@0: michael@0: class nsHtml5FlushLoopGuard michael@0: { michael@0: private: michael@0: nsRefPtr mExecutor; michael@0: #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH michael@0: uint32_t mStartTime; michael@0: #endif michael@0: public: michael@0: nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor) michael@0: : mExecutor(aExecutor) michael@0: #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH michael@0: , mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow())) michael@0: #endif michael@0: { michael@0: mExecutor->mRunFlushLoopOnStack = true; michael@0: } michael@0: ~nsHtml5FlushLoopGuard() michael@0: { michael@0: #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH michael@0: uint32_t timeOffTheEventLoop = michael@0: PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime; michael@0: if (timeOffTheEventLoop > michael@0: nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) { michael@0: nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = michael@0: timeOffTheEventLoop; michael@0: } michael@0: printf("Longest time off the event loop: %d\n", michael@0: nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop); michael@0: #endif michael@0: michael@0: mExecutor->mRunFlushLoopOnStack = false; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The purpose of the loop here is to avoid returning to the main event loop michael@0: */ michael@0: void michael@0: nsHtml5TreeOpExecutor::RunFlushLoop() michael@0: { michael@0: PROFILER_LABEL("html5", "RunFlushLoop"); michael@0: if (mRunFlushLoopOnStack) { michael@0: // There's already a RunFlushLoop() on the call stack. michael@0: return; michael@0: } michael@0: michael@0: nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu! michael@0: michael@0: nsCOMPtr parserKungFuDeathGrip(mParser); michael@0: michael@0: // Remember the entry time michael@0: (void) nsContentSink::WillParseImpl(); michael@0: michael@0: for (;;) { michael@0: if (!mParser) { michael@0: // Parse has terminated. michael@0: mOpQueue.Clear(); // clear in order to be able to assert in destructor michael@0: return; michael@0: } michael@0: michael@0: if (NS_FAILED(IsBroken())) { michael@0: return; michael@0: } michael@0: michael@0: if (!mParser->IsParserEnabled()) { michael@0: // The parser is blocked. michael@0: return; michael@0: } michael@0: michael@0: if (mFlushState != eNotFlushing) { michael@0: // XXX Can this happen? In case it can, let's avoid crashing. michael@0: return; michael@0: } michael@0: michael@0: // If there are scripts executing, then the content sink is jumping the gun michael@0: // (probably due to a synchronous XMLHttpRequest) and will re-enable us michael@0: // later, see bug 460706. michael@0: if (IsScriptExecuting()) { michael@0: return; michael@0: } michael@0: michael@0: if (mReadingFromStage) { michael@0: nsTArray speculativeLoadQueue; michael@0: mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, speculativeLoadQueue); michael@0: // Make sure speculative loads never start after the corresponding michael@0: // normal loads for the same URLs. michael@0: const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements(); michael@0: const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); michael@0: for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start; michael@0: iter < end; michael@0: ++iter) { michael@0: iter->Perform(this); michael@0: if (MOZ_UNLIKELY(!mParser)) { michael@0: // An extension terminated the parser from a HTTP observer. michael@0: mOpQueue.Clear(); // clear in order to be able to assert in destructor michael@0: return; michael@0: } michael@0: } michael@0: } else { michael@0: FlushSpeculativeLoads(); // Make sure speculative loads never start after michael@0: // the corresponding normal loads for the same michael@0: // URLs. michael@0: if (MOZ_UNLIKELY(!mParser)) { michael@0: // An extension terminated the parser from a HTTP observer. michael@0: mOpQueue.Clear(); // clear in order to be able to assert in destructor michael@0: return; michael@0: } michael@0: // Not sure if this grip is still needed, but previously, the code michael@0: // gripped before calling ParseUntilBlocked(); michael@0: nsRefPtr streamKungFuDeathGrip = michael@0: GetParser()->GetStreamParser(); michael@0: // Now parse content left in the document.write() buffer queue if any. michael@0: // This may generate tree ops on its own or dequeue a speculation. michael@0: nsresult rv = GetParser()->ParseUntilBlocked(); michael@0: if (NS_FAILED(rv)) { michael@0: MarkAsBroken(rv); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (mOpQueue.IsEmpty()) { michael@0: // Avoid bothering the rest of the engine with a doc update if there's michael@0: // nothing to do. michael@0: return; michael@0: } michael@0: michael@0: mFlushState = eInFlush; michael@0: michael@0: nsIContent* scriptElement = nullptr; michael@0: michael@0: BeginDocUpdate(); michael@0: michael@0: uint32_t numberOfOpsToFlush = mOpQueue.Length(); michael@0: michael@0: SetAppendBatchCapacity(numberOfOpsToFlush * 2); michael@0: michael@0: const nsHtml5TreeOperation* first = mOpQueue.Elements(); michael@0: const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1; michael@0: for (nsHtml5TreeOperation* iter = const_cast(first);;) { michael@0: if (MOZ_UNLIKELY(!mParser)) { michael@0: // The previous tree op caused a call to nsIParser::Terminate(). michael@0: break; michael@0: } michael@0: NS_ASSERTION(mFlushState == eInDocUpdate, michael@0: "Tried to perform tree op outside update batch."); michael@0: nsresult rv = iter->Perform(this, &scriptElement); michael@0: if (NS_FAILED(rv)) { michael@0: MarkAsBroken(rv); michael@0: break; michael@0: } michael@0: michael@0: // Be sure not to check the deadline if the last op was just performed. michael@0: if (MOZ_UNLIKELY(iter == last)) { michael@0: break; michael@0: } else if (MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() == michael@0: NS_ERROR_HTMLPARSER_INTERRUPTED)) { michael@0: mOpQueue.RemoveElementsAt(0, (iter - first) + 1); michael@0: michael@0: EndDocUpdate(); michael@0: michael@0: mFlushState = eNotFlushing; michael@0: michael@0: #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH michael@0: printf("REFLUSH SCHEDULED (executing ops): %d\n", michael@0: ++sTimesFlushLoopInterrupted); michael@0: #endif michael@0: nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); michael@0: return; michael@0: } michael@0: ++iter; michael@0: } michael@0: michael@0: mOpQueue.Clear(); michael@0: michael@0: EndDocUpdate(); michael@0: michael@0: mFlushState = eNotFlushing; michael@0: michael@0: if (MOZ_UNLIKELY(!mParser)) { michael@0: // The parse ended already. michael@0: return; michael@0: } michael@0: michael@0: if (scriptElement) { michael@0: // must be tail call when mFlushState is eNotFlushing michael@0: RunScript(scriptElement); michael@0: michael@0: // Always check the clock in nsContentSink right after a script michael@0: StopDeflecting(); michael@0: if (nsContentSink::DidProcessATokenImpl() == michael@0: NS_ERROR_HTMLPARSER_INTERRUPTED) { michael@0: #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH michael@0: printf("REFLUSH SCHEDULED (after script): %d\n", michael@0: ++sTimesFlushLoopInterrupted); michael@0: #endif michael@0: nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHtml5TreeOpExecutor::FlushDocumentWrite() michael@0: { michael@0: nsresult rv = IsBroken(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: FlushSpeculativeLoads(); // Make sure speculative loads never start after the michael@0: // corresponding normal loads for the same URLs. michael@0: michael@0: if (MOZ_UNLIKELY(!mParser)) { michael@0: // The parse has ended. michael@0: mOpQueue.Clear(); // clear in order to be able to assert in destructor michael@0: return rv; michael@0: } michael@0: michael@0: if (mFlushState != eNotFlushing) { michael@0: // XXX Can this happen? In case it can, let's avoid crashing. michael@0: return rv; michael@0: } michael@0: michael@0: mFlushState = eInFlush; michael@0: michael@0: // avoid crashing near EOF michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: nsRefPtr parserKungFuDeathGrip(mParser); michael@0: michael@0: NS_ASSERTION(!mReadingFromStage, michael@0: "Got doc write flush when reading from stage"); michael@0: michael@0: #ifdef DEBUG michael@0: mStage.AssertEmpty(); michael@0: #endif michael@0: michael@0: nsIContent* scriptElement = nullptr; michael@0: michael@0: BeginDocUpdate(); michael@0: michael@0: uint32_t numberOfOpsToFlush = mOpQueue.Length(); michael@0: michael@0: SetAppendBatchCapacity(numberOfOpsToFlush * 2); michael@0: michael@0: const nsHtml5TreeOperation* start = mOpQueue.Elements(); michael@0: const nsHtml5TreeOperation* end = start + numberOfOpsToFlush; michael@0: for (nsHtml5TreeOperation* iter = const_cast(start); michael@0: iter < end; michael@0: ++iter) { michael@0: if (MOZ_UNLIKELY(!mParser)) { michael@0: // The previous tree op caused a call to nsIParser::Terminate(). michael@0: break; michael@0: } michael@0: NS_ASSERTION(mFlushState == eInDocUpdate, michael@0: "Tried to perform tree op outside update batch."); michael@0: rv = iter->Perform(this, &scriptElement); michael@0: if (NS_FAILED(rv)) { michael@0: MarkAsBroken(rv); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: mOpQueue.Clear(); michael@0: michael@0: EndDocUpdate(); michael@0: michael@0: mFlushState = eNotFlushing; michael@0: michael@0: if (MOZ_UNLIKELY(!mParser)) { michael@0: // Ending the doc update caused a call to nsIParser::Terminate(). michael@0: return rv; michael@0: } michael@0: michael@0: if (scriptElement) { michael@0: // must be tail call when mFlushState is eNotFlushing michael@0: RunScript(scriptElement); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: // copied from HTML content sink michael@0: bool michael@0: nsHtml5TreeOpExecutor::IsScriptEnabled() michael@0: { michael@0: if (!mDocument || !mDocShell) michael@0: return true; michael@0: nsCOMPtr globalObject = do_QueryInterface(mDocument->GetInnerWindow()); michael@0: // Getting context is tricky if the document hasn't had its michael@0: // GlobalObject set yet michael@0: if (!globalObject) { michael@0: globalObject = mDocShell->GetScriptGlobalObject(); michael@0: NS_ENSURE_TRUE(globalObject, true); michael@0: } michael@0: NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true); michael@0: return nsContentUtils::GetSecurityManager()-> michael@0: ScriptAllowed(globalObject->GetGlobalJSObject()); michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::StartLayout() { michael@0: if (mLayoutStarted || !mDocument) { michael@0: return; michael@0: } michael@0: michael@0: EndDocUpdate(); michael@0: michael@0: if (MOZ_UNLIKELY(!mParser)) { michael@0: // got terminate michael@0: return; michael@0: } michael@0: michael@0: nsContentSink::StartLayout(false); michael@0: michael@0: BeginDocUpdate(); michael@0: } michael@0: michael@0: /** michael@0: * The reason why this code is here and not in the tree builder even in the michael@0: * main-thread case is to allow the control to return from the tokenizer michael@0: * before scripts run. This way, the tokenizer is not invoked re-entrantly michael@0: * although the parser is. michael@0: * michael@0: * The reason why this is called as a tail call when mFlushState is set to michael@0: * eNotFlushing is to allow re-entry to Flush() but only after the current michael@0: * Flush() has cleared the op queue and is otherwise done cleaning up after michael@0: * itself. michael@0: */ michael@0: void michael@0: nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement) michael@0: { michael@0: if (mRunsToCompletion) { michael@0: // We are in createContextualFragment() or in the upcoming document.parse(). michael@0: // Do nothing. Let's not even mark scripts malformed here, because that michael@0: // could cause serialization weirdness later. michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(aScriptElement, "No script to run"); michael@0: nsCOMPtr sele = do_QueryInterface(aScriptElement); michael@0: michael@0: if (!mParser) { michael@0: NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed."); michael@0: // We got here not because of an end tag but because the tree builder michael@0: // popped an incomplete script element on EOF. Returning here to avoid michael@0: // calling back into mParser anymore. michael@0: return; michael@0: } michael@0: michael@0: if (sele->GetScriptDeferred() || sele->GetScriptAsync()) { michael@0: DebugOnly block = sele->AttemptToExecute(); michael@0: NS_ASSERTION(!block, "Defer or async script tried to block."); michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(mFlushState == eNotFlushing, "Tried to run script when flushing."); michael@0: michael@0: mReadingFromStage = false; michael@0: michael@0: sele->SetCreatorParser(GetParser()); michael@0: michael@0: // Copied from nsXMLContentSink michael@0: // Now tell the script that it's ready to go. This may execute the script michael@0: // or return true, or neither if the script doesn't need executing. michael@0: bool block = sele->AttemptToExecute(); michael@0: michael@0: // If the act of insertion evaluated the script, we're fine. michael@0: // Else, block the parser till the script has loaded. michael@0: if (block) { michael@0: if (mParser) { michael@0: GetParser()->BlockParser(); michael@0: } michael@0: } else { michael@0: // mParser may have been nulled out by now, but the flusher deals michael@0: michael@0: // If this event isn't needed, it doesn't do anything. It is sometimes michael@0: // necessary for the parse to continue after complex situations. michael@0: nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::Start() michael@0: { michael@0: NS_PRECONDITION(!mStarted, "Tried to start when already started."); michael@0: mStarted = true; michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const char* aEncoding, michael@0: int32_t aSource, michael@0: uint32_t aLineNumber) michael@0: { michael@0: EndDocUpdate(); michael@0: michael@0: if (MOZ_UNLIKELY(!mParser)) { michael@0: // got terminate michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr wss = do_QueryInterface(mDocShell); michael@0: if (!wss) { michael@0: return; michael@0: } michael@0: michael@0: // ask the webshellservice to load the URL michael@0: if (NS_SUCCEEDED(wss->StopDocumentLoad())) { michael@0: wss->ReloadDocument(aEncoding, aSource); michael@0: } michael@0: // if the charset switch was accepted, wss has called Terminate() on the michael@0: // parser by now michael@0: michael@0: if (!mParser) { michael@0: // success michael@0: if (aSource == kCharsetFromMetaTag) { michael@0: MaybeComplainAboutCharset("EncLateMetaReload", false, aLineNumber); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (aSource == kCharsetFromMetaTag) { michael@0: MaybeComplainAboutCharset("EncLateMetaTooLate", true, aLineNumber); michael@0: } michael@0: michael@0: GetParser()->ContinueAfterFailedCharsetSwitch(); michael@0: michael@0: BeginDocUpdate(); michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId, michael@0: bool aError, michael@0: uint32_t aLineNumber) michael@0: { michael@0: if (mAlreadyComplainedAboutCharset) { michael@0: return; michael@0: } michael@0: // The EncNoDeclaration case for advertising iframes is so common that it michael@0: // would result is way too many errors. The iframe case doesn't matter michael@0: // when the ad is an image or a Flash animation anyway. When the ad is michael@0: // textual, a misrendered ad probably isn't a huge loss for users. michael@0: // Let's suppress the message in this case. michael@0: // This means that errors about other different-origin iframes in mashups michael@0: // are lost as well, but generally, the site author isn't in control of michael@0: // the embedded different-origin pages anyway and can't fix problems even michael@0: // if alerted about them. michael@0: if (!strcmp(aMsgId, "EncNoDeclaration") && mDocShell) { michael@0: nsCOMPtr parent; michael@0: mDocShell->GetSameTypeParent(getter_AddRefs(parent)); michael@0: if (parent) { michael@0: return; michael@0: } michael@0: } michael@0: mAlreadyComplainedAboutCharset = true; michael@0: nsContentUtils::ReportToConsole(aError ? nsIScriptError::errorFlag michael@0: : nsIScriptError::warningFlag, michael@0: NS_LITERAL_CSTRING("HTML parser"), michael@0: mDocument, michael@0: nsContentUtils::eHTMLPARSER_PROPERTIES, michael@0: aMsgId, michael@0: nullptr, michael@0: 0, michael@0: nullptr, michael@0: EmptyString(), michael@0: aLineNumber); michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset(nsIDocument* aDoc) michael@0: { michael@0: NS_ASSERTION(!mAlreadyComplainedAboutCharset, michael@0: "How come we already managed to complain?"); michael@0: mAlreadyComplainedAboutCharset = true; michael@0: nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, michael@0: NS_LITERAL_CSTRING("HTML parser"), michael@0: aDoc, michael@0: nsContentUtils::eHTMLPARSER_PROPERTIES, michael@0: "EncProtocolUnsupported"); michael@0: } michael@0: michael@0: nsHtml5Parser* michael@0: nsHtml5TreeOpExecutor::GetParser() michael@0: { michael@0: MOZ_ASSERT(!mRunsToCompletion); michael@0: return static_cast(mParser.get()); michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray& aOpQueue) michael@0: { michael@0: NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution."); michael@0: if (mOpQueue.IsEmpty()) { michael@0: mOpQueue.SwapElements(aOpQueue); michael@0: return; michael@0: } michael@0: mOpQueue.MoveElementsFrom(aOpQueue); michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine) michael@0: { michael@0: GetParser()->InitializeDocWriteParserState(aState, aLine); michael@0: } michael@0: michael@0: nsIURI* michael@0: nsHtml5TreeOpExecutor::GetViewSourceBaseURI() michael@0: { michael@0: if (!mViewSourceBaseURI) { michael@0: michael@0: // We query the channel for the baseURI because in certain situations it michael@0: // cannot otherwise be determined. If this process fails, fall back to the michael@0: // standard method. michael@0: nsCOMPtr vsc; michael@0: vsc = do_QueryInterface(mDocument->GetChannel()); michael@0: if (vsc) { michael@0: nsresult rv = vsc->GetBaseURI(getter_AddRefs(mViewSourceBaseURI)); michael@0: if (NS_SUCCEEDED(rv) && mViewSourceBaseURI) { michael@0: return mViewSourceBaseURI; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr orig = mDocument->GetOriginalURI(); michael@0: bool isViewSource; michael@0: orig->SchemeIs("view-source", &isViewSource); michael@0: if (isViewSource) { michael@0: nsCOMPtr nested = do_QueryInterface(orig); michael@0: NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!"); michael@0: nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI)); michael@0: } else { michael@0: // Fail gracefully if the base URL isn't a view-source: URL. michael@0: // Not sure if this can ever happen. michael@0: mViewSourceBaseURI = orig; michael@0: } michael@0: } michael@0: return mViewSourceBaseURI; michael@0: } michael@0: michael@0: //static michael@0: void michael@0: nsHtml5TreeOpExecutor::InitializeStatics() michael@0: { michael@0: mozilla::Preferences::AddBoolVarCache(&sExternalViewSource, michael@0: "view_source.editor.external"); michael@0: } michael@0: michael@0: bool michael@0: nsHtml5TreeOpExecutor::IsExternalViewSource() michael@0: { michael@0: if (!sExternalViewSource) { michael@0: return false; michael@0: } michael@0: bool isViewSource = false; michael@0: if (mDocumentURI) { michael@0: mDocumentURI->SchemeIs("view-source", &isViewSource); michael@0: } michael@0: return isViewSource; michael@0: } michael@0: michael@0: // Speculative loading michael@0: michael@0: already_AddRefed michael@0: nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(const nsAString& aURL) michael@0: { michael@0: if (aURL.IsEmpty()) { michael@0: return nullptr; michael@0: } michael@0: // The URL of the document without michael@0: nsIURI* documentURI = mDocument->GetDocumentURI(); michael@0: // The URL of the document with non-speculative michael@0: nsIURI* documentBaseURI = mDocument->GetDocBaseURI(); michael@0: michael@0: // If the two above are different, use documentBaseURI. If they are the michael@0: // same, the document object isn't aware of a , so attempt to use the michael@0: // mSpeculationBaseURI or, failing, that, documentURI. michael@0: nsIURI* base = (documentURI == documentBaseURI) ? michael@0: (mSpeculationBaseURI ? michael@0: mSpeculationBaseURI.get() : documentURI) michael@0: : documentBaseURI; michael@0: const nsCString& charset = mDocument->GetDocumentCharacterSet(); michael@0: nsCOMPtr uri; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), base); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to create a URI"); michael@0: return nullptr; michael@0: } michael@0: nsAutoCString spec; michael@0: uri->GetSpec(spec); michael@0: if (mPreloadedURLs.Contains(spec)) { michael@0: return nullptr; michael@0: } michael@0: mPreloadedURLs.PutEntry(spec); michael@0: return uri.forget(); michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL, michael@0: const nsAString& aCharset, michael@0: const nsAString& aType, michael@0: const nsAString& aCrossOrigin, michael@0: bool aScriptFromHead) michael@0: { michael@0: nsCOMPtr uri = ConvertIfNotPreloadedYet(aURL); michael@0: if (!uri) { michael@0: return; michael@0: } michael@0: mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin, michael@0: aScriptFromHead); michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL, michael@0: const nsAString& aCharset, michael@0: const nsAString& aCrossOrigin) michael@0: { michael@0: nsCOMPtr uri = ConvertIfNotPreloadedYet(aURL); michael@0: if (!uri) { michael@0: return; michael@0: } michael@0: mDocument->PreloadStyle(uri, aCharset, aCrossOrigin); michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL, michael@0: const nsAString& aCrossOrigin) michael@0: { michael@0: nsCOMPtr uri = ConvertIfNotPreloadedYet(aURL); michael@0: if (!uri) { michael@0: return; michael@0: } michael@0: mDocument->MaybePreLoadImage(uri, aCrossOrigin); michael@0: } michael@0: michael@0: void michael@0: nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL) michael@0: { michael@0: if (mSpeculationBaseURI) { michael@0: // the first one wins michael@0: return; michael@0: } michael@0: const nsCString& charset = mDocument->GetDocumentCharacterSet(); michael@0: DebugOnly rv = NS_NewURI(getter_AddRefs(mSpeculationBaseURI), aURL, michael@0: charset.get(), mDocument->GetDocumentURI()); michael@0: NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to create a URI"); michael@0: } michael@0: michael@0: #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH michael@0: uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0; michael@0: uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0; michael@0: uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0; michael@0: uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0; michael@0: uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0; michael@0: #endif michael@0: bool nsHtml5TreeOpExecutor::sExternalViewSource = false;