summaryrefslogtreecommitdiffstats
path: root/dom/base/nsContentSink.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/base/nsContentSink.cpp971
1 files changed, 971 insertions, 0 deletions
diff --git a/dom/base/nsContentSink.cpp b/dom/base/nsContentSink.cpp
new file mode 100644
index 0000000000..b7f13db239
--- /dev/null
+++ b/dom/base/nsContentSink.cpp
@@ -0,0 +1,971 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+/*
+ * Base class for the XML and HTML content sinks, which construct a
+ * DOM based on information from the parser.
+ */
+
+#include "nsContentSink.h"
+#include "mozilla/Components.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_content.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/LinkStyle.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/MutationObservers.h"
+#include "mozilla/dom/SRILogHelper.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsIDocShell.h"
+#include "nsILoadContext.h"
+#include "nsIPrefetchService.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsIMIMEHeaderParam.h"
+#include "nsIProtocolHandler.h"
+#include "nsIHttpChannel.h"
+#include "nsIContent.h"
+#include "nsPresContext.h"
+#include "nsViewManager.h"
+#include "nsAtom.h"
+#include "nsGkAtoms.h"
+#include "nsGlobalWindowInner.h"
+#include "nsNetCID.h"
+#include "nsICookieService.h"
+#include "nsContentUtils.h"
+#include "nsNodeInfoManager.h"
+#include "nsIAppShell.h"
+#include "nsIWidget.h"
+#include "nsWidgetsCID.h"
+#include "mozAutoDocUpdate.h"
+#include "nsIWebNavigation.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIObserverService.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/dom/HTMLDNSPrefetch.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "nsParserConstants.h"
+#include "nsSandboxFlags.h"
+#include "Link.h"
+#include "HTMLLinkElement.h"
+#include "MediaList.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include <stdint.h>
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsLiteralString.h"
+#include "nsIContentPolicy.h"
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+
+LazyLogModule gContentSinkLogModuleInfo("nscontentsink");
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentSink)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsContentSink)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsContentSink)
+ NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsContentSink)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsContentSink)
+ if (tmp->mDocument) {
+ tmp->mDocument->RemoveObserver(tmp);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNodeInfoManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptLoader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsContentSink)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+nsContentSink::nsContentSink()
+ : mBackoffCount(0),
+ mLastNotificationTime(0),
+ mLayoutStarted(0),
+ mDynamicLowerValue(0),
+ mParsing(0),
+ mDroppedTimer(0),
+ mDeferredLayoutStart(0),
+ mDeferredFlushTags(0),
+ mIsDocumentObserver(0),
+ mRunsToCompletion(0),
+ mIsBlockingOnload(false),
+ mDeflectedCount(0),
+ mHasPendingEvent(false),
+ mCurrentParseEndTime(0),
+ mBeginLoadTime(0),
+ mLastSampledUserEventTime(0),
+ mInMonolithicContainer(0),
+ mInNotification(0),
+ mUpdatesInNotification(0),
+ mPendingSheetCount(0) {
+ NS_ASSERTION(!mLayoutStarted, "What?");
+ NS_ASSERTION(!mDynamicLowerValue, "What?");
+ NS_ASSERTION(!mParsing, "What?");
+ NS_ASSERTION(mLastSampledUserEventTime == 0, "What?");
+ NS_ASSERTION(mDeflectedCount == 0, "What?");
+ NS_ASSERTION(!mDroppedTimer, "What?");
+ NS_ASSERTION(mInMonolithicContainer == 0, "What?");
+ NS_ASSERTION(mInNotification == 0, "What?");
+ NS_ASSERTION(!mDeferredLayoutStart, "What?");
+}
+
+nsContentSink::~nsContentSink() {
+ if (mDocument) {
+ // Remove ourselves just to be safe, though we really should have
+ // been removed in DidBuildModel if everything worked right.
+ mDocument->RemoveObserver(this);
+ }
+}
+
+nsresult nsContentSink::Init(Document* aDoc, nsIURI* aURI,
+ nsISupports* aContainer, nsIChannel* aChannel) {
+ MOZ_ASSERT(aDoc, "null ptr");
+ MOZ_ASSERT(aURI, "null ptr");
+
+ if (!aDoc || !aURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ mDocument = aDoc;
+
+ mDocumentURI = aURI;
+ mDocShell = do_QueryInterface(aContainer);
+ mScriptLoader = mDocument->ScriptLoader();
+
+ if (!mRunsToCompletion) {
+ if (mDocShell) {
+ uint32_t loadType = 0;
+ mDocShell->GetLoadType(&loadType);
+ mDocument->SetChangeScrollPosWhenScrollingToRef(
+ (loadType & nsIDocShell::LOAD_CMD_HISTORY) == 0);
+ }
+
+ ProcessHTTPHeaders(aChannel);
+ }
+
+ mCSSLoader = aDoc->CSSLoader();
+
+ mNodeInfoManager = aDoc->NodeInfoManager();
+
+ mBackoffCount = StaticPrefs::content_notify_backoffcount();
+
+ if (StaticPrefs::content_sink_enable_perf_mode() != 0) {
+ mDynamicLowerValue = StaticPrefs::content_sink_enable_perf_mode() == 1;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentSink::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) {
+ MOZ_ASSERT(!mRunsToCompletion, "How come a fragment parser observed sheets?");
+ if (aWasDeferred) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(mPendingSheetCount > 0, "How'd that happen?");
+ --mPendingSheetCount;
+
+ const bool loadedAllSheets = !mPendingSheetCount;
+ if (loadedAllSheets && (mDeferredLayoutStart || mDeferredFlushTags)) {
+ if (mDeferredFlushTags) {
+ FlushTags();
+ }
+ if (mDeferredLayoutStart) {
+ // We might not have really started layout, since this sheet was still
+ // loading. Do it now. Probably doesn't matter whether we do this
+ // before or after we unblock scripts, but before feels saner. Note
+ // that if mDeferredLayoutStart is true, that means any subclass
+ // StartLayout() stuff that needs to happen has already happened, so
+ // we don't need to worry about it.
+ StartLayout(false);
+ }
+
+ // Go ahead and try to scroll to our ref if we have one
+ ScrollToRef();
+ }
+
+ mScriptLoader->RemoveParserBlockingScriptExecutionBlocker();
+
+ if (loadedAllSheets &&
+ mDocument->GetReadyStateEnum() >= Document::READYSTATE_INTERACTIVE) {
+ mScriptLoader->DeferCheckpointReached();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsContentSink::ProcessHTTPHeaders(nsIChannel* aChannel) {
+ nsCOMPtr<nsIHttpChannel> httpchannel(do_QueryInterface(aChannel));
+
+ if (!httpchannel) {
+ return NS_OK;
+ }
+
+ bool gotEarlyHints = false;
+ if (nsCOMPtr<mozilla::net::HttpBaseChannel> baseChannel =
+ do_QueryInterface(aChannel)) {
+ nsTArray<mozilla::net::EarlyHintConnectArgs> earlyHints =
+ baseChannel->TakeEarlyHints();
+ gotEarlyHints = !earlyHints.IsEmpty();
+ mDocument->SetEarlyHints(std::move(earlyHints));
+ }
+
+ // Note that the only header we care about is the "link" header, since we
+ // have all the infrastructure for kicking off stylesheet loads.
+
+ nsAutoCString linkHeader;
+
+ nsresult rv = httpchannel->GetResponseHeader("link"_ns, linkHeader);
+ bool gotLinkHeader = NS_SUCCEEDED(rv) && !linkHeader.IsEmpty();
+ if (gotLinkHeader) {
+ mDocument->SetHeaderData(nsGkAtoms::link,
+ NS_ConvertASCIItoUTF16(linkHeader));
+ }
+ if (gotLinkHeader || gotEarlyHints) {
+ NS_ASSERTION(!mProcessLinkHeaderEvent.get(),
+ "Already dispatched an event?");
+
+ mProcessLinkHeaderEvent =
+ NewNonOwningRunnableMethod("nsContentSink::DoProcessLinkHeader", this,
+ &nsContentSink::DoProcessLinkHeader);
+ rv = NS_DispatchToCurrentThread(mProcessLinkHeaderEvent.get());
+ if (NS_FAILED(rv)) {
+ mProcessLinkHeaderEvent.Forget();
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsContentSink::DoProcessLinkHeader() {
+ for (const auto& earlyHint : mDocument->GetEarlyHints()) {
+ ProcessLinkFromHeader(earlyHint.link(), earlyHint.earlyHintPreloaderId());
+ }
+
+ nsAutoString value;
+
+ // Getting the header data and parsing the link header together roughly
+ // implement <https://httpwg.org/specs/rfc8288.html#parse-set>.
+ mDocument->GetHeaderData(nsGkAtoms::link, value);
+ auto linkHeaders = net::ParseLinkHeader(value);
+
+ for (const auto& linkHeader : linkHeaders) {
+ ProcessLinkFromHeader(linkHeader, 0);
+ }
+}
+
+nsresult nsContentSink::ProcessLinkFromHeader(const net::LinkHeader& aHeader,
+ uint64_t aEarlyHintPreloaderId) {
+ uint32_t linkTypes = LinkStyle::ParseLinkTypes(aHeader.mRel);
+
+ // The link relation may apply to a different resource, specified
+ // in the anchor parameter. For the link relations supported so far,
+ // we simply abort if the link applies to a resource different to the
+ // one we've loaded
+ if (!nsContentUtils::LinkContextIsURI(aHeader.mAnchor,
+ mDocument->GetDocumentURI())) {
+ return NS_OK;
+ }
+
+ if (nsContentUtils::PrefetchPreloadEnabled(mDocShell)) {
+ // prefetch href if relation is "next" or "prefetch"
+ if ((linkTypes & LinkStyle::eNEXT) || (linkTypes & LinkStyle::ePREFETCH)) {
+ PrefetchHref(aHeader.mHref, aHeader.mAs, aHeader.mType, aHeader.mMedia);
+ }
+
+ if (!aHeader.mHref.IsEmpty() && (linkTypes & LinkStyle::eDNS_PREFETCH)) {
+ PrefetchDNS(aHeader.mHref);
+ }
+
+ if (!aHeader.mHref.IsEmpty() && (linkTypes & LinkStyle::ePRECONNECT)) {
+ Preconnect(aHeader.mHref, aHeader.mCrossOrigin);
+ }
+
+ if (linkTypes & LinkStyle::ePRELOAD) {
+ PreloadHref(aHeader.mHref, aHeader.mAs, aHeader.mType, aHeader.mMedia,
+ aHeader.mNonce, aHeader.mIntegrity, aHeader.mSrcset,
+ aHeader.mSizes, aHeader.mCrossOrigin, aHeader.mReferrerPolicy,
+ aEarlyHintPreloaderId, aHeader.mFetchPriority);
+ }
+
+ if ((linkTypes & LinkStyle::eMODULE_PRELOAD) &&
+ mDocument->ScriptLoader()->GetModuleLoader()) {
+ PreloadModule(aHeader.mHref, aHeader.mAs, aHeader.mMedia, aHeader.mNonce,
+ aHeader.mIntegrity, aHeader.mCrossOrigin,
+ aHeader.mReferrerPolicy, aEarlyHintPreloaderId,
+ aHeader.mFetchPriority);
+ }
+ }
+
+ // is it a stylesheet link?
+ if (!(linkTypes & LinkStyle::eSTYLESHEET)) {
+ return NS_OK;
+ }
+
+ bool isAlternate = linkTypes & LinkStyle::eALTERNATE;
+ return ProcessStyleLinkFromHeader(aHeader.mHref, isAlternate, aHeader.mTitle,
+ aHeader.mIntegrity, aHeader.mType,
+ aHeader.mMedia, aHeader.mReferrerPolicy,
+ aHeader.mFetchPriority);
+}
+
+nsresult nsContentSink::ProcessStyleLinkFromHeader(
+ const nsAString& aHref, bool aAlternate, const nsAString& aTitle,
+ const nsAString& aIntegrity, const nsAString& aType,
+ const nsAString& aMedia, const nsAString& aReferrerPolicy,
+ const nsAString& aFetchPriority) {
+ if (aAlternate && aTitle.IsEmpty()) {
+ // alternates must have title return without error, for now
+ return NS_OK;
+ }
+
+ nsAutoString mimeType;
+ nsAutoString params;
+ nsContentUtils::SplitMimeType(aType, mimeType, params);
+
+ // see bug 18817
+ if (!mimeType.IsEmpty() && !mimeType.LowerCaseEqualsLiteral("text/css")) {
+ // Unknown stylesheet language
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = NS_NewURI(getter_AddRefs(url), aHref, nullptr,
+ mDocument->GetDocBaseURI());
+
+ if (NS_FAILED(rv)) {
+ // The URI is bad, move along, don't propagate the error (for now)
+ return NS_OK;
+ }
+
+ // Link header is working like a <link> node, so referrerPolicy attr should
+ // have higher priority than referrer policy from document.
+ ReferrerPolicy policy =
+ ReferrerInfo::ReferrerPolicyAttributeFromString(aReferrerPolicy);
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateFromDocumentAndPolicyOverride(mDocument, policy);
+
+ const FetchPriority fetchPriority =
+ nsGenericHTMLElement::ToFetchPriority(aFetchPriority);
+
+ Loader::SheetInfo info{
+ *mDocument,
+ nullptr,
+ url.forget(),
+ nullptr,
+ referrerInfo.forget(),
+ CORS_NONE,
+ aTitle,
+ aMedia,
+ aIntegrity,
+ /* nonce = */ u""_ns,
+ aAlternate ? Loader::HasAlternateRel::Yes : Loader::HasAlternateRel::No,
+ Loader::IsInline::No,
+ Loader::IsExplicitlyEnabled::No,
+ fetchPriority,
+ };
+
+ auto loadResultOrErr =
+ mCSSLoader->LoadStyleLink(info, mRunsToCompletion ? nullptr : this);
+ if (loadResultOrErr.isErr()) {
+ return loadResultOrErr.unwrapErr();
+ }
+
+ if (loadResultOrErr.inspect().ShouldBlock() && !mRunsToCompletion) {
+ ++mPendingSheetCount;
+ mScriptLoader->AddParserBlockingScriptExecutionBlocker();
+ }
+
+ return NS_OK;
+}
+
+void nsContentSink::PrefetchHref(const nsAString& aHref, const nsAString& aAs,
+ const nsAString& aType,
+ const nsAString& aMedia) {
+ nsCOMPtr<nsIPrefetchService> prefetchService(components::Prefetch::Service());
+ if (prefetchService) {
+ // construct URI using document charset
+ auto encoding = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aHref, encoding, mDocument->GetDocBaseURI());
+ if (uri) {
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*mDocument);
+ referrerInfo = referrerInfo->CloneWithNewOriginalReferrer(mDocumentURI);
+
+ prefetchService->PrefetchURI(uri, referrerInfo, mDocument, true);
+ }
+ }
+}
+
+void nsContentSink::PreloadHref(const nsAString& aHref, const nsAString& aAs,
+ const nsAString& aType, const nsAString& aMedia,
+ const nsAString& aNonce,
+ const nsAString& aIntegrity,
+ const nsAString& aSrcset,
+ const nsAString& aSizes, const nsAString& aCORS,
+ const nsAString& aReferrerPolicy,
+ uint64_t aEarlyHintPreloaderId,
+ const nsAString& aFetchPriority) {
+ auto encoding = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aHref, encoding, mDocument->GetDocBaseURI());
+ if (!uri) {
+ // URL parsing failed.
+ return;
+ }
+
+ nsAttrValue asAttr;
+ mozilla::net::ParseAsValue(aAs, asAttr);
+
+ nsAutoString mimeType;
+ nsAutoString notUsed;
+ nsContentUtils::SplitMimeType(aType, mimeType, notUsed);
+
+ auto policyType = mozilla::net::AsValueToContentPolicy(asAttr);
+ if (policyType == nsIContentPolicy::TYPE_INVALID ||
+ !mozilla::net::CheckPreloadAttrs(asAttr, mimeType, aMedia, mDocument)) {
+ // Ignore preload wrong or empty attributes.
+ mozilla::net::WarnIgnoredPreload(*mDocument, *uri);
+ return;
+ }
+
+ mDocument->Preloads().PreloadLinkHeader(
+ uri, aHref, policyType, aAs, aType, aNonce, aIntegrity, aSrcset, aSizes,
+ aCORS, aReferrerPolicy, aEarlyHintPreloaderId, aFetchPriority);
+}
+
+void nsContentSink::PreloadModule(
+ const nsAString& aHref, const nsAString& aAs, const nsAString& aMedia,
+ const nsAString& aNonce, const nsAString& aIntegrity,
+ const nsAString& aCORS, const nsAString& aReferrerPolicy,
+ uint64_t aEarlyHintPreloaderId, const nsAString& aFetchPriority) {
+ ModuleLoader* moduleLoader = mDocument->ScriptLoader()->GetModuleLoader();
+
+ if (!StaticPrefs::network_modulepreload()) {
+ // Keep behavior from https://phabricator.services.mozilla.com/D149371,
+ // prior to main implementation of modulepreload
+ moduleLoader->DisallowImportMaps();
+ return;
+ }
+
+ RefPtr<mozilla::dom::MediaList> mediaList =
+ mozilla::dom::MediaList::Create(NS_ConvertUTF16toUTF8(aMedia));
+ if (!mediaList->Matches(*mDocument)) {
+ return;
+ }
+
+ if (aHref.IsEmpty()) {
+ return;
+ }
+
+ if (!net::IsScriptLikeOrInvalid(aAs)) {
+ return;
+ }
+
+ auto encoding = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aHref, encoding, mDocument->GetDocBaseURI());
+ if (!uri) {
+ return;
+ }
+
+ moduleLoader->DisallowImportMaps();
+
+ mDocument->Preloads().PreloadLinkHeader(
+ uri, aHref, nsIContentPolicy::TYPE_SCRIPT, u"script"_ns, u"module"_ns,
+ aNonce, aIntegrity, u""_ns, u""_ns, aCORS, aReferrerPolicy,
+ aEarlyHintPreloaderId, aFetchPriority);
+}
+
+void nsContentSink::PrefetchDNS(const nsAString& aHref) {
+ nsAutoString hostname;
+ bool isHttps = false;
+
+ if (StringBeginsWith(aHref, u"//"_ns)) {
+ hostname = Substring(aHref, 2);
+ } else {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aHref);
+ if (!uri) {
+ return;
+ }
+ nsresult rv;
+ bool isLocalResource = false;
+ rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+ &isLocalResource);
+ if (NS_SUCCEEDED(rv) && !isLocalResource) {
+ nsAutoCString host;
+ uri->GetHost(host);
+ CopyUTF8toUTF16(host, hostname);
+ }
+ isHttps = uri->SchemeIs("https");
+ }
+
+ if (!hostname.IsEmpty() && HTMLDNSPrefetch::IsAllowed(mDocument)) {
+ OriginAttributes oa;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(mDocument, oa);
+
+ HTMLDNSPrefetch::Prefetch(hostname, isHttps, oa,
+ mDocument->GetChannel()->GetTRRMode(),
+ HTMLDNSPrefetch::Priority::Low);
+ }
+}
+
+void nsContentSink::Preconnect(const nsAString& aHref,
+ const nsAString& aCrossOrigin) {
+ // construct URI using document charset
+ auto encoding = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aHref, encoding, mDocument->GetDocBaseURI());
+
+ if (uri && mDocument) {
+ mDocument->MaybePreconnect(uri,
+ dom::Element::StringToCORSMode(aCrossOrigin));
+ }
+}
+
+void nsContentSink::ScrollToRef() {
+ RefPtr<Document> document = mDocument;
+ document->ScrollToRef();
+}
+
+void nsContentSink::StartLayout(bool aIgnorePendingSheets) {
+ if (mLayoutStarted) {
+ // Nothing to do here
+ return;
+ }
+
+ mDeferredLayoutStart = true;
+
+ if (!aIgnorePendingSheets &&
+ (WaitForPendingSheets() || mDocument->HasPendingInitialTranslation())) {
+ // Bail out; we'll start layout when the sheets and l10n load
+ return;
+ }
+
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS(
+ "Layout", LAYOUT, mDocumentURI->GetSpecOrDefault());
+
+ mDeferredLayoutStart = false;
+
+ if (aIgnorePendingSheets) {
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Layout"_ns, mDocument,
+ nsContentUtils::eLAYOUT_PROPERTIES, "ForcedLayoutStart");
+ }
+
+ // Notify on all our content. If none of our presshells have started layout
+ // yet it'll be a no-op except for updating our data structures, a la
+ // UpdateChildCounts() (because we don't want to double-notify on whatever we
+ // have right now). If some of them _have_ started layout, we want to make
+ // sure to flush tags instead of just calling UpdateChildCounts() after we
+ // loop over the shells.
+ FlushTags();
+
+ mLayoutStarted = true;
+ mLastNotificationTime = PR_Now();
+
+ mDocument->SetMayStartLayout(true);
+ RefPtr<PresShell> presShell = mDocument->GetPresShell();
+ // Make sure we don't call Initialize() for a shell that has
+ // already called it. This can happen when the layout frame for
+ // an iframe is constructed *between* the Embed() call for the
+ // docshell in the iframe, and the content sink's call to OpenBody().
+ // (Bug 153815)
+ if (presShell && !presShell->DidInitialize()) {
+ nsresult rv = presShell->Initialize();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+
+ // If the document we are loading has a reference or it is a
+ // frameset document, disable the scroll bars on the views.
+
+ mDocument->SetScrollToRef(mDocument->GetDocumentURI());
+}
+
+void nsContentSink::NotifyAppend(nsIContent* aContainer, uint32_t aStartIndex) {
+ mInNotification++;
+
+ {
+ // Scope so we call EndUpdate before we decrease mInNotification
+ //
+ // Note that aContainer->OwnerDoc() may not be mDocument.
+ MOZ_AUTO_DOC_UPDATE(aContainer->OwnerDoc(), true);
+ MutationObservers::NotifyContentAppended(
+ aContainer, aContainer->GetChildAt_Deprecated(aStartIndex));
+ mLastNotificationTime = PR_Now();
+ }
+
+ mInNotification--;
+}
+
+NS_IMETHODIMP
+nsContentSink::Notify(nsITimer* timer) {
+ if (mParsing) {
+ // We shouldn't interfere with our normal DidProcessAToken logic
+ mDroppedTimer = true;
+ return NS_OK;
+ }
+
+ if (WaitForPendingSheets()) {
+ mDeferredFlushTags = true;
+ } else {
+ FlushTags();
+
+ // Now try and scroll to the reference
+ // XXX Should we scroll unconditionally for history loads??
+ ScrollToRef();
+ }
+
+ mNotificationTimer = nullptr;
+ return NS_OK;
+}
+
+bool nsContentSink::IsTimeToNotify() {
+ if (!StaticPrefs::content_notify_ontimer() || !mLayoutStarted ||
+ !mBackoffCount || mInMonolithicContainer) {
+ return false;
+ }
+
+ if (WaitForPendingSheets()) {
+ mDeferredFlushTags = true;
+ return false;
+ }
+
+ PRTime now = PR_Now();
+
+ int64_t interval = GetNotificationInterval();
+ int64_t diff = now - mLastNotificationTime;
+
+ if (diff > interval) {
+ mBackoffCount--;
+ return true;
+ }
+
+ return false;
+}
+
+nsresult nsContentSink::WillInterruptImpl() {
+ nsresult result = NS_OK;
+
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_CALLS, ("nsContentSink::WillInterrupt: this=%p", this));
+#ifndef SINK_NO_INCREMENTAL
+ if (WaitForPendingSheets()) {
+ mDeferredFlushTags = true;
+ } else if (StaticPrefs::content_notify_ontimer() && mLayoutStarted) {
+ if (mBackoffCount && !mInMonolithicContainer) {
+ int64_t now = PR_Now();
+ int64_t interval = GetNotificationInterval();
+ int64_t diff = now - mLastNotificationTime;
+
+ // If it's already time for us to have a notification
+ if (diff > interval || mDroppedTimer) {
+ mBackoffCount--;
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_REFLOW,
+ ("nsContentSink::WillInterrupt: flushing tags since we've "
+ "run out time; backoff count: %d",
+ mBackoffCount));
+ result = FlushTags();
+ if (mDroppedTimer) {
+ ScrollToRef();
+ mDroppedTimer = false;
+ }
+ } else if (!mNotificationTimer) {
+ interval -= diff;
+ int32_t delay = interval;
+
+ // Convert to milliseconds
+ delay /= PR_USEC_PER_MSEC;
+
+ NS_NewTimerWithCallback(getter_AddRefs(mNotificationTimer), this, delay,
+ nsITimer::TYPE_ONE_SHOT);
+ if (mNotificationTimer) {
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_REFLOW,
+ ("nsContentSink::WillInterrupt: setting up timer with "
+ "delay %d",
+ delay));
+ }
+ }
+ }
+ } else {
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_REFLOW,
+ ("nsContentSink::WillInterrupt: flushing tags "
+ "unconditionally"));
+ result = FlushTags();
+ }
+#endif
+
+ mParsing = false;
+
+ return result;
+}
+
+void nsContentSink::WillResumeImpl() {
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_CALLS, ("nsContentSink::WillResume: this=%p", this));
+
+ mParsing = true;
+}
+
+nsresult nsContentSink::DidProcessATokenImpl() {
+ if (mRunsToCompletion || !mParser) {
+ return NS_OK;
+ }
+
+ // Get the current user event time
+ PresShell* presShell = mDocument->GetPresShell();
+ if (!presShell) {
+ // If there's no pres shell in the document, return early since
+ // we're not laying anything out here.
+ return NS_OK;
+ }
+
+ // Increase before comparing to gEventProbeRate
+ ++mDeflectedCount;
+
+ // Check if there's a pending event
+ if (StaticPrefs::content_sink_pending_event_mode() != 0 &&
+ !mHasPendingEvent &&
+ (mDeflectedCount % StaticPrefs::content_sink_event_probe_rate()) == 0) {
+ nsViewManager* vm = presShell->GetViewManager();
+ NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
+ mHasPendingEvent = widget && widget->HasPendingInputEvent();
+ }
+
+ if (mHasPendingEvent && StaticPrefs::content_sink_pending_event_mode() == 2) {
+ return NS_ERROR_HTMLPARSER_INTERRUPTED;
+ }
+
+ // Have we processed enough tokens to check time?
+ if (!mHasPendingEvent &&
+ mDeflectedCount <
+ uint32_t(mDynamicLowerValue
+ ? StaticPrefs::content_sink_interactive_deflect_count()
+ : StaticPrefs::content_sink_perf_deflect_count())) {
+ return NS_OK;
+ }
+
+ mDeflectedCount = 0;
+
+ // Check if it's time to return to the main event loop
+ if (PR_IntervalToMicroseconds(PR_IntervalNow()) > mCurrentParseEndTime) {
+ return NS_ERROR_HTMLPARSER_INTERRUPTED;
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+void nsContentSink::BeginUpdate(Document* aDocument) {
+ // Remember nested updates from updates that we started.
+ if (mInNotification > 0 && mUpdatesInNotification < 2) {
+ ++mUpdatesInNotification;
+ }
+
+ // If we're in a script and we didn't do the notification,
+ // something else in the script processing caused the
+ // notification to occur. Since this could result in frame
+ // creation, make sure we've flushed everything before we
+ // continue.
+
+ if (!mInNotification++) {
+ FlushTags();
+ }
+}
+
+void nsContentSink::EndUpdate(Document* aDocument) {
+ // If we're in a script and we didn't do the notification,
+ // something else in the script processing caused the
+ // notification to occur. Update our notion of how much
+ // has been flushed to include any new content if ending
+ // this update leaves us not inside a notification.
+ if (!--mInNotification) {
+ UpdateChildCounts();
+ }
+}
+
+void nsContentSink::DidBuildModelImpl(bool aTerminated) {
+ MOZ_ASSERT(aTerminated || (mParser && mParser->IsParserClosed()) ||
+ mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING,
+ "Bad readyState");
+ mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE);
+
+ if (mScriptLoader) {
+ mScriptLoader->ParsingComplete(aTerminated);
+ if (!mPendingSheetCount) {
+ mScriptLoader->DeferCheckpointReached();
+ }
+ }
+
+ if (!mDocument->HaveFiredDOMTitleChange()) {
+ mDocument->NotifyPossibleTitleChange(false);
+ }
+
+ // Cancel a timer if we had one out there
+ if (mNotificationTimer) {
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_REFLOW,
+ ("nsContentSink::DidBuildModel: canceling notification "
+ "timeout"));
+ mNotificationTimer->Cancel();
+ mNotificationTimer = nullptr;
+ }
+}
+
+void nsContentSink::DropParserAndPerfHint(void) {
+ if (!mParser) {
+ // Make sure we don't unblock unload too many times
+ return;
+ }
+
+ // Ref. Bug 49115
+ // Do this hack to make sure that the parser
+ // doesn't get destroyed, accidently, before
+ // the circularity, between sink & parser, is
+ // actually broken.
+ // Drop our reference to the parser to get rid of a circular
+ // reference.
+ RefPtr<nsParserBase> kungFuDeathGrip = std::move(mParser);
+ mozilla::Unused << kungFuDeathGrip;
+
+ // Call UnblockOnload only if mRunsToComletion is false and if
+ // we have already started loading because it's possible that this function
+ // is called (i.e. the parser is terminated) before we start loading due to
+ // destroying the window inside unload event callbacks for the previous
+ // document.
+ if (!mRunsToCompletion && mIsBlockingOnload) {
+ mDocument->UnblockOnload(true);
+ mIsBlockingOnload = false;
+ }
+}
+
+bool nsContentSink::IsScriptExecutingImpl() {
+ return !!mScriptLoader->GetCurrentScript();
+}
+
+nsresult nsContentSink::WillParseImpl(void) {
+ if (mRunsToCompletion || !mDocument) {
+ return NS_OK;
+ }
+
+ PresShell* presShell = mDocument->GetPresShell();
+ if (!presShell) {
+ return NS_OK;
+ }
+
+ uint32_t currentTime = PR_IntervalToMicroseconds(PR_IntervalNow());
+
+ if (StaticPrefs::content_sink_enable_perf_mode() == 0) {
+ nsViewManager* vm = presShell->GetViewManager();
+ NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE);
+ uint32_t lastEventTime;
+ vm->GetLastUserEventTime(lastEventTime);
+
+ bool newDynLower = mDocument->IsInBackgroundWindow() ||
+ ((currentTime - mBeginLoadTime) >
+ StaticPrefs::content_sink_initial_perf_time() &&
+ (currentTime - lastEventTime) <
+ StaticPrefs::content_sink_interactive_time());
+
+ if (mDynamicLowerValue != newDynLower) {
+ mDynamicLowerValue = newDynLower;
+ }
+ }
+
+ mDeflectedCount = 0;
+ mHasPendingEvent = false;
+
+ mCurrentParseEndTime =
+ currentTime + (mDynamicLowerValue
+ ? StaticPrefs::content_sink_interactive_parse_time()
+ : StaticPrefs::content_sink_perf_parse_time());
+
+ return NS_OK;
+}
+
+void nsContentSink::WillBuildModelImpl() {
+ if (!mRunsToCompletion) {
+ mDocument->BlockOnload();
+ mIsBlockingOnload = true;
+
+ mBeginLoadTime = PR_IntervalToMicroseconds(PR_IntervalNow());
+ }
+
+ mDocument->ResetScrolledToRefAlready();
+
+ if (mProcessLinkHeaderEvent.get()) {
+ mProcessLinkHeaderEvent.Revoke();
+
+ DoProcessLinkHeader();
+ }
+}
+
+/* static */
+void nsContentSink::NotifyDocElementCreated(Document* aDoc) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ MOZ_ASSERT(observerService);
+
+ auto* win = nsGlobalWindowInner::Cast(aDoc->GetInnerWindow());
+ bool fireInitialInsertion = !win || !win->DidFireDocElemInserted();
+ if (win) {
+ win->SetDidFireDocElemInserted();
+ }
+ if (fireInitialInsertion) {
+ observerService->NotifyObservers(ToSupports(aDoc),
+ "initial-document-element-inserted", u"");
+ }
+ observerService->NotifyObservers(ToSupports(aDoc),
+ "document-element-inserted", u"");
+
+ nsContentUtils::DispatchChromeEvent(aDoc, aDoc, u"DOMDocElementInserted"_ns,
+ CanBubble::eYes, Cancelable::eNo);
+}
+
+NS_IMETHODIMP
+nsContentSink::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsContentSink_timer");
+ return NS_OK;
+}