gecko-dev/dom/base/nsContentSink.cpp

1254 строки
38 KiB
C++

/* -*- 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/dom/Document.h"
#include "mozilla/dom/LinkStyle.h"
#include "mozilla/css/Loader.h"
#include "mozilla/dom/MutationObservers.h"
#include "mozilla/dom/SRILogHelper.h"
#include "mozilla/StoragePrincipalHelper.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"
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;
FavorPerformanceHint(!mDynamicLowerValue, 0);
}
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;
}
// 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);
if (NS_SUCCEEDED(rv) && !linkHeader.IsEmpty()) {
mDocument->SetHeaderData(nsGkAtoms::link,
NS_ConvertASCIItoUTF16(linkHeader));
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() {
nsAutoString value;
mDocument->GetHeaderData(nsGkAtoms::link, value);
ProcessLinkHeader(value);
}
// check whether the Link header field applies to the context resource
// see <http://tools.ietf.org/html/rfc5988#section-5.2>
bool nsContentSink::LinkContextIsOurDocument(const nsAString& aAnchor) {
if (aAnchor.IsEmpty()) {
// anchor parameter not present or empty -> same document reference
return true;
}
nsIURI* docUri = mDocument->GetDocumentURI();
// the document URI might contain a fragment identifier ("#...')
// we want to ignore that because it's invisible to the server
// and just affects the local interpretation in the recipient
nsCOMPtr<nsIURI> contextUri;
nsresult rv = NS_GetURIWithoutRef(docUri, getter_AddRefs(contextUri));
if (NS_FAILED(rv)) {
// copying failed
return false;
}
// resolve anchor against context
nsCOMPtr<nsIURI> resolvedUri;
rv = NS_NewURI(getter_AddRefs(resolvedUri), aAnchor, nullptr, contextUri);
if (NS_FAILED(rv)) {
// resolving failed
return false;
}
bool same;
rv = contextUri->Equals(resolvedUri, &same);
if (NS_FAILED(rv)) {
// comparison failed
return false;
}
return same;
}
// Decode a parameter value using the encoding defined in RFC 5987 (in place)
//
// charset "'" [ language ] "'" value-chars
//
// returns true when decoding happened successfully (otherwise leaves
// passed value alone)
bool nsContentSink::Decode5987Format(nsAString& aEncoded) {
nsresult rv;
nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
if (NS_FAILED(rv)) return false;
nsAutoCString asciiValue;
const char16_t* encstart = aEncoded.BeginReading();
const char16_t* encend = aEncoded.EndReading();
// create a plain ASCII string, aborting if we can't do that
// converted form is always shorter than input
while (encstart != encend) {
if (*encstart > 0 && *encstart < 128) {
asciiValue.Append((char)*encstart);
} else {
return false;
}
encstart++;
}
nsAutoString decoded;
nsAutoCString language;
rv = mimehdrpar->DecodeRFC5987Param(asciiValue, language, decoded);
if (NS_FAILED(rv)) return false;
aEncoded = decoded;
return true;
}
nsresult nsContentSink::ProcessLinkHeader(const nsAString& aLinkData) {
nsresult rv = NS_OK;
// keep track where we are within the header field
bool seenParameters = false;
// parse link content and call process style link
nsAutoString href;
nsAutoString rel;
nsAutoString title;
nsAutoString titleStar;
nsAutoString integrity;
nsAutoString srcset;
nsAutoString sizes;
nsAutoString type;
nsAutoString media;
nsAutoString anchor;
nsAutoString crossOrigin;
nsAutoString referrerPolicy;
nsAutoString as;
crossOrigin.SetIsVoid(true);
// copy to work buffer
nsAutoString stringList(aLinkData);
// put an extra null at the end
stringList.Append(kNullCh);
char16_t* start = stringList.BeginWriting();
char16_t* end = start;
char16_t* last = start;
char16_t endCh;
while (*start != kNullCh) {
// skip leading space
while ((*start != kNullCh) && nsCRT::IsAsciiSpace(*start)) {
++start;
}
end = start;
last = end - 1;
bool wasQuotedString = false;
// look for semicolon or comma
while (*end != kNullCh && *end != kSemicolon && *end != kComma) {
char16_t ch = *end;
if (ch == kQuote || ch == kLessThan) {
// quoted string
char16_t quote = ch;
if (quote == kLessThan) {
quote = kGreaterThan;
}
wasQuotedString = (ch == kQuote);
char16_t* closeQuote = (end + 1);
// seek closing quote
while (*closeQuote != kNullCh && quote != *closeQuote) {
// in quoted-string, "\" is an escape character
if (wasQuotedString && *closeQuote == kBackSlash &&
*(closeQuote + 1) != kNullCh) {
++closeQuote;
}
++closeQuote;
}
if (quote == *closeQuote) {
// found closer
// skip to close quote
end = closeQuote;
last = end - 1;
ch = *(end + 1);
if (ch != kNullCh && ch != kSemicolon && ch != kComma) {
// end string here
*(++end) = kNullCh;
ch = *(end + 1);
// keep going until semi or comma
while (ch != kNullCh && ch != kSemicolon && ch != kComma) {
++end;
ch = *(end + 1);
}
}
}
}
++end;
++last;
}
endCh = *end;
// end string here
*end = kNullCh;
if (start < end) {
if ((*start == kLessThan) && (*last == kGreaterThan)) {
*last = kNullCh;
// first instance of <...> wins
// also, do not allow hrefs after the first param was seen
if (href.IsEmpty() && !seenParameters) {
href = (start + 1);
href.StripWhitespace();
}
} else {
char16_t* equals = start;
seenParameters = true;
while ((*equals != kNullCh) && (*equals != kEqual)) {
equals++;
}
const bool hadEquals = *equals != kNullCh;
*equals = kNullCh;
nsAutoString attr(start);
attr.StripWhitespace();
char16_t* value = hadEquals ? ++equals : equals;
while (nsCRT::IsAsciiSpace(*value)) {
value++;
}
if ((*value == kQuote) && (*value == *last)) {
*last = kNullCh;
value++;
}
if (wasQuotedString) {
// unescape in-place
char16_t* unescaped = value;
char16_t* src = value;
while (*src != kNullCh) {
if (*src == kBackSlash && *(src + 1) != kNullCh) {
src++;
}
*unescaped++ = *src++;
}
*unescaped = kNullCh;
}
if (attr.LowerCaseEqualsLiteral("rel")) {
if (rel.IsEmpty()) {
rel = value;
rel.CompressWhitespace();
}
} else if (attr.LowerCaseEqualsLiteral("title")) {
if (title.IsEmpty()) {
title = value;
title.CompressWhitespace();
}
} else if (attr.LowerCaseEqualsLiteral("title*")) {
if (titleStar.IsEmpty() && !wasQuotedString) {
// RFC 5987 encoding; uses token format only, so skip if we get
// here with a quoted-string
nsAutoString tmp;
tmp = value;
if (Decode5987Format(tmp)) {
titleStar = tmp;
titleStar.CompressWhitespace();
} else {
// header value did not parse, throw it away
titleStar.Truncate();
}
}
} else if (attr.LowerCaseEqualsLiteral("type")) {
if (type.IsEmpty()) {
type = value;
type.StripWhitespace();
}
} else if (attr.LowerCaseEqualsLiteral("media")) {
if (media.IsEmpty()) {
media = value;
// The HTML5 spec is formulated in terms of the CSS3 spec,
// which specifies that media queries are case insensitive.
nsContentUtils::ASCIIToLower(media);
}
} else if (attr.LowerCaseEqualsLiteral("anchor")) {
if (anchor.IsEmpty()) {
anchor = value;
anchor.StripWhitespace();
}
} else if (attr.LowerCaseEqualsLiteral("crossorigin")) {
if (crossOrigin.IsVoid()) {
crossOrigin.SetIsVoid(false);
crossOrigin = value;
crossOrigin.StripWhitespace();
}
} else if (attr.LowerCaseEqualsLiteral("as")) {
if (as.IsEmpty()) {
as = value;
as.CompressWhitespace();
}
} else if (attr.LowerCaseEqualsLiteral("referrerpolicy")) {
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#referrer-policy-attribute
// Specs says referrer policy attribute is an enumerated attribute,
// case insensitive and includes the empty string
// We will parse the value with AttributeReferrerPolicyFromString
// later, which will handle parsing it as an enumerated attribute.
if (referrerPolicy.IsEmpty()) {
referrerPolicy = value;
}
} else if (attr.LowerCaseEqualsLiteral("integrity")) {
if (integrity.IsEmpty()) {
integrity = value;
}
} else if (attr.LowerCaseEqualsLiteral("imagesrcset")) {
if (srcset.IsEmpty()) {
srcset = value;
}
} else if (attr.LowerCaseEqualsLiteral("imagesizes")) {
if (sizes.IsEmpty()) {
sizes = value;
}
}
}
}
if (endCh == kComma) {
// hit a comma, process what we've got so far
href.Trim(" \t\n\r\f"); // trim HTML5 whitespace
if (!href.IsEmpty() && !rel.IsEmpty()) {
rv = ProcessLinkFromHeader(
anchor, href, rel,
// prefer RFC 5987 variant over non-I18zed version
titleStar.IsEmpty() ? title : titleStar, integrity, srcset, sizes,
type, media, crossOrigin, referrerPolicy, as);
}
href.Truncate();
rel.Truncate();
title.Truncate();
type.Truncate();
integrity.Truncate();
srcset.Truncate();
sizes.Truncate();
media.Truncate();
anchor.Truncate();
referrerPolicy.Truncate();
crossOrigin.SetIsVoid(true);
as.Truncate();
seenParameters = false;
}
start = ++end;
}
href.Trim(" \t\n\r\f"); // trim HTML5 whitespace
if (!href.IsEmpty() && !rel.IsEmpty()) {
rv = ProcessLinkFromHeader(
anchor, href, rel,
// prefer RFC 5987 variant over non-I18zed version
titleStar.IsEmpty() ? title : titleStar, integrity, srcset, sizes, type,
media, crossOrigin, referrerPolicy, as);
}
return rv;
}
nsresult nsContentSink::ProcessLinkFromHeader(
const nsAString& aAnchor, const nsAString& aHref, const nsAString& aRel,
const nsAString& aTitle, const nsAString& aIntegrity,
const nsAString& aSrcset, const nsAString& aSizes, const nsAString& aType,
const nsAString& aMedia, const nsAString& aCrossOrigin,
const nsAString& aReferrerPolicy, const nsAString& aAs) {
uint32_t linkTypes = LinkStyle::ParseLinkTypes(aRel);
// 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 (!LinkContextIsOurDocument(aAnchor)) {
return NS_OK;
}
if (nsContentUtils::PrefetchPreloadEnabled(mDocShell)) {
// prefetch href if relation is "next" or "prefetch"
if ((linkTypes & LinkStyle::eNEXT) || (linkTypes & LinkStyle::ePREFETCH)) {
PrefetchHref(aHref, aAs, aType, aMedia);
}
if (!aHref.IsEmpty() && (linkTypes & LinkStyle::eDNS_PREFETCH)) {
PrefetchDNS(aHref);
}
if (!aHref.IsEmpty() && (linkTypes & LinkStyle::ePRECONNECT)) {
Preconnect(aHref, aCrossOrigin);
}
if (linkTypes & LinkStyle::ePRELOAD) {
PreloadHref(aHref, aAs, aType, aMedia, aIntegrity, aSrcset, aSizes,
aCrossOrigin, aReferrerPolicy);
}
}
// is it a stylesheet link?
if (!(linkTypes & LinkStyle::eSTYLESHEET)) {
return NS_OK;
}
bool isAlternate = linkTypes & LinkStyle::eALTERNATE;
return ProcessStyleLinkFromHeader(aHref, isAlternate, aTitle, aIntegrity,
aType, aMedia, aReferrerPolicy);
}
nsresult nsContentSink::ProcessStyleLinkFromHeader(
const nsAString& aHref, bool aAlternate, const nsAString& aTitle,
const nsAString& aIntegrity, const nsAString& aType,
const nsAString& aMedia, const nsAString& aReferrerPolicy) {
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);
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,
};
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& aIntegrity,
const nsAString& aSrcset,
const nsAString& aSizes, const nsAString& aCORS,
const nsAString& aReferrerPolicy) {
auto encoding = mDocument->GetDocumentCharacterSet();
nsCOMPtr<nsIURI> uri;
NS_NewURI(getter_AddRefs(uri), aHref, encoding, mDocument->GetDocBaseURI());
if (!uri) {
// URL parsing failed.
return;
}
nsAttrValue asAttr;
HTMLLinkElement::ParseAsValue(aAs, asAttr);
nsAutoString mimeType;
nsAutoString notUsed;
nsContentUtils::SplitMimeType(aType, mimeType, notUsed);
auto policyType = HTMLLinkElement::AsValueToContentPolicy(asAttr);
if (policyType == nsIContentPolicy::TYPE_INVALID ||
!HTMLLinkElement::CheckPreloadAttrs(asAttr, mimeType, aMedia,
mDocument)) {
// Ignore preload wrong or empty attributes.
HTMLLinkElement::WarnIgnoredPreload(*mDocument, *uri);
return;
}
mDocument->Preloads().PreloadLinkHeader(uri, aHref, policyType, aAs, aType,
aIntegrity, aSrcset, aSizes, aCORS,
aReferrerPolicy);
}
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) {
AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("nsContentSink::StartLayout", LAYOUT,
mDocumentURI->GetSpecOrDefault());
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;
}
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;
}
nsresult nsContentSink::WillResumeImpl() {
SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
SINK_TRACE_CALLS, ("nsContentSink::WillResume: this=%p", this));
mParsing = true;
return NS_OK;
}
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(getter_AddRefs(widget));
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::FavorPerformanceHint(bool perfOverStarvation,
uint32_t starvationDelay) {
static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
if (appShell)
appShell->FavorPerformanceHint(perfOverStarvation, starvationDelay);
}
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 ||
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;
if (mDynamicLowerValue) {
// Reset the performance hint which was set to FALSE
// when mDynamicLowerValue was set.
FavorPerformanceHint(true, 0);
}
// 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) {
FavorPerformanceHint(!newDynLower, 0);
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, ToSupports(aDoc),
u"DOMDocElementInserted"_ns,
CanBubble::eYes, Cancelable::eNo);
}
NS_IMETHODIMP
nsContentSink::GetName(nsACString& aName) {
aName.AssignLiteral("nsContentSink_timer");
return NS_OK;
}