gecko-dev/parser/html/nsHtml5TreeOpExecutor.cpp

1106 lines
33 KiB
C++

/* -*- 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<nsHtml5TreeOpExecutor> mExecutor;
public:
nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
: mExecutor(aExecutor)
{}
NS_IMETHODIMP Run()
{
mExecutor->RunFlushLoop();
return NS_OK;
}
};
static mozilla::LinkedList<nsHtml5TreeOpExecutor>* 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<nsIMarkupDocumentViewer> mucv;
nsCOMPtr<nsIContentViewer> 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<nsIDocShellTreeItem> docShellAsItem =
do_QueryInterface(mDocShell);
if (!docShellAsItem) {
return;
}
nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
docShellAsItem->GetSameTypeParent(getter_AddRefs(parentAsItem));
nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem));
if (parent) {
nsCOMPtr<nsIContentViewer> 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<nsIRunnable> 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<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->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<nsIStyleSheetLinkingElement> 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 <link rel="next" href="url">
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<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:
nsRefPtr<nsHtml5TreeOpExecutor> 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<nsISupports> 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<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();
nsRefPtr<nsHtml5StreamParser> 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<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.");
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<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
nsRefPtr<nsParserBase> 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<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.");
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<nsIScriptGlobalObject> globalObject = mDocument->GetScriptGlobalObject();
// Getting context is tricky if the document hasn't had its
// GlobalObject set yet
if (!globalObject) {
nsCOMPtr<nsIScriptGlobalObjectOwner> 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<nsIHTMLDocument> 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<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();
}
}
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<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> treeItem = do_QueryInterface(mDocShell);
nsCOMPtr<nsIDocShellTreeItem> 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<nsHtml5Parser*>(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<nsHtml5TreeOperation>& 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<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
already_AddRefed<nsIURI>
nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(const nsAString& aURL)
{
if (aURL.IsEmpty()) {
return nullptr;
}
// 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.
nsIURI* base = (documentURI == documentBaseURI) ?
(mSpeculationBaseURI ?
mSpeculationBaseURI.get() : documentURI)
: documentBaseURI;
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;
}
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<nsIURI> 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<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
if (!uri) {
return;
}
mDocument->PreloadStyle(uri, aCharset, aCrossOrigin);
}
void
nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
const nsAString& aCrossOrigin)
{
nsCOMPtr<nsIURI> 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<nsresult> 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;