/* -*- 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 "nsScriptLoader.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" #include "mozilla/css/Loader.h" #include "mozilla/dom/SRILogHelper.h" #include "nsStyleLinkElement.h" #include "nsIDocShell.h" #include "nsILoadContext.h" #include "nsCPrefetchService.h" #include "nsIURI.h" #include "nsNetUtil.h" #include "nsIMIMEHeaderParam.h" #include "nsIProtocolHandler.h" #include "nsIHttpChannel.h" #include "nsIContent.h" #include "nsIPresShell.h" #include "nsPresContext.h" #include "nsViewManager.h" #include "nsIAtom.h" #include "nsGkAtoms.h" #include "nsNetCID.h" #include "nsIOfflineCacheUpdate.h" #include "nsIApplicationCache.h" #include "nsIApplicationCacheContainer.h" #include "nsIApplicationCacheChannel.h" #include "nsIScriptSecurityManager.h" #include "nsICookieService.h" #include "nsContentUtils.h" #include "nsNodeInfoManager.h" #include "nsIAppShell.h" #include "nsIWidget.h" #include "nsWidgetsCID.h" #include "nsIDOMNode.h" #include "mozAutoDocUpdate.h" #include "nsIWebNavigation.h" #include "nsGenericHTMLElement.h" #include "nsHTMLDNSPrefetch.h" #include "nsIObserverService.h" #include "mozilla/Preferences.h" #include "nsParserConstants.h" #include "nsSandboxFlags.h" using namespace mozilla; 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_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(mCSSLoader) NS_IMPL_CYCLE_COLLECTION_UNLINK(mNodeInfoManager) NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptLoader) 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(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) , mBeganUpdate(0) , mLayoutStarted(0) , mDynamicLowerValue(0) , mParsing(0) , mDroppedTimer(0) , mDeferredLayoutStart(0) , mDeferredFlushTags(0) , mIsDocumentObserver(0) , mRunsToCompletion(0) , 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); } } bool nsContentSink::sNotifyOnTimer; int32_t nsContentSink::sBackoffCount; int32_t nsContentSink::sNotificationInterval; int32_t nsContentSink::sInteractiveDeflectCount; int32_t nsContentSink::sPerfDeflectCount; int32_t nsContentSink::sPendingEventMode; int32_t nsContentSink::sEventProbeRate; int32_t nsContentSink::sInteractiveParseTime; int32_t nsContentSink::sPerfParseTime; int32_t nsContentSink::sInteractiveTime; int32_t nsContentSink::sInitialPerfTime; int32_t nsContentSink::sEnablePerfMode; void nsContentSink::InitializeStatics() { Preferences::AddBoolVarCache(&sNotifyOnTimer, "content.notify.ontimer", true); // -1 means never. Preferences::AddIntVarCache(&sBackoffCount, "content.notify.backoffcount", -1); // The gNotificationInterval has a dramatic effect on how long it // takes to initially display content for slow connections. // The current value provides good // incremental display of content without causing an increase // in page load time. If this value is set below 1/10 of second // it starts to impact page load performance. // see bugzilla bug 72138 for more info. Preferences::AddIntVarCache(&sNotificationInterval, "content.notify.interval", 120000); Preferences::AddIntVarCache(&sInteractiveDeflectCount, "content.sink.interactive_deflect_count", 0); Preferences::AddIntVarCache(&sPerfDeflectCount, "content.sink.perf_deflect_count", 200); Preferences::AddIntVarCache(&sPendingEventMode, "content.sink.pending_event_mode", 1); Preferences::AddIntVarCache(&sEventProbeRate, "content.sink.event_probe_rate", 1); Preferences::AddIntVarCache(&sInteractiveParseTime, "content.sink.interactive_parse_time", 3000); Preferences::AddIntVarCache(&sPerfParseTime, "content.sink.perf_parse_time", 360000); Preferences::AddIntVarCache(&sInteractiveTime, "content.sink.interactive_time", 750000); Preferences::AddIntVarCache(&sInitialPerfTime, "content.sink.initial_perf_time", 2000000); Preferences::AddIntVarCache(&sEnablePerfMode, "content.sink.enable_perf_mode", 0); } nsresult nsContentSink::Init(nsIDocument* aDoc, nsIURI* aURI, nsISupports* aContainer, nsIChannel* aChannel) { NS_PRECONDITION(aDoc, "null ptr"); NS_PRECONDITION(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 = sBackoffCount; if (sEnablePerfMode != 0) { mDynamicLowerValue = sEnablePerfMode == 1; FavorPerformanceHint(!mDynamicLowerValue, 0); } return NS_OK; } NS_IMETHODIMP nsContentSink::StyleSheetLoaded(StyleSheet* aSheet, bool aWasAlternate, nsresult aStatus) { NS_ASSERTION(!mRunsToCompletion, "How come a fragment parser observed sheets?"); if (!aWasAlternate) { NS_ASSERTION(mPendingSheetCount > 0, "How'd that happen?"); --mPendingSheetCount; if (mPendingSheetCount == 0 && (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(); } return NS_OK; } nsresult nsContentSink::ProcessHTTPHeaders(nsIChannel* aChannel) { nsCOMPtr 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(NS_LITERAL_CSTRING("link"), linkHeader); if (NS_SUCCEEDED(rv) && !linkHeader.IsEmpty()) { mDocument->SetHeaderData(nsGkAtoms::link, NS_ConvertASCIItoUTF16(linkHeader)); NS_ASSERTION(!mProcessLinkHeaderEvent.get(), "Already dispatched an event?"); mProcessLinkHeaderEvent = NewNonOwningRunnableMethod(this, &nsContentSink::DoProcessLinkHeader); rv = NS_DispatchToCurrentThread(mProcessLinkHeaderEvent.get()); if (NS_FAILED(rv)) { mProcessLinkHeaderEvent.Forget(); } } return NS_OK; } nsresult nsContentSink::ProcessHeaderData(nsIAtom* aHeader, const nsAString& aValue, nsIContent* aContent) { nsresult rv = NS_OK; // necko doesn't process headers coming in from the parser mDocument->SetHeaderData(aHeader, aValue); if (aHeader == nsGkAtoms::setcookie) { // Note: Necko already handles cookies set via the channel. We can't just // call SetCookie on the channel because we want to do some security checks // here. nsCOMPtr cookieServ = do_GetService(NS_COOKIESERVICE_CONTRACTID, &rv); if (NS_FAILED(rv)) { return rv; } // Get a URI from the document principal // We use the original codebase in case the codebase was changed // by SetDomain // Note that a non-codebase principal (eg the system principal) will return // a null URI. nsCOMPtr codebaseURI; rv = mDocument->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI)); NS_ENSURE_TRUE(codebaseURI, rv); nsCOMPtr channel; if (mParser) { mParser->GetChannel(getter_AddRefs(channel)); } rv = cookieServ->SetCookieString(codebaseURI, nullptr, NS_ConvertUTF16toUTF8(aValue).get(), channel); if (NS_FAILED(rv)) { return rv; } } else if (aHeader == nsGkAtoms::msthemecompatible) { // Disable theming for the presshell if the value is no. // XXXbz don't we want to support this as an HTTP header too? nsAutoString value(aValue); if (value.LowerCaseEqualsLiteral("no")) { nsIPresShell* shell = mDocument->GetShell(); if (shell) { shell->DisableThemeSupport(); } } } return rv; } void nsContentSink::DoProcessLinkHeader() { nsAutoString value; mDocument->GetHeaderData(nsGkAtoms::link, value); ProcessLinkHeader(value); } // check whether the Link header field applies to the context resource // see bool nsContentSink::LinkContextIsOurDocument(const nsSubstring& 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 contextUri; nsresult rv = docUri->CloneIgnoringRef(getter_AddRefs(contextUri)); if (NS_FAILED(rv)) { // copying failed return false; } // resolve anchor against context nsCOMPtr 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 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 type; nsAutoString media; nsAutoString anchor; nsAutoString crossOrigin; 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; } } } } ++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++; } if (*equals != kNullCh) { *equals = kNullCh; nsAutoString attr(start); attr.StripWhitespace(); char16_t* value = ++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(); } } } } } 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 = ProcessLink(anchor, href, rel, // prefer RFC 5987 variant over non-I18zed version titleStar.IsEmpty() ? title : titleStar, type, media, crossOrigin); } href.Truncate(); rel.Truncate(); title.Truncate(); type.Truncate(); media.Truncate(); anchor.Truncate(); crossOrigin.SetIsVoid(true); seenParameters = false; } start = ++end; } href.Trim(" \t\n\r\f"); // trim HTML5 whitespace if (!href.IsEmpty() && !rel.IsEmpty()) { rv = ProcessLink(anchor, href, rel, // prefer RFC 5987 variant over non-I18zed version titleStar.IsEmpty() ? title : titleStar, type, media, crossOrigin); } return rv; } nsresult nsContentSink::ProcessLink(const nsSubstring& aAnchor, const nsSubstring& aHref, const nsSubstring& aRel, const nsSubstring& aTitle, const nsSubstring& aType, const nsSubstring& aMedia, const nsSubstring& aCrossOrigin) { uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(aRel, mDocument->NodePrincipal()); // 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::PrefetchEnabled(mDocShell)) { return NS_OK; } bool hasPrefetch = linkTypes & nsStyleLinkElement::ePREFETCH; // prefetch href if relation is "next" or "prefetch" if (hasPrefetch || (linkTypes & nsStyleLinkElement::eNEXT)) { PrefetchHref(aHref, mDocument, hasPrefetch); } if (!aHref.IsEmpty() && (linkTypes & nsStyleLinkElement::eDNS_PREFETCH)) { PrefetchDNS(aHref); } if (!aHref.IsEmpty() && (linkTypes & nsStyleLinkElement::ePRECONNECT)) { Preconnect(aHref, aCrossOrigin); } // is it a stylesheet link? if (!(linkTypes & nsStyleLinkElement::eSTYLESHEET)) { return NS_OK; } bool isAlternate = linkTypes & nsStyleLinkElement::eALTERNATE; return ProcessStyleLink(nullptr, aHref, isAlternate, aTitle, aType, aMedia); } nsresult nsContentSink::ProcessStyleLink(nsIContent* aElement, const nsSubstring& aHref, bool aAlternate, const nsSubstring& aTitle, const nsSubstring& aType, const nsSubstring& aMedia) { 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 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; } NS_ASSERTION(!aElement || aElement->NodeType() == nsIDOMNode::PROCESSING_INSTRUCTION_NODE, "We only expect processing instructions here"); nsAutoString integrity; if (aElement) { aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity); } if (!integrity.IsEmpty()) { MOZ_LOG(dom::SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, ("nsContentSink::ProcessStyleLink, integrity=%s", NS_ConvertUTF16toUTF8(integrity).get())); } // If this is a fragment parser, we don't want to observe. // We don't support CORS for processing instructions bool isAlternate; rv = mCSSLoader->LoadStyleLink(aElement, url, aTitle, aMedia, aAlternate, CORS_NONE, mDocument->GetReferrerPolicy(), integrity, mRunsToCompletion ? nullptr : this, &isAlternate); NS_ENSURE_SUCCESS(rv, rv); if (!isAlternate && !mRunsToCompletion) { ++mPendingSheetCount; mScriptLoader->AddParserBlockingScriptExecutionBlocker(); } return NS_OK; } nsresult nsContentSink::ProcessMETATag(nsIContent* aContent) { NS_ASSERTION(aContent, "missing meta-element"); nsresult rv = NS_OK; // set any HTTP-EQUIV data into document's header data as well as url nsAutoString header; aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header); if (!header.IsEmpty()) { // Ignore META REFRESH when document is sandboxed from automatic features. nsContentUtils::ASCIIToLower(header); if (nsGkAtoms::refresh->Equals(header) && (mDocument->GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) { return NS_OK; } nsAutoString result; aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result); if (!result.IsEmpty()) { nsCOMPtr fieldAtom(NS_Atomize(header)); rv = ProcessHeaderData(fieldAtom, result, aContent); } } NS_ENSURE_SUCCESS(rv, rv); if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, nsGkAtoms::handheldFriendly, eIgnoreCase)) { nsAutoString result; aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result); if (!result.IsEmpty()) { nsContentUtils::ASCIIToLower(result); mDocument->SetHeaderData(nsGkAtoms::handheldFriendly, result); } } return rv; } void nsContentSink::PrefetchHref(const nsAString &aHref, nsINode *aSource, bool aExplicit) { nsCOMPtr prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID)); if (prefetchService) { // construct URI using document charset const nsACString &charset = mDocument->GetDocumentCharacterSet(); nsCOMPtr uri; NS_NewURI(getter_AddRefs(uri), aHref, charset.IsEmpty() ? nullptr : PromiseFlatCString(charset).get(), mDocument->GetDocBaseURI()); if (uri) { nsCOMPtr domNode = do_QueryInterface(aSource); prefetchService->PrefetchURI(uri, mDocumentURI, domNode, aExplicit); } } } void nsContentSink::PrefetchDNS(const nsAString &aHref) { nsAutoString hostname; if (StringBeginsWith(aHref, NS_LITERAL_STRING("//"))) { hostname = Substring(aHref, 2); } else { nsCOMPtr 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); } } if (!hostname.IsEmpty() && nsHTMLDNSPrefetch::IsAllowed(mDocument)) { nsHTMLDNSPrefetch::PrefetchLow(hostname); } } void nsContentSink::Preconnect(const nsAString& aHref, const nsAString& aCrossOrigin) { // construct URI using document charset const nsACString& charset = mDocument->GetDocumentCharacterSet(); nsCOMPtr uri; NS_NewURI(getter_AddRefs(uri), aHref, charset.IsEmpty() ? nullptr : PromiseFlatCString(charset).get(), mDocument->GetDocBaseURI()); if (uri && mDocument) { mDocument->MaybePreconnect(uri, dom::Element::StringToCORSMode(aCrossOrigin)); } } nsresult nsContentSink::SelectDocAppCache(nsIApplicationCache *aLoadApplicationCache, nsIURI *aManifestURI, bool aFetchedWithHTTPGetOrEquiv, CacheSelectionAction *aAction) { nsresult rv; *aAction = CACHE_SELECTION_NONE; nsCOMPtr applicationCacheDocument = do_QueryInterface(mDocument); NS_ASSERTION(applicationCacheDocument, "mDocument must implement nsIApplicationCacheContainer."); if (aLoadApplicationCache) { nsCOMPtr groupURI; rv = aLoadApplicationCache->GetManifestURI(getter_AddRefs(groupURI)); NS_ENSURE_SUCCESS(rv, rv); bool equal = false; rv = groupURI->Equals(aManifestURI, &equal); NS_ENSURE_SUCCESS(rv, rv); if (!equal) { // This is a foreign entry, force a reload to avoid loading the foreign // entry. The entry will be marked as foreign to avoid loading it again. *aAction = CACHE_SELECTION_RELOAD; } else { // The http manifest attribute URI is equal to the manifest URI of // the cache the document was loaded from - associate the document with // that cache and invoke the cache update process. #ifdef DEBUG nsAutoCString docURISpec, clientID; mDocumentURI->GetAsciiSpec(docURISpec); aLoadApplicationCache->GetClientID(clientID); SINK_TRACE(static_cast(gContentSinkLogModuleInfo), SINK_TRACE_CALLS, ("Selection: assigning app cache %s to document %s", clientID.get(), docURISpec.get())); #endif rv = applicationCacheDocument->SetApplicationCache(aLoadApplicationCache); NS_ENSURE_SUCCESS(rv, rv); // Document will be added as implicit entry to the cache as part of // the update process. *aAction = CACHE_SELECTION_UPDATE; } } else { // The document was not loaded from an application cache // Here we know the manifest has the same origin as the // document. There is call to CheckMayLoad() on it above. if (!aFetchedWithHTTPGetOrEquiv) { // The document was not loaded using HTTP GET or equivalent // method. The spec says to run the cache selection algorithm w/o // the manifest specified. *aAction = CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST; } else { // Always do an update in this case *aAction = CACHE_SELECTION_UPDATE; } } return NS_OK; } nsresult nsContentSink::SelectDocAppCacheNoManifest(nsIApplicationCache *aLoadApplicationCache, nsIURI **aManifestURI, CacheSelectionAction *aAction) { *aManifestURI = nullptr; *aAction = CACHE_SELECTION_NONE; nsresult rv; if (aLoadApplicationCache) { // The document was loaded from an application cache, use that // application cache as the document's application cache. nsCOMPtr applicationCacheDocument = do_QueryInterface(mDocument); NS_ASSERTION(applicationCacheDocument, "mDocument must implement nsIApplicationCacheContainer."); #ifdef DEBUG nsAutoCString docURISpec, clientID; mDocumentURI->GetAsciiSpec(docURISpec); aLoadApplicationCache->GetClientID(clientID); SINK_TRACE(static_cast(gContentSinkLogModuleInfo), SINK_TRACE_CALLS, ("Selection, no manifest: assigning app cache %s to document %s", clientID.get(), docURISpec.get())); #endif rv = applicationCacheDocument->SetApplicationCache(aLoadApplicationCache); NS_ENSURE_SUCCESS(rv, rv); // Return the uri and invoke the update process for the selected // application cache. rv = aLoadApplicationCache->GetManifestURI(aManifestURI); NS_ENSURE_SUCCESS(rv, rv); *aAction = CACHE_SELECTION_UPDATE; } return NS_OK; } void nsContentSink::ProcessOfflineManifest(nsIContent *aElement) { // Only check the manifest for root document nodes. if (aElement != mDocument->GetRootElement()) { return; } // Don't bother processing offline manifest for documents // without a docshell if (!mDocShell) { return; } // Check for a manifest= attribute. nsAutoString manifestSpec; aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec); ProcessOfflineManifest(manifestSpec); } void nsContentSink::ProcessOfflineManifest(const nsAString& aManifestSpec) { // Don't bother processing offline manifest for documents // without a docshell if (!mDocShell) { return; } // If this document has been interecepted, let's skip the processing of the // manifest. if (nsContentUtils::IsControlledByServiceWorker(mDocument)) { return; } // If the docshell's in private browsing mode, we don't want to do any // manifest processing. nsCOMPtr loadContext = do_QueryInterface(mDocShell); if (loadContext->UsePrivateBrowsing()) { return; } nsresult rv; // Grab the application cache the document was loaded from, if any. nsCOMPtr applicationCache; nsCOMPtr applicationCacheChannel = do_QueryInterface(mDocument->GetChannel()); if (applicationCacheChannel) { bool loadedFromApplicationCache; rv = applicationCacheChannel->GetLoadedFromApplicationCache( &loadedFromApplicationCache); if (NS_FAILED(rv)) { return; } if (loadedFromApplicationCache) { rv = applicationCacheChannel->GetApplicationCache( getter_AddRefs(applicationCache)); if (NS_FAILED(rv)) { return; } } } if (aManifestSpec.IsEmpty() && !applicationCache) { // Not loaded from an application cache, and no manifest // attribute. Nothing to do here. return; } CacheSelectionAction action = CACHE_SELECTION_NONE; nsCOMPtr manifestURI; if (aManifestSpec.IsEmpty()) { action = CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST; } else { nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(manifestURI), aManifestSpec, mDocument, mDocumentURI); if (!manifestURI) { return; } // Documents must list a manifest from the same origin rv = mDocument->NodePrincipal()->CheckMayLoad(manifestURI, true, false); if (NS_FAILED(rv)) { action = CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST; } else { // Only continue if the document has permission to use offline APIs or // when preferences indicate to permit it automatically. if (!nsContentUtils::OfflineAppAllowed(mDocument->NodePrincipal()) && !nsContentUtils::MaybeAllowOfflineAppByDefault(mDocument->NodePrincipal()) && !nsContentUtils::OfflineAppAllowed(mDocument->NodePrincipal())) { return; } bool fetchedWithHTTPGetOrEquiv = false; nsCOMPtr httpChannel(do_QueryInterface(mDocument->GetChannel())); if (httpChannel) { nsAutoCString method; rv = httpChannel->GetRequestMethod(method); if (NS_SUCCEEDED(rv)) fetchedWithHTTPGetOrEquiv = method.EqualsLiteral("GET"); } rv = SelectDocAppCache(applicationCache, manifestURI, fetchedWithHTTPGetOrEquiv, &action); if (NS_FAILED(rv)) { return; } } } if (action == CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST) { rv = SelectDocAppCacheNoManifest(applicationCache, getter_AddRefs(manifestURI), &action); if (NS_FAILED(rv)) { return; } } switch (action) { case CACHE_SELECTION_NONE: break; case CACHE_SELECTION_UPDATE: { nsCOMPtr updateService = do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID); if (updateService) { nsCOMPtr domdoc = do_QueryInterface(mDocument); updateService->ScheduleOnDocumentStop(manifestURI, mDocumentURI, mDocument->NodePrincipal(), domdoc); } break; } case CACHE_SELECTION_RELOAD: { // This situation occurs only for toplevel documents, see bottom // of SelectDocAppCache method. // The document has been loaded from a different offline cache group than // the manifest it refers to, i.e. this is a foreign entry, mark it as such // and force a reload to avoid loading it. The next attempt will not // choose it. applicationCacheChannel->MarkOfflineCacheEntryAsForeign(); nsCOMPtr webNav = do_QueryInterface(mDocShell); webNav->Stop(nsIWebNavigation::STOP_ALL); webNav->Reload(nsIWebNavigation::LOAD_FLAGS_NONE); break; } default: NS_ASSERTION(false, "Cache selection algorithm didn't decide on proper action"); break; } } void nsContentSink::ScrollToRef() { mDocument->ScrollToRef(); } void nsContentSink::StartLayout(bool aIgnorePendingSheets) { if (mLayoutStarted) { // Nothing to do here return; } mDeferredLayoutStart = true; if (!aIgnorePendingSheets && WaitForPendingSheets()) { // Bail out; we'll start layout when the sheets load return; } mDeferredLayoutStart = false; // 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); nsCOMPtr shell = mDocument->GetShell(); // 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 (shell && !shell->DidInitialize()) { nsRect r = shell->GetPresContext()->GetVisibleArea(); nsCOMPtr shellGrip = shell; nsresult rv = shell->Initialize(r.width, r.height); 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) { if (aContainer->GetUncomposedDoc() != mDocument) { // aContainer is not actually in our document anymore.... Just bail out of // here; notifying on our document for this append would be wrong. return; } mInNotification++; { // Scope so we call EndUpdate before we decrease mInNotification MOZ_AUTO_DOC_UPDATE(mDocument, UPDATE_CONTENT_MODEL, !mBeganUpdate); nsNodeUtils::ContentAppended(aContainer, aContainer->GetChildAt(aStartIndex), 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 (!sNotifyOnTimer || !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(gContentSinkLogModuleInfo), SINK_TRACE_CALLS, ("nsContentSink::WillInterrupt: this=%p", this)); #ifndef SINK_NO_INCREMENTAL if (WaitForPendingSheets()) { mDeferredFlushTags = true; } else if (sNotifyOnTimer && 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(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; mNotificationTimer = do_CreateInstance("@mozilla.org/timer;1", &result); if (NS_SUCCEEDED(result)) { SINK_TRACE(static_cast(gContentSinkLogModuleInfo), SINK_TRACE_REFLOW, ("nsContentSink::WillInterrupt: setting up timer with " "delay %d", delay)); result = mNotificationTimer->InitWithCallback(this, delay, nsITimer::TYPE_ONE_SHOT); if (NS_FAILED(result)) { mNotificationTimer = nullptr; } } } } } else { SINK_TRACE(static_cast(gContentSinkLogModuleInfo), SINK_TRACE_REFLOW, ("nsContentSink::WillInterrupt: flushing tags " "unconditionally")); result = FlushTags(); } #endif mParsing = false; return result; } nsresult nsContentSink::WillResumeImpl() { SINK_TRACE(static_cast(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 nsIPresShell *shell = mDocument->GetShell(); if (!shell) { // 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 (sPendingEventMode != 0 && !mHasPendingEvent && (mDeflectedCount % sEventProbeRate) == 0) { nsViewManager* vm = shell->GetViewManager(); NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE); nsCOMPtr widget; vm->GetRootWidget(getter_AddRefs(widget)); mHasPendingEvent = widget && widget->HasPendingInputEvent(); } if (mHasPendingEvent && sPendingEventMode == 2) { return NS_ERROR_HTMLPARSER_INTERRUPTED; } // Have we processed enough tokens to check time? if (!mHasPendingEvent && mDeflectedCount < uint32_t(mDynamicLowerValue ? sInteractiveDeflectCount : sPerfDeflectCount)) { 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 appShell = do_GetService(kAppShellCID); if (appShell) appShell->FavorPerformanceHint(perfOverStarvation, starvationDelay); } void nsContentSink::BeginUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType) { // 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(nsIDocument *aDocument, nsUpdateType aUpdateType) { // 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) { if (mDocument) { MOZ_ASSERT(aTerminated || mDocument->GetReadyStateEnum() == nsIDocument::READYSTATE_LOADING, "Bad readyState"); mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE); } if (mScriptLoader) { mScriptLoader->ParsingComplete(aTerminated); } if (!mDocument->HaveFiredDOMTitleChange()) { mDocument->NotifyPossibleTitleChange(false); } // Cancel a timer if we had one out there if (mNotificationTimer) { SINK_TRACE(static_cast(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 kungFuDeathGrip(mParser.forget()); if (mDynamicLowerValue) { // Reset the performance hint which was set to FALSE // when mDynamicLowerValue was set. FavorPerformanceHint(true, 0); } if (!mRunsToCompletion) { mDocument->UnblockOnload(true); } } bool nsContentSink::IsScriptExecutingImpl() { return !!mScriptLoader->GetCurrentScript(); } nsresult nsContentSink::WillParseImpl(void) { if (mRunsToCompletion || !mDocument) { return NS_OK; } nsIPresShell *shell = mDocument->GetShell(); if (!shell) { return NS_OK; } uint32_t currentTime = PR_IntervalToMicroseconds(PR_IntervalNow()); if (sEnablePerfMode == 0) { nsViewManager* vm = shell->GetViewManager(); NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE); uint32_t lastEventTime; vm->GetLastUserEventTime(lastEventTime); bool newDynLower = mDocument->IsInBackgroundWindow() || ((currentTime - mBeginLoadTime) > uint32_t(sInitialPerfTime) && (currentTime - lastEventTime) < uint32_t(sInteractiveTime)); if (mDynamicLowerValue != newDynLower) { FavorPerformanceHint(!newDynLower, 0); mDynamicLowerValue = newDynLower; } } mDeflectedCount = 0; mHasPendingEvent = false; mCurrentParseEndTime = currentTime + (mDynamicLowerValue ? sInteractiveParseTime : sPerfParseTime); return NS_OK; } void nsContentSink::WillBuildModelImpl() { if (!mRunsToCompletion) { mDocument->BlockOnload(); mBeginLoadTime = PR_IntervalToMicroseconds(PR_IntervalNow()); } mDocument->ResetScrolledToRefAlready(); if (mProcessLinkHeaderEvent.get()) { mProcessLinkHeaderEvent.Revoke(); DoProcessLinkHeader(); } } /* static */ void nsContentSink::NotifyDocElementCreated(nsIDocument* aDoc) { nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { nsCOMPtr domDoc = do_QueryInterface(aDoc); observerService-> NotifyObservers(domDoc, "document-element-inserted", EmptyString().get()); } nsContentUtils::DispatchChromeEvent(aDoc, aDoc, NS_LITERAL_STRING("DOMDocElementInserted"), true, false); }