/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=2 et tw=80: */ /* 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/. */
class nsHtml5ExecutorReflusher : public Runnable { private:
RefPtr<nsHtml5TreeOpExecutor> mExecutor;
public: explicit nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
: Runnable("nsHtml5ExecutorReflusher"), mExecutor(aExecutor) {}
NS_IMETHOD Run() override {
dom::Document* doc = mExecutor->GetDocument(); if (XRE_IsContentProcess() &&
nsContentUtils::
HighPriorityEventPendingForTopLevelDocumentBeforeContentfulPaint(
doc)) { // Possible early paint pending, reuse the runnable and try to // call RunFlushLoop later.
nsCOMPtr<nsIRunnable> flusher = this; if (NS_SUCCEEDED(doc->Dispatch(flusher.forget()))) {
PROFILER_MARKER_UNTYPED("HighPrio blocking parser flushing(2)", DOM); return NS_OK;
}
}
mExecutor->RunFlushLoop(); return NS_OK;
}
};
class MOZ_RAII nsHtml5AutoFlush final { private:
RefPtr<nsHtml5TreeOpExecutor> mExecutor;
size_t mOpsToRemove;
public: explicit nsHtml5AutoFlush(nsHtml5TreeOpExecutor* aExecutor)
: mExecutor(aExecutor), mOpsToRemove(aExecutor->OpQueueLength()) {
mExecutor->BeginFlush();
mExecutor->BeginDocUpdate();
}
~nsHtml5AutoFlush() { if (mExecutor->IsInDocUpdate()) {
mExecutor->EndDocUpdate();
} else { // We aren't in an update if nsHtml5AutoPauseUpdate // caused something to terminate the parser.
MOZ_RELEASE_ASSERT(
mExecutor->IsComplete(), "How do we have mParser but the doc update isn't open?");
}
mExecutor->EndFlush();
mExecutor->RemoveFromStartOfOpQueue(mOpsToRemove);
} void SetNumberOfOpsToRemove(size_t aOpsToRemove) {
MOZ_ASSERT(aOpsToRemove < mOpsToRemove, "Requested partial clearing of op queue but the number to clear " "wasn't less than the length of the queue.");
mOpsToRemove = aOpsToRemove;
}
};
nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
: nsHtml5DocumentBuilder(false),
mSuppressEOF(false),
mReadingFromStage(false),
mStreamParser(nullptr),
mPreloadedURLs(23), // Mean # of preloadable resources per page on dmoz
mStarted(false),
mRunFlushLoopOnStack(false),
mCallContinueInterruptedParsingIfEnabled(false),
mAlreadyComplainedAboutCharset(false),
mAlreadyComplainedAboutDeepTree(false) {}
nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor() { if (gBackgroundFlushList && isInList()) {
ClearOpQueue();
removeFrom(*gBackgroundFlushList); if (gBackgroundFlushList->isEmpty()) { delete gBackgroundFlushList;
gBackgroundFlushList = nullptr; if (gBackgroundFlushRunner) {
gBackgroundFlushRunner->Cancel();
gBackgroundFlushRunner = nullptr;
}
}
}
MOZ_ASSERT(NS_FAILED(mBroken) || mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue.");
}
// nsIContentSink
NS_IMETHODIMP
nsHtml5TreeOpExecutor::WillParse() {
MOZ_ASSERT_UNREACHABLE("No one should call this"); return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult nsHtml5TreeOpExecutor::WillBuildModel() {
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 (mRunsToCompletion) { return NS_OK;
}
MOZ_RELEASE_ASSERT(!IsInDocUpdate(), "DidBuildModel from inside a doc update.");
RefPtr<nsHtml5TreeOpExecutor> pin(this); auto queueClearer = MakeScopeExit([&] { if (aTerminated && (mFlushState == eNotFlushing)) {
ClearOpQueue(); // clear in order to be able to assert in destructor
}
});
// 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()));
bool destroying = true; if (mDocShell) {
mDocShell->IsBeingDestroyed(&destroying);
}
if (!destroying) {
mDocument->OnParsingCompleted();
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.
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();
// Log outcome only for top-level content navigations in order to // avoid noise from ad iframes. bool topLevel = false; if (mozilla::dom::BrowsingContext* bc = mDocument->GetBrowsingContext()) {
topLevel = bc->IsTopContent();
}
// Log outcome only for text/html and text/plain (excluding CSS, JS, // etc. being viewed as text.)
nsAutoString contentType;
mDocument->GetContentType(contentType); bool htmlOrPlain = contentType.EqualsLiteral(u"text/html") ||
contentType.EqualsLiteral(u"text/plain");
// Log outcome only for HTTP status code 200 in order to exclude // error pages. bool httpOk = false;
nsCOMPtr<nsIChannel> channel;
nsresult rv = GetParser()->GetChannel(getter_AddRefs(channel)); if (NS_SUCCEEDED(rv) && channel) {
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel); if (httpChannel) {
uint32_t httpStatus;
rv = httpChannel->GetResponseStatus(&httpStatus); if (NS_SUCCEEDED(rv) && httpStatus == 200) {
httpOk = true;
}
}
}
// Log chardetng outcome
MOZ_ASSERT(mDocument->IsHTMLDocument()); if (httpOk && htmlOrPlain && topLevel && !aTerminated &&
!mDocument->AsHTMLDocument()->IsViewSource()) { // We deliberately measure only normally-completed (non-aborted) loads // that are not View Source loads. This seems like a better place for // checking normal completion than anything in nsHtml5StreamParser. bool plain = mDocument->AsHTMLDocument()->IsPlainText();
int32_t charsetSource = mDocument->GetDocumentCharacterSetSource(); switch (charsetSource) { case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8: if (plain) {
LOGCHARDETNG(("TEXT::UtfInitial"));
} else {
LOGCHARDETNG(("HTML::UtfInitial"));
} break; case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic: if (plain) {
LOGCHARDETNG(("TEXT::GenericInitial"));
} else {
LOGCHARDETNG(("HTML::GenericInitial"));
} break; case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content: if (plain) {
LOGCHARDETNG(("TEXT::ContentInitial"));
} else {
LOGCHARDETNG(("HTML::ContentInitial"));
} break; case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: if (plain) {
LOGCHARDETNG(("TEXT::TldInitial"));
} else {
LOGCHARDETNG(("HTML::TldInitial"));
} break; case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII: if (plain) {
LOGCHARDETNG(("TEXT::UtfFinal"));
} else {
LOGCHARDETNG(("HTML::UtfFinal"));
} break; case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic: if (plain) {
LOGCHARDETNG(("TEXT::GenericFinal"));
} else {
LOGCHARDETNG(("HTML::GenericFinal"));
} break; case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII: if (plain) {
LOGCHARDETNG(("TEXT::GenericFinalA"));
} else {
LOGCHARDETNG(("HTML::GenericFinalA"));
} break; case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content: if (plain) {
LOGCHARDETNG(("TEXT::ContentFinal"));
} else {
LOGCHARDETNG(("HTML::ContentFinal"));
} break; case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII: if (plain) {
LOGCHARDETNG(("TEXT::ContentFinalA"));
} else {
LOGCHARDETNG(("HTML::ContentFinalA"));
} break; case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: if (plain) {
LOGCHARDETNG(("TEXT::TldFinal"));
} else {
LOGCHARDETNG(("HTML::TldFinal"));
} break; case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII: if (plain) {
LOGCHARDETNG(("TEXT::TldFinalA"));
} else {
LOGCHARDETNG(("HTML::TldFinalA"));
} break; default: // Chardetng didn't run automatically or the input was all ASCII. break;
}
}
}
// Dropping the stream parser changes the parser's apparent // script-createdness, which is why the stream parser must not be dropped // before this executor's nsHtml5Parser has been made unreachable from its // nsHTMLDocument. (mDocument->EndLoad() above drops the parser from the // document.)
GetParser()->DropStreamParser();
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
LOG(("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize)); if (sAppendBatchExaminations != 0) {
LOG(("AVERAGE SLOTS EXAMINED: %d\n",
sAppendBatchSlotsExamined / sAppendBatchExaminations));
} #endif return NS_OK;
}
nsresult nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason) {
MOZ_ASSERT(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 && mDocument) { // can mParser ever be null here?
nsCOMPtr<nsIRunnable> terminator = NewRunnableMethod( "nsHtml5Parser::Terminate", GetParser(), &nsHtml5Parser::Terminate); if (NS_FAILED(mDocument->Dispatch(terminator.forget()))) {
NS_WARNING("failed to dispatch executor flush event");
}
} return aReason;
}
void nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync() { if (mDocument && !mDocument->IsInBackgroundWindow()) {
nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this); if (NS_FAILED(mDocument->Dispatch(flusher.forget()))) {
NS_WARNING("failed to dispatch executor flush event");
}
} else { if (!gBackgroundFlushList) {
gBackgroundFlushList = new LinkedList<nsHtml5TreeOpExecutor>();
} if (!isInList()) {
gBackgroundFlushList->insertBack(this);
} if (gBackgroundFlushRunner) { return;
} // Now we set up a repetitive idle scheduler for flushing background list.
gBackgroundFlushRunner = IdleTaskRunner::Create(
&BackgroundFlushCallback, "nsHtml5TreeOpExecutor::BackgroundFlushCallback",
0, // Start looking for idle time immediately.
TimeDuration::FromMilliseconds(250), // The hard deadline.
TimeDuration::FromMicroseconds(
StaticPrefs::content_sink_interactive_parse_time()), // Required // budget. true, // repeating
[] { returnfalse; }); // MayStopProcessing
}
}
void nsHtml5TreeOpExecutor::FlushSpeculativeLoads() {
nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); for (nsHtml5SpeculativeLoad* iter = start; iter < end; ++iter) { if (MOZ_UNLIKELY(!mParser)) { // An extension terminated the parser from a HTTP observer. return;
}
iter->Perform(this);
}
}
/** * The purpose of the loop here is to avoid returning to the main event loop
*/ void nsHtml5TreeOpExecutor::RunFlushLoop() {
AUTO_PROFILER_LABEL("nsHtml5TreeOpExecutor::RunFlushLoop", 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);
RefPtr<nsHtml5StreamParser> streamParserGrip; if (mParser) {
streamParserGrip = GetParser()->GetStreamParser();
}
Unused << streamParserGrip; // Intentionally not used within function
// Remember the entry time
(void)nsContentSink::WillParseImpl();
for (;;) { if (!mParser) { // Parse has terminated.
ClearOpQueue(); // 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;
MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing, "mOpQueue modified during flush."); if (!mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue,
speculativeLoadQueue)) {
MarkAsBroken(nsresult::NS_ERROR_OUT_OF_MEMORY); return;
}
// Make sure speculative loads never start after the corresponding // normal loads for the same URLs.
nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); for (nsHtml5SpeculativeLoad* iter = start; iter < end; ++iter) {
iter->Perform(this); if (MOZ_UNLIKELY(!mParser)) { // An extension terminated the parser from a HTTP observer.
ClearOpQueue(); // 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.
ClearOpQueue(); // clear in order to be able to assert in destructor return;
} // 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();
// ParseUntilBlocked flushes operations from the stage to the OpQueue. // Those operations may have accompanying speculative operations. // If so, we have to flush those speculative loads so that we maintain // the invariant that no speculative load starts after the corresponding // normal load for the same URL. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1513292#c80 // for a more detailed explanation of why this is necessary.
FlushSpeculativeLoads();
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;
}
{ // autoFlush clears mOpQueue in its destructor unless // SetNumberOfOpsToRemove is called first, in which case only // some ops from the start of the queue are cleared.
nsHtml5AutoFlush autoFlush(this); // Profiler marker deliberately not counting layout and script // execution.
AUTO_PROFILER_MARKER_TEXT( "HTMLParserTreeOps", DOM,
MarkerOptions(MarkerInnerWindowIdFromDocShell(mDocShell)), ""_ns);
nsHtml5TreeOperation* first = mOpQueue.Elements();
nsHtml5TreeOperation* last = first + mOpQueue.Length() - 1; for (nsHtml5TreeOperation* iter = first;; ++iter) { if (MOZ_UNLIKELY(!mParser)) { // The previous tree op caused a call to nsIParser::Terminate(). return;
}
MOZ_ASSERT(IsInDocUpdate(), "Tried to perform tree op outside update batch.");
nsresult rv =
iter->Perform(this, &scriptElement, &interrupted, &streamEnded); 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;
} elseif (MOZ_UNLIKELY(interrupted) ||
MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
NS_ERROR_HTMLPARSER_INTERRUPTED)) {
autoFlush.SetNumberOfOpsToRemove((iter - first) + 1);
if (MOZ_UNLIKELY(!mParser)) { // The parse ended during an update pause. return;
} if (streamEnded) {
GetParser()->PermanentlyUndefineInsertionPoint();
}
} // end autoFlush
if (MOZ_UNLIKELY(!mParser)) { // Ending the doc update caused a call to nsIParser::Terminate(). return;
}
if (streamEnded) {
DidBuildModel(false); #ifdef DEBUG if (scriptElement) {
nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(scriptElement); if (!sele) {
MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, "Node didn't QI to script, but SVG wasn't disabled.");
}
MOZ_ASSERT(sele->IsMalformed(), "Script wasn't marked as malformed.");
} #endif
} elseif (scriptElement) { // must be tail call when mFlushState is eNotFlushing
RunScript(scriptElement, true);
// Always check the clock in nsContentSink right after a script
StopDeflecting(); if (nsContentSink::DidProcessATokenImpl() ==
NS_ERROR_HTMLPARSER_INTERRUPTED) { #ifdef DEBUG
LOG(("REFLUSH SCHEDULED (after script): %d\n",
++sTimesFlushLoopInterrupted)); #endif
nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); return;
}
}
}
}
FlushSpeculativeLoads(); // Make sure speculative loads never start after the // corresponding normal loads for the same URLs.
if (MOZ_UNLIKELY(!mParser)) { // The parse has ended.
ClearOpQueue(); // 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;
}
// avoid crashing near EOF
RefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
Unused << parserKungFuDeathGrip; // Intentionally not used within function
RefPtr<nsHtml5StreamParser> streamParserGrip; if (mParser) {
streamParserGrip = GetParser()->GetStreamParser();
}
Unused << streamParserGrip; // Intentionally not used within function
MOZ_RELEASE_ASSERT(!mReadingFromStage, "Got doc write flush when reading from stage");
{ // autoFlush clears mOpQueue in its destructor.
nsHtml5AutoFlush autoFlush(this); // Profiler marker deliberately not counting layout and script // execution.
AUTO_PROFILER_MARKER_TEXT( "HTMLParserTreeOps", DOM,
MarkerOptions(MarkerInnerWindowIdFromDocShell(mDocShell)), "document.write"_ns);
nsHtml5TreeOperation* start = mOpQueue.Elements();
nsHtml5TreeOperation* end = start + mOpQueue.Length(); for (nsHtml5TreeOperation* iter = start; iter < end; ++iter) { if (MOZ_UNLIKELY(!mParser)) { // The previous tree op caused a call to nsIParser::Terminate(). return rv;
}
NS_ASSERTION(IsInDocUpdate(), "Tried to perform tree op outside update batch.");
rv = iter->Perform(this, &scriptElement, &interrupted, &streamEnded); if (NS_FAILED(rv)) {
MarkAsBroken(rv); break;
}
}
if (MOZ_UNLIKELY(!mParser)) { // The parse ended during an update pause. return rv;
} if (streamEnded) { // This should be redundant but let's do it just in case.
GetParser()->PermanentlyUndefineInsertionPoint();
}
} // autoFlush
if (MOZ_UNLIKELY(!mParser)) { // Ending the doc update caused a call to nsIParser::Terminate(). return rv;
}
if (streamEnded) {
DidBuildModel(false); #ifdef DEBUG if (scriptElement) {
nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(scriptElement); if (!sele) {
MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, "Node didn't QI to script, but SVG wasn't disabled.");
}
MOZ_ASSERT(sele->IsMalformed(), "Script wasn't marked as malformed.");
} #endif
} elseif (scriptElement) { // must be tail call when mFlushState is eNotFlushing
RunScript(scriptElement, true);
} return rv;
}
void nsHtml5TreeOpExecutor::CommitToInternalEncoding() { if (MOZ_UNLIKELY(!mParser || !mStreamParser)) { // An extension terminated the parser from a HTTP observer.
ClearOpQueue(); // clear in order to be able to assert in destructor return;
}
mStreamParser->ContinueAfterScriptsOrEncodingCommitment(nullptr, nullptr, false);
}
// 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) { returntrue;
}
if (MOZ_UNLIKELY(!mParser)) { // got terminate return;
}
nsContentSink::StartLayout(false);
if (mParser) {
*aInterrupted = !GetParser()->IsParserEnabled();
}
}
void nsHtml5TreeOpExecutor::PauseDocUpdate(bool* aInterrupted) { // Pausing the document update allows JS to run, and potentially block // further parsing.
nsHtml5AutoPauseUpdate autoPause(this);
if (MOZ_LIKELY(mParser)) {
*aInterrupted = !GetParser()->IsParserEnabled();
}
}
/** * 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 with `aMayDocumentWriteOrBlock=true` 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, bool aMayDocumentWriteOrBlock) { 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;
}
MOZ_ASSERT(mParser, "Trying to run script with a terminated parser.");
MOZ_ASSERT(aScriptElement, "No script to run");
nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement); if (!sele) {
MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, "Node didn't QI to script, but SVG wasn't disabled."); return;
}
sele->SetCreatorParser(GetParser());
if (!aMayDocumentWriteOrBlock) {
MOZ_ASSERT(sele->GetScriptDeferred() || sele->GetScriptAsync() ||
sele->GetScriptIsModule() || sele->GetScriptIsImportMap() ||
aScriptElement->AsElement()->HasAttr(nsGkAtoms::nomodule));
DebugOnly<bool> block = sele->AttemptToExecute();
MOZ_ASSERT(!block, "Defer, async, module, importmap, or nomodule tried to block."); return;
}
MOZ_RELEASE_ASSERT(
mFlushState == eNotFlushing, "Tried to run a potentially-blocking script while flushing.");
mReadingFromStage = false;
// 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() {
MOZ_ASSERT(!mStarted, "Tried to start when already started.");
mStarted = true;
}
if (NS_SUCCEEDED(docShell->CharsetChangeStopDocumentLoad())) {
docShell->CharsetChangeReloadDocument(aEncoding, aSource);
} // if the charset switch was accepted, mDocShell has called Terminate() on the // parser by now if (!mParser) { return;
}
[[nodiscard]] bool nsHtml5TreeOpExecutor::MoveOpsFrom(
nsTArray<nsHtml5TreeOperation>& aOpQueue) {
MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing, "Ops added to mOpQueue during tree op execution."); return !!mOpQueue.AppendElements(std::move(aOpQueue), mozilla::fallible_t());
}
void nsHtml5TreeOpExecutor::ClearOpQueue() {
MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing, "mOpQueue cleared during tree op execution.");
mOpQueue.Clear();
}
void nsHtml5TreeOpExecutor::RemoveFromStartOfOpQueue(
size_t aNumberOfOpsToRemove) {
MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing, "Ops removed from mOpQueue during tree op execution.");
mOpQueue.RemoveElementsAt(0, aNumberOfOpsToRemove);
}
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(); if (orig->SchemeIs("view-source")) {
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;
}
bool nsHtml5TreeOpExecutor::IsExternalViewSource() { if (!StaticPrefs::view_source_editor_external()) { returnfalse;
} if (mDocumentURI) { return mDocumentURI->SchemeIs("view-source");
} returnfalse;
}
// 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::ConvertIfNotPreloadedYetAndMediaApplies( const nsAString& aURL, const nsAString& aMedia) {
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL); if (!uri) { return nullptr;
}
if (!MediaApplies(aMedia)) { return nullptr;
} return uri.forget();
}
bool nsHtml5TreeOpExecutor::MediaApplies(const nsAString& aMedia) { using dom::MediaList;
if (aMedia.IsEmpty()) { returntrue;
}
RefPtr<MediaList> media = MediaList::Create(NS_ConvertUTF16toUTF8(aMedia)); return media->Matches(*mDocument);
}
// 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);
}
// Check the document's CSP usually delivered via the CSP header. if (nsCOMPtr<nsIContentSecurityPolicy> csp = mDocument->GetCsp()) { // base-uri should not fallback to the default-src and preloads should not // trigger violation reports. bool cspPermitsBaseURI = true;
nsresult rv = csp->Permits(
nullptr, nullptr, newBaseURI,
nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true/* aSpecific */, false/* aSendViolationReports */, &cspPermitsBaseURI); if (NS_FAILED(rv) || !cspPermitsBaseURI) { return;
}
}
// Also check the CSP discovered from the <meta> tag during speculative // parsing. if (nsCOMPtr<nsIContentSecurityPolicy> csp = mDocument->GetPreloadCsp()) { bool cspPermitsBaseURI = true;
nsresult rv = csp->Permits(
nullptr, nullptr, newBaseURI,
nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true/* aSpecific */, false/* aSendViolationReports */, &cspPermitsBaseURI); if (NS_FAILED(rv) || !cspPermitsBaseURI) { return;
}
}
// Please note that multiple meta CSPs 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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.