forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			943 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			943 lines
		
	
	
	
		
			30 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);
 | ||
|   auto linkHeaders = net::ParseLinkHeader(value);
 | ||
|   for (const auto& linkHeader : linkHeaders) {
 | ||
|     ProcessLinkFromHeader(linkHeader);
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| // 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;
 | ||
| }
 | ||
| 
 | ||
| nsresult nsContentSink::ProcessLinkFromHeader(const net::LinkHeader& aHeader) {
 | ||
|   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 (!LinkContextIsOurDocument(aHeader.mAnchor)) {
 | ||
|     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.mIntegrity, aHeader.mSrcset, aHeader.mSizes,
 | ||
|                   aHeader.mCrossOrigin, aHeader.mReferrerPolicy);
 | ||
|     }
 | ||
| 
 | ||
|     if (linkTypes & LinkStyle::eMODULE_PRELOAD) {
 | ||
|       // https://wicg.github.io/import-maps/#wait-for-import-maps
 | ||
|       // Step 1.2: Set document’s acquiring import maps to false.
 | ||
|       // When fetch a modulepreload module script graph.
 | ||
|       mDocument->ScriptLoader()->GetModuleLoader()->SetAcquiringImportMaps(
 | ||
|           false);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // 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);
 | ||
| }
 | ||
| 
 | ||
| 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;
 | ||
|   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,
 | ||
|                                           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) {
 | ||
|   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::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;
 | ||
| }
 | 
