gecko-dev/parser/html/nsHtml5TreeOpExecutor.cpp
Wes Kocher f7ccafcb58 Backed out 8 changesets (bug 1331804, bug 1332100) for windows vm debug dt5 failures a=backout
Backed out changeset 8bf7f0e27c6c (bug 1331804)
Backed out changeset 600c0b9026c2 (bug 1331804)
Backed out changeset 3a5b5b9ecace (bug 1331804)
Backed out changeset c76432c9954e (bug 1331804)
Backed out changeset 46a9096745e7 (bug 1332100)
Backed out changeset 8b751230fa23 (bug 1331804)
Backed out changeset 2810212347fd (bug 1331804)
Backed out changeset be72b7763910 (bug 1331804)

MozReview-Commit-ID: Ywdsr4GZ4a
2017-01-24 15:12:21 -08:00

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