forked from mirrors/gecko-dev
		
	 3421b8fcff
			
		
	
	
		3421b8fcff
		
	
	
	
	
		
			
			Replacing js and text occurences of asyncOpen2 Replacing open2 with open Differential Revision: https://phabricator.services.mozilla.com/D16885 --HG-- rename : layout/style/test/test_asyncopen2.html => layout/style/test/test_asyncopen.html extra : moz-landing-system : lando
		
			
				
	
	
		
			12597 lines
		
	
	
	
		
			409 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			12597 lines
		
	
	
	
		
			409 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 all our document implementations.
 | |
|  */
 | |
| 
 | |
| #include "AudioChannelService.h"
 | |
| #include "mozilla/dom/Document.h"
 | |
| #include "DocumentInlines.h"
 | |
| #include "mozilla/AnimationComparator.h"
 | |
| #include "mozilla/AntiTrackingCommon.h"
 | |
| #include "mozilla/ArrayUtils.h"
 | |
| #include "mozilla/AutoRestore.h"
 | |
| #include "mozilla/BinarySearch.h"
 | |
| #include "mozilla/CSSEnabledState.h"
 | |
| #include "mozilla/DebugOnly.h"
 | |
| #include "mozilla/EffectSet.h"
 | |
| #include "mozilla/EnumSet.h"
 | |
| #include "mozilla/IdentifierMapEntry.h"
 | |
| #include "mozilla/IntegerRange.h"
 | |
| #include "mozilla/MemoryReporting.h"
 | |
| #include "mozilla/Likely.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/StaticPrefs.h"
 | |
| #include "mozilla/URLExtraData.h"
 | |
| #include <algorithm>
 | |
| 
 | |
| #include "mozilla/Logging.h"
 | |
| #include "plstr.h"
 | |
| #include "mozilla/Sprintf.h"
 | |
| 
 | |
| #include "mozilla/Telemetry.h"
 | |
| #include "nsIInterfaceRequestor.h"
 | |
| #include "nsIInterfaceRequestorUtils.h"
 | |
| #include "nsILoadContext.h"
 | |
| #include "nsITextControlFrame.h"
 | |
| #include "nsNumberControlFrame.h"
 | |
| #include "nsUnicharUtils.h"
 | |
| #include "nsContentList.h"
 | |
| #include "nsCSSPseudoElements.h"
 | |
| #include "nsIObserver.h"
 | |
| #include "nsIBaseWindow.h"
 | |
| #include "nsILayoutHistoryState.h"
 | |
| #include "mozilla/css/Loader.h"
 | |
| #include "mozilla/css/ImageLoader.h"
 | |
| #include "nsDocShell.h"
 | |
| #include "nsDocShellLoadTypes.h"
 | |
| #include "nsIDocShellTreeItem.h"
 | |
| #include "nsCOMArray.h"
 | |
| #include "nsQueryObject.h"
 | |
| #include "mozilla/Services.h"
 | |
| #include "nsScreen.h"
 | |
| #include "ChildIterator.h"
 | |
| 
 | |
| #include "mozilla/AsyncEventDispatcher.h"
 | |
| #include "mozilla/BasicEvents.h"
 | |
| #include "mozilla/EventListenerManager.h"
 | |
| #include "mozilla/EventStateManager.h"
 | |
| #include "mozilla/FullscreenChange.h"
 | |
| #include "mozilla/PendingAnimationTracker.h"
 | |
| 
 | |
| #include "mozilla/dom/Attr.h"
 | |
| #include "mozilla/dom/BindingDeclarations.h"
 | |
| #include "mozilla/dom/ContentChild.h"
 | |
| #include "mozilla/dom/Element.h"
 | |
| #include "mozilla/dom/Event.h"
 | |
| #include "mozilla/dom/FeaturePolicy.h"
 | |
| #include "mozilla/dom/FramingChecker.h"
 | |
| #include "mozilla/dom/HTMLSharedElement.h"
 | |
| #include "mozilla/dom/Navigator.h"
 | |
| #include "mozilla/dom/Performance.h"
 | |
| #include "mozilla/dom/ServiceWorkerContainer.h"
 | |
| #include "mozilla/dom/ScriptLoader.h"
 | |
| #include "mozilla/dom/ShadowIncludingTreeIterator.h"
 | |
| #include "mozilla/dom/StyleSheetList.h"
 | |
| #include "mozilla/dom/SVGUseElement.h"
 | |
| #include "nsGenericHTMLElement.h"
 | |
| #include "mozilla/dom/CDATASection.h"
 | |
| #include "mozilla/dom/ProcessingInstruction.h"
 | |
| #include "nsDOMString.h"
 | |
| #include "nsNodeUtils.h"
 | |
| #include "nsLayoutUtils.h"  // for GetFrameForPoint
 | |
| #include "nsIFrame.h"
 | |
| #include "nsITabChild.h"
 | |
| 
 | |
| #include "nsRange.h"
 | |
| #include "mozilla/dom/DocumentType.h"
 | |
| #include "mozilla/dom/NodeIterator.h"
 | |
| #include "mozilla/dom/Promise.h"
 | |
| #include "mozilla/dom/PromiseNativeHandler.h"
 | |
| #include "mozilla/dom/TreeWalker.h"
 | |
| 
 | |
| #include "nsIServiceManager.h"
 | |
| #include "mozilla/dom/ServiceWorkerManager.h"
 | |
| #include "imgLoader.h"
 | |
| 
 | |
| #include "nsAboutProtocolUtils.h"
 | |
| #include "nsCanvasFrame.h"
 | |
| #include "nsContentCID.h"
 | |
| #include "nsError.h"
 | |
| #include "nsPresContext.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "nsNodeInfoManager.h"
 | |
| #include "nsIFileChannel.h"
 | |
| #include "nsIMultiPartChannel.h"
 | |
| #include "nsIRefreshURI.h"
 | |
| #include "nsIWebNavigation.h"
 | |
| #include "nsIScriptError.h"
 | |
| #include "nsISimpleEnumerator.h"
 | |
| #include "nsIRequestContext.h"
 | |
| #include "nsStyleSheetService.h"
 | |
| 
 | |
| #include "nsNetUtil.h"  // for NS_NewURI
 | |
| #include "nsIInputStreamChannel.h"
 | |
| #include "nsIAuthPrompt.h"
 | |
| #include "nsIAuthPrompt2.h"
 | |
| 
 | |
| #include "nsIScriptSecurityManager.h"
 | |
| #include "nsIPermissionManager.h"
 | |
| #include "nsIPrincipal.h"
 | |
| #include "ExpandedPrincipal.h"
 | |
| #include "mozilla/NullPrincipal.h"
 | |
| 
 | |
| #include "nsIDOMWindow.h"
 | |
| #include "nsPIDOMWindow.h"
 | |
| #include "nsFocusManager.h"
 | |
| #include "nsICookieService.h"
 | |
| 
 | |
| #include "nsBidiUtils.h"
 | |
| 
 | |
| #include "nsContentCreatorFunctions.h"
 | |
| 
 | |
| #include "nsIScriptContext.h"
 | |
| #include "nsBindingManager.h"
 | |
| #include "nsHTMLDocument.h"
 | |
| #include "nsIRequest.h"
 | |
| #include "mozilla/dom/BlobURLProtocolHandler.h"
 | |
| 
 | |
| #include "nsCharsetSource.h"
 | |
| #include "nsIParser.h"
 | |
| #include "nsIContentSink.h"
 | |
| 
 | |
| #include "mozilla/EventDispatcher.h"
 | |
| #include "mozilla/EventStates.h"
 | |
| #include "mozilla/InternalMutationEvent.h"
 | |
| #include "nsDOMCID.h"
 | |
| 
 | |
| #include "jsapi.h"
 | |
| #include "nsIXPConnect.h"
 | |
| #include "xpcpublic.h"
 | |
| #include "nsCCUncollectableMarker.h"
 | |
| #include "nsIContentPolicy.h"
 | |
| #include "nsContentPolicyUtils.h"
 | |
| #include "nsICategoryManager.h"
 | |
| #include "nsIDocumentLoaderFactory.h"
 | |
| #include "nsIDocumentLoader.h"
 | |
| #include "nsIContentViewer.h"
 | |
| #include "nsIXMLContentSink.h"
 | |
| #include "nsIPrompt.h"
 | |
| #include "nsIPropertyBag2.h"
 | |
| #include "mozilla/dom/PageTransitionEvent.h"
 | |
| #include "mozilla/dom/StyleRuleChangeEvent.h"
 | |
| #include "mozilla/dom/StyleSheetChangeEvent.h"
 | |
| #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
 | |
| #include "nsJSUtils.h"
 | |
| #include "nsFrameLoader.h"
 | |
| #include "nsEscape.h"
 | |
| #include "nsObjectLoadingContent.h"
 | |
| #include "nsHtml5TreeOpExecutor.h"
 | |
| #include "mozilla/dom/HTMLFormElement.h"
 | |
| #include "mozilla/dom/HTMLLinkElement.h"
 | |
| #include "mozilla/dom/HTMLMediaElement.h"
 | |
| #include "mozilla/dom/HTMLIFrameElement.h"
 | |
| #include "mozilla/dom/HTMLImageElement.h"
 | |
| #include "mozilla/dom/HTMLTextAreaElement.h"
 | |
| #include "mozilla/dom/MediaSource.h"
 | |
| 
 | |
| #include "mozAutoDocUpdate.h"
 | |
| #include "nsGlobalWindow.h"
 | |
| #include "mozilla/Encoding.h"
 | |
| #include "nsDOMNavigationTiming.h"
 | |
| 
 | |
| #include "mozilla/SMILAnimationController.h"
 | |
| #include "imgIContainer.h"
 | |
| #include "nsSVGUtils.h"
 | |
| 
 | |
| #include "nsRefreshDriver.h"
 | |
| 
 | |
| // FOR CSP (autogenerated by xpidl)
 | |
| #include "nsIContentSecurityPolicy.h"
 | |
| #include "mozilla/dom/nsCSPContext.h"
 | |
| #include "mozilla/dom/nsCSPService.h"
 | |
| #include "mozilla/dom/nsCSPUtils.h"
 | |
| #include "nsHTMLStyleSheet.h"
 | |
| #include "nsHTMLCSSStyleSheet.h"
 | |
| #include "mozilla/dom/DOMImplementation.h"
 | |
| #include "mozilla/dom/ShadowRoot.h"
 | |
| #include "mozilla/dom/Comment.h"
 | |
| #include "nsTextNode.h"
 | |
| #include "mozilla/dom/Link.h"
 | |
| #include "mozilla/dom/HTMLCollectionBinding.h"
 | |
| #include "mozilla/dom/HTMLElementBinding.h"
 | |
| #include "nsXULAppAPI.h"
 | |
| #include "mozilla/dom/Touch.h"
 | |
| #include "mozilla/dom/TouchEvent.h"
 | |
| 
 | |
| #include "mozilla/Preferences.h"
 | |
| 
 | |
| #include "imgILoader.h"
 | |
| #include "imgRequestProxy.h"
 | |
| #include "nsWrapperCacheInlines.h"
 | |
| #include "nsSandboxFlags.h"
 | |
| #include "mozilla/dom/AnimatableBinding.h"
 | |
| #include "mozilla/dom/AnonymousContent.h"
 | |
| #include "mozilla/dom/BindingUtils.h"
 | |
| #include "mozilla/dom/ClientInfo.h"
 | |
| #include "mozilla/dom/ClientState.h"
 | |
| #include "mozilla/dom/DocumentFragment.h"
 | |
| #include "mozilla/dom/DocumentL10n.h"
 | |
| #include "mozilla/dom/DocumentTimeline.h"
 | |
| #include "mozilla/dom/Event.h"
 | |
| #include "mozilla/dom/HTMLBodyElement.h"
 | |
| #include "mozilla/dom/HTMLInputElement.h"
 | |
| #include "mozilla/dom/ImageTracker.h"
 | |
| #include "mozilla/dom/MediaQueryList.h"
 | |
| #include "mozilla/dom/NodeFilterBinding.h"
 | |
| #include "mozilla/OwningNonNull.h"
 | |
| #include "mozilla/dom/TabChild.h"
 | |
| #include "mozilla/dom/WebComponentsBinding.h"
 | |
| #include "mozilla/dom/CustomElementRegistryBinding.h"
 | |
| #include "mozilla/dom/CustomElementRegistry.h"
 | |
| #include "mozilla/dom/ServiceWorkerDescriptor.h"
 | |
| #include "mozilla/dom/TimeoutManager.h"
 | |
| #include "mozilla/ExtensionPolicyService.h"
 | |
| #include "nsFrame.h"
 | |
| #include "nsDOMCaretPosition.h"
 | |
| #include "nsViewportInfo.h"
 | |
| #include "mozilla/StaticPtr.h"
 | |
| #include "nsITextControlElement.h"
 | |
| #include "nsIEditor.h"
 | |
| #include "nsIHttpChannelInternal.h"
 | |
| #include "nsISecurityConsoleMessage.h"
 | |
| #include "nsCharSeparatedTokenizer.h"
 | |
| #include "mozilla/dom/XPathEvaluator.h"
 | |
| #include "mozilla/dom/XPathNSResolverBinding.h"
 | |
| #include "mozilla/dom/XPathResult.h"
 | |
| #include "nsIDocumentEncoder.h"
 | |
| #include "nsIDocumentActivity.h"
 | |
| #include "nsIStructuredCloneContainer.h"
 | |
| #include "nsIMutableArray.h"
 | |
| #include "mozilla/dom/DOMStringList.h"
 | |
| #include "nsWindowSizes.h"
 | |
| #include "mozilla/dom/Location.h"
 | |
| #include "mozilla/dom/FontFaceSet.h"
 | |
| #include "gfxPrefs.h"
 | |
| #include "nsISupportsPrimitives.h"
 | |
| #include "mozilla/ServoStyleSet.h"
 | |
| #include "mozilla/StyleSheet.h"
 | |
| #include "mozilla/StyleSheetInlines.h"
 | |
| #include "mozilla/dom/SVGDocument.h"
 | |
| #include "mozilla/dom/SVGSVGElement.h"
 | |
| #include "mozilla/dom/DocGroup.h"
 | |
| #include "mozilla/dom/TabGroup.h"
 | |
| #ifdef MOZ_XUL
 | |
| #  include "mozilla/dom/XULBroadcastManager.h"
 | |
| #  include "mozilla/dom/XULPersist.h"
 | |
| #  include "nsIXULWindow.h"
 | |
| #  include "nsXULCommandDispatcher.h"
 | |
| #  include "nsXULPopupManager.h"
 | |
| #  include "nsIDocShellTreeOwner.h"
 | |
| #endif
 | |
| #include "nsIPresShellInlines.h"
 | |
| #include "mozilla/dom/BoxObject.h"
 | |
| 
 | |
| #include "mozilla/DocLoadingTimelineMarker.h"
 | |
| 
 | |
| #include "mozilla/dom/WindowGlobalChild.h"
 | |
| 
 | |
| #include "nsISpeculativeConnect.h"
 | |
| 
 | |
| #include "mozilla/MediaManager.h"
 | |
| 
 | |
| #include "AutoplayPolicy.h"
 | |
| #include "nsIURIMutator.h"
 | |
| #include "mozilla/DocumentStyleRootIterator.h"
 | |
| #include "mozilla/PendingFullscreenEvent.h"
 | |
| #include "mozilla/RestyleManager.h"
 | |
| #include "mozilla/ClearOnShutdown.h"
 | |
| #include "nsHTMLTags.h"
 | |
| #include "NodeUbiReporting.h"
 | |
| #include "nsICookieService.h"
 | |
| #include "mozilla/net/ChannelEventQueue.h"
 | |
| #include "mozilla/net/RequestContextService.h"
 | |
| #include "StorageAccessPermissionRequest.h"
 | |
| #include "mozilla/dom/WindowProxyHolder.h"
 | |
| #include "ThirdPartyUtil.h"
 | |
| 
 | |
| #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
 | |
| #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
 | |
| #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
 | |
| #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
 | |
| 
 | |
| extern bool sDisablePrefetchHTTPSPref;
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace dom {
 | |
| 
 | |
| typedef nsTArray<Link*> LinkArray;
 | |
| 
 | |
| static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
 | |
| static LazyLogModule gCspPRLog("CSP");
 | |
| LazyLogModule gUserInteractionPRLog("UserInteraction");
 | |
| 
 | |
| static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
 | |
|                                      nsIHttpChannel** aHttpChannel) {
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
 | |
|   if (httpChannel) {
 | |
|     httpChannel.forget(aHttpChannel);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
 | |
|   if (!multipart) {
 | |
|     *aHttpChannel = nullptr;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIChannel> baseChannel;
 | |
|   nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   httpChannel = do_QueryInterface(baseChannel);
 | |
|   httpChannel.forget(aHttpChannel);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| }  // namespace dom
 | |
| 
 | |
| #define NAME_NOT_VALID ((nsSimpleContentList*)1)
 | |
| 
 | |
| IdentifierMapEntry::IdentifierMapEntry(
 | |
|     const IdentifierMapEntry::AtomOrString& aKey)
 | |
|     : mKey(aKey) {}
 | |
| 
 | |
| IdentifierMapEntry::IdentifierMapEntry(
 | |
|     const IdentifierMapEntry::AtomOrString* aKey)
 | |
|     : mKey(aKey ? *aKey : nullptr) {}
 | |
| 
 | |
| IdentifierMapEntry::~IdentifierMapEntry() {}
 | |
| 
 | |
| IdentifierMapEntry::IdentifierMapEntry(IdentifierMapEntry&& aOther)
 | |
|     : PLDHashEntryHdr(std::move(aOther)),
 | |
|       mKey(std::move(aOther.mKey)),
 | |
|       mIdContentList(std::move(aOther.mIdContentList)),
 | |
|       mNameContentList(std::move(aOther.mNameContentList)),
 | |
|       mChangeCallbacks(std::move(aOther.mChangeCallbacks)),
 | |
|       mImageElement(std::move(aOther.mImageElement)) {}
 | |
| 
 | |
| void IdentifierMapEntry::Traverse(
 | |
|     nsCycleCollectionTraversalCallback* aCallback) {
 | |
|   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
 | |
|                                      "mIdentifierMap mNameContentList");
 | |
|   aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
 | |
| 
 | |
|   if (mImageElement) {
 | |
|     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
 | |
|                                        "mIdentifierMap mImageElement element");
 | |
|     nsIContent* imageElement = mImageElement;
 | |
|     aCallback->NoteXPCOMChild(imageElement);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool IdentifierMapEntry::IsEmpty() {
 | |
|   return mIdContentList.IsEmpty() && !mNameContentList && !mChangeCallbacks &&
 | |
|          !mImageElement;
 | |
| }
 | |
| 
 | |
| bool IdentifierMapEntry::HasNameElement() const {
 | |
|   return mNameContentList && mNameContentList->Length() != 0;
 | |
| }
 | |
| 
 | |
| Element* IdentifierMapEntry::GetIdElement() {
 | |
|   return mIdContentList.SafeElementAt(0);
 | |
| }
 | |
| 
 | |
| Element* IdentifierMapEntry::GetImageIdElement() {
 | |
|   return mImageElement ? mImageElement.get() : GetIdElement();
 | |
| }
 | |
| 
 | |
| void IdentifierMapEntry::AddContentChangeCallback(
 | |
|     Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
 | |
|   if (!mChangeCallbacks) {
 | |
|     mChangeCallbacks = new nsTHashtable<ChangeCallbackEntry>;
 | |
|   }
 | |
| 
 | |
|   ChangeCallback cc = {aCallback, aData, aForImage};
 | |
|   mChangeCallbacks->PutEntry(cc);
 | |
| }
 | |
| 
 | |
| void IdentifierMapEntry::RemoveContentChangeCallback(
 | |
|     Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
 | |
|   if (!mChangeCallbacks) return;
 | |
|   ChangeCallback cc = {aCallback, aData, aForImage};
 | |
|   mChangeCallbacks->RemoveEntry(cc);
 | |
|   if (mChangeCallbacks->Count() == 0) {
 | |
|     mChangeCallbacks = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
 | |
|                                              Element* aNewElement,
 | |
|                                              bool aImageOnly) {
 | |
|   if (!mChangeCallbacks) return;
 | |
| 
 | |
|   for (auto iter = mChangeCallbacks->ConstIter(); !iter.Done(); iter.Next()) {
 | |
|     IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
 | |
|     // Don't fire image changes for non-image observers, and don't fire element
 | |
|     // changes for image observers when an image override is active.
 | |
|     if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
 | |
|       iter.Remove();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| struct PositionComparator {
 | |
|   Element* const mElement;
 | |
|   explicit PositionComparator(Element* const aElement) : mElement(aElement) {}
 | |
| 
 | |
|   int operator()(void* aElement) const {
 | |
|     Element* curElement = static_cast<Element*>(aElement);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mElement != curElement);
 | |
|     if (nsContentUtils::PositionIsBefore(mElement, curElement)) {
 | |
|       return -1;
 | |
|     }
 | |
|     return 1;
 | |
|   }
 | |
| };
 | |
| 
 | |
| void IdentifierMapEntry::AddIdElement(Element* aElement) {
 | |
|   MOZ_ASSERT(aElement, "Must have element");
 | |
|   MOZ_ASSERT(!mIdContentList.Contains(nullptr), "Why is null in our list?");
 | |
| 
 | |
|   // Common case
 | |
|   if (mIdContentList.IsEmpty()) {
 | |
|     mIdContentList.AppendElement(aElement);
 | |
|     FireChangeCallbacks(nullptr, aElement);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   Element* currentElement = mIdContentList.ElementAt(0);
 | |
| #endif
 | |
| 
 | |
|   // We seem to have multiple content nodes for the same id, or XUL is messing
 | |
|   // with us.  Search for the right place to insert the content.
 | |
| 
 | |
|   size_t idx;
 | |
|   BinarySearchIf(mIdContentList, 0, mIdContentList.Length(),
 | |
|                  PositionComparator(aElement), &idx);
 | |
| 
 | |
|   mIdContentList.InsertElementAt(idx, aElement);
 | |
| 
 | |
|   if (idx == 0) {
 | |
|     Element* oldElement = mIdContentList.SafeElementAt(1);
 | |
|     NS_ASSERTION(currentElement == oldElement, "How did that happen?");
 | |
|     FireChangeCallbacks(oldElement, aElement);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
 | |
|   MOZ_ASSERT(aElement, "Missing element");
 | |
| 
 | |
|   // This should only be called while the document is in an update.
 | |
|   // Assertions near the call to this method guarantee this.
 | |
| 
 | |
|   // This could fire in OOM situations
 | |
|   // Only assert this in HTML documents for now as XUL does all sorts of weird
 | |
|   // crap.
 | |
|   NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
 | |
|                    mIdContentList.Contains(aElement),
 | |
|                "Removing id entry that doesn't exist");
 | |
| 
 | |
|   // XXXbz should this ever Compact() I guess when all the content is gone
 | |
|   // we'll just get cleaned up in the natural order of things...
 | |
|   Element* currentElement = mIdContentList.SafeElementAt(0);
 | |
|   mIdContentList.RemoveElement(aElement);
 | |
|   if (currentElement == aElement) {
 | |
|     FireChangeCallbacks(currentElement, mIdContentList.SafeElementAt(0));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void IdentifierMapEntry::SetImageElement(Element* aElement) {
 | |
|   Element* oldElement = GetImageIdElement();
 | |
|   mImageElement = aElement;
 | |
|   Element* newElement = GetImageIdElement();
 | |
|   if (oldElement != newElement) {
 | |
|     FireChangeCallbacks(oldElement, newElement, true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| namespace dom {
 | |
| 
 | |
| class SimpleHTMLCollection final : public nsSimpleContentList,
 | |
|                                    public nsIHTMLCollection {
 | |
|  public:
 | |
|   explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
 | |
| 
 | |
|   NS_DECL_ISUPPORTS_INHERITED
 | |
| 
 | |
|   virtual nsINode* GetParentObject() override {
 | |
|     return nsSimpleContentList::GetParentObject();
 | |
|   }
 | |
|   virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
 | |
|   virtual Element* GetElementAt(uint32_t aIndex) override {
 | |
|     return mElements.SafeElementAt(aIndex)->AsElement();
 | |
|   }
 | |
| 
 | |
|   virtual Element* GetFirstNamedElement(const nsAString& aName,
 | |
|                                         bool& aFound) override {
 | |
|     aFound = false;
 | |
|     RefPtr<nsAtom> name = NS_Atomize(aName);
 | |
|     for (uint32_t i = 0; i < mElements.Length(); i++) {
 | |
|       MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
 | |
|       Element* element = mElements[i]->AsElement();
 | |
|       if (element->GetID() == name ||
 | |
|           (element->HasName() &&
 | |
|            element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
 | |
|         aFound = true;
 | |
|         return element;
 | |
|       }
 | |
|     }
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
 | |
|     AutoTArray<nsAtom*, 8> atoms;
 | |
|     for (uint32_t i = 0; i < mElements.Length(); i++) {
 | |
|       MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
 | |
|       Element* element = mElements[i]->AsElement();
 | |
| 
 | |
|       nsAtom* id = element->GetID();
 | |
|       MOZ_ASSERT(id != nsGkAtoms::_empty);
 | |
|       if (id && !atoms.Contains(id)) {
 | |
|         atoms.AppendElement(id);
 | |
|       }
 | |
| 
 | |
|       if (element->HasName()) {
 | |
|         nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
 | |
|         MOZ_ASSERT(name && name != nsGkAtoms::_empty);
 | |
|         if (name && !atoms.Contains(name)) {
 | |
|           atoms.AppendElement(name);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     nsString* names = aNames.AppendElements(atoms.Length());
 | |
|     for (uint32_t i = 0; i < atoms.Length(); i++) {
 | |
|       atoms[i]->ToString(names[i]);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   virtual JSObject* GetWrapperPreserveColorInternal() override {
 | |
|     return nsWrapperCache::GetWrapperPreserveColor();
 | |
|   }
 | |
|   virtual void PreserveWrapperInternal(
 | |
|       nsISupports* aScriptObjectHolder) override {
 | |
|     nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
 | |
|   }
 | |
|   virtual JSObject* WrapObject(JSContext* aCx,
 | |
|                                JS::Handle<JSObject*> aGivenProto) override {
 | |
|     return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
 | |
|   }
 | |
| 
 | |
|   using nsBaseContentList::Item;
 | |
| 
 | |
|  private:
 | |
|   virtual ~SimpleHTMLCollection() {}
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
 | |
|                             nsIHTMLCollection)
 | |
| 
 | |
| }  // namespace dom
 | |
| 
 | |
| void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
 | |
|   if (!mNameContentList) {
 | |
|     mNameContentList = new dom::SimpleHTMLCollection(aNode);
 | |
|   }
 | |
| 
 | |
|   mNameContentList->AppendElement(aElement);
 | |
| }
 | |
| 
 | |
| void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
 | |
|   if (mNameContentList) {
 | |
|     mNameContentList->RemoveElement(aElement);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() {
 | |
|   Element* idElement = GetIdElement();
 | |
|   return idElement &&
 | |
|          nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
 | |
| }
 | |
| 
 | |
| size_t IdentifierMapEntry::SizeOfExcludingThis(
 | |
|     MallocSizeOf aMallocSizeOf) const {
 | |
|   return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
 | |
| }
 | |
| 
 | |
| // Helper structs for the content->subdoc map
 | |
| 
 | |
| class SubDocMapEntry : public PLDHashEntryHdr {
 | |
|  public:
 | |
|   // Both of these are strong references
 | |
|   Element* mKey;  // must be first, to look like PLDHashEntryStub
 | |
|   dom::Document* mSubDocument;
 | |
| };
 | |
| 
 | |
| class OnloadBlocker final : public nsIRequest {
 | |
|  public:
 | |
|   OnloadBlocker() {}
 | |
| 
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_NSIREQUEST
 | |
| 
 | |
|  private:
 | |
|   ~OnloadBlocker() {}
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| OnloadBlocker::GetName(nsACString& aResult) {
 | |
|   aResult.AssignLiteral("about:document-onload-blocker");
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| OnloadBlocker::IsPending(bool* _retval) {
 | |
|   *_retval = true;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| OnloadBlocker::GetStatus(nsresult* status) {
 | |
|   *status = NS_OK;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
 | |
| NS_IMETHODIMP
 | |
| OnloadBlocker::Suspend(void) { return NS_OK; }
 | |
| NS_IMETHODIMP
 | |
| OnloadBlocker::Resume(void) { return NS_OK; }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
 | |
|   *aLoadGroup = nullptr;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
 | |
|   *aLoadFlags = nsIRequest::LOAD_NORMAL;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
 | |
| 
 | |
| // ==================================================================
 | |
| 
 | |
| namespace dom {
 | |
| 
 | |
| ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
 | |
| 
 | |
| Document* ExternalResourceMap::RequestResource(
 | |
|     nsIURI* aURI, nsIURI* aReferrer, uint32_t aReferrerPolicy,
 | |
|     nsINode* aRequestingNode, Document* aDisplayDocument,
 | |
|     ExternalResourceLoad** aPendingLoad) {
 | |
|   // If we ever start allowing non-same-origin loads here, we might need to do
 | |
|   // something interesting with aRequestingPrincipal even for the hashtable
 | |
|   // gets.
 | |
|   MOZ_ASSERT(aURI, "Must have a URI");
 | |
|   MOZ_ASSERT(aRequestingNode, "Must have a node");
 | |
|   *aPendingLoad = nullptr;
 | |
|   if (mHaveShutDown) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // First, make sure we strip the ref from aURI.
 | |
|   nsCOMPtr<nsIURI> clone;
 | |
|   nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
 | |
|   if (NS_FAILED(rv) || !clone) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   ExternalResource* resource;
 | |
|   mMap.Get(clone, &resource);
 | |
|   if (resource) {
 | |
|     return resource->mDocument;
 | |
|   }
 | |
| 
 | |
|   RefPtr<PendingLoad>& loadEntry = mPendingLoads.GetOrInsert(clone);
 | |
|   if (loadEntry) {
 | |
|     RefPtr<PendingLoad> load(loadEntry);
 | |
|     load.forget(aPendingLoad);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<PendingLoad> load(new PendingLoad(aDisplayDocument));
 | |
|   loadEntry = load;
 | |
| 
 | |
|   if (NS_FAILED(load->StartLoad(clone, aReferrer, aReferrerPolicy,
 | |
|                                 aRequestingNode))) {
 | |
|     // Make sure we don't thrash things by trying this load again, since
 | |
|     // chances are it failed for good reasons (security check, etc).
 | |
|     AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
 | |
|   } else {
 | |
|     load.forget(aPendingLoad);
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void ExternalResourceMap::EnumerateResources(Document::SubDocEnumFunc aCallback,
 | |
|                                              void* aData) {
 | |
|   for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) {
 | |
|     ExternalResourceMap::ExternalResource* resource = iter.UserData();
 | |
|     if (resource->mDocument && !aCallback(resource->mDocument, aData)) {
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ExternalResourceMap::Traverse(
 | |
|     nsCycleCollectionTraversalCallback* aCallback) const {
 | |
|   // mPendingLoads will get cleared out as the requests complete, so
 | |
|   // no need to worry about those here.
 | |
|   for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
 | |
|     ExternalResourceMap::ExternalResource* resource = iter.UserData();
 | |
| 
 | |
|     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
 | |
|                                        "mExternalResourceMap.mMap entry"
 | |
|                                        "->mDocument");
 | |
|     aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
 | |
| 
 | |
|     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
 | |
|                                        "mExternalResourceMap.mMap entry"
 | |
|                                        "->mViewer");
 | |
|     aCallback->NoteXPCOMChild(resource->mViewer);
 | |
| 
 | |
|     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
 | |
|                                        "mExternalResourceMap.mMap entry"
 | |
|                                        "->mLoadGroup");
 | |
|     aCallback->NoteXPCOMChild(resource->mLoadGroup);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ExternalResourceMap::HideViewers() {
 | |
|   for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) {
 | |
|     nsCOMPtr<nsIContentViewer> viewer = iter.UserData()->mViewer;
 | |
|     if (viewer) {
 | |
|       viewer->Hide();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ExternalResourceMap::ShowViewers() {
 | |
|   for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) {
 | |
|     nsCOMPtr<nsIContentViewer> viewer = iter.UserData()->mViewer;
 | |
|     if (viewer) {
 | |
|       viewer->Show();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TransferZoomLevels(Document* aFromDoc, Document* aToDoc) {
 | |
|   MOZ_ASSERT(aFromDoc && aToDoc, "transferring zoom levels from/to null doc");
 | |
| 
 | |
|   nsPresContext* fromCtxt = aFromDoc->GetPresContext();
 | |
|   if (!fromCtxt) return;
 | |
| 
 | |
|   nsPresContext* toCtxt = aToDoc->GetPresContext();
 | |
|   if (!toCtxt) return;
 | |
| 
 | |
|   toCtxt->SetFullZoom(fromCtxt->GetFullZoom());
 | |
|   toCtxt->SetTextZoom(fromCtxt->TextZoom());
 | |
|   toCtxt->SetOverrideDPPX(fromCtxt->GetOverrideDPPX());
 | |
| }
 | |
| 
 | |
| void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
 | |
|   MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
 | |
| 
 | |
|   if (aFromDoc->IsShowing()) {
 | |
|     aToDoc->OnPageShow(true, nullptr);
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
 | |
|                                                   nsIContentViewer* aViewer,
 | |
|                                                   nsILoadGroup* aLoadGroup,
 | |
|                                                   Document* aDisplayDocument) {
 | |
|   MOZ_ASSERT(aURI, "Unexpected call");
 | |
|   MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
 | |
|              "Must have both or neither");
 | |
| 
 | |
|   RefPtr<PendingLoad> load;
 | |
|   mPendingLoads.Remove(aURI, getter_AddRefs(load));
 | |
| 
 | |
|   nsresult rv = NS_OK;
 | |
| 
 | |
|   nsCOMPtr<Document> doc;
 | |
|   if (aViewer) {
 | |
|     doc = aViewer->GetDocument();
 | |
|     NS_ASSERTION(doc, "Must have a document");
 | |
| 
 | |
|     if (doc->IsXULDocument()) {
 | |
|       // We don't handle XUL stuff here yet.
 | |
|       rv = NS_ERROR_NOT_AVAILABLE;
 | |
|     } else {
 | |
|       doc->SetDisplayDocument(aDisplayDocument);
 | |
| 
 | |
|       // Make sure that hiding our viewer will tear down its presentation.
 | |
|       aViewer->SetSticky(false);
 | |
| 
 | |
|       rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0));
 | |
|       if (NS_SUCCEEDED(rv)) {
 | |
|         rv = aViewer->Open(nullptr, nullptr);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (NS_FAILED(rv)) {
 | |
|       doc = nullptr;
 | |
|       aViewer = nullptr;
 | |
|       aLoadGroup = nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ExternalResource* newResource = new ExternalResource();
 | |
|   mMap.Put(aURI, newResource);
 | |
| 
 | |
|   newResource->mDocument = doc;
 | |
|   newResource->mViewer = aViewer;
 | |
|   newResource->mLoadGroup = aLoadGroup;
 | |
|   if (doc) {
 | |
|     TransferZoomLevels(aDisplayDocument, doc);
 | |
|     TransferShowingState(aDisplayDocument, doc);
 | |
|   }
 | |
| 
 | |
|   const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
 | |
|   for (uint32_t i = 0; i < obs.Length(); ++i) {
 | |
|     obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
 | |
|                     nullptr);
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
 | |
|                   nsIRequestObserver)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest,
 | |
|                                                  nsISupports* aContext) {
 | |
|   ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
 | |
|   if (map.HaveShutDown()) {
 | |
|     return NS_BINDING_ABORTED;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContentViewer> viewer;
 | |
|   nsCOMPtr<nsILoadGroup> loadGroup;
 | |
|   nsresult rv =
 | |
|       SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
 | |
| 
 | |
|   // Make sure to do this no matter what
 | |
|   nsresult rv2 =
 | |
|       map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
|   if (NS_FAILED(rv2)) {
 | |
|     mTargetListener = nullptr;
 | |
|     return rv2;
 | |
|   }
 | |
| 
 | |
|   return mTargetListener->OnStartRequest(aRequest, aContext);
 | |
| }
 | |
| 
 | |
| nsresult ExternalResourceMap::PendingLoad::SetupViewer(
 | |
|     nsIRequest* aRequest, nsIContentViewer** aViewer,
 | |
|     nsILoadGroup** aLoadGroup) {
 | |
|   MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
 | |
|   *aViewer = nullptr;
 | |
|   *aLoadGroup = nullptr;
 | |
| 
 | |
|   nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
 | |
|   NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
 | |
|   if (httpChannel) {
 | |
|     bool requestSucceeded;
 | |
|     if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
 | |
|         !requestSucceeded) {
 | |
|       // Bail out on this load, since it looks like we have an HTTP error page
 | |
|       return NS_BINDING_ABORTED;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsAutoCString type;
 | |
|   chan->GetContentType(type);
 | |
| 
 | |
|   nsCOMPtr<nsILoadGroup> loadGroup;
 | |
|   chan->GetLoadGroup(getter_AddRefs(loadGroup));
 | |
| 
 | |
|   // Give this document its own loadgroup
 | |
|   nsCOMPtr<nsILoadGroup> newLoadGroup =
 | |
|       do_CreateInstance(NS_LOADGROUP_CONTRACTID);
 | |
|   NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
 | |
|   newLoadGroup->SetLoadGroup(loadGroup);
 | |
| 
 | |
|   nsCOMPtr<nsIInterfaceRequestor> callbacks;
 | |
|   loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
 | |
| 
 | |
|   nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
 | |
|       new LoadgroupCallbacks(callbacks);
 | |
|   newLoadGroup->SetNotificationCallbacks(newCallbacks);
 | |
| 
 | |
|   // This is some serious hackery cribbed from docshell
 | |
|   nsCOMPtr<nsICategoryManager> catMan =
 | |
|       do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
 | |
|   NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
 | |
|   nsCString contractId;
 | |
|   nsresult rv =
 | |
|       catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
 | |
|       do_GetService(contractId.get());
 | |
|   NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
 | |
| 
 | |
|   nsCOMPtr<nsIContentViewer> viewer;
 | |
|   nsCOMPtr<nsIStreamListener> listener;
 | |
|   rv = docLoaderFactory->CreateInstance(
 | |
|       "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
 | |
|       getter_AddRefs(listener), getter_AddRefs(viewer));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
 | |
| 
 | |
|   nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
 | |
|   if (!parser) {
 | |
|     /// We don't want to deal with the various fake documents yet
 | |
|     return NS_ERROR_NOT_IMPLEMENTED;
 | |
|   }
 | |
| 
 | |
|   // We can't handle HTML and other weird things here yet.
 | |
|   nsIContentSink* sink = parser->GetContentSink();
 | |
|   nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
 | |
|   if (!xmlSink) {
 | |
|     return NS_ERROR_NOT_IMPLEMENTED;
 | |
|   }
 | |
| 
 | |
|   listener.swap(mTargetListener);
 | |
|   viewer.forget(aViewer);
 | |
|   newLoadGroup.forget(aLoadGroup);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
 | |
|                                                   nsISupports* aContext,
 | |
|                                                   nsIInputStream* aStream,
 | |
|                                                   uint64_t aOffset,
 | |
|                                                   uint32_t aCount) {
 | |
|   // mTargetListener might be null if SetupViewer or AddExternalResource failed.
 | |
|   NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
 | |
|   if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
 | |
|     return NS_BINDING_ABORTED;
 | |
|   }
 | |
|   return mTargetListener->OnDataAvailable(aRequest, aContext, aStream, aOffset,
 | |
|                                           aCount);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
 | |
|                                                 nsISupports* aContext,
 | |
|                                                 nsresult aStatus) {
 | |
|   // mTargetListener might be null if SetupViewer or AddExternalResource failed
 | |
|   if (mTargetListener) {
 | |
|     nsCOMPtr<nsIStreamListener> listener;
 | |
|     mTargetListener.swap(listener);
 | |
|     return listener->OnStopRequest(aRequest, aContext, aStatus);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult ExternalResourceMap::PendingLoad::StartLoad(nsIURI* aURI,
 | |
|                                                      nsIURI* aReferrer,
 | |
|                                                      uint32_t aReferrerPolicy,
 | |
|                                                      nsINode* aRequestingNode) {
 | |
|   MOZ_ASSERT(aURI, "Must have a URI");
 | |
|   MOZ_ASSERT(aRequestingNode, "Must have a node");
 | |
| 
 | |
|   nsCOMPtr<nsILoadGroup> loadGroup =
 | |
|       aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
 | |
| 
 | |
|   nsresult rv = NS_OK;
 | |
|   nsCOMPtr<nsIChannel> channel;
 | |
|   rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
 | |
|                      nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
 | |
|                      nsIContentPolicy::TYPE_OTHER,
 | |
|                      nullptr,  // aPerformanceStorage
 | |
|                      loadGroup);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
 | |
|   if (httpChannel) {
 | |
|     rv = httpChannel->SetReferrerWithPolicy(aReferrer, aReferrerPolicy);
 | |
|     Unused << NS_WARN_IF(NS_FAILED(rv));
 | |
|   }
 | |
| 
 | |
|   mURI = aURI;
 | |
| 
 | |
|   return channel->AsyncOpen(this);
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
 | |
|                   nsIInterfaceRequestor)
 | |
| 
 | |
| #define IMPL_SHIM(_i) \
 | |
|   NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
 | |
| 
 | |
| IMPL_SHIM(nsILoadContext)
 | |
| IMPL_SHIM(nsIProgressEventSink)
 | |
| IMPL_SHIM(nsIChannelEventSink)
 | |
| IMPL_SHIM(nsIApplicationCacheContainer)
 | |
| 
 | |
| #undef IMPL_SHIM
 | |
| 
 | |
| #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
 | |
| 
 | |
| #define TRY_SHIM(_i)                                 \
 | |
|   PR_BEGIN_MACRO                                     \
 | |
|   if (IID_IS(_i)) {                                  \
 | |
|     nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
 | |
|     if (!real) {                                     \
 | |
|       return NS_NOINTERFACE;                         \
 | |
|     }                                                \
 | |
|     nsCOMPtr<_i> shim = new _i##Shim(this, real);    \
 | |
|     shim.forget(aSink);                              \
 | |
|     return NS_OK;                                    \
 | |
|   }                                                  \
 | |
|   PR_END_MACRO
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
 | |
|                                                       void** aSink) {
 | |
|   if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
 | |
|                      IID_IS(nsIAuthPrompt2) || IID_IS(nsITabChild))) {
 | |
|     return mCallbacks->GetInterface(aIID, aSink);
 | |
|   }
 | |
| 
 | |
|   *aSink = nullptr;
 | |
| 
 | |
|   TRY_SHIM(nsILoadContext);
 | |
|   TRY_SHIM(nsIProgressEventSink);
 | |
|   TRY_SHIM(nsIChannelEventSink);
 | |
|   TRY_SHIM(nsIApplicationCacheContainer);
 | |
| 
 | |
|   return NS_NOINTERFACE;
 | |
| }
 | |
| 
 | |
| #undef TRY_SHIM
 | |
| #undef IID_IS
 | |
| 
 | |
| ExternalResourceMap::ExternalResource::~ExternalResource() {
 | |
|   if (mViewer) {
 | |
|     mViewer->Close(nullptr);
 | |
|     mViewer->Destroy();
 | |
|   }
 | |
| }
 | |
| 
 | |
| // ==================================================================
 | |
| // =
 | |
| // ==================================================================
 | |
| 
 | |
| // If we ever have an nsIDocumentObserver notification for stylesheet title
 | |
| // changes we should update the list from that instead of overriding
 | |
| // EnsureFresh.
 | |
| class DOMStyleSheetSetList final : public DOMStringList {
 | |
|  public:
 | |
|   explicit DOMStyleSheetSetList(Document* aDocument);
 | |
| 
 | |
|   void Disconnect() { mDocument = nullptr; }
 | |
| 
 | |
|   virtual void EnsureFresh() override;
 | |
| 
 | |
|  protected:
 | |
|   Document* mDocument;  // Our document; weak ref.  It'll let us know if it
 | |
|                         // dies.
 | |
| };
 | |
| 
 | |
| DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
 | |
|     : mDocument(aDocument) {
 | |
|   NS_ASSERTION(mDocument, "Must have document!");
 | |
| }
 | |
| 
 | |
| void DOMStyleSheetSetList::EnsureFresh() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   mNames.Clear();
 | |
| 
 | |
|   if (!mDocument) {
 | |
|     return;  // Spec says "no exceptions", and we have no style sets if we have
 | |
|              // no document, for sure
 | |
|   }
 | |
| 
 | |
|   size_t count = mDocument->SheetCount();
 | |
|   nsAutoString title;
 | |
|   for (size_t index = 0; index < count; index++) {
 | |
|     StyleSheet* sheet = mDocument->SheetAt(index);
 | |
|     NS_ASSERTION(sheet, "Null sheet in sheet list!");
 | |
|     sheet->GetTitle(title);
 | |
|     if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| // ==================================================================
 | |
| Document::SelectorCache::SelectorCache(nsIEventTarget* aEventTarget)
 | |
|     : nsExpirationTracker<SelectorCacheKey, 4>(1000, "Document::SelectorCache",
 | |
|                                                aEventTarget) {}
 | |
| 
 | |
| Document::SelectorCache::~SelectorCache() { AgeAllGenerations(); }
 | |
| 
 | |
| void Document::SelectorCache::NotifyExpired(SelectorCacheKey* aSelector) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aSelector);
 | |
| 
 | |
|   // There is no guarantee that this method won't be re-entered when selector
 | |
|   // matching is ongoing because "memory-pressure" could be notified immediately
 | |
|   // when OOM happens according to the design of nsExpirationTracker.
 | |
|   // The perfect solution is to delete the |aSelector| and its
 | |
|   // RawServoSelectorList in mTable asynchronously.
 | |
|   // We remove these objects synchronously for now because NotifyExpired() will
 | |
|   // never be triggered by "memory-pressure" which is not implemented yet in
 | |
|   // the stage 2 of mozalloc_handle_oom().
 | |
|   // Once these objects are removed asynchronously, we should update the warning
 | |
|   // added in mozalloc_handle_oom() as well.
 | |
|   RemoveObject(aSelector);
 | |
|   mTable.Remove(aSelector->mKey);
 | |
|   delete aSelector;
 | |
| }
 | |
| 
 | |
| struct Document::FrameRequest {
 | |
|   FrameRequest(FrameRequestCallback& aCallback, int32_t aHandle)
 | |
|       : mCallback(&aCallback), mHandle(aHandle) {}
 | |
| 
 | |
|   // Conversion operator so that we can append these to a
 | |
|   // FrameRequestCallbackList
 | |
|   operator const RefPtr<FrameRequestCallback>&() const { return mCallback; }
 | |
| 
 | |
|   // Comparator operators to allow RemoveElementSorted with an
 | |
|   // integer argument on arrays of FrameRequest
 | |
|   bool operator==(int32_t aHandle) const { return mHandle == aHandle; }
 | |
|   bool operator<(int32_t aHandle) const { return mHandle < aHandle; }
 | |
| 
 | |
|   RefPtr<FrameRequestCallback> mCallback;
 | |
|   int32_t mHandle;
 | |
| };
 | |
| 
 | |
| // ==================================================================
 | |
| // =
 | |
| // ==================================================================
 | |
| Document::Document(const char* aContentType)
 | |
|     : nsINode(nullptr),
 | |
|       DocumentOrShadowRoot(*this),
 | |
|       mReferrerPolicySet(false),
 | |
|       mReferrerPolicy(mozilla::net::RP_Unset),
 | |
|       mBlockAllMixedContent(false),
 | |
|       mBlockAllMixedContentPreloads(false),
 | |
|       mUpgradeInsecureRequests(false),
 | |
|       mUpgradeInsecurePreloads(false),
 | |
|       mCharacterSet(WINDOWS_1252_ENCODING),
 | |
|       mCharacterSetSource(0),
 | |
|       mParentDocument(nullptr),
 | |
|       mCachedRootElement(nullptr),
 | |
|       mNodeInfoManager(nullptr),
 | |
| #ifdef DEBUG
 | |
|       mStyledLinksCleared(false),
 | |
| #endif
 | |
|       mBidiEnabled(false),
 | |
|       mFontGroupCacheDirty(true),
 | |
|       mMathMLEnabled(false),
 | |
|       mIsInitialDocumentInWindow(false),
 | |
|       mIgnoreDocGroupMismatches(false),
 | |
|       mLoadedAsData(false),
 | |
|       mLoadedAsInteractiveData(false),
 | |
|       mMayStartLayout(true),
 | |
|       mHaveFiredTitleChange(false),
 | |
|       mIsShowing(false),
 | |
|       mVisible(true),
 | |
|       mRemovedFromDocShell(false),
 | |
|       // mAllowDNSPrefetch starts true, so that we can always reliably && it
 | |
|       // with various values that might disable it.  Since we never prefetch
 | |
|       // unless we get a window, and in that case the docshell value will get
 | |
|       // &&-ed in, this is safe.
 | |
|       mAllowDNSPrefetch(true),
 | |
|       mIsStaticDocument(false),
 | |
|       mCreatingStaticClone(false),
 | |
|       mInUnlinkOrDeletion(false),
 | |
|       mHasHadScriptHandlingObject(false),
 | |
|       mIsBeingUsedAsImage(false),
 | |
|       mIsSyntheticDocument(false),
 | |
|       mHasLinksToUpdateRunnable(false),
 | |
|       mFlushingPendingLinkUpdates(false),
 | |
|       mMayHaveDOMMutationObservers(false),
 | |
|       mMayHaveAnimationObservers(false),
 | |
|       mHasMixedActiveContentLoaded(false),
 | |
|       mHasMixedActiveContentBlocked(false),
 | |
|       mHasMixedDisplayContentLoaded(false),
 | |
|       mHasMixedDisplayContentBlocked(false),
 | |
|       mHasMixedContentObjectSubrequest(false),
 | |
|       mHasCSP(false),
 | |
|       mHasUnsafeEvalCSP(false),
 | |
|       mHasUnsafeInlineCSP(false),
 | |
|       mBFCacheDisallowed(false),
 | |
|       mHasHadDefaultView(false),
 | |
|       mStyleSheetChangeEventsEnabled(false),
 | |
|       mIsSrcdocDocument(false),
 | |
|       mDidDocumentOpen(false),
 | |
|       mHasDisplayDocument(false),
 | |
|       mFontFaceSetDirty(true),
 | |
|       mDidFireDOMContentLoaded(true),
 | |
|       mHasScrollLinkedEffect(false),
 | |
|       mFrameRequestCallbacksScheduled(false),
 | |
|       mIsTopLevelContentDocument(false),
 | |
|       mIsContentDocument(false),
 | |
|       mDidCallBeginLoad(false),
 | |
|       mAllowPaymentRequest(false),
 | |
|       mEncodingMenuDisabled(false),
 | |
|       mIsSVGGlyphsDocument(false),
 | |
|       mInDestructor(false),
 | |
|       mIsGoingAway(false),
 | |
|       mInXBLUpdate(false),
 | |
|       mNeedsReleaseAfterStackRefCntRelease(false),
 | |
|       mStyleSetFilled(false),
 | |
|       mSSApplicableStateNotificationPending(false),
 | |
|       mMayHaveTitleElement(false),
 | |
|       mDOMLoadingSet(false),
 | |
|       mDOMInteractiveSet(false),
 | |
|       mDOMCompleteSet(false),
 | |
|       mAutoFocusFired(false),
 | |
|       mScrolledToRefAlready(false),
 | |
|       mChangeScrollPosWhenScrollingToRef(false),
 | |
|       mHasWarnedAboutBoxObjects(false),
 | |
|       mDelayFrameLoaderInitialization(false),
 | |
|       mSynchronousDOMContentLoaded(false),
 | |
|       mMaybeServiceWorkerControlled(false),
 | |
|       mAllowZoom(false),
 | |
|       mValidScaleFloat(false),
 | |
|       mValidMaxScale(false),
 | |
|       mScaleStrEmpty(false),
 | |
|       mWidthStrEmpty(false),
 | |
|       mParserAborted(false),
 | |
|       mReportedUseCounters(false),
 | |
|       mHasReportedShadowDOMUsage(false),
 | |
|       mDocTreeHadAudibleMedia(false),
 | |
|       mDocTreeHadPlayRevoked(false),
 | |
| #ifdef DEBUG
 | |
|       mWillReparent(false),
 | |
| #endif
 | |
|       mHasDelayedRefreshEvent(false),
 | |
|       mPendingFullscreenRequests(0),
 | |
|       mXMLDeclarationBits(0),
 | |
|       mOnloadBlockCount(0),
 | |
|       mAsyncOnloadBlockCount(0),
 | |
|       mCompatMode(eCompatibility_FullStandards),
 | |
|       mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
 | |
|       mAncestorIsLoading(false),
 | |
| #ifdef MOZILLA_INTERNAL_API
 | |
|       mVisibilityState(dom::VisibilityState::Hidden),
 | |
| #else
 | |
|       mDummy(0),
 | |
| #endif
 | |
|       mType(eUnknown),
 | |
|       mDefaultElementType(0),
 | |
|       mAllowXULXBL(eTriUnset),
 | |
|       mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
 | |
|       mSandboxFlags(0),
 | |
|       mPartID(0),
 | |
|       mMarkedCCGeneration(0),
 | |
|       mPresShell(nullptr),
 | |
|       mSubtreeModifiedDepth(0),
 | |
|       mPreloadPictureDepth(0),
 | |
|       mEventsSuppressed(0),
 | |
|       mIgnoreDestructiveWritesCounter(0),
 | |
|       mFrameRequestCallbackCounter(0),
 | |
|       mStaticCloneCount(0),
 | |
|       mWindow(nullptr),
 | |
|       mBFCacheEntry(nullptr),
 | |
|       mInSyncOperationCount(0),
 | |
|       mBlockDOMContentLoaded(0),
 | |
|       mUseCounters(0),
 | |
|       mChildDocumentUseCounters(0),
 | |
|       mNotifiedPageForUseCounter(0),
 | |
|       mUserHasInteracted(false),
 | |
|       mHasUserInteractionTimerScheduled(false),
 | |
|       mStackRefCnt(0),
 | |
|       mUpdateNestLevel(0),
 | |
|       mViewportType(Unknown),
 | |
|       mViewportOverflowType(ViewportOverflowType::NoOverflow),
 | |
|       mSubDocuments(nullptr),
 | |
|       mHeaderData(nullptr),
 | |
|       mFlashClassification(FlashClassification::Unknown),
 | |
|       mScrollAnchorAdjustmentLength(0),
 | |
|       mScrollAnchorAdjustmentCount(0),
 | |
|       mBoxObjectTable(nullptr),
 | |
|       mCurrentOrientationAngle(0),
 | |
|       mCurrentOrientationType(OrientationType::Portrait_primary),
 | |
|       mServoRestyleRootDirtyBits(0),
 | |
|       mThrowOnDynamicMarkupInsertionCounter(0),
 | |
|       mIgnoreOpensDuringUnloadCounter(0),
 | |
|       mDocLWTheme(Doc_Theme_Uninitialized),
 | |
|       mSavedResolution(1.0f),
 | |
|       mPendingInitialTranslation(false) {
 | |
|   MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
 | |
| 
 | |
|   SetIsInDocument();
 | |
|   SetIsConnected(true);
 | |
| 
 | |
|   if (StaticPrefs::layout_css_use_counters_enabled()) {
 | |
|     mStyleUseCounters.reset(Servo_UseCounters_Create());
 | |
|   }
 | |
| 
 | |
|   SetContentTypeInternal(nsDependentCString(aContentType));
 | |
| 
 | |
|   // Start out mLastStyleSheetSet as null, per spec
 | |
|   SetDOMStringToNull(mLastStyleSheetSet);
 | |
| 
 | |
|   // void state used to differentiate an empty source from an unselected source
 | |
|   mPreloadPictureFoundSource.SetIsVoid(true);
 | |
| 
 | |
|   RecomputeLanguageFromCharset();
 | |
| }
 | |
| 
 | |
| void Document::ClearAllBoxObjects() {
 | |
|   if (mBoxObjectTable) {
 | |
|     for (auto iter = mBoxObjectTable->Iter(); !iter.Done(); iter.Next()) {
 | |
|       nsPIBoxObject* boxObject = iter.UserData();
 | |
|       if (boxObject) {
 | |
|         boxObject->Clear();
 | |
|       }
 | |
|     }
 | |
|     delete mBoxObjectTable;
 | |
|     mBoxObjectTable = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool Document::IsAboutPage() const {
 | |
|   nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   principal->GetURI(getter_AddRefs(uri));
 | |
|   bool isAboutScheme = true;
 | |
|   if (uri) {
 | |
|     uri->SchemeIs("about", &isAboutScheme);
 | |
|   }
 | |
|   return isAboutScheme;
 | |
| }
 | |
| 
 | |
| void Document::ConstructUbiNode(void* storage) {
 | |
|   JS::ubi::Concrete<Document>::construct(storage, this);
 | |
| }
 | |
| 
 | |
| Document::~Document() {
 | |
|   MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
 | |
|   MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
 | |
|              "Can't be top-level and a resource doc at the same time");
 | |
| 
 | |
|   NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
 | |
| 
 | |
|   if (IsTopLevelContentDocument()) {
 | |
|     // don't report for about: pages
 | |
|     if (!IsAboutPage()) {
 | |
|       // Record the page load
 | |
|       uint32_t pageLoaded = 1;
 | |
|       Accumulate(Telemetry::MIXED_CONTENT_UNBLOCK_COUNTER, pageLoaded);
 | |
|       // Record the mixed content status of the docshell in Telemetry
 | |
|       enum {
 | |
|         NO_MIXED_CONTENT = 0,  // There is no Mixed Content on the page
 | |
|         MIXED_DISPLAY_CONTENT =
 | |
|             1,  // The page attempted to load Mixed Display Content
 | |
|         MIXED_ACTIVE_CONTENT =
 | |
|             2,  // The page attempted to load Mixed Active Content
 | |
|         MIXED_DISPLAY_AND_ACTIVE_CONTENT =
 | |
|             3  // The page attempted to load Mixed Display & Mixed Active
 | |
|                // Content
 | |
|       };
 | |
| 
 | |
|       bool mixedActiveLoaded = GetHasMixedActiveContentLoaded();
 | |
|       bool mixedActiveBlocked = GetHasMixedActiveContentBlocked();
 | |
| 
 | |
|       bool mixedDisplayLoaded = GetHasMixedDisplayContentLoaded();
 | |
|       bool mixedDisplayBlocked = GetHasMixedDisplayContentBlocked();
 | |
| 
 | |
|       bool hasMixedDisplay = (mixedDisplayBlocked || mixedDisplayLoaded);
 | |
|       bool hasMixedActive = (mixedActiveBlocked || mixedActiveLoaded);
 | |
| 
 | |
|       uint32_t mixedContentLevel = NO_MIXED_CONTENT;
 | |
|       if (hasMixedDisplay && hasMixedActive) {
 | |
|         mixedContentLevel = MIXED_DISPLAY_AND_ACTIVE_CONTENT;
 | |
|       } else if (hasMixedActive) {
 | |
|         mixedContentLevel = MIXED_ACTIVE_CONTENT;
 | |
|       } else if (hasMixedDisplay) {
 | |
|         mixedContentLevel = MIXED_DISPLAY_CONTENT;
 | |
|       }
 | |
|       Accumulate(Telemetry::MIXED_CONTENT_PAGE_LOAD, mixedContentLevel);
 | |
| 
 | |
|       // record mixed object subrequest telemetry
 | |
|       if (mHasMixedContentObjectSubrequest) {
 | |
|         /* mixed object subrequest loaded on page*/
 | |
|         Accumulate(Telemetry::MIXED_CONTENT_OBJECT_SUBREQUEST, 1);
 | |
|       } else {
 | |
|         /* no mixed object subrequests loaded on page*/
 | |
|         Accumulate(Telemetry::MIXED_CONTENT_OBJECT_SUBREQUEST, 0);
 | |
|       }
 | |
| 
 | |
|       // record CSP telemetry on this document
 | |
|       if (mHasCSP) {
 | |
|         Accumulate(Telemetry::CSP_DOCUMENTS_COUNT, 1);
 | |
|       }
 | |
|       if (mHasUnsafeInlineCSP) {
 | |
|         Accumulate(Telemetry::CSP_UNSAFE_INLINE_DOCUMENTS_COUNT, 1);
 | |
|       }
 | |
|       if (mHasUnsafeEvalCSP) {
 | |
|         Accumulate(Telemetry::CSP_UNSAFE_EVAL_DOCUMENTS_COUNT, 1);
 | |
|       }
 | |
| 
 | |
|       if (MOZ_UNLIKELY(mMathMLEnabled)) {
 | |
|         ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
 | |
|       }
 | |
| 
 | |
|       ScalarAdd(Telemetry::ScalarID::MEDIA_PAGE_COUNT, 1);
 | |
|       if (mDocTreeHadAudibleMedia) {
 | |
|         ScalarAdd(Telemetry::ScalarID::MEDIA_PAGE_HAD_MEDIA_COUNT, 1);
 | |
|       }
 | |
|       if (mDocTreeHadPlayRevoked) {
 | |
|         ScalarAdd(Telemetry::ScalarID::MEDIA_PAGE_HAD_PLAY_REVOKED_COUNT, 1);
 | |
|       }
 | |
| 
 | |
|       if (IsHTMLDocument()) {
 | |
|         switch (GetCompatibilityMode()) {
 | |
|           case eCompatibility_FullStandards:
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_QUIRKS_MODE::FullStandards);
 | |
|             break;
 | |
|           case eCompatibility_AlmostStandards:
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
 | |
|             break;
 | |
|           case eCompatibility_NavQuirks:
 | |
|             Telemetry::AccumulateCategorical(
 | |
|                 Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
 | |
|             break;
 | |
|           default:
 | |
|             MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
 | |
|             break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ReportUseCounters();
 | |
| 
 | |
|   mInDestructor = true;
 | |
|   mInUnlinkOrDeletion = true;
 | |
| 
 | |
|   mozilla::DropJSObjects(this);
 | |
| 
 | |
|   // Clear mObservers to keep it in sync with the mutationobserver list
 | |
|   mObservers.Clear();
 | |
| 
 | |
|   mIntersectionObservers.Clear();
 | |
| 
 | |
|   if (mStyleSheetSetList) {
 | |
|     mStyleSheetSetList->Disconnect();
 | |
|   }
 | |
| 
 | |
|   if (mAnimationController) {
 | |
|     mAnimationController->Disconnect();
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(mTimelines.isEmpty());
 | |
| 
 | |
|   mParentDocument = nullptr;
 | |
| 
 | |
|   // Kill the subdocument map, doing this will release its strong
 | |
|   // references, if any.
 | |
|   delete mSubDocuments;
 | |
|   mSubDocuments = nullptr;
 | |
| 
 | |
|   // Destroy link map now so we don't waste time removing
 | |
|   // links one by one
 | |
|   DestroyElementMaps();
 | |
| 
 | |
|   nsAutoScriptBlocker scriptBlocker;
 | |
| 
 | |
|   // Invalidate cached array of child nodes
 | |
|   InvalidateChildNodes();
 | |
| 
 | |
|   // We should not have child nodes when destructor is called,
 | |
|   // since child nodes keep their owner document alive.
 | |
|   MOZ_ASSERT(!HasChildren());
 | |
| 
 | |
|   mCachedRootElement = nullptr;
 | |
| 
 | |
|   for (auto& sheets : mAdditionalSheets) {
 | |
|     for (StyleSheet* sheet : sheets) {
 | |
|       sheet->ClearAssociatedDocumentOrShadowRoot();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mAttrStyleSheet) {
 | |
|     mAttrStyleSheet->SetOwningDocument(nullptr);
 | |
|   }
 | |
| 
 | |
|   if (mListenerManager) {
 | |
|     mListenerManager->Disconnect();
 | |
|     UnsetFlags(NODE_HAS_LISTENERMANAGER);
 | |
|   }
 | |
| 
 | |
|   if (mScriptLoader) {
 | |
|     mScriptLoader->DropDocumentReference();
 | |
|   }
 | |
| 
 | |
|   if (mCSSLoader) {
 | |
|     // Could be null here if Init() failed or if we have been unlinked.
 | |
|     mCSSLoader->DropDocumentReference();
 | |
|   }
 | |
| 
 | |
|   if (mStyleImageLoader) {
 | |
|     mStyleImageLoader->DropDocumentReference();
 | |
|   }
 | |
| 
 | |
|   if (mXULBroadcastManager) {
 | |
|     mXULBroadcastManager->DropDocumentReference();
 | |
|   }
 | |
| 
 | |
|   if (mXULPersist) {
 | |
|     mXULPersist->DropDocumentReference();
 | |
|   }
 | |
| 
 | |
|   delete mHeaderData;
 | |
| 
 | |
|   ClearAllBoxObjects();
 | |
| 
 | |
|   mPendingTitleChangeEvent.Revoke();
 | |
| 
 | |
|   mPlugins.Clear();
 | |
| 
 | |
|   MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
 | |
|              "must not have media query lists left");
 | |
| 
 | |
|   if (mNodeInfoManager) {
 | |
|     mNodeInfoManager->DropDocumentReference();
 | |
|   }
 | |
| 
 | |
|   if (mDocGroup) {
 | |
|     mDocGroup->RemoveDocument(this);
 | |
|   }
 | |
| 
 | |
|   UnlinkOriginalDocumentIfStatic();
 | |
| }
 | |
| 
 | |
| NS_INTERFACE_TABLE_HEAD(Document)
 | |
|   NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
 | |
|   NS_INTERFACE_TABLE_BEGIN
 | |
|     NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
 | |
|     NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
 | |
|     NS_INTERFACE_TABLE_ENTRY(Document, Document)
 | |
|     NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
 | |
|     NS_INTERFACE_TABLE_ENTRY(Document, mozilla::dom::EventTarget)
 | |
|     NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
 | |
|     NS_INTERFACE_TABLE_ENTRY(Document, nsIRadioGroupContainer)
 | |
|     NS_INTERFACE_TABLE_ENTRY(Document, nsIMutationObserver)
 | |
|     NS_INTERFACE_TABLE_ENTRY(Document, nsIApplicationCacheContainer)
 | |
|   NS_INTERFACE_TABLE_END
 | |
|   NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
 | |
| NS_IMETHODIMP_(MozExternalRefCountType)
 | |
| Document::Release() {
 | |
|   MOZ_ASSERT(0 != mRefCnt, "dup release");
 | |
|   NS_ASSERT_OWNINGTHREAD(Document);
 | |
|   nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(Document)::Upcast(this);
 | |
|   bool shouldDelete = false;
 | |
|   nsrefcnt count = mRefCnt.decr(base, &shouldDelete);
 | |
|   NS_LOG_RELEASE(this, count, "Document");
 | |
|   if (count == 0) {
 | |
|     if (mStackRefCnt && !mNeedsReleaseAfterStackRefCntRelease) {
 | |
|       mNeedsReleaseAfterStackRefCntRelease = true;
 | |
|       NS_ADDREF_THIS();
 | |
|       return mRefCnt.get();
 | |
|     }
 | |
|     mRefCnt.incr(base);
 | |
|     nsNodeUtils::LastRelease(this);
 | |
|     mRefCnt.decr(base);
 | |
|     if (shouldDelete) {
 | |
|       mRefCnt.stabilizeForDeletion();
 | |
|       DeleteCycleCollectable();
 | |
|     }
 | |
|   }
 | |
|   return count;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP_(void)
 | |
| Document::DeleteCycleCollectable() { delete this; }
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
 | |
|   if (Element::CanSkip(tmp, aRemovingAllowed)) {
 | |
|     EventListenerManager* elm = tmp->GetExistingListenerManager();
 | |
|     if (elm) {
 | |
|       elm->MarkForCC();
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
| NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
 | |
|   return Element::CanSkipInCC(tmp);
 | |
| NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
 | |
|   return Element::CanSkipThis(tmp);
 | |
| NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
 | |
|   if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
 | |
|     char name[512];
 | |
|     nsAutoCString loadedAsData;
 | |
|     if (tmp->IsLoadedAsData()) {
 | |
|       loadedAsData.AssignLiteral("data");
 | |
|     } else {
 | |
|       loadedAsData.AssignLiteral("normal");
 | |
|     }
 | |
|     uint32_t nsid = tmp->GetDefaultNamespaceID();
 | |
|     nsAutoCString uri;
 | |
|     if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
 | |
|     static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)", "(xhtml)",
 | |
|                                     "(XLink)",  "(XSLT)",  "(XBL)", "(MathML)",
 | |
|                                     "(RDF)",    "(XUL)"};
 | |
|     if (nsid < ArrayLength(kNSURIs)) {
 | |
|       SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
 | |
|                      kNSURIs[nsid], uri.get());
 | |
|     } else {
 | |
|       SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
 | |
|     }
 | |
|     cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
 | |
|   } else {
 | |
|     NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
 | |
|   }
 | |
| 
 | |
|   if (!nsINode::Traverse(tmp, cb)) {
 | |
|     return NS_SUCCESS_INTERRUPTED_TRAVERSE;
 | |
|   }
 | |
| 
 | |
|   if (tmp->mMaybeEndOutermostXBLUpdateRunner) {
 | |
|     // The cached runnable keeps a reference to the document object..
 | |
|     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
 | |
|         cb, "mMaybeEndOutermostXBLUpdateRunner.mObj");
 | |
|     cb.NoteXPCOMChild(ToSupports(tmp));
 | |
|   }
 | |
| 
 | |
|   for (auto iter = tmp->mIdentifierMap.ConstIter(); !iter.Done(); iter.Next()) {
 | |
|     iter.Get()->Traverse(&cb);
 | |
|   }
 | |
| 
 | |
|   tmp->mExternalResourceMap.Traverse(&cb);
 | |
| 
 | |
|   // Traverse all Document pointer members.
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
 | |
| 
 | |
|   // Traverse all Document nsCOMPtrs.
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
 | |
| 
 | |
|   DocumentOrShadowRoot::Traverse(tmp, cb);
 | |
| 
 | |
|   // The boxobject for an element will only exist as long as it's in the
 | |
|   // document, so we'll traverse the table here instead of from the element.
 | |
|   if (tmp->mBoxObjectTable) {
 | |
|     for (auto iter = tmp->mBoxObjectTable->Iter(); !iter.Done(); iter.Next()) {
 | |
|       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mBoxObjectTable entry");
 | |
|       cb.NoteXPCOMChild(iter.UserData());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLayoutHistoryState)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStateObjectCached)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
 | |
| 
 | |
|   // Traverse all our nsCOMArrays.
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
 | |
| 
 | |
|   for (uint32_t i = 0; i < tmp->mFrameRequestCallbacks.Length(); ++i) {
 | |
|     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameRequestCallbacks[i]");
 | |
|     cb.NoteXPCOMChild(tmp->mFrameRequestCallbacks[i].mCallback);
 | |
|   }
 | |
| 
 | |
|   // Traverse animation components
 | |
|   if (tmp->mAnimationController) {
 | |
|     tmp->mAnimationController->Traverse(&cb);
 | |
|   }
 | |
| 
 | |
|   if (tmp->mSubDocuments) {
 | |
|     for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
 | |
|       auto entry = static_cast<SubDocMapEntry*>(iter.Get());
 | |
| 
 | |
|       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
 | |
|       cb.NoteXPCOMChild(entry->mKey);
 | |
|       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
 | |
|                                          "mSubDocuments entry->mSubDocument");
 | |
|       cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
 | |
| 
 | |
|   // We own only the items in mDOMMediaQueryLists that have listeners;
 | |
|   // this reference is managed by their AddListener and RemoveListener
 | |
|   // methods.
 | |
|   for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
 | |
|        mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
 | |
|     if (mql->HasListeners() &&
 | |
|         NS_SUCCEEDED(mql->CheckInnerWindowCorrectness())) {
 | |
|       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
 | |
|       cb.NoteXPCOMChild(mql);
 | |
|     }
 | |
|   }
 | |
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Document)
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
 | |
|   tmp->mInUnlinkOrDeletion = true;
 | |
| 
 | |
|   // Clear out our external resources
 | |
|   tmp->mExternalResourceMap.Shutdown();
 | |
| 
 | |
|   nsAutoScriptBlocker scriptBlocker;
 | |
| 
 | |
|   nsINode::Unlink(tmp);
 | |
| 
 | |
|   while (tmp->HasChildren()) {
 | |
|     // Hold a strong ref to the node when we remove it, because we may be
 | |
|     // the last reference to it.
 | |
|     // If this code changes, change the corresponding code in Document's
 | |
|     // unlink impl and ContentUnbinder::UnbindSubtree.
 | |
|     nsCOMPtr<nsIContent> child = tmp->GetLastChild();
 | |
|     tmp->DisconnectChild(child);
 | |
|     child->UnbindFromTree();
 | |
|   }
 | |
| 
 | |
|   tmp->UnlinkOriginalDocumentIfStatic();
 | |
| 
 | |
|   tmp->mCachedRootElement = nullptr;  // Avoid a dangling pointer
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaybeEndOutermostXBLUpdateRunner)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle);
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n);
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
 | |
| 
 | |
|   tmp->mParentDocument = nullptr;
 | |
| 
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
 | |
| 
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
 | |
| 
 | |
|   tmp->ClearAllBoxObjects();
 | |
| 
 | |
|   if (tmp->mListenerManager) {
 | |
|     tmp->mListenerManager->Disconnect();
 | |
|     tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
 | |
|     tmp->mListenerManager = nullptr;
 | |
|   }
 | |
| 
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets)
 | |
| 
 | |
|   if (tmp->mStyleSheetSetList) {
 | |
|     tmp->mStyleSheetSetList->Disconnect();
 | |
|     tmp->mStyleSheetSetList = nullptr;
 | |
|   }
 | |
| 
 | |
|   delete tmp->mSubDocuments;
 | |
|   tmp->mSubDocuments = nullptr;
 | |
| 
 | |
|   tmp->mFrameRequestCallbacks.Clear();
 | |
|   MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
 | |
|                      "How did we get here without our presshell going away "
 | |
|                      "first?");
 | |
| 
 | |
|   DocumentOrShadowRoot::Unlink(tmp);
 | |
| 
 | |
|   // Document has a pretty complex destructor, so we're going to
 | |
|   // assume that *most* cycles you actually want to break somewhere
 | |
|   // else, and not unlink an awful lot here.
 | |
| 
 | |
|   tmp->mIdentifierMap.Clear();
 | |
|   tmp->mExpandoAndGeneration.OwnerUnlinked();
 | |
| 
 | |
|   if (tmp->mAnimationController) {
 | |
|     tmp->mAnimationController->Unlink();
 | |
|   }
 | |
| 
 | |
|   tmp->mPendingTitleChangeEvent.Revoke();
 | |
| 
 | |
|   if (tmp->mCSSLoader) {
 | |
|     tmp->mCSSLoader->DropDocumentReference();
 | |
|     NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
 | |
|   }
 | |
| 
 | |
|   // We own only the items in mDOMMediaQueryLists that have listeners;
 | |
|   // this reference is managed by their AddListener and RemoveListener
 | |
|   // methods.
 | |
|   for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
 | |
|     MediaQueryList* next =
 | |
|         static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
 | |
|     mql->Disconnect();
 | |
|     mql = next;
 | |
|   }
 | |
| 
 | |
|   tmp->mInUnlinkOrDeletion = false;
 | |
| NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 | |
| 
 | |
| nsresult Document::Init() {
 | |
|   if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
 | |
|     return NS_ERROR_ALREADY_INITIALIZED;
 | |
|   }
 | |
| 
 | |
|   // Force initialization.
 | |
|   nsINode::nsSlots* slots = Slots();
 | |
| 
 | |
|   // Prepend self as mutation-observer whether we need it or not (some
 | |
|   // subclasses currently do, other don't). This is because the code in
 | |
|   // nsNodeUtils always notifies the first observer first, expecting the
 | |
|   // first observer to be the document.
 | |
|   slots->mMutationObservers.PrependElementUnlessExists(
 | |
|       static_cast<nsIMutationObserver*>(this));
 | |
| 
 | |
|   mOnloadBlocker = new OnloadBlocker();
 | |
|   mCSSLoader = new mozilla::css::Loader(this);
 | |
|   // Assume we're not quirky, until we know otherwise
 | |
|   mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
 | |
| 
 | |
|   mStyleImageLoader = new mozilla::css::ImageLoader(this);
 | |
| 
 | |
|   mNodeInfoManager = new nsNodeInfoManager();
 | |
|   nsresult rv = mNodeInfoManager->Init(this);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // mNodeInfo keeps NodeInfoManager alive!
 | |
|   mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
 | |
|   NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
 | |
|   MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
 | |
|              "Bad NodeType in aNodeInfo");
 | |
| 
 | |
|   NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
 | |
| 
 | |
|   // If after creation the owner js global is not set for a document
 | |
|   // we use the default compartment for this document, instead of creating
 | |
|   // wrapper in some random compartment when the document is exposed to js
 | |
|   // via some events.
 | |
|   nsCOMPtr<nsIGlobalObject> global =
 | |
|       xpc::NativeGlobal(xpc::PrivilegedJunkScope());
 | |
|   NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
 | |
|   mScopeObject = do_GetWeakReference(global);
 | |
|   MOZ_ASSERT(mScopeObject);
 | |
| 
 | |
|   mScriptLoader = new dom::ScriptLoader(this);
 | |
| 
 | |
|   // we need to create a policy here so getting the policy within
 | |
|   // ::Policy() can *always* return a non null policy
 | |
|   mFeaturePolicy = new FeaturePolicy(this);
 | |
|   mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
 | |
| 
 | |
|   mozilla::HoldJSObjects(this);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void Document::DeleteAllProperties() { PropertyTable().DeleteAllProperties(); }
 | |
| 
 | |
| void Document::DeleteAllPropertiesFor(nsINode* aNode) {
 | |
|   PropertyTable().DeleteAllPropertiesFor(aNode);
 | |
| }
 | |
| 
 | |
| bool Document::IsVisibleConsideringAncestors() const {
 | |
|   const Document* parent = this;
 | |
|   do {
 | |
|     if (!parent->IsVisible()) {
 | |
|       return false;
 | |
|     }
 | |
|   } while ((parent = parent->GetParentDocument()));
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   nsCOMPtr<nsIPrincipal> principal;
 | |
|   if (aChannel) {
 | |
|     // Note: this code is duplicated in XULDocument::StartDocumentLoad and
 | |
|     // nsScriptSecurityManager::GetChannelResultPrincipal.
 | |
|     // Note: this should match nsDocShell::OnLoadingSite
 | |
|     NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
 | |
| 
 | |
|     bool isWyciwyg = false;
 | |
|     uri->SchemeIs("wyciwyg", &isWyciwyg);
 | |
|     if (isWyciwyg) {
 | |
|       nsCOMPtr<nsIURI> cleanURI;
 | |
|       nsresult rv =
 | |
|           nsContentUtils::RemoveWyciwygScheme(uri, getter_AddRefs(cleanURI));
 | |
|       if (NS_SUCCEEDED(rv)) {
 | |
|         uri = cleanURI;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     nsIScriptSecurityManager* securityManager =
 | |
|         nsContentUtils::GetSecurityManager();
 | |
|     if (securityManager) {
 | |
|       securityManager->GetChannelResultPrincipal(aChannel,
 | |
|                                                  getter_AddRefs(principal));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   principal = MaybeDowngradePrincipal(principal);
 | |
| 
 | |
|   ResetToURI(uri, aLoadGroup, principal);
 | |
| 
 | |
|   // Note that, since mTiming does not change during a reset, the
 | |
|   // navigationStart time remains unchanged and therefore any future new
 | |
|   // timeline will have the same global clock time as the old one.
 | |
|   mDocumentTimeline = nullptr;
 | |
| 
 | |
|   nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
 | |
|   if (bag) {
 | |
|     nsCOMPtr<nsIURI> baseURI;
 | |
|     bag->GetPropertyAsInterface(NS_LITERAL_STRING("baseURI"),
 | |
|                                 NS_GET_IID(nsIURI), getter_AddRefs(baseURI));
 | |
|     if (baseURI) {
 | |
|       mDocumentBaseURI = baseURI;
 | |
|       mChromeXHRDocBaseURI = nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mChannel = aChannel;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Determine whether the principal is allowed access to the localization system.
 | |
|  * We don't want the web to ever see this but all our UI including in content
 | |
|  * pages should pass this test.
 | |
|  */
 | |
| bool PrincipalAllowsL10n(nsIPrincipal* principal) {
 | |
|   // The system principal is always allowed.
 | |
|   if (nsContentUtils::IsSystemPrincipal(principal)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   nsresult rv = principal->GetURI(getter_AddRefs(uri));
 | |
|   NS_ENSURE_SUCCESS(rv, false);
 | |
| 
 | |
|   bool hasFlags;
 | |
| 
 | |
|   // Allow access to uris that cannot be loaded by web content.
 | |
|   rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
 | |
|                            &hasFlags);
 | |
|   NS_ENSURE_SUCCESS(rv, false);
 | |
|   if (hasFlags) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // UI resources also get access.
 | |
|   rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
 | |
|                            &hasFlags);
 | |
|   NS_ENSURE_SUCCESS(rv, false);
 | |
|   return hasFlags;
 | |
| }
 | |
| 
 | |
| void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
 | |
|                           nsIPrincipal* aPrincipal) {
 | |
|   MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
 | |
| 
 | |
|   MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
 | |
|           ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
 | |
| 
 | |
|   mSecurityInfo = nullptr;
 | |
| 
 | |
|   nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
 | |
|   if (!aLoadGroup || group != aLoadGroup) {
 | |
|     mDocumentLoadGroup = nullptr;
 | |
|   }
 | |
| 
 | |
|   // Delete references to sub-documents and kill the subdocument map,
 | |
|   // if any. It holds strong references
 | |
|   delete mSubDocuments;
 | |
|   mSubDocuments = nullptr;
 | |
| 
 | |
|   // Destroy link map now so we don't waste time removing
 | |
|   // links one by one
 | |
|   DestroyElementMaps();
 | |
| 
 | |
|   bool oldVal = mInUnlinkOrDeletion;
 | |
|   mInUnlinkOrDeletion = true;
 | |
|   {  // Scope for update
 | |
|     MOZ_AUTO_DOC_UPDATE(this, true);
 | |
| 
 | |
|     // Invalidate cached array of child nodes
 | |
|     InvalidateChildNodes();
 | |
| 
 | |
|     while (HasChildren()) {
 | |
|       nsCOMPtr<nsIContent> content = GetLastChild();
 | |
|       nsIContent* previousSibling = content->GetPreviousSibling();
 | |
|       DisconnectChild(content);
 | |
|       if (content == mCachedRootElement) {
 | |
|         // Immediately clear mCachedRootElement, now that it's been removed
 | |
|         // from mChildren, so that GetRootElement() will stop returning this
 | |
|         // now-stale value.
 | |
|         mCachedRootElement = nullptr;
 | |
|       }
 | |
|       nsNodeUtils::ContentRemoved(this, content, previousSibling);
 | |
|       content->UnbindFromTree();
 | |
|     }
 | |
|     MOZ_ASSERT(!mCachedRootElement,
 | |
|                "After removing all children, there should be no root elem");
 | |
|   }
 | |
|   mInUnlinkOrDeletion = oldVal;
 | |
| 
 | |
|   // Reset our stylesheets
 | |
|   ResetStylesheetsToURI(aURI);
 | |
| 
 | |
|   // Release the listener manager
 | |
|   if (mListenerManager) {
 | |
|     mListenerManager->Disconnect();
 | |
|     mListenerManager = nullptr;
 | |
|   }
 | |
| 
 | |
|   // Release the stylesheets list.
 | |
|   mDOMStyleSheets = nullptr;
 | |
| 
 | |
|   // Release our principal after tearing down the document, rather than before.
 | |
|   // This ensures that, during teardown, the document and the dying window
 | |
|   // (which already nulled out its document pointer and cached the principal)
 | |
|   // have matching principals.
 | |
|   SetPrincipal(nullptr);
 | |
| 
 | |
|   // Clear the original URI so SetDocumentURI sets it.
 | |
|   mOriginalURI = nullptr;
 | |
| 
 | |
|   SetDocumentURI(aURI);
 | |
|   mChromeXHRDocURI = nullptr;
 | |
|   // If mDocumentBaseURI is null, Document::GetBaseURI() returns
 | |
|   // mDocumentURI.
 | |
|   mDocumentBaseURI = nullptr;
 | |
|   mChromeXHRDocBaseURI = nullptr;
 | |
| 
 | |
|   if (aLoadGroup) {
 | |
|     mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
 | |
|     // there was an assertion here that aLoadGroup was not null.  This
 | |
|     // is no longer valid: nsDocShell::SetDocument does not create a
 | |
|     // load group, and it works just fine
 | |
| 
 | |
|     // XXXbz what does "just fine" mean exactly?  And given that there
 | |
|     // is no nsDocShell::SetDocument, what is this talking about?
 | |
| 
 | |
|     if (IsContentDocument()) {
 | |
|       // Inform the associated request context about this load start so
 | |
|       // any of its internal load progress flags gets reset.
 | |
|       nsCOMPtr<nsIRequestContextService> rcsvc =
 | |
|           mozilla::net::RequestContextService::GetOrCreate();
 | |
|       if (rcsvc) {
 | |
|         nsCOMPtr<nsIRequestContext> rc;
 | |
|         rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
 | |
|         if (rc) {
 | |
|           rc->BeginLoad();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mLastModified.Truncate();
 | |
|   // XXXbz I guess we're assuming that the caller will either pass in
 | |
|   // a channel with a useful type or call SetContentType?
 | |
|   SetContentTypeInternal(EmptyCString());
 | |
|   mContentLanguage.Truncate();
 | |
|   mBaseTarget.Truncate();
 | |
|   mReferrer.Truncate();
 | |
| 
 | |
|   mXMLDeclarationBits = 0;
 | |
| 
 | |
|   // Now get our new principal
 | |
|   if (aPrincipal) {
 | |
|     SetPrincipal(aPrincipal);
 | |
|   } else {
 | |
|     nsIScriptSecurityManager* securityManager =
 | |
|         nsContentUtils::GetSecurityManager();
 | |
|     if (securityManager) {
 | |
|       nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
 | |
| 
 | |
|       if (!loadContext && aLoadGroup) {
 | |
|         nsCOMPtr<nsIInterfaceRequestor> cbs;
 | |
|         aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
 | |
|         loadContext = do_GetInterface(cbs);
 | |
|       }
 | |
| 
 | |
|       MOZ_ASSERT(loadContext,
 | |
|                  "must have a load context or pass in an explicit principal");
 | |
| 
 | |
|       nsCOMPtr<nsIPrincipal> principal;
 | |
|       nsresult rv = securityManager->GetLoadContextCodebasePrincipal(
 | |
|           mDocumentURI, loadContext, getter_AddRefs(principal));
 | |
|       if (NS_SUCCEEDED(rv)) {
 | |
|         SetPrincipal(principal);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mFontFaceSet) {
 | |
|     mFontFaceSet->RefreshStandardFontLoadPrincipal();
 | |
|   }
 | |
| 
 | |
|   // Refresh the principal on the realm.
 | |
|   if (nsPIDOMWindowInner* win = GetInnerWindow()) {
 | |
|     nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
 | |
|     nsIPrincipal* aPrincipal) {
 | |
|   if (!aPrincipal) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // We can't load a document with an expanded principal. If we're given one,
 | |
|   // automatically downgrade it to the last principal it subsumes (which is the
 | |
|   // extension principal, in the case of extension content scripts).
 | |
|   auto* basePrin = BasePrincipal::Cast(aPrincipal);
 | |
|   if (basePrin->Is<ExpandedPrincipal>()) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(false,
 | |
|                           "Should never try to create a document with "
 | |
|                           "an expanded principal");
 | |
| 
 | |
|     auto* expanded = basePrin->As<ExpandedPrincipal>();
 | |
|     return do_AddRef(expanded->AllowList().LastElement());
 | |
|   }
 | |
| 
 | |
|   if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
 | |
|     // We basically want the parent document here, but because this is very
 | |
|     // early in the load, GetParentDocument() returns null, so we use the
 | |
|     // docshell hierarchy to get this information instead.
 | |
|     if (mDocumentContainer) {
 | |
|       nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem;
 | |
|       mDocumentContainer->GetParent(getter_AddRefs(parentDocShellItem));
 | |
|       nsCOMPtr<nsIDocShell> parentDocShell =
 | |
|           do_QueryInterface(parentDocShellItem);
 | |
|       if (parentDocShell) {
 | |
|         nsCOMPtr<Document> parentDoc;
 | |
|         parentDoc = parentDocShell->GetDocument();
 | |
|         if (!parentDoc ||
 | |
|             !nsContentUtils::IsSystemPrincipal(parentDoc->NodePrincipal())) {
 | |
|           nsCOMPtr<nsIPrincipal> nullPrincipal =
 | |
|               do_CreateInstance("@mozilla.org/nullprincipal;1");
 | |
|           return nullPrincipal.forget();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   nsCOMPtr<nsIPrincipal> principal(aPrincipal);
 | |
|   return principal.forget();
 | |
| }
 | |
| 
 | |
| void Document::RemoveDocStyleSheetsFromStyleSets() {
 | |
|   // The stylesheets should forget us
 | |
|   for (StyleSheet* sheet : Reversed(mStyleSheets)) {
 | |
|     sheet->ClearAssociatedDocumentOrShadowRoot();
 | |
| 
 | |
|     if (sheet->IsApplicable()) {
 | |
|       nsCOMPtr<nsIPresShell> shell = GetShell();
 | |
|       if (shell) {
 | |
|         shell->StyleSet()->RemoveDocStyleSheet(sheet);
 | |
|       }
 | |
|     }
 | |
|     // XXX Tell observers?
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::RemoveStyleSheetsFromStyleSets(
 | |
|     const nsTArray<RefPtr<StyleSheet>>& aSheets, SheetType aType) {
 | |
|   // The stylesheets should forget us
 | |
|   for (StyleSheet* sheet : Reversed(aSheets)) {
 | |
|     sheet->ClearAssociatedDocumentOrShadowRoot();
 | |
| 
 | |
|     if (sheet->IsApplicable()) {
 | |
|       nsCOMPtr<nsIPresShell> shell = GetShell();
 | |
|       if (shell) {
 | |
|         shell->StyleSet()->RemoveStyleSheet(aType, sheet);
 | |
|       }
 | |
|     }
 | |
|     // XXX Tell observers?
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::ResetStylesheetsToURI(nsIURI* aURI) {
 | |
|   MOZ_ASSERT(aURI);
 | |
| 
 | |
|   if (mStyleSetFilled) {
 | |
|     // Skip removing style sheets from the style set if we know we haven't
 | |
|     // filled the style set.  (This allows us to avoid calling
 | |
|     // GetStyleBackendType() too early.)
 | |
|     RemoveDocStyleSheetsFromStyleSets();
 | |
|     RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAgentSheet],
 | |
|                                    SheetType::Agent);
 | |
|     RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eUserSheet],
 | |
|                                    SheetType::User);
 | |
|     RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAuthorSheet],
 | |
|                                    SheetType::Doc);
 | |
| 
 | |
|     if (nsStyleSheetService* sheetService =
 | |
|             nsStyleSheetService::GetInstance()) {
 | |
|       RemoveStyleSheetsFromStyleSets(*sheetService->AuthorStyleSheets(),
 | |
|                                      SheetType::Doc);
 | |
|     }
 | |
| 
 | |
|     mStyleSetFilled = false;
 | |
|   }
 | |
| 
 | |
|   // Release all the sheets
 | |
|   mStyleSheets.Clear();
 | |
|   for (auto& sheets : mAdditionalSheets) {
 | |
|     sheets.Clear();
 | |
|   }
 | |
| 
 | |
|   // NOTE:  We don't release the catalog sheets.  It doesn't really matter
 | |
|   // now, but it could in the future -- in which case not releasing them
 | |
|   // is probably the right thing to do.
 | |
| 
 | |
|   // Now reset our inline style and attribute sheets.
 | |
|   if (mAttrStyleSheet) {
 | |
|     mAttrStyleSheet->Reset();
 | |
|     mAttrStyleSheet->SetOwningDocument(this);
 | |
|   } else {
 | |
|     mAttrStyleSheet = new nsHTMLStyleSheet(this);
 | |
|   }
 | |
| 
 | |
|   if (!mStyleAttrStyleSheet) {
 | |
|     mStyleAttrStyleSheet = new nsHTMLCSSStyleSheet();
 | |
|   }
 | |
| 
 | |
|   // Now set up our style sets
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     FillStyleSet(shell->StyleSet());
 | |
|     if (shell->StyleSet()->StyleSheetsHaveChanged()) {
 | |
|       shell->ApplicableStylesChanged();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void AppendSheetsToStyleSet(ServoStyleSet* aStyleSet,
 | |
|                                    const nsTArray<RefPtr<StyleSheet>>& aSheets,
 | |
|                                    SheetType aType) {
 | |
|   for (StyleSheet* sheet : Reversed(aSheets)) {
 | |
|     aStyleSet->AppendStyleSheet(aType, sheet);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::FillStyleSet(ServoStyleSet* aStyleSet) {
 | |
|   MOZ_ASSERT(aStyleSet, "Must have a style set");
 | |
|   MOZ_ASSERT(aStyleSet->SheetCount(SheetType::Doc) == 0,
 | |
|              "Style set already has document sheets?");
 | |
| 
 | |
|   MOZ_ASSERT(!mStyleSetFilled);
 | |
| 
 | |
|   for (StyleSheet* sheet : Reversed(mStyleSheets)) {
 | |
|     if (sheet->IsApplicable()) {
 | |
|       aStyleSet->AddDocStyleSheet(sheet, this);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance()) {
 | |
|     nsTArray<RefPtr<StyleSheet>>& sheets = *sheetService->AuthorStyleSheets();
 | |
|     for (StyleSheet* sheet : sheets) {
 | |
|       aStyleSet->AppendStyleSheet(SheetType::Doc, sheet);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAgentSheet],
 | |
|                          SheetType::Agent);
 | |
|   AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eUserSheet],
 | |
|                          SheetType::User);
 | |
|   AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAuthorSheet],
 | |
|                          SheetType::Doc);
 | |
| 
 | |
|   mStyleSetFilled = true;
 | |
| }
 | |
| 
 | |
| static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
 | |
|                                      uint32_t aSandboxFlags,
 | |
|                                      nsIChannel* aChannel) {
 | |
|   // If the document is sandboxed (via the HTML5 iframe sandbox
 | |
|   // attribute) and both the allow-scripts and allow-same-origin
 | |
|   // keywords are supplied, the sandboxed document can call into its
 | |
|   // parent document and remove its sandboxing entirely - we print a
 | |
|   // warning to the web console in this case.
 | |
|   if (aSandboxFlags & SANDBOXED_NAVIGATION &&
 | |
|       !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
 | |
|       !(aSandboxFlags & SANDBOXED_ORIGIN)) {
 | |
|     nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
 | |
|     aDocShell->GetSameTypeParent(getter_AddRefs(parentAsItem));
 | |
|     nsCOMPtr<nsIDocShell> parentDocShell = do_QueryInterface(parentAsItem);
 | |
|     if (!parentDocShell) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Don't warn if our parent is not the top-level document.
 | |
|     nsCOMPtr<nsIDocShellTreeItem> grandParentAsItem;
 | |
|     parentDocShell->GetSameTypeParent(getter_AddRefs(grandParentAsItem));
 | |
|     if (grandParentAsItem) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsIChannel> parentChannel;
 | |
|     parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
 | |
|     if (!parentChannel) {
 | |
|       return;
 | |
|     }
 | |
|     nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
 | |
|     nsCOMPtr<nsIURI> iframeUri;
 | |
|     parentChannel->GetURI(getter_AddRefs(iframeUri));
 | |
|     nsContentUtils::ReportToConsole(
 | |
|         nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Iframe Sandbox"),
 | |
|         parentDocument, nsContentUtils::eSECURITY_PROPERTIES,
 | |
|         "BothAllowScriptsAndSameOriginPresent", nullptr, 0, iframeUri);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool Document::IsSynthesized() {
 | |
|   nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->GetLoadInfo() : nullptr;
 | |
|   return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
 | |
| }
 | |
| 
 | |
| // static
 | |
| bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
 | |
|   nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
 | |
|   return principal && (nsContentUtils::IsSystemPrincipal(principal) ||
 | |
|                        principal->GetIsAddonOrExpandedAddonPrincipal());
 | |
| }
 | |
| 
 | |
| nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
 | |
|                                      nsILoadGroup* aLoadGroup,
 | |
|                                      nsISupports* aContainer,
 | |
|                                      nsIStreamListener** aDocListener,
 | |
|                                      bool aReset, nsIContentSink* aSink) {
 | |
|   if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
 | |
|     nsCOMPtr<nsIURI> uri;
 | |
|     aChannel->GetURI(getter_AddRefs(uri));
 | |
|     MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
 | |
|             ("DOCUMENT %p StartDocumentLoad %s", this,
 | |
|              uri ? uri->GetSpecOrDefault().get() : ""));
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(
 | |
|       NodePrincipal()->GetAppId() != nsIScriptSecurityManager::UNKNOWN_APP_ID,
 | |
|       "Document should never have UNKNOWN_APP_ID");
 | |
| 
 | |
|   MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
 | |
|              "Bad readyState");
 | |
|   SetReadyStateInternal(READYSTATE_LOADING);
 | |
| 
 | |
|   if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
 | |
|     mLoadedAsData = true;
 | |
|     // We need to disable script & style loading in this case.
 | |
|     // We leave them disabled even in EndLoad(), and let anyone
 | |
|     // who puts the document on display to worry about enabling.
 | |
| 
 | |
|     // Do not load/process scripts when loading as data
 | |
|     ScriptLoader()->SetEnabled(false);
 | |
| 
 | |
|     // styles
 | |
|     CSSLoader()->SetEnabled(
 | |
|         false);  // Do not load/process styles when loading as data
 | |
|   } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
 | |
|     // Allow CSS, but not scripts
 | |
|     ScriptLoader()->SetEnabled(false);
 | |
|   }
 | |
| 
 | |
|   mMayStartLayout = false;
 | |
|   MOZ_ASSERT(!mReadyForIdle,
 | |
|              "We should never hit DOMContentLoaded before this point");
 | |
| 
 | |
|   if (aReset) {
 | |
|     Reset(aChannel, aLoadGroup);
 | |
|   }
 | |
| 
 | |
|   nsAutoCString contentType;
 | |
|   nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
 | |
|   if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(
 | |
|                   NS_LITERAL_STRING("contentType"), contentType))) ||
 | |
|       NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
 | |
|     // XXX this is only necessary for viewsource:
 | |
|     nsACString::const_iterator start, end, semicolon;
 | |
|     contentType.BeginReading(start);
 | |
|     contentType.EndReading(end);
 | |
|     semicolon = start;
 | |
|     FindCharInReadable(';', semicolon, end);
 | |
|     SetContentTypeInternal(Substring(start, semicolon));
 | |
|   }
 | |
| 
 | |
|   RetrieveRelevantHeaders(aChannel);
 | |
| 
 | |
|   mChannel = aChannel;
 | |
|   nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
 | |
|   if (inStrmChan) {
 | |
|     bool isSrcdocChannel;
 | |
|     inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
 | |
|     if (isSrcdocChannel) {
 | |
|       mIsSrcdocDocument = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mChannel) {
 | |
|     nsLoadFlags loadFlags;
 | |
|     mChannel->GetLoadFlags(&loadFlags);
 | |
|     bool isDocument = false;
 | |
|     mChannel->GetIsDocument(&isDocument);
 | |
|     if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
 | |
|         IsSynthesized() && XRE_IsContentProcess()) {
 | |
|       ContentChild::UpdateCookieStatus(mChannel);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If this document is being loaded by a docshell, copy its sandbox flags
 | |
|   // to the document, and store the fullscreen enabled flag. These are
 | |
|   // immutable after being set here.
 | |
|   nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
 | |
| 
 | |
|   // If this is an error page, don't inherit sandbox flags from docshell
 | |
|   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
 | |
|   if (docShell && !(loadInfo && loadInfo->GetLoadErrorPage())) {
 | |
|     nsresult rv = docShell->GetSandboxFlags(&mSandboxFlags);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|     WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
 | |
|   }
 | |
| 
 | |
|   // The CSP directive upgrade-insecure-requests not only applies to the
 | |
|   // toplevel document, but also to nested documents. Let's propagate that
 | |
|   // flag from the parent to the nested document.
 | |
|   nsCOMPtr<nsIDocShellTreeItem> treeItem = this->GetDocShell();
 | |
|   if (treeItem) {
 | |
|     nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
 | |
|     treeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
 | |
|     if (sameTypeParent) {
 | |
|       Document* doc = sameTypeParent->GetDocument();
 | |
|       mBlockAllMixedContent = doc->GetBlockAllMixedContent(false);
 | |
|       // if the parent document makes use of block-all-mixed-content
 | |
|       // then subdocument preloads should always be blocked.
 | |
|       mBlockAllMixedContentPreloads =
 | |
|           mBlockAllMixedContent || doc->GetBlockAllMixedContent(true);
 | |
| 
 | |
|       mUpgradeInsecureRequests = doc->GetUpgradeInsecureRequests(false);
 | |
|       // if the parent document makes use of upgrade-insecure-requests
 | |
|       // then subdocument preloads should always be upgraded.
 | |
|       mUpgradeInsecurePreloads =
 | |
|           mUpgradeInsecureRequests || doc->GetUpgradeInsecureRequests(true);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If this is not a data document, set CSP.
 | |
|   if (!mLoadedAsData) {
 | |
|     nsresult rv = InitCSP(aChannel);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   // Initialize FeaturePolicy
 | |
|   nsresult rv = InitFeaturePolicy(aChannel);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // XFO needs to be checked after CSP because it is ignored if
 | |
|   // the CSP defines frame-ancestors.
 | |
|   if (!FramingChecker::CheckFrameOptions(aChannel, docShell, NodePrincipal())) {
 | |
|     MOZ_LOG(gCspPRLog, LogLevel::Debug,
 | |
|             ("XFO doesn't like frame's ancestry, not loading."));
 | |
|     // stop!  ERROR page!
 | |
|     aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
 | |
|   for (uint32_t i = 0; i < aMessages.Length(); ++i) {
 | |
|     nsAutoString messageTag;
 | |
|     aMessages[i]->GetTag(messageTag);
 | |
| 
 | |
|     nsAutoString category;
 | |
|     aMessages[i]->GetCategory(category);
 | |
| 
 | |
|     nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
 | |
|                                     NS_ConvertUTF16toUTF8(category), this,
 | |
|                                     nsContentUtils::eSECURITY_PROPERTIES,
 | |
|                                     NS_ConvertUTF16toUTF8(messageTag).get());
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::ApplySettingsFromCSP(bool aSpeculative) {
 | |
|   nsresult rv = NS_OK;
 | |
|   if (!aSpeculative) {
 | |
|     // 1) apply settings from regular CSP
 | |
|     nsCOMPtr<nsIContentSecurityPolicy> csp;
 | |
|     rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
 | |
|     NS_ENSURE_SUCCESS_VOID(rv);
 | |
|     if (csp) {
 | |
|       // Set up 'block-all-mixed-content' if not already inherited
 | |
|       // from the parent context or set by any other CSP.
 | |
|       if (!mBlockAllMixedContent) {
 | |
|         rv = csp->GetBlockAllMixedContent(&mBlockAllMixedContent);
 | |
|         NS_ENSURE_SUCCESS_VOID(rv);
 | |
|       }
 | |
|       if (!mBlockAllMixedContentPreloads) {
 | |
|         mBlockAllMixedContentPreloads = mBlockAllMixedContent;
 | |
|       }
 | |
| 
 | |
|       // Set up 'upgrade-insecure-requests' if not already inherited
 | |
|       // from the parent context or set by any other CSP.
 | |
|       if (!mUpgradeInsecureRequests) {
 | |
|         rv = csp->GetUpgradeInsecureRequests(&mUpgradeInsecureRequests);
 | |
|         NS_ENSURE_SUCCESS_VOID(rv);
 | |
|       }
 | |
|       if (!mUpgradeInsecurePreloads) {
 | |
|         mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
 | |
|       }
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // 2) apply settings from speculative csp
 | |
|   nsCOMPtr<nsIContentSecurityPolicy> preloadCsp;
 | |
|   rv = NodePrincipal()->GetPreloadCsp(getter_AddRefs(preloadCsp));
 | |
|   NS_ENSURE_SUCCESS_VOID(rv);
 | |
|   if (preloadCsp) {
 | |
|     if (!mBlockAllMixedContentPreloads) {
 | |
|       rv = preloadCsp->GetBlockAllMixedContent(&mBlockAllMixedContentPreloads);
 | |
|       NS_ENSURE_SUCCESS_VOID(rv);
 | |
|     }
 | |
|     if (!mUpgradeInsecurePreloads) {
 | |
|       rv = preloadCsp->GetUpgradeInsecureRequests(&mUpgradeInsecurePreloads);
 | |
|       NS_ENSURE_SUCCESS_VOID(rv);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult Document::InitCSP(nsIChannel* aChannel) {
 | |
|   MOZ_ASSERT(!mScriptGlobalObject,
 | |
|              "CSP must be initialized before mScriptGlobalObject is set!");
 | |
|   if (!StaticPrefs::security_csp_enable()) {
 | |
|     MOZ_LOG(gCspPRLog, LogLevel::Debug,
 | |
|             ("CSP is disabled, skipping CSP init for document %p", this));
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString tCspHeaderValue, tCspROHeaderValue;
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel;
 | |
|   nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (httpChannel) {
 | |
|     Unused << httpChannel->GetResponseHeader(
 | |
|         NS_LITERAL_CSTRING("content-security-policy"), tCspHeaderValue);
 | |
| 
 | |
|     Unused << httpChannel->GetResponseHeader(
 | |
|         NS_LITERAL_CSTRING("content-security-policy-report-only"),
 | |
|         tCspROHeaderValue);
 | |
|   }
 | |
|   NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
 | |
|   NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
 | |
| 
 | |
|   // Check if this is a document from a WebExtension.
 | |
|   nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
 | |
|   auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
 | |
| 
 | |
|   // Check if this is a signed content to apply default CSP.
 | |
|   bool applySignedContentCSP = false;
 | |
|   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
 | |
|   if (loadInfo && loadInfo->GetVerifySignedContent()) {
 | |
|     applySignedContentCSP = true;
 | |
|   }
 | |
| 
 | |
|   // If there's no CSP to apply, go ahead and return early
 | |
|   if (!addonPolicy && !applySignedContentCSP && cspHeaderValue.IsEmpty() &&
 | |
|       cspROHeaderValue.IsEmpty()) {
 | |
|     if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
 | |
|       nsCOMPtr<nsIURI> chanURI;
 | |
|       aChannel->GetURI(getter_AddRefs(chanURI));
 | |
|       nsAutoCString aspec;
 | |
|       chanURI->GetAsciiSpec(aspec);
 | |
|       MOZ_LOG(gCspPRLog, LogLevel::Debug,
 | |
|               ("no CSP for document, %s", aspec.get()));
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   MOZ_LOG(gCspPRLog, LogLevel::Debug,
 | |
|           ("Document is an add-on or CSP header specified %p", this));
 | |
| 
 | |
|   nsCOMPtr<nsIContentSecurityPolicy> csp;
 | |
|   rv = principal->EnsureCSP(this, getter_AddRefs(csp));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // ----- if the doc is an addon, apply its CSP.
 | |
|   if (addonPolicy) {
 | |
|     nsCOMPtr<nsIAddonPolicyService> aps =
 | |
|         do_GetService("@mozilla.org/addons/policy-service;1");
 | |
| 
 | |
|     nsAutoString addonCSP;
 | |
|     Unused << ExtensionPolicyService::GetSingleton().GetBaseCSP(addonCSP);
 | |
|     csp->AppendPolicy(addonCSP, false, false);
 | |
| 
 | |
|     csp->AppendPolicy(addonPolicy->ContentSecurityPolicy(), false, false);
 | |
|   }
 | |
| 
 | |
|   // ----- if the doc is a signed content, apply the default CSP.
 | |
|   // Note that when the content signing becomes a standard, we might have
 | |
|   // to restrict this enforcement to "remote content" only.
 | |
|   if (applySignedContentCSP) {
 | |
|     nsAutoString signedContentCSP;
 | |
|     Preferences::GetString("security.signed_content.CSP.default",
 | |
|                            signedContentCSP);
 | |
|     csp->AppendPolicy(signedContentCSP, false, false);
 | |
|   }
 | |
| 
 | |
|   // ----- if there's a full-strength CSP header, apply it.
 | |
|   if (!cspHeaderValue.IsEmpty()) {
 | |
|     rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   // ----- if there's a report-only CSP header, apply it.
 | |
|   if (!cspROHeaderValue.IsEmpty()) {
 | |
|     rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   // ----- Enforce sandbox policy if supplied in CSP header
 | |
|   // The document may already have some sandbox flags set (e.g. if the document
 | |
|   // is an iframe with the sandbox attribute set). If we have a CSP sandbox
 | |
|   // directive, intersect the CSP sandbox flags with the existing flags. This
 | |
|   // corresponds to the _least_ permissive policy.
 | |
|   uint32_t cspSandboxFlags = SANDBOXED_NONE;
 | |
|   rv = csp->GetCSPSandboxFlags(&cspSandboxFlags);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Probably the iframe sandbox attribute already caused the creation of a
 | |
|   // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
 | |
|   // and no one has been created yet.
 | |
|   bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
 | |
|                               !(mSandboxFlags & SANDBOXED_ORIGIN);
 | |
| 
 | |
|   mSandboxFlags |= cspSandboxFlags;
 | |
| 
 | |
|   if (needNewNullPrincipal) {
 | |
|     principal = NullPrincipal::CreateWithInheritedAttributes(principal);
 | |
|     principal->SetCsp(csp);
 | |
|     SetPrincipal(principal);
 | |
|   }
 | |
| 
 | |
|   // ----- Enforce frame-ancestor policy on any applied policies
 | |
|   nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
 | |
|   if (docShell) {
 | |
|     bool safeAncestry = false;
 | |
| 
 | |
|     // PermitsAncestry sends violation reports when necessary
 | |
|     rv = csp->PermitsAncestry(docShell, &safeAncestry);
 | |
| 
 | |
|     if (NS_FAILED(rv) || !safeAncestry) {
 | |
|       MOZ_LOG(gCspPRLog, LogLevel::Debug,
 | |
|               ("CSP doesn't like frame's ancestry, not loading."));
 | |
|       // stop!  ERROR page!
 | |
|       aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
 | |
|     }
 | |
|   }
 | |
|   ApplySettingsFromCSP(false);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
 | |
|   MOZ_ASSERT(mFeaturePolicy, "we should only call init once");
 | |
| 
 | |
|   mFeaturePolicy->ResetDeclaredPolicy();
 | |
| 
 | |
|   if (!StaticPrefs::dom_security_featurePolicy_enabled()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
 | |
| 
 | |
|   RefPtr<FeaturePolicy> parentPolicy = nullptr;
 | |
|   if (mDocumentContainer) {
 | |
|     nsPIDOMWindowOuter* containerWindow = mDocumentContainer->GetWindow();
 | |
|     if (containerWindow) {
 | |
|       nsCOMPtr<nsINode> node = containerWindow->GetFrameElementInternal();
 | |
|       HTMLIFrameElement* iframe = HTMLIFrameElement::FromNodeOrNull(node);
 | |
|       if (iframe) {
 | |
|         parentPolicy = iframe->Policy();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (parentPolicy) {
 | |
|     // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
 | |
|     mFeaturePolicy->InheritPolicy(parentPolicy);
 | |
|   }
 | |
| 
 | |
|   // We don't want to parse the http Feature-Policy header if this pref is off.
 | |
|   if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel;
 | |
|   nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (!httpChannel) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // query the policy from the header
 | |
|   nsAutoCString value;
 | |
|   rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Feature-Policy"),
 | |
|                                       value);
 | |
|   if (NS_SUCCEEDED(rv)) {
 | |
|     mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
 | |
|                                       NodePrincipal(), nullptr);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void Document::StopDocumentLoad() {
 | |
|   if (mParser) {
 | |
|     mParserAborted = true;
 | |
|     mParser->Terminate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::SetDocumentURI(nsIURI* aURI) {
 | |
|   nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
 | |
|   mDocumentURI = aURI;
 | |
|   nsIURI* newBase = GetDocBaseURI();
 | |
| 
 | |
|   bool equalBases = false;
 | |
|   // Changing just the ref of a URI does not change how relative URIs would
 | |
|   // resolve wrt to it, so we can treat the bases as equal as long as they're
 | |
|   // equal ignoring the ref.
 | |
|   if (oldBase && newBase) {
 | |
|     oldBase->EqualsExceptRef(newBase, &equalBases);
 | |
|   } else {
 | |
|     equalBases = !oldBase && !newBase;
 | |
|   }
 | |
| 
 | |
|   // If this is the first time we're setting the document's URI, set the
 | |
|   // document's original URI.
 | |
|   if (!mOriginalURI) mOriginalURI = mDocumentURI;
 | |
| 
 | |
|   // If changing the document's URI changed the base URI of the document, we
 | |
|   // need to refresh the hrefs of all the links on the page.
 | |
|   if (!equalBases) {
 | |
|     RefreshLinkHrefs();
 | |
|   }
 | |
| 
 | |
|   // Recalculate our base domain
 | |
|   mBaseDomain.Truncate();
 | |
|   ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
 | |
|   if (thirdPartyUtil) {
 | |
|     Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
 | |
|   }
 | |
| 
 | |
|   // Tell our WindowGlobalParent that the document's URI has been changed.
 | |
|   nsPIDOMWindowInner* inner = GetInnerWindow();
 | |
|   WindowGlobalChild* wgc = inner ? inner->GetWindowGlobalChild() : nullptr;
 | |
|   if (wgc) {
 | |
|     Unused << wgc->SendUpdateDocumentURI(mDocumentURI);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void GetFormattedTimeString(PRTime aTime,
 | |
|                                    nsAString& aFormattedTimeString) {
 | |
|   PRExplodedTime prtime;
 | |
|   PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
 | |
|   // "MM/DD/YYYY hh:mm:ss"
 | |
|   char formatedTime[24];
 | |
|   if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
 | |
|                      prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
 | |
|                      prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
 | |
|     CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
 | |
|   } else {
 | |
|     // If we for whatever reason failed to find the last modified time
 | |
|     // (or even the current time), fall back to what NS4.x returned.
 | |
|     aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::GetLastModified(nsAString& aLastModified) const {
 | |
|   if (!mLastModified.IsEmpty()) {
 | |
|     aLastModified.Assign(mLastModified);
 | |
|   } else {
 | |
|     GetFormattedTimeString(PR_Now(), aLastModified);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void IncrementExpandoGeneration(Document& aDoc) {
 | |
|   ++aDoc.mExpandoAndGeneration.generation;
 | |
| }
 | |
| 
 | |
| void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
 | |
|   MOZ_ASSERT(
 | |
|       nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
 | |
|       "Only put elements that need to be exposed as document['name'] in "
 | |
|       "the named table.");
 | |
| 
 | |
|   IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
 | |
| 
 | |
|   // Null for out-of-memory
 | |
|   if (entry) {
 | |
|     if (!entry->HasNameElement() &&
 | |
|         !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
 | |
|       IncrementExpandoGeneration(*this);
 | |
|     }
 | |
|     entry->AddNameElement(this, aElement);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
 | |
|   // Speed up document teardown
 | |
|   if (mIdentifierMap.Count() == 0) return;
 | |
| 
 | |
|   IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
 | |
|   if (!entry)  // Could be false if the element was anonymous, hence never added
 | |
|     return;
 | |
| 
 | |
|   entry->RemoveNameElement(aElement);
 | |
|   if (!entry->HasNameElement() &&
 | |
|       !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
 | |
|     IncrementExpandoGeneration(*this);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
 | |
|   IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
 | |
| 
 | |
|   if (entry) { /* True except on OOM */
 | |
|     if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
 | |
|         !entry->HasNameElement() &&
 | |
|         !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
 | |
|       IncrementExpandoGeneration(*this);
 | |
|     }
 | |
|     entry->AddIdElement(aElement);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
 | |
|   NS_ASSERTION(aId, "huhwhatnow?");
 | |
| 
 | |
|   // Speed up document teardown
 | |
|   if (mIdentifierMap.Count() == 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
 | |
|   if (!entry)  // Can be null for XML elements with changing ids.
 | |
|     return;
 | |
| 
 | |
|   entry->RemoveIdElement(aElement);
 | |
|   if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
 | |
|       !entry->HasNameElement() &&
 | |
|       !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
 | |
|     IncrementExpandoGeneration(*this);
 | |
|   }
 | |
|   if (entry->IsEmpty()) {
 | |
|     mIdentifierMap.RemoveEntry(entry);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::SetPrincipal(nsIPrincipal* aNewPrincipal) {
 | |
|   if (aNewPrincipal && mAllowDNSPrefetch && sDisablePrefetchHTTPSPref) {
 | |
|     nsCOMPtr<nsIURI> uri;
 | |
|     aNewPrincipal->GetURI(getter_AddRefs(uri));
 | |
|     bool isHTTPS;
 | |
|     if (!uri || NS_FAILED(uri->SchemeIs("https", &isHTTPS)) || isHTTPS) {
 | |
|       mAllowDNSPrefetch = false;
 | |
|     }
 | |
|   }
 | |
|   mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   // Validate that the docgroup is set correctly by calling its getter and
 | |
|   // triggering its sanity check.
 | |
|   //
 | |
|   // If we're setting the principal to null, we don't want to perform the check,
 | |
|   // as the document is entering an intermediate state where it does not have a
 | |
|   // principal. It will be given another real principal shortly which we will
 | |
|   // check. It's not unsafe to have a document which has a null principal in the
 | |
|   // same docgroup as another document, so this should not be a problem.
 | |
|   if (aNewPrincipal) {
 | |
|     GetDocGroup();
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| mozilla::dom::DocGroup* Document::GetDocGroup() const {
 | |
| #ifdef DEBUG
 | |
|   // Sanity check that we have an up-to-date and accurate docgroup
 | |
|   if (mDocGroup) {
 | |
|     nsAutoCString docGroupKey;
 | |
| 
 | |
|     // GetKey() can fail, e.g. after the TLD service has shut down.
 | |
|     nsresult rv = mozilla::dom::DocGroup::GetKey(NodePrincipal(), docGroupKey);
 | |
|     if (NS_SUCCEEDED(rv)) {
 | |
|       MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
 | |
|     }
 | |
|     // XXX: Check that the TabGroup is correct as well!
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   return mDocGroup;
 | |
| }
 | |
| 
 | |
| nsresult Document::Dispatch(TaskCategory aCategory,
 | |
|                             already_AddRefed<nsIRunnable>&& aRunnable) {
 | |
|   // Note that this method may be called off the main thread.
 | |
|   if (mDocGroup) {
 | |
|     return mDocGroup->Dispatch(aCategory, std::move(aRunnable));
 | |
|   }
 | |
|   return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
 | |
| }
 | |
| 
 | |
| nsISerialEventTarget* Document::EventTargetFor(TaskCategory aCategory) const {
 | |
|   if (mDocGroup) {
 | |
|     return mDocGroup->EventTargetFor(aCategory);
 | |
|   }
 | |
|   return DispatcherTrait::EventTargetFor(aCategory);
 | |
| }
 | |
| 
 | |
| AbstractThread* Document::AbstractMainThreadFor(
 | |
|     mozilla::TaskCategory aCategory) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   if (mDocGroup) {
 | |
|     return mDocGroup->AbstractMainThreadFor(aCategory);
 | |
|   }
 | |
|   return DispatcherTrait::AbstractMainThreadFor(aCategory);
 | |
| }
 | |
| 
 | |
| void Document::NoteScriptTrackingStatus(const nsACString& aURL,
 | |
|                                         bool aIsTracking) {
 | |
|   if (aIsTracking) {
 | |
|     mTrackingScripts.PutEntry(aURL);
 | |
|   } else {
 | |
|     MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool Document::IsScriptTracking(const nsACString& aURL) const {
 | |
|   return mTrackingScripts.Contains(aURL);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| Document::GetApplicationCache(nsIApplicationCache** aApplicationCache) {
 | |
|   NS_IF_ADDREF(*aApplicationCache = mApplicationCache);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| Document::SetApplicationCache(nsIApplicationCache* aApplicationCache) {
 | |
|   mApplicationCache = aApplicationCache;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void Document::GetContentType(nsAString& aContentType) {
 | |
|   CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
 | |
| }
 | |
| 
 | |
| void Document::SetContentType(const nsAString& aContentType) {
 | |
|   SetContentTypeInternal(NS_ConvertUTF16toUTF8(aContentType));
 | |
| }
 | |
| 
 | |
| bool Document::GetAllowPlugins() {
 | |
|   // First, we ask our docshell if it allows plugins.
 | |
|   nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
 | |
| 
 | |
|   if (docShell) {
 | |
|     bool allowPlugins = false;
 | |
|     docShell->GetAllowPlugins(&allowPlugins);
 | |
|     if (!allowPlugins) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // If the docshell allows plugins, we check whether
 | |
|     // we are sandboxed and plugins should not be allowed.
 | |
|     if (mSandboxFlags & SANDBOXED_PLUGINS) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   FlashClassification classification = DocumentFlashClassification();
 | |
|   if (classification == FlashClassification::Denied) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::InitializeLocalization(nsTArray<nsString>& aResourceIds) {
 | |
|   MOZ_ASSERT(!mDocumentL10n, "mDocumentL10n should not be initialized yet");
 | |
| 
 | |
|   DocumentL10n* l10n = new DocumentL10n(this);
 | |
|   MOZ_ALWAYS_TRUE(l10n->Init(aResourceIds));
 | |
|   mDocumentL10n = l10n;
 | |
| }
 | |
| 
 | |
| DocumentL10n* Document::GetL10n() { return mDocumentL10n; }
 | |
| 
 | |
| bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
 | |
|   nsCOMPtr<nsIPrincipal> callerPrincipal =
 | |
|       nsContentUtils::SubjectPrincipal(aCx);
 | |
|   return PrincipalAllowsL10n(callerPrincipal);
 | |
| }
 | |
| 
 | |
| void Document::LocalizationLinkAdded(Element* aLinkElement) {
 | |
|   if (!PrincipalAllowsL10n(NodePrincipal())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoString href;
 | |
|   aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
 | |
|   // If the link is added after the DocumentL10n instance
 | |
|   // has been initialized, just pass the resource ID to it.
 | |
|   if (mDocumentL10n) {
 | |
|     AutoTArray<nsString, 1> resourceIds;
 | |
|     resourceIds.AppendElement(href);
 | |
|     mDocumentL10n->AddResourceIds(resourceIds);
 | |
|   } else if (mReadyState >= READYSTATE_INTERACTIVE) {
 | |
|     // Otherwise, if the document has already been parsed
 | |
|     // we need to lazily initialize the localization.
 | |
|     AutoTArray<nsString, 1> resourceIds;
 | |
|     resourceIds.AppendElement(href);
 | |
|     InitializeLocalization(resourceIds);
 | |
|     mDocumentL10n->TriggerInitialDocumentTranslation();
 | |
|   } else {
 | |
|     // Otherwise, we're still parsing the document.
 | |
|     // In that case, add it to the pending list. This list
 | |
|     // will be resolved once the end of l10n resource
 | |
|     // container is reached.
 | |
|     mL10nResources.AppendElement(href);
 | |
| 
 | |
|     mPendingInitialTranslation = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::LocalizationLinkRemoved(Element* aLinkElement) {
 | |
|   if (!PrincipalAllowsL10n(NodePrincipal())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoString href;
 | |
|   aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
 | |
|   if (mDocumentL10n) {
 | |
|     AutoTArray<nsString, 1> resourceIds;
 | |
|     resourceIds.AppendElement(href);
 | |
|     uint32_t remaining = mDocumentL10n->RemoveResourceIds(resourceIds);
 | |
|     if (remaining == 0) {
 | |
|       mDocumentL10n = nullptr;
 | |
|     }
 | |
|   } else {
 | |
|     mL10nResources.RemoveElement(href);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * This method should be called once the end of the l10n
 | |
|  * resource container has been parsed.
 | |
|  *
 | |
|  * In XUL this is the end of the first </linkset>,
 | |
|  * In XHTML/HTML this is the end of </head>.
 | |
|  *
 | |
|  * This milestone is used to allow for batch
 | |
|  * localization context I/O and building done
 | |
|  * once when all resources in the document have been
 | |
|  * collected.
 | |
|  */
 | |
| void Document::OnL10nResourceContainerParsed() {
 | |
|   if (!mL10nResources.IsEmpty()) {
 | |
|     InitializeLocalization(mL10nResources);
 | |
|     mL10nResources.Clear();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::TriggerInitialDocumentTranslation() {
 | |
|   // Let's call it again, in case the resource
 | |
|   // container has not been closed, and only
 | |
|   // now we're closing the document.
 | |
|   OnL10nResourceContainerParsed();
 | |
| 
 | |
|   if (mDocumentL10n) {
 | |
|     mDocumentL10n->TriggerInitialDocumentTranslation();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::InitialDocumentTranslationCompleted() {
 | |
|   mPendingInitialTranslation = false;
 | |
| }
 | |
| 
 | |
| bool Document::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   return nsContentUtils::IsSystemCaller(aCx) ||
 | |
|          nsContentUtils::AnimationsAPICoreEnabled();
 | |
| }
 | |
| 
 | |
| bool Document::IsWebAnimationsEnabled(CallerType aCallerType) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   return aCallerType == dom::CallerType::System ||
 | |
|          nsContentUtils::AnimationsAPICoreEnabled();
 | |
| }
 | |
| 
 | |
| bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
 | |
|                                                    JSObject* /*unused*/
 | |
| ) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   return nsContentUtils::IsSystemCaller(aCx) ||
 | |
|          StaticPrefs::dom_animations_api_getAnimations_enabled();
 | |
| }
 | |
| 
 | |
| bool Document::AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
 | |
|                                                         JSObject* /*unused*/
 | |
| ) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   return nsContentUtils::IsSystemCaller(aCx) ||
 | |
|          StaticPrefs::dom_animations_api_implicit_keyframes_enabled();
 | |
| }
 | |
| 
 | |
| bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
 | |
|                                                 JSObject* /*unused*/
 | |
| ) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   return nsContentUtils::IsSystemCaller(aCx) ||
 | |
|          StaticPrefs::dom_animations_api_timelines_enabled();
 | |
| }
 | |
| 
 | |
| DocumentTimeline* Document::Timeline() {
 | |
|   if (!mDocumentTimeline) {
 | |
|     mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
 | |
|   }
 | |
| 
 | |
|   return mDocumentTimeline;
 | |
| }
 | |
| 
 | |
| void Document::GetAnimations(nsTArray<RefPtr<Animation>>& aAnimations) {
 | |
|   // Hold a strong ref for the root element since Element::GetAnimations() calls
 | |
|   // FlushPendingNotifications() which may destroy the element.
 | |
|   RefPtr<Element> root = GetRootElement();
 | |
|   if (!root) {
 | |
|     return;
 | |
|   }
 | |
|   AnimationFilter filter;
 | |
|   filter.mSubtree = true;
 | |
|   root->GetAnimations(filter, aAnimations);
 | |
| }
 | |
| 
 | |
| SVGSVGElement* Document::GetSVGRootElement() const {
 | |
|   Element* root = GetRootElement();
 | |
|   if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return static_cast<SVGSVGElement*>(root);
 | |
| }
 | |
| 
 | |
| /* Return true if the document is in the focused top-level window, and is an
 | |
|  * ancestor of the focused DOMWindow. */
 | |
| bool Document::HasFocus(ErrorResult& rv) const {
 | |
|   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
 | |
|   if (!fm) {
 | |
|     rv.Throw(NS_ERROR_NOT_AVAILABLE);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Is there a focused DOMWindow?
 | |
|   nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
 | |
|   fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
 | |
|   if (!focusedWindow) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsPIDOMWindowOuter* piWindow = nsPIDOMWindowOuter::From(focusedWindow);
 | |
| 
 | |
|   // Are we an ancestor of the focused DOMWindow?
 | |
|   for (Document* currentDoc = piWindow->GetDoc(); currentDoc;
 | |
|        currentDoc = currentDoc->GetParentDocument()) {
 | |
|     if (currentDoc == this) {
 | |
|       // Yes, we are an ancestor
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
 | |
| 
 | |
| void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
 | |
|                         aFocusTime >= mLastFocusTime);
 | |
|   mLastFocusTime = aFocusTime;
 | |
| }
 | |
| 
 | |
| void Document::GetReferrer(nsAString& aReferrer) const {
 | |
|   if (mIsSrcdocDocument && mParentDocument)
 | |
|     mParentDocument->GetReferrer(aReferrer);
 | |
|   else
 | |
|     CopyUTF8toUTF16(mReferrer, aReferrer);
 | |
| }
 | |
| 
 | |
| mozilla::net::ReferrerPolicy Document::GetReferrerPolicy() const {
 | |
|   if (mIsSrcdocDocument && mParentDocument &&
 | |
|       mReferrerPolicy == mozilla::net::RP_Unset) {
 | |
|     return mParentDocument->GetReferrerPolicy();
 | |
|   }
 | |
|   return mReferrerPolicy;
 | |
| }
 | |
| 
 | |
| nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
 | |
|   if (mIsSrcdocDocument) {
 | |
|     nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
 | |
|     if (inStrmChan) {
 | |
|       return inStrmChan->GetSrcdocData(aSrcdocData);
 | |
|     }
 | |
|   }
 | |
|   aSrcdocData = VoidString();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| Nullable<WindowProxyHolder> Document::GetDefaultView() const {
 | |
|   nsPIDOMWindowOuter* win = GetWindow();
 | |
|   if (!win) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return WindowProxyHolder(win->GetBrowsingContext());
 | |
| }
 | |
| 
 | |
| Element* Document::GetActiveElement() {
 | |
|   // Get the focused element.
 | |
|   Element* focusedElement = GetRetargetedFocusedElement();
 | |
|   if (focusedElement) {
 | |
|     return focusedElement;
 | |
|   }
 | |
| 
 | |
|   // No focused element anywhere in this document.  Try to get the BODY.
 | |
|   if (IsHTMLOrXHTML()) {
 | |
|     Element* bodyElement = AsHTMLDocument()->GetBody();
 | |
|     if (bodyElement) {
 | |
|       return bodyElement;
 | |
|     }
 | |
|     // Special case to handle the transition to browser.xhtml where there is
 | |
|     // currently not a body element, but we need to match the XUL behavior.
 | |
|     // This should be removed when bug 1492582 is resolved.
 | |
|     if (nsContentUtils::IsChromeDoc(this)) {
 | |
|       Element* docElement = GetDocumentElement();
 | |
|       if (docElement && docElement->IsXULElement()) {
 | |
|         return docElement;
 | |
|       }
 | |
|     }
 | |
|     // Because of IE compatibility, return null when html document doesn't have
 | |
|     // a body.
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // If we couldn't get a BODY, return the root element.
 | |
|   return GetDocumentElement();
 | |
| }
 | |
| 
 | |
| Element* Document::GetCurrentScript() {
 | |
|   nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
 | |
|   return el;
 | |
| }
 | |
| 
 | |
| void Document::ReleaseCapture() const {
 | |
|   // only release the capture if the caller can access it. This prevents a
 | |
|   // page from stopping a scrollbar grab for example.
 | |
|   nsCOMPtr<nsINode> node = nsIPresShell::GetCapturingContent();
 | |
|   if (node && nsContentUtils::CanCallerAccess(node)) {
 | |
|     nsIPresShell::SetCapturingContent(nullptr, 0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIURI> Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
 | |
|     uri = mChromeXHRDocBaseURI;
 | |
|   } else {
 | |
|     uri = GetDocBaseURI();
 | |
|   }
 | |
| 
 | |
|   return uri.forget();
 | |
| }
 | |
| 
 | |
| void Document::SetBaseURI(nsIURI* aURI) {
 | |
|   if (!aURI && !mDocumentBaseURI) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Don't do anything if the URI wasn't actually changed.
 | |
|   if (aURI && mDocumentBaseURI) {
 | |
|     bool equalBases = false;
 | |
|     mDocumentBaseURI->Equals(aURI, &equalBases);
 | |
|     if (equalBases) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mDocumentBaseURI = aURI;
 | |
|   RefreshLinkHrefs();
 | |
| }
 | |
| 
 | |
| URLExtraData* Document::DefaultStyleAttrURLData() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   nsIURI* baseURI = GetDocBaseURI();
 | |
|   nsIURI* docURI = GetDocumentURI();
 | |
|   nsIPrincipal* principal = NodePrincipal();
 | |
|   mozilla::net::ReferrerPolicy policy = GetReferrerPolicy();
 | |
|   if (!mCachedURLData || mCachedURLData->BaseURI() != baseURI ||
 | |
|       mCachedURLData->GetReferrer() != docURI ||
 | |
|       mCachedURLData->GetReferrerPolicy() != policy ||
 | |
|       mCachedURLData->Principal() != principal) {
 | |
|     mCachedURLData = new URLExtraData(baseURI, docURI, principal, policy);
 | |
|   }
 | |
|   return mCachedURLData;
 | |
| }
 | |
| 
 | |
| void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
 | |
|   if (mCharacterSet != aEncoding) {
 | |
|     mCharacterSet = aEncoding;
 | |
|     mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
 | |
|     RecomputeLanguageFromCharset();
 | |
| 
 | |
|     if (nsPresContext* context = GetPresContext()) {
 | |
|       context->DispatchCharSetChange(aEncoding);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
 | |
|   nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
 | |
| }
 | |
| 
 | |
| void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
 | |
|   aData.Truncate();
 | |
|   const DocHeaderData* data = mHeaderData;
 | |
|   while (data) {
 | |
|     if (data->mField == aHeaderField) {
 | |
|       aData = data->mData;
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     data = data->mNext;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
 | |
|   if (!aHeaderField) {
 | |
|     NS_ERROR("null headerField");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mHeaderData) {
 | |
|     if (!aData.IsEmpty()) {  // don't bother storing empty string
 | |
|       mHeaderData = new DocHeaderData(aHeaderField, aData);
 | |
|     }
 | |
|   } else {
 | |
|     DocHeaderData* data = mHeaderData;
 | |
|     DocHeaderData** lastPtr = &mHeaderData;
 | |
|     bool found = false;
 | |
|     do {  // look for existing and replace
 | |
|       if (data->mField == aHeaderField) {
 | |
|         if (!aData.IsEmpty()) {
 | |
|           data->mData.Assign(aData);
 | |
|         } else {  // don't store empty string
 | |
|           *lastPtr = data->mNext;
 | |
|           data->mNext = nullptr;
 | |
|           delete data;
 | |
|         }
 | |
|         found = true;
 | |
| 
 | |
|         break;
 | |
|       }
 | |
|       lastPtr = &(data->mNext);
 | |
|       data = *lastPtr;
 | |
|     } while (data);
 | |
| 
 | |
|     if (!aData.IsEmpty() && !found) {
 | |
|       // didn't find, append
 | |
|       *lastPtr = new DocHeaderData(aHeaderField, aData);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aHeaderField == nsGkAtoms::headerContentLanguage) {
 | |
|     CopyUTF16toUTF8(aData, mContentLanguage);
 | |
|     ResetLangPrefs();
 | |
|     if (auto* presContext = GetPresContext()) {
 | |
|       presContext->ContentLanguageChanged();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
 | |
|     SetPreferredStyleSheetSet(aData);
 | |
|   }
 | |
| 
 | |
|   if (aHeaderField == nsGkAtoms::refresh) {
 | |
|     // We get into this code before we have a script global yet, so get to
 | |
|     // our container via mDocumentContainer.
 | |
|     nsCOMPtr<nsIRefreshURI> refresher(mDocumentContainer);
 | |
|     if (refresher) {
 | |
|       // Note: using mDocumentURI instead of mBaseURI here, for consistency
 | |
|       // (used to just use the current URI of our webnavigation, but that
 | |
|       // should really be the same thing).  Note that this code can run
 | |
|       // before the current URI of the webnavigation has been updated, so we
 | |
|       // can't assert equality here.
 | |
|       refresher->SetupRefreshURIFromHeader(mDocumentURI, NodePrincipal(),
 | |
|                                            NS_ConvertUTF16toUTF8(aData));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
 | |
|       mAllowDNSPrefetch) {
 | |
|     // Chromium treats any value other than 'on' (case insensitive) as 'off'.
 | |
|     mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
 | |
|   }
 | |
| 
 | |
|   if (aHeaderField == nsGkAtoms::viewport ||
 | |
|       aHeaderField == nsGkAtoms::handheldFriendly ||
 | |
|       aHeaderField == nsGkAtoms::viewport_minimum_scale ||
 | |
|       aHeaderField == nsGkAtoms::viewport_maximum_scale ||
 | |
|       aHeaderField == nsGkAtoms::viewport_initial_scale ||
 | |
|       aHeaderField == nsGkAtoms::viewport_height ||
 | |
|       aHeaderField == nsGkAtoms::viewport_width ||
 | |
|       aHeaderField == nsGkAtoms::viewport_user_scalable) {
 | |
|     mViewportType = Unknown;
 | |
|     mViewportOverflowType = ViewportOverflowType::NoOverflow;
 | |
|   }
 | |
| 
 | |
|   // Referrer policy spec says to ignore any empty referrer policies.
 | |
|   if (aHeaderField == nsGkAtoms::referrer && !aData.IsEmpty()) {
 | |
|     enum mozilla::net::ReferrerPolicy policy =
 | |
|         mozilla::net::ReferrerPolicyFromString(aData);
 | |
|     // If policy is not the empty string, then set element's node document's
 | |
|     // referrer policy to policy
 | |
|     if (policy != mozilla::net::RP_Unset) {
 | |
|       // Referrer policy spec (section 6.1) says that we always use the newest
 | |
|       // referrer policy we find
 | |
|       mReferrerPolicy = policy;
 | |
|       mReferrerPolicySet = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aHeaderField == nsGkAtoms::headerReferrerPolicy && !aData.IsEmpty()) {
 | |
|     enum mozilla::net::ReferrerPolicy policy =
 | |
|         nsContentUtils::GetReferrerPolicyFromHeader(aData);
 | |
|     if (policy != mozilla::net::RP_Unset) {
 | |
|       mReferrerPolicy = policy;
 | |
|       mReferrerPolicySet = true;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
 | |
|                                  NotNull<const Encoding*>& aEncoding,
 | |
|                                  nsHtml5TreeOpExecutor* aExecutor) {
 | |
|   if (aChannel) {
 | |
|     nsAutoCString charsetVal;
 | |
|     nsresult rv = aChannel->GetContentCharset(charsetVal);
 | |
|     if (NS_SUCCEEDED(rv)) {
 | |
|       const Encoding* preferred = Encoding::ForLabel(charsetVal);
 | |
|       if (preferred) {
 | |
|         aEncoding = WrapNotNull(preferred);
 | |
|         aCharsetSource = kCharsetFromChannel;
 | |
|         return;
 | |
|       } else if (aExecutor && !charsetVal.IsEmpty()) {
 | |
|         aExecutor->ComplainAboutBogusProtocolCharset(this);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
 | |
| #ifdef DEBUG
 | |
|   for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
 | |
|     const Element* element = Element::FromNode(node);
 | |
|     if (!element) {
 | |
|       continue;
 | |
|     }
 | |
|     MOZ_ASSERT(!element->HasServoData());
 | |
|     if (nsXBLBinding* binding = element->GetXBLBinding()) {
 | |
|       if (nsXBLBinding* bindingWithContent = binding->GetBindingWithContent()) {
 | |
|         nsIContent* content = bindingWithContent->GetAnonymousContent();
 | |
|         // Need to do this instead of just AssertNoStaleServoDataIn(*content),
 | |
|         // because the parent of the children of the <content> element isn't the
 | |
|         // <content> element, but the bound element, and that confuses
 | |
|         // GetNextNode a lot.
 | |
|         MOZ_ASSERT(!content->AsElement()->HasServoData());
 | |
|         for (nsINode* child = content->GetFirstChild(); child;
 | |
|              child = child->GetNextSibling()) {
 | |
|           AssertNoStaleServoDataIn(*child);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIPresShell> Document::CreateShell(
 | |
|     nsPresContext* aContext, nsViewManager* aViewManager,
 | |
|     UniquePtr<ServoStyleSet> aStyleSet) {
 | |
|   NS_ASSERTION(!mPresShell, "We have a presshell already!");
 | |
| 
 | |
|   NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
 | |
| 
 | |
|   FillStyleSet(aStyleSet.get());
 | |
|   AssertNoStaleServoDataIn(*this);
 | |
| 
 | |
|   RefPtr<PresShell> shell = new PresShell;
 | |
|   // Note: we don't hold a ref to the shell (it holds a ref to us)
 | |
|   mPresShell = shell;
 | |
|   shell->Init(this, aContext, aViewManager, std::move(aStyleSet));
 | |
| 
 | |
|   // Make sure to never paint if we belong to an invisible DocShell.
 | |
|   nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
 | |
|   if (docShell && docShell->IsInvisible()) shell->SetNeverPainting(true);
 | |
| 
 | |
|   MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
 | |
|           ("DOCUMENT %p with PressShell %p and DocShell %p", this, shell.get(),
 | |
|            docShell.get()));
 | |
| 
 | |
|   mExternalResourceMap.ShowViewers();
 | |
| 
 | |
|   UpdateFrameRequestCallbackSchedulingState();
 | |
| 
 | |
|   // Now that we have a shell, we might have @font-face rules (the presence of a
 | |
|   // shell may change which rules apply to us). We don't need to do anything
 | |
|   // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
 | |
|   // is ready to update we'll flush the font set.
 | |
|   MarkUserFontSetDirty();
 | |
| 
 | |
|   return shell.forget();
 | |
| }
 | |
| 
 | |
| void Document::UpdateFrameRequestCallbackSchedulingState(
 | |
|     nsIPresShell* aOldShell) {
 | |
|   // If the condition for shouldBeScheduled changes to depend on some other
 | |
|   // variable, add UpdateFrameRequestCallbackSchedulingState() calls to the
 | |
|   // places where that variable can change.
 | |
|   bool shouldBeScheduled = mPresShell && IsEventHandlingEnabled() &&
 | |
|                            !mFrameRequestCallbacks.IsEmpty();
 | |
|   if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
 | |
|     // nothing to do
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsIPresShell* presShell = aOldShell ? aOldShell : mPresShell;
 | |
|   MOZ_RELEASE_ASSERT(presShell);
 | |
| 
 | |
|   nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
 | |
|   if (shouldBeScheduled) {
 | |
|     rd->ScheduleFrameRequestCallbacks(this);
 | |
|   } else {
 | |
|     rd->RevokeFrameRequestCallbacks(this);
 | |
|   }
 | |
| 
 | |
|   mFrameRequestCallbacksScheduled = shouldBeScheduled;
 | |
| }
 | |
| 
 | |
| void Document::TakeFrameRequestCallbacks(FrameRequestCallbackList& aCallbacks) {
 | |
|   aCallbacks.AppendElements(mFrameRequestCallbacks);
 | |
|   mFrameRequestCallbacks.Clear();
 | |
|   // No need to manually remove ourselves from the refresh driver; it will
 | |
|   // handle that part.  But we do have to update our state.
 | |
|   mFrameRequestCallbacksScheduled = false;
 | |
| }
 | |
| 
 | |
| bool Document::ShouldThrottleFrameRequests() {
 | |
|   if (mStaticCloneCount > 0) {
 | |
|     // Even if we're not visible, a static clone may be, so run at full speed.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (Hidden()) {
 | |
|     // We're not visible (probably in a background tab or the bf cache).
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (!mPresShell) {
 | |
|     return false;  // Can't do anything smarter.
 | |
|   }
 | |
| 
 | |
|   nsIFrame* frame = mPresShell->GetRootFrame();
 | |
|   if (!frame) {
 | |
|     return false;  // Can't do anything smarter.
 | |
|   }
 | |
| 
 | |
|   nsIFrame* displayRootFrame = nsLayoutUtils::GetDisplayRootFrame(frame);
 | |
|   if (!displayRootFrame) {
 | |
|     return false;  // Can't do anything smarter.
 | |
|   }
 | |
| 
 | |
|   if (!displayRootFrame->DidPaintPresShell(mPresShell)) {
 | |
|     // We didn't get painted during the last paint, so we're not visible.
 | |
|     // Throttle. Note that because we have to paint this document at least
 | |
|     // once to unthrottle it, we will drop one requestAnimationFrame frame
 | |
|     // when a document that previously wasn't visible scrolls into view. This
 | |
|     // is acceptable since it would happen outside the viewport on APZ
 | |
|     // platforms and is unlikely to be human-perceivable on non-APZ platforms.
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // We got painted during the last paint, so run at full speed.
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void Document::DeleteShell() {
 | |
|   mExternalResourceMap.HideViewers();
 | |
|   if (nsPresContext* presContext = mPresShell->GetPresContext()) {
 | |
|     presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
 | |
|   }
 | |
| 
 | |
|   // When our shell goes away, request that all our images be immediately
 | |
|   // discarded, so we don't carry around decoded image data for a document we
 | |
|   // no longer intend to paint.
 | |
|   ImageTracker()->RequestDiscardAll();
 | |
| 
 | |
|   // Now that we no longer have a shell, we need to forget about any FontFace
 | |
|   // objects for @font-face rules that came from the style set. There's no need
 | |
|   // to call EnsureStyleFlush either, the shell is going away anyway, so there's
 | |
|   // no point on it.
 | |
|   MarkUserFontSetDirty();
 | |
| 
 | |
|   nsIPresShell* oldShell = mPresShell;
 | |
|   mPresShell = nullptr;
 | |
|   UpdateFrameRequestCallbackSchedulingState(oldShell);
 | |
|   mStyleSetFilled = false;
 | |
| 
 | |
|   ClearStaleServoData();
 | |
|   AssertNoStaleServoDataIn(*this);
 | |
| }
 | |
| 
 | |
| void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
 | |
|   MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
 | |
| 
 | |
|   if (mPresShell) {
 | |
|     if (aEntry) {
 | |
|       mPresShell->StopObservingRefreshDriver();
 | |
|     } else if (mBFCacheEntry) {
 | |
|       mPresShell->StartObservingRefreshDriver();
 | |
|     }
 | |
|   }
 | |
|   mBFCacheEntry = aEntry;
 | |
| }
 | |
| 
 | |
| static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
 | |
|   SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
 | |
| 
 | |
|   NS_RELEASE(e->mKey);
 | |
|   if (e->mSubDocument) {
 | |
|     e->mSubDocument->SetParentDocument(nullptr);
 | |
|     NS_RELEASE(e->mSubDocument);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
 | |
|   SubDocMapEntry* e =
 | |
|       const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
 | |
| 
 | |
|   e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
 | |
|   NS_ADDREF(e->mKey);
 | |
| 
 | |
|   e->mSubDocument = nullptr;
 | |
| }
 | |
| 
 | |
| nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
 | |
|   NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
 | |
| 
 | |
|   if (!aSubDoc) {
 | |
|     // aSubDoc is nullptr, remove the mapping
 | |
| 
 | |
|     if (mSubDocuments) {
 | |
|       Document* subDoc = GetSubDocumentFor(aElement);
 | |
|       if (subDoc) {
 | |
|         subDoc->SetAllowPaymentRequest(false);
 | |
|       }
 | |
|       mSubDocuments->Remove(aElement);
 | |
|     }
 | |
|   } else {
 | |
|     if (!mSubDocuments) {
 | |
|       // Create a new hashtable
 | |
| 
 | |
|       static const PLDHashTableOps hash_table_ops = {
 | |
|           PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
 | |
|           PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
 | |
| 
 | |
|       mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
 | |
|     }
 | |
| 
 | |
|     // Add a mapping to the hash table
 | |
|     auto entry =
 | |
|         static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
 | |
| 
 | |
|     if (!entry) {
 | |
|       return NS_ERROR_OUT_OF_MEMORY;
 | |
|     }
 | |
| 
 | |
|     if (entry->mSubDocument) {
 | |
|       entry->mSubDocument->SetAllowPaymentRequest(false);
 | |
|       entry->mSubDocument->SetParentDocument(nullptr);
 | |
| 
 | |
|       // Release the old sub document
 | |
|       NS_RELEASE(entry->mSubDocument);
 | |
|     }
 | |
| 
 | |
|     entry->mSubDocument = aSubDoc;
 | |
|     NS_ADDREF(entry->mSubDocument);
 | |
| 
 | |
|     // set allowpaymentrequest for the binding subdocument
 | |
|     if (!mAllowPaymentRequest) {
 | |
|       aSubDoc->SetAllowPaymentRequest(false);
 | |
|     } else {
 | |
|       nsresult rv = nsContentUtils::CheckSameOrigin(aElement, aSubDoc);
 | |
|       if (NS_SUCCEEDED(rv)) {
 | |
|         aSubDoc->SetAllowPaymentRequest(true);
 | |
|       } else {
 | |
|         if (aElement->IsHTMLElement(nsGkAtoms::iframe) &&
 | |
|             aElement->GetBoolAttr(nsGkAtoms::allowpaymentrequest)) {
 | |
|           aSubDoc->SetAllowPaymentRequest(true);
 | |
|         } else {
 | |
|           aSubDoc->SetAllowPaymentRequest(false);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     aSubDoc->SetParentDocument(this);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
 | |
|   if (mSubDocuments && aContent->IsElement()) {
 | |
|     auto entry = static_cast<SubDocMapEntry*>(
 | |
|         mSubDocuments->Search(aContent->AsElement()));
 | |
| 
 | |
|     if (entry) {
 | |
|       return entry->mSubDocument;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| Element* Document::FindContentForSubDocument(Document* aDocument) const {
 | |
|   NS_ENSURE_TRUE(aDocument, nullptr);
 | |
| 
 | |
|   if (!mSubDocuments) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
 | |
|     auto entry = static_cast<SubDocMapEntry*>(iter.Get());
 | |
|     if (entry->mSubDocument == aDocument) {
 | |
|       return entry->mKey;
 | |
|     }
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| bool Document::IsNodeOfType(uint32_t aFlags) const { return false; }
 | |
| 
 | |
| Element* Document::GetRootElement() const {
 | |
|   return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
 | |
|              ? mCachedRootElement
 | |
|              : GetRootElementInternal();
 | |
| }
 | |
| 
 | |
| nsIContent* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
 | |
| 
 | |
| Element* Document::GetRootElementInternal() const {
 | |
|   // We invoke GetRootElement() immediately before the servo traversal, so we
 | |
|   // should always have a cache hit from Servo.
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   // Loop backwards because any non-elements, such as doctypes and PIs
 | |
|   // are likely to appear before the root element.
 | |
|   for (nsIContent* child = GetLastChild(); child;
 | |
|        child = child->GetPreviousSibling()) {
 | |
|     if (Element* element = Element::FromNode(child)) {
 | |
|       const_cast<Document*>(this)->mCachedRootElement = element;
 | |
|       return element;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const_cast<Document*>(this)->mCachedRootElement = nullptr;
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| nsresult Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
 | |
|                                      bool aNotify) {
 | |
|   if (aKid->IsElement() && GetRootElement()) {
 | |
|     NS_WARNING("Inserting root element when we already have one");
 | |
|     return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
 | |
|   }
 | |
| 
 | |
|   return nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify);
 | |
| }
 | |
| 
 | |
| void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
 | |
|   if (aKid->IsElement()) {
 | |
|     // Destroy the link map up front before we mess with the child list.
 | |
|     DestroyElementMaps();
 | |
|   }
 | |
| 
 | |
|   // Preemptively clear mCachedRootElement, since we may be about to remove it
 | |
|   // from our child list, and we don't want to return this maybe-obsolete value
 | |
|   // from any GetRootElement() calls that happen inside of RemoveChildNode().
 | |
|   // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
 | |
|   // GetRootElement() calls until after it's removed the child from mChildren.
 | |
|   // Any call before that point would restore this soon-to-be-obsolete cached
 | |
|   // answer, and our clearing here would be fruitless.)
 | |
|   mCachedRootElement = nullptr;
 | |
|   nsINode::RemoveChildNode(aKid, aNotify);
 | |
|   MOZ_ASSERT(mCachedRootElement != aKid,
 | |
|              "Stale pointer in mCachedRootElement, after we tried to clear it "
 | |
|              "(maybe somebody called GetRootElement() too early?)");
 | |
| }
 | |
| 
 | |
| void Document::AddStyleSheetToStyleSets(StyleSheet* aSheet) {
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     shell->StyleSet()->AddDocStyleSheet(aSheet, this);
 | |
|     shell->ApplicableStylesChanged();
 | |
|   }
 | |
| }
 | |
| 
 | |
| #define DO_STYLESHEET_NOTIFICATION(className, type, memberName, argName) \
 | |
|   do {                                                                   \
 | |
|     className##Init init;                                                \
 | |
|     init.mBubbles = true;                                                \
 | |
|     init.mCancelable = true;                                             \
 | |
|     init.mStylesheet = aSheet;                                           \
 | |
|     init.memberName = argName;                                           \
 | |
|                                                                          \
 | |
|     RefPtr<className> event =                                            \
 | |
|         className::Constructor(this, NS_LITERAL_STRING(type), init);     \
 | |
|     event->SetTrusted(true);                                             \
 | |
|     event->SetTarget(this);                                              \
 | |
|     RefPtr<AsyncEventDispatcher> asyncDispatcher =                       \
 | |
|         new AsyncEventDispatcher(this, event);                           \
 | |
|     asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;     \
 | |
|     asyncDispatcher->PostDOMEvent();                                     \
 | |
|   } while (0);
 | |
| 
 | |
| void Document::NotifyStyleSheetAdded(StyleSheet* aSheet, bool aDocumentSheet) {
 | |
|   if (StyleSheetChangeEventsEnabled()) {
 | |
|     DO_STYLESHEET_NOTIFICATION(StyleSheetChangeEvent, "StyleSheetAdded",
 | |
|                                mDocumentSheet, aDocumentSheet);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::NotifyStyleSheetRemoved(StyleSheet* aSheet,
 | |
|                                        bool aDocumentSheet) {
 | |
|   if (StyleSheetChangeEventsEnabled()) {
 | |
|     DO_STYLESHEET_NOTIFICATION(StyleSheetChangeEvent, "StyleSheetRemoved",
 | |
|                                mDocumentSheet, aDocumentSheet);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::RemoveStyleSheetFromStyleSets(StyleSheet* aSheet) {
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     shell->StyleSet()->RemoveDocStyleSheet(aSheet);
 | |
|     shell->ApplicableStylesChanged();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::RemoveStyleSheet(StyleSheet* aSheet) {
 | |
|   MOZ_ASSERT(aSheet);
 | |
|   RefPtr<StyleSheet> sheet = DocumentOrShadowRoot::RemoveSheet(*aSheet);
 | |
| 
 | |
|   if (!sheet) {
 | |
|     NS_ASSERTION(mInUnlinkOrDeletion, "stylesheet not found");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mIsGoingAway) {
 | |
|     if (sheet->IsApplicable()) {
 | |
|       RemoveStyleSheetFromStyleSets(sheet);
 | |
|     }
 | |
| 
 | |
|     NotifyStyleSheetRemoved(sheet, true);
 | |
|   }
 | |
| 
 | |
|   sheet->ClearAssociatedDocumentOrShadowRoot();
 | |
| }
 | |
| 
 | |
| void Document::UpdateStyleSheets(nsTArray<RefPtr<StyleSheet>>& aOldSheets,
 | |
|                                  nsTArray<RefPtr<StyleSheet>>& aNewSheets) {
 | |
|   // XXX Need to set the sheet on the ownernode, if any
 | |
|   MOZ_ASSERT(aOldSheets.Length() == aNewSheets.Length(),
 | |
|              "The lists must be the same length!");
 | |
|   int32_t count = aOldSheets.Length();
 | |
| 
 | |
|   RefPtr<StyleSheet> oldSheet;
 | |
|   int32_t i;
 | |
|   for (i = 0; i < count; ++i) {
 | |
|     oldSheet = aOldSheets[i];
 | |
| 
 | |
|     // First remove the old sheet.
 | |
|     NS_ASSERTION(oldSheet, "None of the old sheets should be null");
 | |
|     int32_t oldIndex = mStyleSheets.IndexOf(oldSheet);
 | |
|     RemoveStyleSheet(oldSheet);  // This does the right notifications
 | |
| 
 | |
|     // Now put the new one in its place.  If it's null, just ignore it.
 | |
|     StyleSheet* newSheet = aNewSheets[i];
 | |
|     if (newSheet) {
 | |
|       DocumentOrShadowRoot::InsertSheetAt(oldIndex, *newSheet);
 | |
|       if (newSheet->IsApplicable()) {
 | |
|         AddStyleSheetToStyleSets(newSheet);
 | |
|       }
 | |
| 
 | |
|       NotifyStyleSheetAdded(newSheet, true);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
 | |
|   DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
 | |
| 
 | |
|   if (aSheet.IsApplicable()) {
 | |
|     AddStyleSheetToStyleSets(&aSheet);
 | |
|   }
 | |
| 
 | |
|   NotifyStyleSheetAdded(&aSheet, true);
 | |
| }
 | |
| 
 | |
| void Document::SetStyleSheetApplicableState(StyleSheet* aSheet,
 | |
|                                             bool aApplicable) {
 | |
|   MOZ_ASSERT(aSheet, "null arg");
 | |
| 
 | |
|   // If we're actually in the document style sheet list
 | |
|   if (mStyleSheets.IndexOf(aSheet) != mStyleSheets.NoIndex) {
 | |
|     if (aApplicable) {
 | |
|       AddStyleSheetToStyleSets(aSheet);
 | |
|     } else {
 | |
|       RemoveStyleSheetFromStyleSets(aSheet);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (StyleSheetChangeEventsEnabled()) {
 | |
|     DO_STYLESHEET_NOTIFICATION(StyleSheetApplicableStateChangeEvent,
 | |
|                                "StyleSheetApplicableStateChanged", mApplicable,
 | |
|                                aApplicable);
 | |
|   }
 | |
| 
 | |
|   if (!mSSApplicableStateNotificationPending) {
 | |
|     MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
|     nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(
 | |
|         "Document::NotifyStyleSheetApplicableStateChanged", this,
 | |
|         &Document::NotifyStyleSheetApplicableStateChanged);
 | |
|     mSSApplicableStateNotificationPending =
 | |
|         NS_SUCCEEDED(Dispatch(TaskCategory::Other, notification.forget()));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::NotifyStyleSheetApplicableStateChanged() {
 | |
|   mSSApplicableStateNotificationPending = false;
 | |
|   nsCOMPtr<nsIObserverService> observerService =
 | |
|       mozilla::services::GetObserverService();
 | |
|   if (observerService) {
 | |
|     observerService->NotifyObservers(
 | |
|         ToSupports(this), "style-sheet-applicable-state-changed", nullptr);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static SheetType ConvertAdditionalSheetType(
 | |
|     Document::additionalSheetType aType) {
 | |
|   switch (aType) {
 | |
|     case Document::eAgentSheet:
 | |
|       return SheetType::Agent;
 | |
|     case Document::eUserSheet:
 | |
|       return SheetType::User;
 | |
|     case Document::eAuthorSheet:
 | |
|       return SheetType::Doc;
 | |
|     default:
 | |
|       MOZ_ASSERT(false, "wrong type");
 | |
|       // we must return something although this should never happen
 | |
|       return SheetType::Count;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
 | |
|                          nsIURI* aSheetURI) {
 | |
|   for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
 | |
|     bool bEqual;
 | |
|     nsIURI* uri = aSheets[i]->GetSheetURI();
 | |
| 
 | |
|     if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
 | |
|       return i;
 | |
|   }
 | |
| 
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
 | |
|                                             nsIURI* aSheetURI) {
 | |
|   MOZ_ASSERT(aSheetURI, "null arg");
 | |
| 
 | |
|   // Checking if we have loaded this one already.
 | |
|   if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
| 
 | |
|   // Loading the sheet sync.
 | |
|   RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
 | |
| 
 | |
|   css::SheetParsingMode parsingMode;
 | |
|   switch (aType) {
 | |
|     case Document::eAgentSheet:
 | |
|       parsingMode = css::eAgentSheetFeatures;
 | |
|       break;
 | |
| 
 | |
|     case Document::eUserSheet:
 | |
|       parsingMode = css::eUserSheetFeatures;
 | |
|       break;
 | |
| 
 | |
|     case Document::eAuthorSheet:
 | |
|       parsingMode = css::eAuthorSheetFeatures;
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       MOZ_CRASH("impossible value for aType");
 | |
|   }
 | |
| 
 | |
|   RefPtr<StyleSheet> sheet;
 | |
|   nsresult rv = loader->LoadSheetSync(aSheetURI, parsingMode, true, &sheet);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   sheet->SetAssociatedDocumentOrShadowRoot(
 | |
|       this, StyleSheet::OwnedByDocumentOrShadowRoot);
 | |
|   MOZ_ASSERT(sheet->IsApplicable());
 | |
| 
 | |
|   return AddAdditionalStyleSheet(aType, sheet);
 | |
| }
 | |
| 
 | |
| nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
 | |
|                                            StyleSheet* aSheet) {
 | |
|   if (mAdditionalSheets[aType].Contains(aSheet)) return NS_ERROR_INVALID_ARG;
 | |
| 
 | |
|   if (!aSheet->IsApplicable()) return NS_ERROR_INVALID_ARG;
 | |
| 
 | |
|   mAdditionalSheets[aType].AppendElement(aSheet);
 | |
| 
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     SheetType type = ConvertAdditionalSheetType(aType);
 | |
|     shell->StyleSet()->AppendStyleSheet(type, aSheet);
 | |
|     shell->ApplicableStylesChanged();
 | |
|   }
 | |
| 
 | |
|   // Passing false, so documet.styleSheets.length will not be affected by
 | |
|   // these additional sheets.
 | |
|   NotifyStyleSheetAdded(aSheet, false);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
 | |
|                                           nsIURI* aSheetURI) {
 | |
|   MOZ_ASSERT(aSheetURI);
 | |
| 
 | |
|   nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
 | |
| 
 | |
|   int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
 | |
|   if (i >= 0) {
 | |
|     RefPtr<StyleSheet> sheetRef = sheets[i];
 | |
|     sheets.RemoveElementAt(i);
 | |
| 
 | |
|     if (!mIsGoingAway) {
 | |
|       MOZ_ASSERT(sheetRef->IsApplicable());
 | |
|       if (nsIPresShell* shell = GetShell()) {
 | |
|         SheetType type = ConvertAdditionalSheetType(aType);
 | |
|         shell->StyleSet()->RemoveStyleSheet(type, sheetRef);
 | |
|         shell->ApplicableStylesChanged();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Passing false, so documet.styleSheets.length will not be affected by
 | |
|     // these additional sheets.
 | |
|     NotifyStyleSheetRemoved(sheetRef, false);
 | |
|     sheetRef->ClearAssociatedDocumentOrShadowRoot();
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsIGlobalObject* Document::GetScopeObject() const {
 | |
|   nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
 | |
|   return scope;
 | |
| }
 | |
| 
 | |
| void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
 | |
|   mScopeObject = do_GetWeakReference(aGlobal);
 | |
|   if (aGlobal) {
 | |
|     mHasHadScriptHandlingObject = true;
 | |
| 
 | |
|     nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
 | |
|     if (window) {
 | |
|       // We want to get the tabgroup unconditionally, such that we can make
 | |
|       // certain that it is cached in the inner window early enough.
 | |
|       mozilla::dom::TabGroup* tabgroup = window->TabGroup();
 | |
|       // We should already have the principal, and now that we have been added
 | |
|       // to a window, we should be able to join a DocGroup!
 | |
|       nsAutoCString docGroupKey;
 | |
|       nsresult rv =
 | |
|           mozilla::dom::DocGroup::GetKey(NodePrincipal(), docGroupKey);
 | |
|       if (mDocGroup) {
 | |
|         if (NS_SUCCEEDED(rv)) {
 | |
|           MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
 | |
|         }
 | |
|         MOZ_RELEASE_ASSERT(mDocGroup->GetTabGroup() == tabgroup);
 | |
|       } else {
 | |
|         mDocGroup = tabgroup->AddDocument(docGroupKey, this);
 | |
|         MOZ_ASSERT(mDocGroup);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void CheckIfContainsEMEContent(nsISupports* aSupports,
 | |
|                                       void* aContainsEME) {
 | |
|   nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
 | |
|   if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
 | |
|     bool* contains = static_cast<bool*>(aContainsEME);
 | |
|     if (mediaElem->GetMediaKeys()) {
 | |
|       *contains = true;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool Document::ContainsEMEContent() {
 | |
|   bool containsEME = false;
 | |
|   EnumerateActivityObservers(CheckIfContainsEMEContent,
 | |
|                              static_cast<void*>(&containsEME));
 | |
|   return containsEME;
 | |
| }
 | |
| 
 | |
| static void CheckIfContainsMSEContent(nsISupports* aSupports,
 | |
|                                       void* aContainsMSE) {
 | |
|   nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
 | |
|   if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
 | |
|     bool* contains = static_cast<bool*>(aContainsMSE);
 | |
|     RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
 | |
|     if (ms) {
 | |
|       *contains = true;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool Document::ContainsMSEContent() {
 | |
|   bool containsMSE = false;
 | |
|   EnumerateActivityObservers(CheckIfContainsMSEContent,
 | |
|                              static_cast<void*>(&containsMSE));
 | |
|   return containsMSE;
 | |
| }
 | |
| 
 | |
| static void NotifyActivityChanged(nsISupports* aSupports, void* aUnused) {
 | |
|   nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
 | |
|   if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
 | |
|     mediaElem->NotifyOwnerDocumentActivityChanged();
 | |
|   }
 | |
|   nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
 | |
|       do_QueryInterface(aSupports));
 | |
|   if (objectLoadingContent) {
 | |
|     nsObjectLoadingContent* olc =
 | |
|         static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
 | |
|     olc->NotifyOwnerDocumentActivityChanged();
 | |
|   }
 | |
|   nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
 | |
|       do_QueryInterface(aSupports));
 | |
|   if (objectDocumentActivity) {
 | |
|     objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool Document::IsTopLevelWindowInactive() const {
 | |
|   nsCOMPtr<nsIDocShellTreeItem> treeItem = GetDocShell();
 | |
|   if (!treeItem) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDocShellTreeItem> rootItem;
 | |
|   treeItem->GetRootTreeItem(getter_AddRefs(rootItem));
 | |
|   if (!rootItem) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> domWindow = rootItem->GetWindow();
 | |
|   return domWindow && !domWindow->IsActive();
 | |
| }
 | |
| 
 | |
| void Document::SetContainer(nsDocShell* aContainer) {
 | |
|   if (aContainer) {
 | |
|     mDocumentContainer = aContainer;
 | |
|   } else {
 | |
|     mDocumentContainer = WeakPtr<nsDocShell>();
 | |
|   }
 | |
| 
 | |
|   EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 | |
| 
 | |
|   // IsTopLevelWindowInactive depends on the docshell, so
 | |
|   // update the cached value now that it's available.
 | |
|   UpdateDocumentStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
 | |
|   if (!aContainer) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Get the Docshell
 | |
|   if (aContainer->ItemType() == nsIDocShellTreeItem::typeContent) {
 | |
|     // check if same type root
 | |
|     nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
 | |
|     aContainer->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
 | |
|     NS_ASSERTION(
 | |
|         sameTypeRoot,
 | |
|         "No document shell root tree item from document shell tree item!");
 | |
| 
 | |
|     if (sameTypeRoot == aContainer) {
 | |
|       SetIsTopLevelContentDocument(true);
 | |
|     }
 | |
| 
 | |
|     SetIsContentDocument(true);
 | |
|   }
 | |
| 
 | |
|   mAncestorPrincipals = aContainer->AncestorPrincipals();
 | |
|   mAncestorOuterWindowIDs = aContainer->AncestorOuterWindowIDs();
 | |
| }
 | |
| 
 | |
| nsISupports* Document::GetContainer() const {
 | |
|   return static_cast<nsIDocShell*>(mDocumentContainer);
 | |
| }
 | |
| 
 | |
| void Document::SetScriptGlobalObject(
 | |
|     nsIScriptGlobalObject* aScriptGlobalObject) {
 | |
|   MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
 | |
|                  mAnimationController->IsPausedByType(
 | |
|                      SMILTimeContainer::PAUSE_PAGEHIDE |
 | |
|                      SMILTimeContainer::PAUSE_BEGIN),
 | |
|              "Clearing window pointer while animations are unpaused");
 | |
| 
 | |
|   if (mScriptGlobalObject && !aScriptGlobalObject) {
 | |
|     // We're detaching from the window.  We need to grab a pointer to
 | |
|     // our layout history state now.
 | |
|     mLayoutHistoryState = GetLayoutHistoryState();
 | |
| 
 | |
|     // Also make sure to remove our onload blocker now if we haven't done it yet
 | |
|     if (mOnloadBlockCount != 0) {
 | |
|       nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
 | |
|       if (loadGroup) {
 | |
|         loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     ErrorResult error;
 | |
|     if (GetController().isSome()) {
 | |
|       imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this);
 | |
|       if (loader) {
 | |
|         loader->ClearCacheForControlledDocument(this);
 | |
|       }
 | |
| 
 | |
|       // We may become controlled again if this document comes back out
 | |
|       // of bfcache.  Clear our state to allow that to happen.  Only
 | |
|       // clear this flag if we are actually controlled, though, so pages
 | |
|       // that were force reloaded don't become controlled when they
 | |
|       // come out of bfcache.
 | |
|       mMaybeServiceWorkerControlled = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // BlockOnload() might be called before mScriptGlobalObject is set.
 | |
|   // We may need to add the blocker once mScriptGlobalObject is set.
 | |
|   bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
 | |
| 
 | |
|   mScriptGlobalObject = aScriptGlobalObject;
 | |
| 
 | |
|   if (needOnloadBlocker) {
 | |
|     EnsureOnloadBlocker();
 | |
|   }
 | |
| 
 | |
|   UpdateFrameRequestCallbackSchedulingState();
 | |
| 
 | |
|   if (aScriptGlobalObject) {
 | |
|     // Go back to using the docshell for the layout history state
 | |
|     mLayoutHistoryState = nullptr;
 | |
|     SetScopeObject(aScriptGlobalObject);
 | |
|     mHasHadDefaultView = true;
 | |
| #ifdef DEBUG
 | |
|     if (!mWillReparent) {
 | |
|       // We really shouldn't have a wrapper here but if we do we need to make
 | |
|       // sure it has the correct parent.
 | |
|       JSObject* obj = GetWrapperPreserveColor();
 | |
|       if (obj) {
 | |
|         JSObject* newScope = aScriptGlobalObject->GetGlobalJSObject();
 | |
|         NS_ASSERTION(JS::GetNonCCWObjectGlobal(obj) == newScope,
 | |
|                      "Wrong scope, this is really bad!");
 | |
|       }
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     if (mAllowDNSPrefetch) {
 | |
|       nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
 | |
|       if (docShell) {
 | |
| #ifdef DEBUG
 | |
|         nsCOMPtr<nsIWebNavigation> webNav =
 | |
|             do_GetInterface(aScriptGlobalObject);
 | |
|         NS_ASSERTION(SameCOMIdentity(webNav, docShell),
 | |
|                      "Unexpected container or script global?");
 | |
| #endif
 | |
|         bool allowDNSPrefetch;
 | |
|         docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
 | |
|         mAllowDNSPrefetch = allowDNSPrefetch;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // If we are set in a window that is already focused we should remember this
 | |
|     // as the time the document gained focus.
 | |
|     IgnoredErrorResult ignored;
 | |
|     bool focused = HasFocus(ignored);
 | |
|     if (focused) {
 | |
|       SetLastFocusTime(TimeStamp::Now());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Remember the pointer to our window (or lack there of), to avoid
 | |
|   // having to QI every time it's asked for.
 | |
|   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
 | |
|   mWindow = window;
 | |
| 
 | |
|   // Now that we know what our window is, we can flush the CSP errors to the
 | |
|   // Web Console. We are flushing all messages that occured and were stored
 | |
|   // in the queue prior to this point.
 | |
|   nsCOMPtr<nsIContentSecurityPolicy> csp;
 | |
|   NodePrincipal()->GetCsp(getter_AddRefs(csp));
 | |
|   if (csp) {
 | |
|     static_cast<nsCSPContext*>(csp.get())->flushConsoleMessages();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannelInternal> internalChannel =
 | |
|       do_QueryInterface(GetChannel());
 | |
|   if (internalChannel) {
 | |
|     nsCOMArray<nsISecurityConsoleMessage> messages;
 | |
|     DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
 | |
|     MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|     SendToConsole(messages);
 | |
|   }
 | |
| 
 | |
|   // Set our visibility state, but do not fire the event.  This is correct
 | |
|   // because either we're coming out of bfcache (in which case IsVisible() will
 | |
|   // still test false at this point and no state change will happen) or we're
 | |
|   // doing the initial document load and don't want to fire the event for this
 | |
|   // change.
 | |
|   dom::VisibilityState oldState = mVisibilityState;
 | |
|   mVisibilityState = ComputeVisibilityState();
 | |
|   // When the visibility is changed, notify it to observers.
 | |
|   // Some observers need the notification, for example HTMLMediaElement uses
 | |
|   // it to update internal media resource allocation.
 | |
|   // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
 | |
|   // creation are already done before Document::SetScriptGlobalObject() call.
 | |
|   // MediaDecoder decides whether starting decoding is decided based on
 | |
|   // document's visibility. When the MediaDecoder is created,
 | |
|   // Document::SetScriptGlobalObject() is not yet called and document is
 | |
|   // hidden state. Therefore the MediaDecoder decides that decoding is
 | |
|   // not yet necessary. But soon after Document::SetScriptGlobalObject()
 | |
|   // call, the document becomes not hidden. At the time, MediaDecoder needs
 | |
|   // to know it and needs to start updating decoding.
 | |
|   if (oldState != mVisibilityState) {
 | |
|     EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 | |
|   }
 | |
| 
 | |
|   // The global in the template contents owner document should be the same.
 | |
|   if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
 | |
|     mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
 | |
|   }
 | |
| 
 | |
|   if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
 | |
|       mScriptGlobalObject && GetChannel()) {
 | |
|     // If we are shift-reloaded, don't associate with a ServiceWorker.
 | |
|     if (mDocumentContainer->IsForceReloading()) {
 | |
|       NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     mMaybeServiceWorkerControlled = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
 | |
|   MOZ_ASSERT(!mScriptGlobalObject,
 | |
|              "Do not call this when mScriptGlobalObject is set!");
 | |
|   if (mHasHadDefaultView) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
 | |
|       do_QueryReferent(mScopeObject);
 | |
|   nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
 | |
|   if (win) {
 | |
|     nsPIDOMWindowOuter* outer = win->GetOuterWindow();
 | |
|     if (!outer || outer->GetCurrentInnerWindow() != win) {
 | |
|       NS_WARNING("Wrong inner/outer window combination!");
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
|   return scriptHandlingObject;
 | |
| }
 | |
| void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
 | |
|   NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
 | |
|                "Wrong script object!");
 | |
|   if (aScriptObject) {
 | |
|     SetScopeObject(aScriptObject);
 | |
|     mHasHadDefaultView = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsPIDOMWindowOuter* Document::GetWindowInternal() const {
 | |
|   MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
 | |
|   // Let's use mScriptGlobalObject. Even if the document is already removed from
 | |
|   // the docshell, the outer window might be still obtainable from the it.
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> win;
 | |
|   if (mRemovedFromDocShell) {
 | |
|     // The docshell returns the outer window we are done.
 | |
|     nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
 | |
|     if (kungFuDeathGrip) {
 | |
|       win = kungFuDeathGrip->GetWindow();
 | |
|     }
 | |
|   } else {
 | |
|     if (nsCOMPtr<nsPIDOMWindowInner> inner =
 | |
|             do_QueryInterface(mScriptGlobalObject)) {
 | |
|       // mScriptGlobalObject is always the inner window, let's get the outer.
 | |
|       win = inner->GetOuterWindow();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return win;
 | |
| }
 | |
| 
 | |
| bool Document::InternalAllowXULXBL() {
 | |
|   if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
 | |
|     mAllowXULXBL = eTriTrue;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   mAllowXULXBL = eTriFalse;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // Note: We don't hold a reference to the document observer; we assume
 | |
| // that it has a live reference to the document.
 | |
| void Document::AddObserver(nsIDocumentObserver* aObserver) {
 | |
|   NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
 | |
|                "Observer already in the list");
 | |
|   mObservers.AppendElement(aObserver);
 | |
|   AddMutationObserver(aObserver);
 | |
| }
 | |
| 
 | |
| bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
 | |
|   // If we're in the process of destroying the document (and we're
 | |
|   // informing the observers of the destruction), don't remove the
 | |
|   // observers from the list. This is not a big deal, since we
 | |
|   // don't hold a live reference to the observers.
 | |
|   if (!mInDestructor) {
 | |
|     RemoveMutationObserver(aObserver);
 | |
|     return mObservers.RemoveElement(aObserver);
 | |
|   }
 | |
| 
 | |
|   return mObservers.Contains(aObserver);
 | |
| }
 | |
| 
 | |
| void Document::MaybeEndOutermostXBLUpdate() {
 | |
|   // Only call BindingManager()->EndOutermostUpdate() when
 | |
|   // we're not in an update and it is safe to run scripts.
 | |
|   if (mUpdateNestLevel == 0 && mInXBLUpdate) {
 | |
|     if (nsContentUtils::IsSafeToRunScript()) {
 | |
|       mInXBLUpdate = false;
 | |
|       BindingManager()->EndOutermostUpdate();
 | |
|     } else if (!mInDestructor) {
 | |
|       if (!mMaybeEndOutermostXBLUpdateRunner) {
 | |
|         mMaybeEndOutermostXBLUpdateRunner =
 | |
|             NewRunnableMethod("Document::MaybeEndOutermostXBLUpdate", this,
 | |
|                               &Document::MaybeEndOutermostXBLUpdate);
 | |
|       }
 | |
|       nsContentUtils::AddScriptRunner(mMaybeEndOutermostXBLUpdateRunner);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::BeginUpdate() {
 | |
|   // If the document is going away, then it's probably okay to do things to it
 | |
|   // in the wrong DocGroup. We're unlikely to run JS or do anything else
 | |
|   // observable at this point. We reach this point when cycle collecting a
 | |
|   // <link> element and the unlink code removes a style sheet.
 | |
|   //
 | |
|   // TODO(emilio): Style updates are gone, can this happen now?
 | |
|   if (mDocGroup && !mIsGoingAway && !mInUnlinkOrDeletion &&
 | |
|       !mIgnoreDocGroupMismatches) {
 | |
|     mDocGroup->ValidateAccess();
 | |
|   }
 | |
| 
 | |
|   if (mUpdateNestLevel == 0 && !mInXBLUpdate) {
 | |
|     mInXBLUpdate = true;
 | |
|     BindingManager()->BeginOutermostUpdate();
 | |
|   }
 | |
| 
 | |
|   ++mUpdateNestLevel;
 | |
|   nsContentUtils::AddScriptBlocker();
 | |
|   NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
 | |
| }
 | |
| 
 | |
| void Document::EndUpdate() {
 | |
|   NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
 | |
| 
 | |
|   nsContentUtils::RemoveScriptBlocker();
 | |
| 
 | |
|   --mUpdateNestLevel;
 | |
| 
 | |
|   // This set of updates may have created XBL bindings.  Let the
 | |
|   // binding manager know we're done.
 | |
|   MaybeEndOutermostXBLUpdate();
 | |
| 
 | |
|   MaybeInitializeFinalizeFrameLoaders();
 | |
|   if (mXULBroadcastManager) {
 | |
|     mXULBroadcastManager->MaybeBroadcast();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::BeginLoad() {
 | |
|   MOZ_ASSERT(!mDidCallBeginLoad);
 | |
|   mDidCallBeginLoad = true;
 | |
| 
 | |
|   // Block onload here to prevent having to deal with blocking and
 | |
|   // unblocking it while we know the document is loading.
 | |
|   BlockOnload();
 | |
|   mDidFireDOMContentLoaded = false;
 | |
|   BlockDOMContentLoaded();
 | |
| 
 | |
|   if (mScriptLoader) {
 | |
|     mScriptLoader->BeginDeferringScripts();
 | |
|   }
 | |
| 
 | |
|   NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
 | |
| }
 | |
| 
 | |
| void Document::MozSetImageElement(const nsAString& aImageElementId,
 | |
|                                   Element* aElement) {
 | |
|   if (aImageElementId.IsEmpty()) return;
 | |
| 
 | |
|   // Hold a script blocker while calling SetImageElement since that can call
 | |
|   // out to id-observers
 | |
|   nsAutoScriptBlocker scriptBlocker;
 | |
| 
 | |
|   IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
 | |
|   if (entry) {
 | |
|     entry->SetImageElement(aElement);
 | |
|     if (entry->IsEmpty()) {
 | |
|       mIdentifierMap.RemoveEntry(entry);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::DispatchContentLoadedEvents() {
 | |
|   // If you add early returns from this method, make sure you're
 | |
|   // calling UnblockOnload properly.
 | |
| 
 | |
|   // Unpin references to preloaded images
 | |
|   mPreloadingImages.Clear();
 | |
| 
 | |
|   // DOM manipulation after content loaded should not care if the element
 | |
|   // came from the preloader.
 | |
|   mPreloadedPreconnects.Clear();
 | |
| 
 | |
|   if (mTiming) {
 | |
|     mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
 | |
|   }
 | |
| 
 | |
|   // Dispatch observer notification to notify observers document is interactive.
 | |
|   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
 | |
|   if (os) {
 | |
|     nsIPrincipal* principal = NodePrincipal();
 | |
|     os->NotifyObservers(ToSupports(this),
 | |
|                         nsContentUtils::IsSystemPrincipal(principal)
 | |
|                             ? "chrome-document-interactive"
 | |
|                             : "content-document-interactive",
 | |
|                         nullptr);
 | |
|   }
 | |
| 
 | |
|   // Fire a DOM event notifying listeners that this document has been
 | |
|   // loaded (excluding images and other loads initiated by this
 | |
|   // document).
 | |
|   nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
 | |
|                                        NS_LITERAL_STRING("DOMContentLoaded"),
 | |
|                                        CanBubble::eYes, Cancelable::eNo);
 | |
| 
 | |
|   if (auto* const window = GetInnerWindow()) {
 | |
|     const RefPtr<ServiceWorkerContainer> serviceWorker =
 | |
|         window->Navigator()->ServiceWorker();
 | |
| 
 | |
|     // This could cause queued messages from a service worker to get
 | |
|     // dispatched on serviceWorker.
 | |
|     serviceWorker->StartMessages();
 | |
|   }
 | |
| 
 | |
|   if (MayStartLayout()) {
 | |
|     MaybeResolveReadyForIdle();
 | |
|   }
 | |
| 
 | |
|   RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
 | |
|   nsIDocShell* docShell = this->GetDocShell();
 | |
| 
 | |
|   if (timelines && timelines->HasConsumer(docShell)) {
 | |
|     timelines->AddMarkerForDocShell(
 | |
|         docShell,
 | |
|         MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
 | |
|   }
 | |
| 
 | |
|   if (mTiming) {
 | |
|     mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
 | |
|   }
 | |
| 
 | |
|   // If this document is a [i]frame, fire a DOMFrameContentLoaded
 | |
|   // event on all parent documents notifying that the HTML (excluding
 | |
|   // other external files such as images and stylesheets) in a frame
 | |
|   // has finished loading.
 | |
| 
 | |
|   // target_frame is the [i]frame element that will be used as the
 | |
|   // target for the event. It's the [i]frame whose content is done
 | |
|   // loading.
 | |
|   nsCOMPtr<EventTarget> target_frame;
 | |
| 
 | |
|   if (mParentDocument) {
 | |
|     target_frame = mParentDocument->FindContentForSubDocument(this);
 | |
|   }
 | |
| 
 | |
|   if (target_frame) {
 | |
|     nsCOMPtr<Document> parent = mParentDocument;
 | |
|     do {
 | |
|       RefPtr<Event> event;
 | |
|       if (parent) {
 | |
|         IgnoredErrorResult ignored;
 | |
|         event = parent->CreateEvent(NS_LITERAL_STRING("Events"),
 | |
|                                     CallerType::System, ignored);
 | |
|       }
 | |
| 
 | |
|       if (event) {
 | |
|         event->InitEvent(NS_LITERAL_STRING("DOMFrameContentLoaded"), true,
 | |
|                          true);
 | |
| 
 | |
|         event->SetTarget(target_frame);
 | |
|         event->SetTrusted(true);
 | |
| 
 | |
|         // To dispatch this event we must manually call
 | |
|         // EventDispatcher::Dispatch() on the ancestor document since the
 | |
|         // target is not in the same document, so the event would never reach
 | |
|         // the ancestor document if we used the normal event
 | |
|         // dispatching code.
 | |
| 
 | |
|         WidgetEvent* innerEvent = event->WidgetEventPtr();
 | |
|         if (innerEvent) {
 | |
|           nsEventStatus status = nsEventStatus_eIgnore;
 | |
| 
 | |
|           if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
 | |
|             EventDispatcher::Dispatch(ToSupports(parent), context, innerEvent,
 | |
|                                       event, &status);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       parent = parent->GetParentDocument();
 | |
|     } while (parent);
 | |
|   }
 | |
| 
 | |
|   // If the document has a manifest attribute, fire a MozApplicationManifest
 | |
|   // event.
 | |
|   Element* root = GetRootElement();
 | |
|   if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::manifest)) {
 | |
|     nsContentUtils::DispatchChromeEvent(
 | |
|         this, ToSupports(this), NS_LITERAL_STRING("MozApplicationManifest"),
 | |
|         CanBubble::eYes, Cancelable::eYes);
 | |
|   }
 | |
| 
 | |
|   nsPIDOMWindowInner* inner = GetInnerWindow();
 | |
|   if (inner) {
 | |
|     inner->NoteDOMContentLoaded();
 | |
|   }
 | |
| 
 | |
|   // TODO
 | |
|   if (mMaybeServiceWorkerControlled) {
 | |
|     using mozilla::dom::ServiceWorkerManager;
 | |
|     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
 | |
|     if (swm) {
 | |
|       Maybe<ClientInfo> clientInfo = GetClientInfo();
 | |
|       if (clientInfo.isSome()) {
 | |
|         swm->MaybeCheckNavigationUpdate(clientInfo.ref());
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   UnblockOnload(true);
 | |
| }
 | |
| 
 | |
| #if defined(DEBUG) && !defined(ANDROID)
 | |
| // We want to get to a point where all about: pages ship with a CSP. This
 | |
| // assertion ensures that we can not deploy new about: pages without a CSP.
 | |
| // Initially we will whitelist legacy about: pages which not yet have a CSP
 | |
| // attached, but ultimately that whitelist should disappear.
 | |
| // Please note that any about: page should not use inline JS or inline CSS,
 | |
| // and instead should load JS and CSS from an external file (*.js, *.css)
 | |
| // which allows us to apply a strong CSP omitting 'unsafe-inline'. Ideally,
 | |
| // the CSP allows precisely the resources that need to be loaded; but it
 | |
| // should at least be as strong as:
 | |
| // <meta http-equiv="Content-Security-Policy" content="default-src chrome:"/>
 | |
| static void AssertAboutPageHasCSP(nsIURI* aDocumentURI,
 | |
|                                   nsIPrincipal* aPrincipal) {
 | |
|   // Check if we are loading an about: URI at all
 | |
|   bool isAboutURI =
 | |
|       (NS_SUCCEEDED(aDocumentURI->SchemeIs("about", &isAboutURI)) &&
 | |
|        isAboutURI);
 | |
| 
 | |
|   if (!isAboutURI ||
 | |
|       Preferences::GetBool("csp.skip_about_page_has_csp_assert")) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Potentially init the legacy whitelist of about URIs without a CSP.
 | |
|   static StaticAutoPtr<nsTArray<nsCString>> sLegacyAboutPagesWithNoCSP;
 | |
|   if (!sLegacyAboutPagesWithNoCSP ||
 | |
|       Preferences::GetBool("csp.overrule_about_uris_without_csp_whitelist")) {
 | |
|     sLegacyAboutPagesWithNoCSP = new nsTArray<nsCString>();
 | |
|     nsAutoCString legacyAboutPages;
 | |
|     Preferences::GetCString("csp.about_uris_without_csp", legacyAboutPages);
 | |
|     for (const nsACString& hostString : legacyAboutPages.Split(',')) {
 | |
|       // please note that for the actual whitelist we only store the path of
 | |
|       // about: URI. Let's reassemble the full about URI here so we don't
 | |
|       // have to remove query arguments later.
 | |
|       nsCString aboutURI;
 | |
|       aboutURI.AppendLiteral("about:");
 | |
|       aboutURI.Append(hostString);
 | |
|       sLegacyAboutPagesWithNoCSP->AppendElement(aboutURI);
 | |
|     }
 | |
|     ClearOnShutdown(&sLegacyAboutPagesWithNoCSP);
 | |
|   }
 | |
| 
 | |
|   // Check if the about URI is whitelisted
 | |
|   nsAutoCString aboutSpec;
 | |
|   aDocumentURI->GetSpec(aboutSpec);
 | |
|   ToLowerCase(aboutSpec);
 | |
|   for (auto& legacyPageEntry : *sLegacyAboutPagesWithNoCSP) {
 | |
|     // please note that we perform a substring match here on purpose,
 | |
|     // so we don't have to deal and parse out all the query arguments
 | |
|     // the various about pages rely on.
 | |
|     if (aboutSpec.Find(legacyPageEntry) == 0) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContentSecurityPolicy> csp;
 | |
|   aPrincipal->GetCsp(getter_AddRefs(csp));
 | |
|   nsAutoString parsedPolicyStr;
 | |
|   if (csp) {
 | |
|     uint32_t policyCount = 0;
 | |
|     csp->GetPolicyCount(&policyCount);
 | |
|     if (policyCount > 0) {
 | |
|       csp->GetPolicyString(0, parsedPolicyStr);
 | |
|     }
 | |
|   }
 | |
|   if (Preferences::GetBool("csp.overrule_about_uris_without_csp_whitelist")) {
 | |
|     NS_ASSERTION(parsedPolicyStr.Find("default-src") >= 0,
 | |
|                  "about: page must have a CSP");
 | |
|     return;
 | |
|   }
 | |
|   MOZ_ASSERT(parsedPolicyStr.Find("default-src") >= 0,
 | |
|              "about: page must contain a CSP including default-src");
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void Document::EndLoad() {
 | |
| #if defined(DEBUG) && !defined(ANDROID)
 | |
|   // only assert if nothing stopped the load on purpose
 | |
|   if (!mParserAborted) {
 | |
|     AssertAboutPageHasCSP(mDocumentURI, NodePrincipal());
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   // EndLoad may have been called without a matching call to BeginLoad, in the
 | |
|   // case of a failed parse (for example, due to timeout). In such a case, we
 | |
|   // still want to execute part of this code to do appropriate cleanup, but we
 | |
|   // gate part of it because it is intended to match 1-for-1 with calls to
 | |
|   // BeginLoad. We have an explicit flag bit for this purpose, since it's
 | |
|   // complicated and error prone to derive this condition from other related
 | |
|   // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
 | |
| 
 | |
|   // Part 1: Code that always executes to cleanup end of parsing, whether
 | |
|   // that parsing was successful or not.
 | |
| 
 | |
|   // Drop the ref to our parser, if any, but keep hold of the sink so that we
 | |
|   // can flush it from FlushPendingNotifications as needed.  We might have to
 | |
|   // do that to get a StartLayout() to happen.
 | |
|   if (mParser) {
 | |
|     mWeakSink = do_GetWeakReference(mParser->GetContentSink());
 | |
|     mParser = nullptr;
 | |
|   }
 | |
| 
 | |
|   NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
 | |
| 
 | |
|   // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
 | |
| 
 | |
|   if (!mDidCallBeginLoad) {
 | |
|     return;
 | |
|   }
 | |
|   mDidCallBeginLoad = false;
 | |
| 
 | |
|   UnblockDOMContentLoaded();
 | |
| }
 | |
| 
 | |
| void Document::UnblockDOMContentLoaded() {
 | |
|   MOZ_ASSERT(mBlockDOMContentLoaded);
 | |
|   if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
 | |
|           ("DOCUMENT %p UnblockDOMContentLoaded", this));
 | |
| 
 | |
|   mDidFireDOMContentLoaded = true;
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     shell->GetRefreshDriver()->NotifyDOMContentLoaded();
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
 | |
|   if (!mSynchronousDOMContentLoaded) {
 | |
|     MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
|     nsCOMPtr<nsIRunnable> ev =
 | |
|         NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
 | |
|                           &Document::DispatchContentLoadedEvents);
 | |
|     Dispatch(TaskCategory::Other, ev.forget());
 | |
|   } else {
 | |
|     DispatchContentLoadedEvents();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::ContentStateChanged(nsIContent* aContent,
 | |
|                                    EventStates aStateMask) {
 | |
|   MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
 | |
|              "Someone forgot a scriptblocker");
 | |
|   NS_DOCUMENT_NOTIFY_OBSERVERS(ContentStateChanged,
 | |
|                                (this, aContent, aStateMask));
 | |
| }
 | |
| 
 | |
| void Document::DocumentStatesChanged(EventStates aStateMask) {
 | |
|   UpdateDocumentStates(aStateMask);
 | |
|   NS_DOCUMENT_NOTIFY_OBSERVERS(DocumentStatesChanged, (this, aStateMask));
 | |
| }
 | |
| 
 | |
| void Document::StyleRuleChanged(StyleSheet* aSheet, css::Rule* aStyleRule) {
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     shell->ApplicableStylesChanged();
 | |
|   }
 | |
| 
 | |
|   if (!StyleSheetChangeEventsEnabled()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent, "StyleRuleChanged", mRule,
 | |
|                              aStyleRule);
 | |
| }
 | |
| 
 | |
| void Document::StyleRuleAdded(StyleSheet* aSheet, css::Rule* aStyleRule) {
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     shell->ApplicableStylesChanged();
 | |
|   }
 | |
| 
 | |
|   if (!StyleSheetChangeEventsEnabled()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent, "StyleRuleAdded", mRule,
 | |
|                              aStyleRule);
 | |
| }
 | |
| 
 | |
| void Document::StyleRuleRemoved(StyleSheet* aSheet, css::Rule* aStyleRule) {
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     shell->ApplicableStylesChanged();
 | |
|   }
 | |
| 
 | |
|   if (!StyleSheetChangeEventsEnabled()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent, "StyleRuleRemoved", mRule,
 | |
|                              aStyleRule);
 | |
| }
 | |
| 
 | |
| #undef DO_STYLESHEET_NOTIFICATION
 | |
| 
 | |
| static Element* GetCustomContentContainer(nsIPresShell* aShell) {
 | |
|   if (!aShell || !aShell->GetCanvasFrame()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return aShell->GetCanvasFrame()->GetCustomContentContainer();
 | |
| }
 | |
| 
 | |
| static void InsertAnonContentIntoCanvas(AnonymousContent& aAnonContent,
 | |
|                                         nsIPresShell* aShell) {
 | |
|   Element* container = GetCustomContentContainer(aShell);
 | |
|   if (!container) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = container->AppendChildTo(&aAnonContent.ContentNode(), true);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   aShell->GetCanvasFrame()->ShowCustomContentContainer();
 | |
| }
 | |
| 
 | |
| already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
 | |
|     Element& aElement, ErrorResult& aRv) {
 | |
|   nsAutoScriptBlocker scriptBlocker;
 | |
| 
 | |
|   // Clone the node to avoid returning a direct reference.
 | |
|   nsCOMPtr<nsINode> clone = aElement.CloneNode(true, aRv);
 | |
|   if (aRv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   auto anonContent =
 | |
|       MakeRefPtr<AnonymousContent>(clone.forget().downcast<Element>());
 | |
|   mAnonymousContents.AppendElement(anonContent);
 | |
| 
 | |
|   InsertAnonContentIntoCanvas(*anonContent, GetShell());
 | |
| 
 | |
|   return anonContent.forget();
 | |
| }
 | |
| 
 | |
| static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
 | |
|                                         nsIPresShell* aShell) {
 | |
|   RefPtr<Element> container = GetCustomContentContainer(aShell);
 | |
|   if (!container) {
 | |
|     return;
 | |
|   }
 | |
|   container->RemoveChild(aAnonContent.ContentNode(), IgnoreErrors());
 | |
| }
 | |
| 
 | |
| void Document::RemoveAnonymousContent(AnonymousContent& aContent,
 | |
|                                       ErrorResult& aRv) {
 | |
|   nsAutoScriptBlocker scriptBlocker;
 | |
| 
 | |
|   auto index = mAnonymousContents.IndexOf(&aContent);
 | |
|   if (index == mAnonymousContents.NoIndex) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mAnonymousContents.RemoveElementAt(index);
 | |
|   RemoveAnonContentFromCanvas(aContent, GetShell());
 | |
| 
 | |
|   if (mAnonymousContents.IsEmpty() && GetCustomContentContainer(GetShell())) {
 | |
|     GetShell()->GetCanvasFrame()->HideCustomContentContainer();
 | |
|   }
 | |
| }
 | |
| 
 | |
| Element* Document::GetAnonRootIfInAnonymousContentContainer(
 | |
|     nsINode* aNode) const {
 | |
|   if (!aNode->IsInNativeAnonymousSubtree()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsIPresShell* shell = GetShell();
 | |
|   if (!shell || !shell->GetCanvasFrame()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsAutoScriptBlocker scriptBlocker;
 | |
|   nsCOMPtr<Element> customContainer =
 | |
|       shell->GetCanvasFrame()->GetCustomContentContainer();
 | |
|   if (!customContainer) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // An arbitrary number of elements can be inserted as children of the custom
 | |
|   // container frame.  We want the one that was added that contains aNode, so
 | |
|   // we need to keep track of the last child separately using |child| here.
 | |
|   nsINode* child = aNode;
 | |
|   nsINode* parent = aNode->GetParentNode();
 | |
|   while (parent && parent->IsInNativeAnonymousSubtree()) {
 | |
|     if (parent == customContainer) {
 | |
|       return Element::FromNode(child);
 | |
|     }
 | |
|     child = parent;
 | |
|     parent = child->GetParentNode();
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| Maybe<ClientInfo> Document::GetClientInfo() const {
 | |
|   nsPIDOMWindowInner* inner = GetInnerWindow();
 | |
|   if (inner) {
 | |
|     return inner->GetClientInfo();
 | |
|   }
 | |
|   return Maybe<ClientInfo>();
 | |
| }
 | |
| 
 | |
| Maybe<ClientState> Document::GetClientState() const {
 | |
|   nsPIDOMWindowInner* inner = GetInnerWindow();
 | |
|   if (inner) {
 | |
|     return inner->GetClientState();
 | |
|   }
 | |
|   return Maybe<ClientState>();
 | |
| }
 | |
| 
 | |
| Maybe<ServiceWorkerDescriptor> Document::GetController() const {
 | |
|   nsPIDOMWindowInner* inner = GetInnerWindow();
 | |
|   if (inner) {
 | |
|     return inner->GetController();
 | |
|   }
 | |
|   return Maybe<ServiceWorkerDescriptor>();
 | |
| }
 | |
| 
 | |
| //
 | |
| // Document interface
 | |
| //
 | |
| DocumentType* Document::GetDoctype() const {
 | |
|   for (nsIContent* child = GetFirstChild(); child;
 | |
|        child = child->GetNextSibling()) {
 | |
|     if (child->NodeType() == DOCUMENT_TYPE_NODE) {
 | |
|       return static_cast<DocumentType*>(child);
 | |
|     }
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
 | |
|   if (!mDOMImplementation) {
 | |
|     nsCOMPtr<nsIURI> uri;
 | |
|     NS_NewURI(getter_AddRefs(uri), "about:blank");
 | |
|     if (!uri) {
 | |
|       rv.Throw(NS_ERROR_OUT_OF_MEMORY);
 | |
|       return nullptr;
 | |
|     }
 | |
|     bool hasHadScriptObject = true;
 | |
|     nsIScriptGlobalObject* scriptObject =
 | |
|         GetScriptHandlingObject(hasHadScriptObject);
 | |
|     if (!scriptObject && hasHadScriptObject) {
 | |
|       rv.Throw(NS_ERROR_UNEXPECTED);
 | |
|       return nullptr;
 | |
|     }
 | |
|     mDOMImplementation = new DOMImplementation(
 | |
|         this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
 | |
|   }
 | |
| 
 | |
|   return mDOMImplementation;
 | |
| }
 | |
| 
 | |
| bool IsLowercaseASCII(const nsAString& aValue) {
 | |
|   int32_t len = aValue.Length();
 | |
|   for (int32_t i = 0; i < len; ++i) {
 | |
|     char16_t c = aValue[i];
 | |
|     if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| // We only support pseudo-elements with two colons in this function.
 | |
| static CSSPseudoElementType GetPseudoElementType(const nsString& aString,
 | |
|                                                  ErrorResult& aRv) {
 | |
|   MOZ_ASSERT(!aString.IsEmpty(),
 | |
|              "GetPseudoElementType aString should be non-null");
 | |
|   if (aString.Length() <= 2 || aString[0] != ':' || aString[1] != ':') {
 | |
|     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
 | |
|     return CSSPseudoElementType::NotPseudo;
 | |
|   }
 | |
|   RefPtr<nsAtom> pseudo = NS_Atomize(Substring(aString, 1));
 | |
|   return nsCSSPseudoElements::GetPseudoType(pseudo,
 | |
|                                             CSSEnabledState::eInUASheets);
 | |
| }
 | |
| 
 | |
| already_AddRefed<Element> Document::CreateElement(
 | |
|     const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
 | |
|     ErrorResult& rv) {
 | |
|   rv = nsContentUtils::CheckQName(aTagName, false);
 | |
|   if (rv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
 | |
|   nsAutoString lcTagName;
 | |
|   if (needsLowercase) {
 | |
|     nsContentUtils::ASCIIToLower(aTagName, lcTagName);
 | |
|   }
 | |
| 
 | |
|   const nsString* is = nullptr;
 | |
|   CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo;
 | |
|   if (aOptions.IsElementCreationOptions()) {
 | |
|     const ElementCreationOptions& options =
 | |
|         aOptions.GetAsElementCreationOptions();
 | |
| 
 | |
|     if (options.mIs.WasPassed()) {
 | |
|       is = &options.mIs.Value();
 | |
|     }
 | |
| 
 | |
|     // Check 'pseudo' and throw an exception if it's not one allowed
 | |
|     // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
 | |
|     if (options.mPseudo.WasPassed()) {
 | |
|       pseudoType = GetPseudoElementType(options.mPseudo.Value(), rv);
 | |
|       if (rv.Failed() || pseudoType == CSSPseudoElementType::NotPseudo ||
 | |
|           !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(pseudoType)) {
 | |
|         rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 | |
|         return nullptr;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
 | |
|                                     nullptr, mDefaultElementType, is);
 | |
| 
 | |
|   if (pseudoType != CSSPseudoElementType::NotPseudo) {
 | |
|     elem->SetPseudoElementType(pseudoType);
 | |
|   }
 | |
| 
 | |
|   return elem.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<Element> Document::CreateElementNS(
 | |
|     const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
 | |
|     const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
 | |
|   RefPtr<mozilla::dom::NodeInfo> nodeInfo;
 | |
|   rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
 | |
|                                             mNodeInfoManager, ELEMENT_NODE,
 | |
|                                             getter_AddRefs(nodeInfo));
 | |
|   if (rv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   const nsString* is = nullptr;
 | |
|   if (aOptions.IsElementCreationOptions()) {
 | |
|     const ElementCreationOptions& options =
 | |
|         aOptions.GetAsElementCreationOptions();
 | |
|     if (options.mIs.WasPassed()) {
 | |
|       is = &options.mIs.Value();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Element> element;
 | |
|   rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
 | |
|                      NOT_FROM_PARSER, is);
 | |
|   if (rv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return element.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<Element> Document::CreateXULElement(
 | |
|     const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
 | |
|     ErrorResult& aRv) {
 | |
|   aRv = nsContentUtils::CheckQName(aTagName, false);
 | |
|   if (aRv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   const nsString* is = nullptr;
 | |
|   if (aOptions.IsElementCreationOptions()) {
 | |
|     const ElementCreationOptions& options =
 | |
|         aOptions.GetAsElementCreationOptions();
 | |
|     if (options.mIs.WasPassed()) {
 | |
|       is = &options.mIs.Value();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
 | |
|   if (!elem) {
 | |
|     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
 | |
|     return nullptr;
 | |
|   }
 | |
|   return elem.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
 | |
|   RefPtr<nsTextNode> text = new nsTextNode(mNodeInfoManager);
 | |
|   return text.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsTextNode> Document::CreateTextNode(
 | |
|     const nsAString& aData) const {
 | |
|   RefPtr<nsTextNode> text = new nsTextNode(mNodeInfoManager);
 | |
|   // Don't notify; this node is still being created.
 | |
|   text->SetText(aData, false);
 | |
|   return text.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
 | |
|   RefPtr<DocumentFragment> frag = new DocumentFragment(mNodeInfoManager);
 | |
|   return frag.forget();
 | |
| }
 | |
| 
 | |
| // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
 | |
| already_AddRefed<dom::Comment> Document::CreateComment(
 | |
|     const nsAString& aData) const {
 | |
|   RefPtr<dom::Comment> comment = new dom::Comment(mNodeInfoManager);
 | |
| 
 | |
|   // Don't notify; this node is still being created.
 | |
|   comment->SetText(aData, false);
 | |
|   return comment.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<CDATASection> Document::CreateCDATASection(
 | |
|     const nsAString& aData, ErrorResult& rv) {
 | |
|   if (IsHTMLDocument()) {
 | |
|     rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (FindInReadable(NS_LITERAL_STRING("]]>"), aData)) {
 | |
|     rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<CDATASection> cdata = new CDATASection(mNodeInfoManager);
 | |
| 
 | |
|   // Don't notify; this node is still being created.
 | |
|   cdata->SetText(aData, false);
 | |
| 
 | |
|   return cdata.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
 | |
|     const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
 | |
|   nsresult res = nsContentUtils::CheckQName(aTarget, false);
 | |
|   if (NS_FAILED(res)) {
 | |
|     rv.Throw(res);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (FindInReadable(NS_LITERAL_STRING("?>"), aData)) {
 | |
|     rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<ProcessingInstruction> pi =
 | |
|       NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
 | |
| 
 | |
|   return pi.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
 | |
|                                                  ErrorResult& rv) {
 | |
|   if (!mNodeInfoManager) {
 | |
|     rv.Throw(NS_ERROR_NOT_INITIALIZED);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsresult res = nsContentUtils::CheckQName(aName, false);
 | |
|   if (NS_FAILED(res)) {
 | |
|     rv.Throw(res);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsAutoString name;
 | |
|   if (IsHTMLDocument()) {
 | |
|     nsContentUtils::ASCIIToLower(aName, name);
 | |
|   } else {
 | |
|     name = aName;
 | |
|   }
 | |
| 
 | |
|   RefPtr<mozilla::dom::NodeInfo> nodeInfo;
 | |
|   res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
 | |
|                                       ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
 | |
|   if (NS_FAILED(res)) {
 | |
|     rv.Throw(res);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<Attr> attribute = new Attr(nullptr, nodeInfo.forget(), EmptyString());
 | |
|   return attribute.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<Attr> Document::CreateAttributeNS(
 | |
|     const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
 | |
|     ErrorResult& rv) {
 | |
|   RefPtr<mozilla::dom::NodeInfo> nodeInfo;
 | |
|   rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
 | |
|                                             mNodeInfoManager, ATTRIBUTE_NODE,
 | |
|                                             getter_AddRefs(nodeInfo));
 | |
|   if (rv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<Attr> attribute = new Attr(nullptr, nodeInfo.forget(), EmptyString());
 | |
|   return attribute.forget();
 | |
| }
 | |
| 
 | |
| void Document::ResolveScheduledSVGPresAttrs() {
 | |
|   for (auto iter = mLazySVGPresElements.Iter(); !iter.Done(); iter.Next()) {
 | |
|     SVGElement* svg = iter.Get()->GetKey();
 | |
|     svg->UpdateContentDeclarationBlock();
 | |
|   }
 | |
|   mLazySVGPresElements.Clear();
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
 | |
|     const {
 | |
|   RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
 | |
| 
 | |
|   nsTArray<nsWeakPtr> blockedNodes;
 | |
|   blockedNodes = mBlockedNodesByClassifier;
 | |
| 
 | |
|   for (unsigned long i = 0; i < blockedNodes.Length(); i++) {
 | |
|     nsWeakPtr weakNode = blockedNodes[i];
 | |
|     nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode);
 | |
|     // Consider only nodes to which we have managed to get strong references.
 | |
|     // Coping with nullptrs since it's expected for nodes to disappear when
 | |
|     // nobody else is referring to them.
 | |
|     if (node) {
 | |
|       list->AppendElement(node);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return list.forget();
 | |
| }
 | |
| 
 | |
| void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
 | |
|   aSheetSet.Truncate();
 | |
| 
 | |
|   // Look through our sheets, find the selected set title
 | |
|   size_t count = SheetCount();
 | |
|   nsAutoString title;
 | |
|   for (size_t index = 0; index < count; index++) {
 | |
|     StyleSheet* sheet = SheetAt(index);
 | |
|     NS_ASSERTION(sheet, "Null sheet in sheet list!");
 | |
| 
 | |
|     if (sheet->Disabled()) {
 | |
|       // Disabled sheets don't affect the currently selected set
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     sheet->GetTitle(title);
 | |
| 
 | |
|     if (aSheetSet.IsEmpty()) {
 | |
|       aSheetSet = title;
 | |
|     } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
 | |
|       // Sheets from multiple sets enabled; return null string, per spec.
 | |
|       SetDOMStringToNull(aSheetSet);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
 | |
|   if (DOMStringIsNull(aSheetSet)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Must update mLastStyleSheetSet before doing anything else with stylesheets
 | |
|   // or CSSLoaders.
 | |
|   mLastStyleSheetSet = aSheetSet;
 | |
|   EnableStyleSheetsForSetInternal(aSheetSet, true);
 | |
| }
 | |
| 
 | |
| void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
 | |
|   mPreferredStyleSheetSet = aSheetSet;
 | |
|   // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
 | |
|   // spec.
 | |
|   if (DOMStringIsNull(mLastStyleSheetSet)) {
 | |
|     // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
 | |
|     // per spec.  The idea here is that we're changing our preferred set and
 | |
|     // that shouldn't change the value of lastStyleSheetSet.  Also, we're
 | |
|     // using the Internal version so we can update the CSSLoader and not have
 | |
|     // to worry about null strings.
 | |
|     EnableStyleSheetsForSetInternal(aSheetSet, true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| DOMStringList* Document::StyleSheetSets() {
 | |
|   if (!mStyleSheetSetList) {
 | |
|     mStyleSheetSetList = new DOMStyleSheetSetList(this);
 | |
|   }
 | |
|   return mStyleSheetSetList;
 | |
| }
 | |
| 
 | |
| void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
 | |
|   // Per spec, passing in null is a no-op.
 | |
|   if (!DOMStringIsNull(aSheetSet)) {
 | |
|     // Note: must make sure to not change the CSSLoader's preferred sheet --
 | |
|     // that value should be equal to either our lastStyleSheetSet (if that's
 | |
|     // non-null) or to our preferredStyleSheetSet.  And this method doesn't
 | |
|     // change either of those.
 | |
|     EnableStyleSheetsForSetInternal(aSheetSet, false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
 | |
|                                                bool aUpdateCSSLoader) {
 | |
|   size_t count = SheetCount();
 | |
|   nsAutoString title;
 | |
|   for (size_t index = 0; index < count; index++) {
 | |
|     StyleSheet* sheet = SheetAt(index);
 | |
|     NS_ASSERTION(sheet, "Null sheet in sheet list!");
 | |
| 
 | |
|     sheet->GetTitle(title);
 | |
|     if (!title.IsEmpty()) {
 | |
|       sheet->SetEnabled(title.Equals(aSheetSet));
 | |
|     }
 | |
|   }
 | |
|   if (aUpdateCSSLoader) {
 | |
|     CSSLoader()->DocumentStyleSheetSetChanged();
 | |
|   }
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     if (shell->StyleSet()->StyleSheetsHaveChanged()) {
 | |
|       shell->ApplicableStylesChanged();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::GetCharacterSet(nsAString& aCharacterSet) const {
 | |
|   nsAutoCString charset;
 | |
|   GetDocumentCharacterSet()->Name(charset);
 | |
|   CopyASCIItoUTF16(charset, aCharacterSet);
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
 | |
|                                                ErrorResult& rv) const {
 | |
|   nsINode* imported = &aNode;
 | |
| 
 | |
|   switch (imported->NodeType()) {
 | |
|     case DOCUMENT_NODE: {
 | |
|       break;
 | |
|     }
 | |
|     case DOCUMENT_FRAGMENT_NODE:
 | |
|     case ATTRIBUTE_NODE:
 | |
|     case ELEMENT_NODE:
 | |
|     case PROCESSING_INSTRUCTION_NODE:
 | |
|     case TEXT_NODE:
 | |
|     case CDATA_SECTION_NODE:
 | |
|     case COMMENT_NODE:
 | |
|     case DOCUMENT_TYPE_NODE: {
 | |
|       return nsNodeUtils::Clone(imported, aDeep, mNodeInfoManager, nullptr, rv);
 | |
|     }
 | |
|     default: {
 | |
|       NS_WARNING("Don't know how to clone this nodetype for importNode.");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void Document::LoadBindingDocument(const nsAString& aURI,
 | |
|                                    nsIPrincipal& aSubjectPrincipal,
 | |
|                                    ErrorResult& rv) {
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   rv = NS_NewURI(getter_AddRefs(uri), aURI, mCharacterSet, GetDocBaseURI());
 | |
|   if (rv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   BindingManager()->LoadBindingDocument(this, uri, &aSubjectPrincipal);
 | |
| }
 | |
| 
 | |
| Element* Document::GetBindingParent(nsINode& aNode) {
 | |
|   nsCOMPtr<nsIContent> content(do_QueryInterface(&aNode));
 | |
|   if (!content) return nullptr;
 | |
| 
 | |
|   nsIContent* bindingParent = content->GetBindingParent();
 | |
|   return bindingParent ? bindingParent->AsElement() : nullptr;
 | |
| }
 | |
| 
 | |
| static Element* GetElementByAttribute(Element* aElement, nsAtom* aAttrName,
 | |
|                                       const nsAString& aAttrValue,
 | |
|                                       bool aUniversalMatch) {
 | |
|   if (aUniversalMatch ? aElement->HasAttr(kNameSpaceID_None, aAttrName)
 | |
|                       : aElement->AttrValueIs(kNameSpaceID_None, aAttrName,
 | |
|                                               aAttrValue, eCaseMatters)) {
 | |
|     return aElement;
 | |
|   }
 | |
| 
 | |
|   for (nsIContent* child = aElement->GetFirstChild(); child;
 | |
|        child = child->GetNextSibling()) {
 | |
|     if (!child->IsElement()) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     Element* matchedElement = GetElementByAttribute(
 | |
|         child->AsElement(), aAttrName, aAttrValue, aUniversalMatch);
 | |
|     if (matchedElement) return matchedElement;
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| Element* Document::GetAnonymousElementByAttribute(
 | |
|     nsIContent* aElement, nsAtom* aAttrName,
 | |
|     const nsAString& aAttrValue) const {
 | |
|   nsINodeList* nodeList = BindingManager()->GetAnonymousNodesFor(aElement);
 | |
|   if (!nodeList) return nullptr;
 | |
| 
 | |
|   uint32_t length = nodeList->Length();
 | |
| 
 | |
|   bool universalMatch = aAttrValue.EqualsLiteral("*");
 | |
| 
 | |
|   for (uint32_t i = 0; i < length; ++i) {
 | |
|     Element* current = Element::FromNode(nodeList->Item(i));
 | |
|     if (!current) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     Element* matchedElm =
 | |
|         GetElementByAttribute(current, aAttrName, aAttrValue, universalMatch);
 | |
|     if (matchedElm) return matchedElm;
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| Element* Document::GetAnonymousElementByAttribute(Element& aElement,
 | |
|                                                   const nsAString& aAttrName,
 | |
|                                                   const nsAString& aAttrValue) {
 | |
|   RefPtr<nsAtom> attribute = NS_Atomize(aAttrName);
 | |
| 
 | |
|   return GetAnonymousElementByAttribute(&aElement, attribute, aAttrValue);
 | |
| }
 | |
| 
 | |
| nsINodeList* Document::GetAnonymousNodes(Element& aElement) {
 | |
|   return BindingManager()->GetAnonymousNodesFor(&aElement);
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
 | |
|   RefPtr<nsRange> range = new nsRange(this);
 | |
|   nsresult res = range->CollapseTo(this, 0);
 | |
|   if (NS_FAILED(res)) {
 | |
|     rv.Throw(res);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return range.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<NodeIterator> Document::CreateNodeIterator(
 | |
|     nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
 | |
|     ErrorResult& rv) const {
 | |
|   RefPtr<NodeIterator> iterator =
 | |
|       new NodeIterator(&aRoot, aWhatToShow, aFilter);
 | |
|   return iterator.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
 | |
|                                                         uint32_t aWhatToShow,
 | |
|                                                         NodeFilter* aFilter,
 | |
|                                                         ErrorResult& rv) const {
 | |
|   RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
 | |
|   return walker.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<Location> Document::GetLocation() const {
 | |
|   nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
 | |
| 
 | |
|   if (!w) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return do_AddRef(w->Location());
 | |
| }
 | |
| 
 | |
| Element* Document::GetHtmlElement() const {
 | |
|   Element* rootElement = GetRootElement();
 | |
|   if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
 | |
|     return rootElement;
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| Element* Document::GetHtmlChildElement(nsAtom* aTag) {
 | |
|   Element* html = GetHtmlElement();
 | |
|   if (!html) return nullptr;
 | |
| 
 | |
|   // Look for the element with aTag inside html. This needs to run
 | |
|   // forwards to find the first such element.
 | |
|   for (nsIContent* child = html->GetFirstChild(); child;
 | |
|        child = child->GetNextSibling()) {
 | |
|     if (child->IsHTMLElement(aTag)) return child->AsElement();
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| nsGenericHTMLElement* Document::GetBody() {
 | |
|   Element* html = GetHtmlElement();
 | |
|   if (!html) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   for (nsIContent* child = html->GetFirstChild(); child;
 | |
|        child = child->GetNextSibling()) {
 | |
|     if (child->IsHTMLElement(nsGkAtoms::body) ||
 | |
|         child->IsHTMLElement(nsGkAtoms::frameset)) {
 | |
|       return static_cast<nsGenericHTMLElement*>(child);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
 | |
|   nsCOMPtr<Element> root = GetRootElement();
 | |
| 
 | |
|   // The body element must be either a body tag or a frameset tag. And we must
 | |
|   // have a root element to be able to add kids to it.
 | |
|   if (!newBody ||
 | |
|       !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset) ||
 | |
|       !root) {
 | |
|     rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Use DOM methods so that we pass through the appropriate security checks.
 | |
|   nsCOMPtr<Element> currentBody = GetBody();
 | |
|   if (currentBody) {
 | |
|     root->ReplaceChild(*newBody, *currentBody, rv);
 | |
|   } else {
 | |
|     root->AppendChild(*newBody, rv);
 | |
|   }
 | |
| }
 | |
| 
 | |
| HTMLSharedElement* Document::GetHead() {
 | |
|   return static_cast<HTMLSharedElement*>(GetHeadElement());
 | |
| }
 | |
| 
 | |
| Element* Document::GetTitleElement() {
 | |
|   // mMayHaveTitleElement will have been set to true if any HTML or SVG
 | |
|   // <title> element has been bound to this document. So if it's false,
 | |
|   // we know there is nothing to do here. This avoids us having to search
 | |
|   // the whole DOM if someone calls document.title on a large document
 | |
|   // without a title.
 | |
|   if (!mMayHaveTitleElement) return nullptr;
 | |
| 
 | |
|   Element* root = GetRootElement();
 | |
|   if (root && root->IsSVGElement(nsGkAtoms::svg)) {
 | |
|     // In SVG, the document's title must be a child
 | |
|     for (nsIContent* child = root->GetFirstChild(); child;
 | |
|          child = child->GetNextSibling()) {
 | |
|       if (child->IsSVGElement(nsGkAtoms::title)) {
 | |
|         return child->AsElement();
 | |
|       }
 | |
|     }
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // We check the HTML namespace even for non-HTML documents, except SVG.  This
 | |
|   // matches the spec and the behavior of all tested browsers.
 | |
|   // We avoid creating a live nsContentList since we don't need to watch for DOM
 | |
|   // tree mutations.
 | |
|   RefPtr<nsContentList> list = new nsContentList(
 | |
|       this, kNameSpaceID_XHTML, nsGkAtoms::title, nsGkAtoms::title,
 | |
|       /* aDeep = */ true,
 | |
|       /* aLiveList = */ false);
 | |
| 
 | |
|   nsIContent* first = list->Item(0, false);
 | |
| 
 | |
|   return first ? first->AsElement() : nullptr;
 | |
| }
 | |
| 
 | |
| void Document::GetTitle(nsAString& aTitle) {
 | |
|   aTitle.Truncate();
 | |
| 
 | |
|   Element* rootElement = GetRootElement();
 | |
|   if (!rootElement) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoString tmp;
 | |
| 
 | |
| #ifdef MOZ_XUL
 | |
|   if (rootElement->IsXULElement()) {
 | |
|     rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, tmp);
 | |
|   } else
 | |
| #endif
 | |
|   {
 | |
|     Element* title = GetTitleElement();
 | |
|     if (!title) {
 | |
|       return;
 | |
|     }
 | |
|     nsContentUtils::GetNodeTextContent(title, false, tmp);
 | |
|   }
 | |
| 
 | |
|   tmp.CompressWhitespace();
 | |
|   aTitle = tmp;
 | |
| }
 | |
| 
 | |
| void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
 | |
|   Element* rootElement = GetRootElement();
 | |
|   if (!rootElement) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_XUL
 | |
|   if (rootElement->IsXULElement()) {
 | |
|     aRv =
 | |
|         rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
 | |
|     return;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   Maybe<mozAutoDocUpdate> updateBatch;
 | |
|   nsCOMPtr<Element> title = GetTitleElement();
 | |
|   if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
 | |
|     if (!title) {
 | |
|       // Batch updates so that mutation events don't change "the title
 | |
|       // element" under us
 | |
|       updateBatch.emplace(this, true);
 | |
|       RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
 | |
|           nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
 | |
|       NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
 | |
|                        NOT_FROM_PARSER);
 | |
|       if (!title) {
 | |
|         return;
 | |
|       }
 | |
|       rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true);
 | |
|     }
 | |
|   } else if (rootElement->IsHTMLElement()) {
 | |
|     if (!title) {
 | |
|       // Batch updates so that mutation events don't change "the title
 | |
|       // element" under us
 | |
|       updateBatch.emplace(this, true);
 | |
|       Element* head = GetHeadElement();
 | |
|       if (!head) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       RefPtr<mozilla::dom::NodeInfo> titleInfo;
 | |
|       titleInfo = mNodeInfoManager->GetNodeInfo(
 | |
|           nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
 | |
|       title = NS_NewHTMLTitleElement(titleInfo.forget());
 | |
|       if (!title) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       head->AppendChildTo(title, true);
 | |
|     }
 | |
|   } else {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
 | |
| }
 | |
| 
 | |
| void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
 | |
|   NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
 | |
|                "Setting a title while unlinking or destroying the element?");
 | |
|   if (mInUnlinkOrDeletion) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aBoundTitleElement) {
 | |
|     mMayHaveTitleElement = true;
 | |
|   }
 | |
|   if (mPendingTitleChangeEvent.IsPending()) return;
 | |
| 
 | |
|   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
|   RefPtr<nsRunnableMethod<Document, void, false>> event =
 | |
|       NewNonOwningRunnableMethod("Document::DoNotifyPossibleTitleChange", this,
 | |
|                                  &Document::DoNotifyPossibleTitleChange);
 | |
|   nsresult rv = Dispatch(TaskCategory::Other, do_AddRef(event));
 | |
|   if (NS_SUCCEEDED(rv)) {
 | |
|     mPendingTitleChangeEvent = std::move(event);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::DoNotifyPossibleTitleChange() {
 | |
|   mPendingTitleChangeEvent.Forget();
 | |
|   mHaveFiredTitleChange = true;
 | |
| 
 | |
|   nsAutoString title;
 | |
|   GetTitle(title);
 | |
| 
 | |
|   nsCOMPtr<nsIPresShell> shell = GetShell();
 | |
|   if (shell) {
 | |
|     nsCOMPtr<nsISupports> container =
 | |
|         shell->GetPresContext()->GetContainerWeak();
 | |
|     if (container) {
 | |
|       nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container);
 | |
|       if (docShellWin) {
 | |
|         docShellWin->SetTitle(title);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Fire a DOM event for the title change.
 | |
|   nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
 | |
|                                       NS_LITERAL_STRING("DOMTitleChanged"),
 | |
|                                       CanBubble::eYes, Cancelable::eYes);
 | |
| }
 | |
| 
 | |
| already_AddRefed<BoxObject> Document::GetBoxObjectFor(Element* aElement,
 | |
|                                                       ErrorResult& aRv) {
 | |
|   if (!aElement) {
 | |
|     aRv.Throw(NS_ERROR_UNEXPECTED);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   Document* doc = aElement->OwnerDoc();
 | |
|   if (doc != this) {
 | |
|     aRv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (!mHasWarnedAboutBoxObjects && !aElement->IsXULElement()) {
 | |
|     mHasWarnedAboutBoxObjects = true;
 | |
|     nsContentUtils::ReportToConsole(
 | |
|         nsIScriptError::warningFlag, NS_LITERAL_CSTRING("BoxObjects"), this,
 | |
|         nsContentUtils::eDOM_PROPERTIES, "UseOfGetBoxObjectForWarning");
 | |
|   }
 | |
| 
 | |
|   if (!mBoxObjectTable) {
 | |
|     mBoxObjectTable =
 | |
|         new nsRefPtrHashtable<nsPtrHashKey<nsIContent>, BoxObject>(6);
 | |
|   }
 | |
| 
 | |
|   RefPtr<BoxObject> boxObject;
 | |
|   auto entry = mBoxObjectTable->LookupForAdd(aElement);
 | |
|   if (entry) {
 | |
|     boxObject = entry.Data();
 | |
|     return boxObject.forget();
 | |
|   }
 | |
| 
 | |
|   boxObject = new BoxObject();
 | |
|   boxObject->Init(aElement);
 | |
|   entry.OrInsert([&boxObject]() { return boxObject; });
 | |
| 
 | |
|   return boxObject.forget();
 | |
| }
 | |
| 
 | |
| void Document::ClearBoxObjectFor(nsIContent* aContent) {
 | |
|   if (mBoxObjectTable) {
 | |
|     if (auto entry = mBoxObjectTable->Lookup(aContent)) {
 | |
|       nsPIBoxObject* boxObject = entry.Data();
 | |
|       boxObject->Clear();
 | |
|       entry.Remove();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<MediaQueryList> Document::MatchMedia(
 | |
|     const nsAString& aMediaQueryList, CallerType aCallerType) {
 | |
|   RefPtr<MediaQueryList> result =
 | |
|       new MediaQueryList(this, aMediaQueryList, aCallerType);
 | |
| 
 | |
|   mDOMMediaQueryLists.insertBack(result);
 | |
| 
 | |
|   return result.forget();
 | |
| }
 | |
| 
 | |
| void Document::FlushSkinBindings() { BindingManager()->FlushSkinBindings(); }
 | |
| 
 | |
| void Document::SetMayStartLayout(bool aMayStartLayout) {
 | |
|   mMayStartLayout = aMayStartLayout;
 | |
|   if (MayStartLayout()) {
 | |
|     // Before starting layout, check whether we're a toplevel chrome
 | |
|     // window.  If we are, setup some state so that we don't have to restyle
 | |
|     // the whole tree after StartLayout.
 | |
|     if (nsCOMPtr<nsIXULWindow> win = GetXULWindowIfToplevelChrome()) {
 | |
|       // We're the chrome document!
 | |
|       win->BeforeStartLayout();
 | |
|     }
 | |
|     ReadyState state = GetReadyStateEnum();
 | |
|     if (state >= READYSTATE_INTERACTIVE) {
 | |
|       // DOMContentLoaded has fired already.
 | |
|       MaybeResolveReadyForIdle();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
 | |
|   mInitializableFrameLoaders.RemoveElement(aLoader);
 | |
|   // Don't even try to initialize.
 | |
|   if (mInDestructor) {
 | |
|     NS_WARNING(
 | |
|         "Trying to initialize a frame loader while"
 | |
|         "document is being deleted");
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   mInitializableFrameLoaders.AppendElement(aLoader);
 | |
|   if (!mFrameLoaderRunner) {
 | |
|     mFrameLoaderRunner =
 | |
|         NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
 | |
|                           &Document::MaybeInitializeFinalizeFrameLoaders);
 | |
|     NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
 | |
|     nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
 | |
|                                        nsIRunnable* aFinalizer) {
 | |
|   mInitializableFrameLoaders.RemoveElement(aLoader);
 | |
|   if (mInDestructor) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   mFrameLoaderFinalizers.AppendElement(aFinalizer);
 | |
|   if (!mFrameLoaderRunner) {
 | |
|     mFrameLoaderRunner =
 | |
|         NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
 | |
|                           &Document::MaybeInitializeFinalizeFrameLoaders);
 | |
|     NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
 | |
|     nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void Document::MaybeInitializeFinalizeFrameLoaders() {
 | |
|   if (mDelayFrameLoaderInitialization || mUpdateNestLevel != 0) {
 | |
|     // This method will be recalled when mUpdateNestLevel drops to 0,
 | |
|     // or when !mDelayFrameLoaderInitialization.
 | |
|     mFrameLoaderRunner = nullptr;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We're not in an update, but it is not safe to run scripts, so
 | |
|   // postpone frameloader initialization and finalization.
 | |
|   if (!nsContentUtils::IsSafeToRunScript()) {
 | |
|     if (!mInDestructor && !mFrameLoaderRunner &&
 | |
|         (mInitializableFrameLoaders.Length() ||
 | |
|          mFrameLoaderFinalizers.Length())) {
 | |
|       mFrameLoaderRunner = NewRunnableMethod(
 | |
|           "Document::MaybeInitializeFinalizeFrameLoaders", this,
 | |
|           &Document::MaybeInitializeFinalizeFrameLoaders);
 | |
|       nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
|   mFrameLoaderRunner = nullptr;
 | |
| 
 | |
|   // Don't use a temporary array for mInitializableFrameLoaders, because
 | |
|   // loading a frame may cause some other frameloader to be removed from the
 | |
|   // array. But be careful to keep the loader alive when starting the load!
 | |
|   while (mInitializableFrameLoaders.Length()) {
 | |
|     RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
 | |
|     mInitializableFrameLoaders.RemoveElementAt(0);
 | |
|     NS_ASSERTION(loader, "null frameloader in the array?");
 | |
|     loader->ReallyStartLoading();
 | |
|   }
 | |
| 
 | |
|   uint32_t length = mFrameLoaderFinalizers.Length();
 | |
|   if (length > 0) {
 | |
|     nsTArray<nsCOMPtr<nsIRunnable>> finalizers;
 | |
|     mFrameLoaderFinalizers.SwapElements(finalizers);
 | |
|     for (uint32_t i = 0; i < length; ++i) {
 | |
|       finalizers[i]->Run();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
 | |
|   uint32_t length = mInitializableFrameLoaders.Length();
 | |
|   for (uint32_t i = 0; i < length; ++i) {
 | |
|     if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
 | |
|       mInitializableFrameLoaders.RemoveElementAt(i);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| Document* Document::RequestExternalResource(
 | |
|     nsIURI* aURI, nsIURI* aReferrer, uint32_t aReferrerPolicy,
 | |
|     nsINode* aRequestingNode, ExternalResourceLoad** aPendingLoad) {
 | |
|   MOZ_ASSERT(aURI, "Must have a URI");
 | |
|   MOZ_ASSERT(aRequestingNode, "Must have a node");
 | |
|   if (mDisplayDocument) {
 | |
|     return mDisplayDocument->RequestExternalResource(
 | |
|         aURI, aReferrer, aReferrerPolicy, aRequestingNode, aPendingLoad);
 | |
|   }
 | |
| 
 | |
|   return mExternalResourceMap.RequestResource(
 | |
|       aURI, aReferrer, aReferrerPolicy, aRequestingNode, this, aPendingLoad);
 | |
| }
 | |
| 
 | |
| void Document::EnumerateExternalResources(SubDocEnumFunc aCallback,
 | |
|                                           void* aData) {
 | |
|   mExternalResourceMap.EnumerateResources(aCallback, aData);
 | |
| }
 | |
| 
 | |
| SMILAnimationController* Document::GetAnimationController() {
 | |
|   // We create the animation controller lazily because most documents won't want
 | |
|   // one and only SVG documents and the like will call this
 | |
|   if (mAnimationController) return mAnimationController;
 | |
|   // Refuse to create an Animation Controller for data documents.
 | |
|   if (mLoadedAsData || mLoadedAsInteractiveData) return nullptr;
 | |
| 
 | |
|   mAnimationController = new SMILAnimationController(this);
 | |
| 
 | |
|   // If there's a presContext then check the animation mode and pause if
 | |
|   // necessary.
 | |
|   nsPresContext* context = GetPresContext();
 | |
|   if (mAnimationController && context &&
 | |
|       context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
 | |
|     mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
 | |
|   }
 | |
| 
 | |
|   // If we're hidden (or being hidden), notify the newly-created animation
 | |
|   // controller. (Skip this check for SVG-as-an-image documents, though,
 | |
|   // because they don't get OnPageShow / OnPageHide calls).
 | |
|   if (!mIsShowing && !mIsBeingUsedAsImage) {
 | |
|     mAnimationController->OnPageHide();
 | |
|   }
 | |
| 
 | |
|   return mAnimationController;
 | |
| }
 | |
| 
 | |
| PendingAnimationTracker* Document::GetOrCreatePendingAnimationTracker() {
 | |
|   if (!mPendingAnimationTracker) {
 | |
|     mPendingAnimationTracker = new PendingAnimationTracker(this);
 | |
|   }
 | |
| 
 | |
|   return mPendingAnimationTracker;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Retrieve the "direction" property of the document.
 | |
|  *
 | |
|  * @lina 01/09/2001
 | |
|  */
 | |
| void Document::GetDir(nsAString& aDirection) const {
 | |
|   aDirection.Truncate();
 | |
|   Element* rootElement = GetHtmlElement();
 | |
|   if (rootElement) {
 | |
|     static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Set the "direction" property of the document.
 | |
|  *
 | |
|  * @lina 01/09/2001
 | |
|  */
 | |
| void Document::SetDir(const nsAString& aDirection) {
 | |
|   Element* rootElement = GetHtmlElement();
 | |
|   if (rootElement) {
 | |
|     rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsIHTMLCollection* Document::Images() {
 | |
|   if (!mImages) {
 | |
|     mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
 | |
|                                 nsGkAtoms::img);
 | |
|   }
 | |
|   return mImages;
 | |
| }
 | |
| 
 | |
| nsIHTMLCollection* Document::Embeds() {
 | |
|   if (!mEmbeds) {
 | |
|     mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
 | |
|                                 nsGkAtoms::embed);
 | |
|   }
 | |
|   return mEmbeds;
 | |
| }
 | |
| 
 | |
| static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
 | |
|                        void* aData) {
 | |
|   return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
 | |
|          aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href);
 | |
| }
 | |
| 
 | |
| nsIHTMLCollection* Document::Links() {
 | |
|   if (!mLinks) {
 | |
|     mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
 | |
|   }
 | |
|   return mLinks;
 | |
| }
 | |
| 
 | |
| nsIHTMLCollection* Document::Forms() {
 | |
|   if (!mForms) {
 | |
|     // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
 | |
|     mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
 | |
|                                nsGkAtoms::form);
 | |
|   }
 | |
| 
 | |
|   return mForms;
 | |
| }
 | |
| 
 | |
| nsIHTMLCollection* Document::Scripts() {
 | |
|   if (!mScripts) {
 | |
|     mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
 | |
|                                  nsGkAtoms::script);
 | |
|   }
 | |
|   return mScripts;
 | |
| }
 | |
| 
 | |
| nsIHTMLCollection* Document::Applets() {
 | |
|   if (!mApplets) {
 | |
|     mApplets = new nsEmptyContentList(this);
 | |
|   }
 | |
|   return mApplets;
 | |
| }
 | |
| 
 | |
| static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
 | |
|                          void* aData) {
 | |
|   return aElement->IsHTMLElement(nsGkAtoms::a) &&
 | |
|          aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
 | |
| }
 | |
| 
 | |
| nsIHTMLCollection* Document::Anchors() {
 | |
|   if (!mAnchors) {
 | |
|     mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
 | |
|   }
 | |
|   return mAnchors;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
 | |
|                                   nsAtom* aAtom, void* aData) {
 | |
|   MOZ_ASSERT(aElement, "Must have element to work with!");
 | |
| 
 | |
|   if (!aElement->HasName()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsString* elementName = static_cast<nsString*>(aData);
 | |
|   return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
 | |
|          aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
 | |
|                                eCaseMatters);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void* Document::UseExistingNameString(nsINode* aRootNode,
 | |
|                                       const nsString* aName) {
 | |
|   return const_cast<nsString*>(aName);
 | |
| }
 | |
| 
 | |
| nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
 | |
|   if (mDocumentURI) {
 | |
|     nsAutoCString uri;
 | |
|     nsresult rv = mDocumentURI->GetSpec(uri);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     CopyUTF8toUTF16(uri, aDocumentURI);
 | |
|   } else {
 | |
|     aDocumentURI.Truncate();
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // Alias of above
 | |
| nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
 | |
| 
 | |
| void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
 | |
|                                     CallerType aCallerType,
 | |
|                                     ErrorResult& aRv) const {
 | |
|   if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
 | |
|     aRv = GetDocumentURI(aDocumentURI);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString uri;
 | |
|   nsresult res = mChromeXHRDocURI->GetSpec(uri);
 | |
|   if (NS_FAILED(res)) {
 | |
|     aRv.Throw(res);
 | |
|     return;
 | |
|   }
 | |
|   CopyUTF8toUTF16(uri, aDocumentURI);
 | |
| }
 | |
| 
 | |
| nsIURI* Document::GetDocumentURIObject() const {
 | |
|   if (!mChromeXHRDocURI) {
 | |
|     return GetDocumentURI();
 | |
|   }
 | |
| 
 | |
|   return mChromeXHRDocURI;
 | |
| }
 | |
| 
 | |
| void Document::GetCompatMode(nsString& aCompatMode) const {
 | |
|   NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
 | |
|                    mCompatMode == eCompatibility_AlmostStandards ||
 | |
|                    mCompatMode == eCompatibility_FullStandards,
 | |
|                "mCompatMode is neither quirks nor strict for this document");
 | |
| 
 | |
|   if (mCompatMode == eCompatibility_NavQuirks) {
 | |
|     aCompatMode.AssignLiteral("BackCompat");
 | |
|   } else {
 | |
|     aCompatMode.AssignLiteral("CSS1Compat");
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace dom
 | |
| }  // namespace mozilla
 | |
| 
 | |
| void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
 | |
|   if (Element* element = Element::FromNode(aNode)) {
 | |
|     if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
 | |
|       while (true) {
 | |
|         RefPtr<Attr> attr;
 | |
|         {
 | |
|           // Use an iterator to get an arbitrary attribute from the
 | |
|           // cache. The iterator must be destroyed before any other
 | |
|           // operations on mAttributeCache, to avoid hash table
 | |
|           // assertions.
 | |
|           auto iter = map->mAttributeCache.ConstIter();
 | |
|           if (iter.Done()) {
 | |
|             break;
 | |
|           }
 | |
|           attr = iter.UserData();
 | |
|         }
 | |
| 
 | |
|         BlastSubtreeToPieces(attr);
 | |
| 
 | |
|         mozilla::DebugOnly<nsresult> rv =
 | |
|             element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
 | |
|                                attr->NodeInfo()->NameAtom(), false);
 | |
| 
 | |
|         // XXX Should we abort here?
 | |
|         NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
 | |
|       BlastSubtreeToPieces(shadow);
 | |
|       element->UnattachShadow();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   while (aNode->HasChildren()) {
 | |
|     nsIContent* node = aNode->GetFirstChild();
 | |
|     BlastSubtreeToPieces(node);
 | |
|     aNode->RemoveChildNode(node, false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace dom {
 | |
| 
 | |
| nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) {
 | |
|   nsINode* adoptedNode = &aAdoptedNode;
 | |
| 
 | |
|   // Scope firing mutation events so that we don't carry any state that
 | |
|   // might be stale
 | |
|   {
 | |
|     nsINode* parent = adoptedNode->GetParentNode();
 | |
|     if (parent) {
 | |
|       nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsAutoScriptBlocker scriptBlocker;
 | |
| 
 | |
|   switch (adoptedNode->NodeType()) {
 | |
|     case ATTRIBUTE_NODE: {
 | |
|       // Remove from ownerElement.
 | |
|       RefPtr<Attr> adoptedAttr = static_cast<Attr*>(adoptedNode);
 | |
| 
 | |
|       nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement(rv);
 | |
|       if (rv.Failed()) {
 | |
|         return nullptr;
 | |
|       }
 | |
| 
 | |
|       if (ownerElement) {
 | |
|         RefPtr<Attr> newAttr =
 | |
|             ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
 | |
|         if (rv.Failed()) {
 | |
|           return nullptr;
 | |
|         }
 | |
| 
 | |
|         newAttr.swap(adoptedAttr);
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     case DOCUMENT_FRAGMENT_NODE: {
 | |
|       if (adoptedNode->IsShadowRoot()) {
 | |
|         rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
 | |
|         return nullptr;
 | |
|       }
 | |
|       MOZ_FALLTHROUGH;
 | |
|     }
 | |
|     case ELEMENT_NODE:
 | |
|     case PROCESSING_INSTRUCTION_NODE:
 | |
|     case TEXT_NODE:
 | |
|     case CDATA_SECTION_NODE:
 | |
|     case COMMENT_NODE:
 | |
|     case DOCUMENT_TYPE_NODE: {
 | |
|       // Don't allow adopting a node's anonymous subtree out from under it.
 | |
|       if (adoptedNode->AsContent()->IsRootOfAnonymousSubtree()) {
 | |
|         rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 | |
|         return nullptr;
 | |
|       }
 | |
| 
 | |
|       // We don't want to adopt an element into its own contentDocument or into
 | |
|       // a descendant contentDocument, so we check if the frameElement of this
 | |
|       // document or any of its parents is the adopted node or one of its
 | |
|       // descendants.
 | |
|       Document* doc = this;
 | |
|       do {
 | |
|         if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
 | |
|           nsCOMPtr<nsINode> node = win->GetFrameElementInternal();
 | |
|           if (node &&
 | |
|               nsContentUtils::ContentIsDescendantOf(node, adoptedNode)) {
 | |
|             rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
 | |
|             return nullptr;
 | |
|           }
 | |
|         }
 | |
|       } while ((doc = doc->GetParentDocument()));
 | |
| 
 | |
|       // Remove from parent.
 | |
|       nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
 | |
|       if (parent) {
 | |
|         parent->RemoveChildNode(adoptedNode->AsContent(), true);
 | |
|       } else {
 | |
|         MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
 | |
| 
 | |
|         // If we're adopting a node that's not in a document, it might still
 | |
|         // have a binding applied. Remove the binding from the element now
 | |
|         // that it's getting adopted into a new document.
 | |
|         // TODO Fully tear down the binding.
 | |
|         if (Element* element = Element::FromNode(adoptedNode)) {
 | |
|           element->SetXBLBinding(nullptr);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     case DOCUMENT_NODE: {
 | |
|       rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 | |
|       return nullptr;
 | |
|     }
 | |
|     default: {
 | |
|       NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
 | |
| 
 | |
|       rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
 | |
|   bool sameDocument = oldDocument == this;
 | |
| 
 | |
|   AutoJSContext cx;
 | |
|   JS::Rooted<JSObject*> newScope(cx, nullptr);
 | |
|   if (!sameDocument) {
 | |
|     newScope = GetWrapper();
 | |
|     if (!newScope && GetScopeObject() &&
 | |
|         GetScopeObject()->GetGlobalJSObject()) {
 | |
|       // Make sure cx is in a semi-sane compartment before we call WrapNative.
 | |
|       // It's kind of irrelevant, given that we're passing aAllowWrapping =
 | |
|       // false, and documents should always insist on being wrapped in an
 | |
|       // canonical scope. But we try to pass something sane anyway.
 | |
|       JSAutoRealm ar(cx, GetScopeObject()->GetGlobalJSObject());
 | |
|       JS::Rooted<JS::Value> v(cx);
 | |
|       rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
 | |
|                                       /* aAllowWrapping = */ false);
 | |
|       if (rv.Failed()) return nullptr;
 | |
|       newScope = &v.toObject();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMArray<nsINode> nodesWithProperties;
 | |
|   nsNodeUtils::Adopt(adoptedNode, sameDocument ? nullptr : mNodeInfoManager,
 | |
|                      newScope, nodesWithProperties, rv);
 | |
|   if (rv.Failed()) {
 | |
|     // Disconnect all nodes from their parents, since some have the old document
 | |
|     // as their ownerDocument and some have this as their ownerDocument.
 | |
|     nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
 | |
| 
 | |
|     if (!sameDocument && oldDocument) {
 | |
|       for (nsINode* node : nodesWithProperties) {
 | |
|         // Remove all properties.
 | |
|         oldDocument->PropertyTable().DeleteAllPropertiesFor(node);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (!sameDocument && oldDocument) {
 | |
|     nsPropertyTable& oldTable = oldDocument->PropertyTable();
 | |
|     nsPropertyTable& newTable = PropertyTable();
 | |
|     for (nsINode* node : nodesWithProperties) {
 | |
|       rv = oldTable.TransferOrDeleteAllPropertiesFor(node, newTable);
 | |
|     }
 | |
| 
 | |
|     if (rv.Failed()) {
 | |
|       // Disconnect all nodes from their parents.
 | |
|       nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
 | |
| 
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   NS_ASSERTION(adoptedNode->OwnerDoc() == this,
 | |
|                "Should still be in the document we just got adopted into");
 | |
| 
 | |
|   return adoptedNode;
 | |
| }
 | |
| 
 | |
| void Document::ParseWidthAndHeightInMetaViewport(
 | |
|     const nsAString& aWidthString, const nsAString& aHeightString,
 | |
|     const nsAString& aScaleString) {
 | |
|   // The width and height properties
 | |
|   // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
 | |
|   //
 | |
|   // The width and height viewport <META> properties are translated into width
 | |
|   // and height descriptors, setting the min-width/min-height value to
 | |
|   // extend-to-zoom and the max-width/max-height value to the length from the
 | |
|   // viewport <META> property as follows:
 | |
|   //
 | |
|   // 1. Non-negative number values are translated to pixel lengths, clamped to
 | |
|   //    the range: [1px, 10000px]
 | |
|   // 2. Negative number values are dropped
 | |
|   // 3. device-width and device-height translate to 100vw and 100vh respectively
 | |
|   // 4. Other keywords and unknown values translate to 1px
 | |
|   mMinWidth = nsViewportInfo::Auto;
 | |
|   mMaxWidth = nsViewportInfo::Auto;
 | |
|   if (!aWidthString.IsEmpty()) {
 | |
|     mMinWidth = nsViewportInfo::ExtendToZoom;
 | |
|     if (aWidthString.EqualsLiteral("device-width")) {
 | |
|       mMaxWidth = nsViewportInfo::DeviceSize;
 | |
|     } else {
 | |
|       nsresult widthErrorCode;
 | |
|       mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
 | |
|       if (NS_FAILED(widthErrorCode)) {
 | |
|         mMaxWidth = 1.0f;
 | |
|       } else if (mMaxWidth >= 0.0f) {
 | |
|         mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
 | |
|       } else {
 | |
|         mMaxWidth = nsViewportInfo::Auto;
 | |
|       }
 | |
|     }
 | |
|     // FIXME: Check the scale is not 'auto' once we support auto value for it.
 | |
|   } else if (!aScaleString.IsEmpty()) {
 | |
|     if (aHeightString.IsEmpty()) {
 | |
|       mMinWidth = nsViewportInfo::ExtendToZoom;
 | |
|       mMaxWidth = nsViewportInfo::ExtendToZoom;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mMinHeight = nsViewportInfo::Auto;
 | |
|   mMaxHeight = nsViewportInfo::Auto;
 | |
|   if (!aHeightString.IsEmpty()) {
 | |
|     mMinHeight = nsViewportInfo::ExtendToZoom;
 | |
|     if (aHeightString.EqualsLiteral("device-height")) {
 | |
|       mMaxHeight = nsViewportInfo::DeviceSize;
 | |
|     } else {
 | |
|       nsresult heightErrorCode;
 | |
|       mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
 | |
|       if (NS_FAILED(heightErrorCode)) {
 | |
|         mMaxHeight = 1.0f;
 | |
|       } else if (mMaxHeight >= 0.0f) {
 | |
|         mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
 | |
|       } else {
 | |
|         mMaxHeight = nsViewportInfo::Auto;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
 | |
|   MOZ_ASSERT(mPresShell);
 | |
| 
 | |
|   // Compute the CSS-to-LayoutDevice pixel scale as the product of the
 | |
|   // widget scale and the full zoom.
 | |
|   nsPresContext* context = mPresShell->GetPresContext();
 | |
|   // When querying the full zoom, get it from the device context rather than
 | |
|   // directly from the pres context, because the device context's value can
 | |
|   // include an adjustment necessay to keep the number of app units per device
 | |
|   // pixel an integer, and we want the adjusted value.
 | |
|   float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
 | |
|   fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
 | |
|   CSSToLayoutDeviceScale layoutDeviceScale =
 | |
|       context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
 | |
| 
 | |
|   CSSToScreenScale defaultScale =
 | |
|       layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
 | |
| 
 | |
|   // Special behaviour for desktop mode, provided we are not on an about: page
 | |
|   nsPIDOMWindowOuter* win = GetWindow();
 | |
|   if (win && win->IsDesktopModeViewport() && !IsAboutPage()) {
 | |
|     CSSCoord viewportWidth = gfxPrefs::DesktopViewportWidth() / fullZoom;
 | |
|     CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
 | |
|     float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
 | |
|     CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
 | |
|     ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
 | |
|     return nsViewportInfo(fakeDesktopSize, scaleToFit,
 | |
|                           nsViewportInfo::ZoomFlag::AllowZoom);
 | |
|   }
 | |
| 
 | |
|   if (!nsLayoutUtils::ShouldHandleMetaViewport(this)) {
 | |
|     return nsViewportInfo(aDisplaySize, defaultScale,
 | |
|                           nsViewportInfo::ZoomFlag::DisallowZoom);
 | |
|   }
 | |
| 
 | |
|   // In cases where the width of the CSS viewport is less than or equal to the
 | |
|   // width of the display (i.e. width <= device-width) then we disable
 | |
|   // double-tap-to-zoom behaviour. See bug 941995 for details.
 | |
| 
 | |
|   switch (mViewportType) {
 | |
|     case DisplayWidthHeight:
 | |
|       return nsViewportInfo(aDisplaySize, defaultScale,
 | |
|                             nsViewportInfo::ZoomFlag::AllowZoom);
 | |
|     case Unknown: {
 | |
|       nsAutoString viewport;
 | |
|       GetHeaderData(nsGkAtoms::viewport, viewport);
 | |
|       if (viewport.IsEmpty()) {
 | |
|         // If the docType specifies that we are on a site optimized for mobile,
 | |
|         // then we want to return specially crafted defaults for the viewport
 | |
|         // info.
 | |
|         RefPtr<DocumentType> docType = GetDoctype();
 | |
|         if (docType) {
 | |
|           nsAutoString docId;
 | |
|           docType->GetPublicId(docId);
 | |
|           if ((docId.Find("WAP") != -1) || (docId.Find("Mobile") != -1) ||
 | |
|               (docId.Find("WML") != -1)) {
 | |
|             // We're making an assumption that the docType can't change here
 | |
|             mViewportType = DisplayWidthHeight;
 | |
|             return nsViewportInfo(aDisplaySize, defaultScale,
 | |
|                                   nsViewportInfo::ZoomFlag::AllowZoom);
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         nsAutoString handheldFriendly;
 | |
|         GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
 | |
|         if (handheldFriendly.EqualsLiteral("true")) {
 | |
|           mViewportType = DisplayWidthHeight;
 | |
|           return nsViewportInfo(aDisplaySize, defaultScale,
 | |
|                                 nsViewportInfo::ZoomFlag::AllowZoom);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       nsAutoString minScaleStr;
 | |
|       GetHeaderData(nsGkAtoms::viewport_minimum_scale, minScaleStr);
 | |
| 
 | |
|       nsresult scaleMinErrorCode;
 | |
|       mScaleMinFloat =
 | |
|           LayoutDeviceToScreenScale(minScaleStr.ToFloat(&scaleMinErrorCode));
 | |
| 
 | |
|       if (NS_FAILED(scaleMinErrorCode)) {
 | |
|         mScaleMinFloat = kViewportMinScale;
 | |
|       }
 | |
| 
 | |
|       mScaleMinFloat = mozilla::clamped(mScaleMinFloat, kViewportMinScale,
 | |
|                                         kViewportMaxScale);
 | |
| 
 | |
|       nsAutoString maxScaleStr;
 | |
|       GetHeaderData(nsGkAtoms::viewport_maximum_scale, maxScaleStr);
 | |
| 
 | |
|       // We define a special error code variable for the scale and max scale,
 | |
|       // because they are used later (see the width calculations).
 | |
|       nsresult scaleMaxErrorCode;
 | |
|       mScaleMaxFloat =
 | |
|           LayoutDeviceToScreenScale(maxScaleStr.ToFloat(&scaleMaxErrorCode));
 | |
| 
 | |
|       if (NS_FAILED(scaleMaxErrorCode)) {
 | |
|         mScaleMaxFloat = kViewportMaxScale;
 | |
|       }
 | |
| 
 | |
|       // Resolve min-zoom and max-zoom values.
 | |
|       // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
 | |
|       if (NS_SUCCEEDED(scaleMaxErrorCode) && NS_SUCCEEDED(scaleMinErrorCode)) {
 | |
|         mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
 | |
|       }
 | |
| 
 | |
|       mScaleMaxFloat = mozilla::clamped(mScaleMaxFloat, kViewportMinScale,
 | |
|                                         kViewportMaxScale);
 | |
| 
 | |
|       nsAutoString scaleStr;
 | |
|       GetHeaderData(nsGkAtoms::viewport_initial_scale, scaleStr);
 | |
| 
 | |
|       nsresult scaleErrorCode;
 | |
|       mScaleFloat =
 | |
|           LayoutDeviceToScreenScale(scaleStr.ToFloat(&scaleErrorCode));
 | |
| 
 | |
|       nsAutoString widthStr, heightStr;
 | |
| 
 | |
|       GetHeaderData(nsGkAtoms::viewport_height, heightStr);
 | |
|       GetHeaderData(nsGkAtoms::viewport_width, widthStr);
 | |
| 
 | |
|       // Parse width and height properties
 | |
|       // This function sets m{Min,Max}{Width,Height}.
 | |
|       ParseWidthAndHeightInMetaViewport(widthStr, heightStr, scaleStr);
 | |
| 
 | |
|       mAllowZoom = true;
 | |
|       nsAutoString userScalable;
 | |
|       GetHeaderData(nsGkAtoms::viewport_user_scalable, userScalable);
 | |
| 
 | |
|       if ((userScalable.EqualsLiteral("0")) ||
 | |
|           (userScalable.EqualsLiteral("no")) ||
 | |
|           (userScalable.EqualsLiteral("false"))) {
 | |
|         mAllowZoom = false;
 | |
|       }
 | |
| 
 | |
|       mScaleStrEmpty = scaleStr.IsEmpty();
 | |
|       mWidthStrEmpty = widthStr.IsEmpty();
 | |
|       mValidScaleFloat = !scaleStr.IsEmpty() && NS_SUCCEEDED(scaleErrorCode);
 | |
|       mValidMaxScale =
 | |
|           !maxScaleStr.IsEmpty() && NS_SUCCEEDED(scaleMaxErrorCode);
 | |
| 
 | |
|       mViewportType = Specified;
 | |
|       mViewportOverflowType = ViewportOverflowType::NoOverflow;
 | |
|       MOZ_FALLTHROUGH;
 | |
|     }
 | |
|     case Specified:
 | |
|     default:
 | |
|       LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
 | |
|       LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
 | |
|       bool effectiveValidMaxScale = mValidMaxScale;
 | |
| 
 | |
|       nsViewportInfo::ZoomFlag effectiveZoomFlag =
 | |
|           mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
 | |
|                      : nsViewportInfo::ZoomFlag::DisallowZoom;
 | |
|       if (gfxPrefs::ForceUserScalable()) {
 | |
|         // If the pref to force user-scalable is enabled, we ignore the values
 | |
|         // from the meta-viewport tag for these properties and just assume they
 | |
|         // allow the page to be scalable. Note in particular that this code is
 | |
|         // in the "Specified" branch of the enclosing switch statement, so that
 | |
|         // calls to GetViewportInfo always use the latest value of the
 | |
|         // ForceUserScalable pref. Other codepaths that return nsViewportInfo
 | |
|         // instances are all consistent with ForceUserScalable() already.
 | |
|         effectiveMinScale = kViewportMinScale;
 | |
|         effectiveMaxScale = kViewportMaxScale;
 | |
|         effectiveValidMaxScale = true;
 | |
|         effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
 | |
|       }
 | |
| 
 | |
|       // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
 | |
|       auto ComputeExtendZoom = [&]() -> float {
 | |
|         if (mValidScaleFloat && effectiveValidMaxScale) {
 | |
|           return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
 | |
|         }
 | |
|         if (mValidScaleFloat) {
 | |
|           return mScaleFloat.scale;
 | |
|         }
 | |
|         if (effectiveValidMaxScale) {
 | |
|           return effectiveMaxScale.scale;
 | |
|         }
 | |
|         return nsViewportInfo::Auto;
 | |
|       };
 | |
| 
 | |
|       // Resolving 'extend-to-zoom'
 | |
|       // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
 | |
|       float extendZoom = ComputeExtendZoom();
 | |
| 
 | |
|       CSSCoord minWidth = mMinWidth;
 | |
|       CSSCoord maxWidth = mMaxWidth;
 | |
|       CSSCoord minHeight = mMinHeight;
 | |
|       CSSCoord maxHeight = mMaxHeight;
 | |
| 
 | |
|       // aDisplaySize is in screen pixels; convert them to CSS pixels for the
 | |
|       // viewport size.
 | |
|       CSSToScreenScale defaultPixelScale =
 | |
|           layoutDeviceScale * LayoutDeviceToScreenScale(1.0f);
 | |
|       CSSSize displaySize = ScreenSize(aDisplaySize) / defaultPixelScale;
 | |
| 
 | |
|       // Resolve device-width and device-height first.
 | |
|       if (maxWidth == nsViewportInfo::DeviceSize) {
 | |
|         maxWidth = displaySize.width;
 | |
|       }
 | |
|       if (maxHeight == nsViewportInfo::DeviceSize) {
 | |
|         maxHeight = displaySize.height;
 | |
|       }
 | |
|       if (extendZoom == nsViewportInfo::Auto) {
 | |
|         if (maxWidth == nsViewportInfo::ExtendToZoom) {
 | |
|           maxWidth = nsViewportInfo::Auto;
 | |
|         }
 | |
|         if (maxHeight == nsViewportInfo::ExtendToZoom) {
 | |
|           maxHeight = nsViewportInfo::Auto;
 | |
|         }
 | |
|         if (minWidth == nsViewportInfo::ExtendToZoom) {
 | |
|           minWidth = maxWidth;
 | |
|         }
 | |
|         if (minHeight == nsViewportInfo::ExtendToZoom) {
 | |
|           minHeight = maxHeight;
 | |
|         }
 | |
|       } else {
 | |
|         CSSSize extendSize = displaySize / extendZoom;
 | |
|         if (maxWidth == nsViewportInfo::ExtendToZoom) {
 | |
|           maxWidth = extendSize.width;
 | |
|         }
 | |
|         if (maxHeight == nsViewportInfo::ExtendToZoom) {
 | |
|           maxHeight = extendSize.height;
 | |
|         }
 | |
|         if (minWidth == nsViewportInfo::ExtendToZoom) {
 | |
|           minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
 | |
|         }
 | |
|         if (minHeight == nsViewportInfo::ExtendToZoom) {
 | |
|           minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
 | |
|         }
 | |
|       }
 | |
|       // Resolve initial width and height from min/max descriptors
 | |
|       // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
 | |
|       CSSCoord width = nsViewportInfo::Auto;
 | |
|       if (minWidth != nsViewportInfo::Auto ||
 | |
|           maxWidth != nsViewportInfo::Auto) {
 | |
|         width = nsViewportInfo::Max(
 | |
|             minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
 | |
|       }
 | |
|       CSSCoord height = nsViewportInfo::Auto;
 | |
|       if (minHeight != nsViewportInfo::Auto ||
 | |
|           maxHeight != nsViewportInfo::Auto) {
 | |
|         height = nsViewportInfo::Max(
 | |
|             minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
 | |
|       }
 | |
| 
 | |
|       // Resolve width value
 | |
|       // https://drafts.csswg.org/css-device-adapt/#resolve-width
 | |
|       if (width == nsViewportInfo::Auto) {
 | |
|         if (height == nsViewportInfo::Auto || aDisplaySize.height == 0) {
 | |
|           // Stretch CSS pixel size of viewport to keep device pixel size
 | |
|           // unchanged after full zoom applied.
 | |
|           // See bug 974242.
 | |
|           width = gfxPrefs::DesktopViewportWidth() / fullZoom;
 | |
|         } else {
 | |
|           width = height * aDisplaySize.width / aDisplaySize.height;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Resolve height value
 | |
|       // https://drafts.csswg.org/css-device-adapt/#resolve-height
 | |
|       if (height == nsViewportInfo::Auto) {
 | |
|         if (aDisplaySize.width == 0) {
 | |
|           height = aDisplaySize.height;
 | |
|         } else {
 | |
|           height = width * aDisplaySize.height / aDisplaySize.width;
 | |
|         }
 | |
|       }
 | |
|       MOZ_ASSERT(width != nsViewportInfo::Auto &&
 | |
|                  height != nsViewportInfo::Auto);
 | |
| 
 | |
|       CSSSize size(width, height);
 | |
| 
 | |
|       CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
 | |
|       CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
 | |
|       CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
 | |
| 
 | |
|       nsViewportInfo::AutoSizeFlag sizeFlag =
 | |
|           nsViewportInfo::AutoSizeFlag::FixedSize;
 | |
|       if (mMaxWidth == nsViewportInfo::DeviceSize ||
 | |
|           (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::DeviceSize ||
 | |
|                               mScaleFloat.scale == 1.0f)) ||
 | |
|           (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::Auto &&
 | |
|            mMaxHeight < 0)) {
 | |
|         sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
 | |
|       }
 | |
| 
 | |
|       // FIXME: Resolving width and height should be done above 'Resolve width
 | |
|       // value' and 'Resolve height value'.
 | |
|       if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
 | |
|         size = displaySize;
 | |
|       }
 | |
| 
 | |
|       // The purpose of clamping the viewport width to a minimum size is to
 | |
|       // prevent page authors from setting it to a ridiculously small value.
 | |
|       // If the page is actually being rendered in a very small area (as might
 | |
|       // happen in e.g. Android 8's picture-in-picture mode), we don't want to
 | |
|       // prevent the viewport from taking on that size.
 | |
|       CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
 | |
| 
 | |
|       size.width = clamped(size.width, effectiveMinSize.width,
 | |
|                            float(kViewportMaxSize.width));
 | |
| 
 | |
|       // Also recalculate the default zoom, if it wasn't specified in the
 | |
|       // metadata, and the width is specified.
 | |
|       if (mScaleStrEmpty && !mWidthStrEmpty) {
 | |
|         CSSToScreenScale defaultScale(float(aDisplaySize.width) / size.width);
 | |
|         scaleFloat = (scaleFloat > defaultScale) ? scaleFloat : defaultScale;
 | |
|       }
 | |
| 
 | |
|       size.height = clamped(size.height, effectiveMinSize.height,
 | |
|                             float(kViewportMaxSize.height));
 | |
| 
 | |
|       // We need to perform a conversion, but only if the initial or maximum
 | |
|       // scale were set explicitly by the user.
 | |
|       if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
 | |
|           scaleFloat <= scaleMaxFloat) {
 | |
|         CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
 | |
|         size.width = std::max(size.width, displaySize.width);
 | |
|         size.height = std::max(size.height, displaySize.height);
 | |
|       } else if (effectiveValidMaxScale) {
 | |
|         CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
 | |
|         size.width = std::max(size.width, displaySize.width);
 | |
|         size.height = std::max(size.height, displaySize.height);
 | |
|       }
 | |
| 
 | |
|       return nsViewportInfo(
 | |
|           scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
 | |
|           mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
 | |
|                            : nsViewportInfo::AutoScaleFlag::AutoScale,
 | |
|           effectiveZoomFlag);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::UpdateViewportOverflowType(nscoord aScrolledWidth,
 | |
|                                           nscoord aScrollportWidth) {
 | |
| #ifdef DEBUG
 | |
|   MOZ_ASSERT(mPresShell);
 | |
|   nsPresContext* pc = GetPresContext();
 | |
|   MOZ_ASSERT(pc->GetViewportScrollStylesOverride().mHorizontal ==
 | |
|                  StyleOverflow::Hidden,
 | |
|              "Should only be called when viewport has overflow-x: hidden");
 | |
|   MOZ_ASSERT(aScrolledWidth > aScrollportWidth,
 | |
|              "Should only be called when viewport is overflowed");
 | |
|   MOZ_ASSERT(IsTopLevelContentDocument(),
 | |
|              "Should only be called for top-level content document");
 | |
| #endif  // DEBUG
 | |
| 
 | |
|   if (!gfxPrefs::MetaViewportEnabled() ||
 | |
|       (GetWindow() && GetWindow()->IsDesktopModeViewport())) {
 | |
|     mViewportOverflowType = ViewportOverflowType::Desktop;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mViewportType == Unknown) {
 | |
|     // The viewport info hasn't been initialized yet. Suppose we would
 | |
|     // get here again at some point after it's initialized.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   static const LayoutDeviceToScreenScale kBlinkDefaultMinScale =
 | |
|       LayoutDeviceToScreenScale(0.25f);
 | |
|   LayoutDeviceToScreenScale minScale;
 | |
|   if (mViewportType == DisplayWidthHeight) {
 | |
|     minScale = kBlinkDefaultMinScale;
 | |
|   } else {
 | |
|     if (mScaleMinFloat == kViewportMinScale) {
 | |
|       minScale = kBlinkDefaultMinScale;
 | |
|     } else {
 | |
|       minScale = mScaleMinFloat;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If the content has overflowed with minimum scale applied, don't
 | |
|   // change it, otherwise update the overflow type.
 | |
|   if (mViewportOverflowType != ViewportOverflowType::MinScaleSize) {
 | |
|     if (aScrolledWidth * minScale.scale < aScrollportWidth) {
 | |
|       mViewportOverflowType = ViewportOverflowType::ButNotMinScaleSize;
 | |
|     } else {
 | |
|       mViewportOverflowType = ViewportOverflowType::MinScaleSize;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::UpdateForScrollAnchorAdjustment(nscoord aLength) {
 | |
|   mScrollAnchorAdjustmentLength += abs(aLength);
 | |
|   mScrollAnchorAdjustmentCount += 1;
 | |
| }
 | |
| 
 | |
| EventListenerManager* Document::GetOrCreateListenerManager() {
 | |
|   if (!mListenerManager) {
 | |
|     mListenerManager =
 | |
|         new EventListenerManager(static_cast<EventTarget*>(this));
 | |
|     SetFlags(NODE_HAS_LISTENERMANAGER);
 | |
|   }
 | |
| 
 | |
|   return mListenerManager;
 | |
| }
 | |
| 
 | |
| EventListenerManager* Document::GetExistingListenerManager() const {
 | |
|   return mListenerManager;
 | |
| }
 | |
| 
 | |
| void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
 | |
|   if (mDocGroup && aVisitor.mEvent->mMessage != eVoidEvent &&
 | |
|       !mIgnoreDocGroupMismatches) {
 | |
|     mDocGroup->ValidateAccess();
 | |
|   }
 | |
| 
 | |
|   aVisitor.mCanHandle = true;
 | |
|   // FIXME! This is a hack to make middle mouse paste working also in Editor.
 | |
|   // Bug 329119
 | |
|   aVisitor.mForceContentDispatch = true;
 | |
| 
 | |
|   // Load events must not propagate to |window| object, see bug 335251.
 | |
|   if (aVisitor.mEvent->mMessage != eLoad) {
 | |
|     nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
 | |
|     aVisitor.SetParentTarget(
 | |
|         window ? window->GetTargetForEventTargetChain() : nullptr, false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
 | |
|                                               CallerType aCallerType,
 | |
|                                               ErrorResult& rv) const {
 | |
|   nsPresContext* presContext = GetPresContext();
 | |
| 
 | |
|   // Create event even without presContext.
 | |
|   RefPtr<Event> ev =
 | |
|       EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
 | |
|                                    nullptr, aEventType, aCallerType);
 | |
|   if (!ev) {
 | |
|     rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 | |
|     return nullptr;
 | |
|   }
 | |
|   WidgetEvent* e = ev->WidgetEventPtr();
 | |
|   e->mFlags.mBubbles = false;
 | |
|   e->mFlags.mCancelable = false;
 | |
|   return ev.forget();
 | |
| }
 | |
| 
 | |
| void Document::FlushPendingNotifications(FlushType aType) {
 | |
|   mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
 | |
|   FlushPendingNotifications(flush);
 | |
| }
 | |
| 
 | |
| class nsDocumentOnStack {
 | |
|  public:
 | |
|   explicit nsDocumentOnStack(Document* aDoc) : mDoc(aDoc) {
 | |
|     mDoc->IncreaseStackRefCnt();
 | |
|   }
 | |
|   ~nsDocumentOnStack() { mDoc->DecreaseStackRefCnt(); }
 | |
| 
 | |
|  private:
 | |
|   Document* mDoc;
 | |
| };
 | |
| 
 | |
| void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
 | |
|   FlushType flushType = aFlush.mFlushType;
 | |
| 
 | |
|   nsDocumentOnStack dos(this);
 | |
| 
 | |
|   // We need to flush the sink for non-HTML documents (because the XML
 | |
|   // parser still does insertion with deferred notifications).  We
 | |
|   // also need to flush the sink if this is a layout-related flush, to
 | |
|   // make sure that layout is started as needed.  But we can skip that
 | |
|   // part if we have no presshell or if it's already done an initial
 | |
|   // reflow.
 | |
|   if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
 | |
|                              mPresShell && !mPresShell->DidInitialize())) &&
 | |
|       (mParser || mWeakSink)) {
 | |
|     nsCOMPtr<nsIContentSink> sink;
 | |
|     if (mParser) {
 | |
|       sink = mParser->GetContentSink();
 | |
|     } else {
 | |
|       sink = do_QueryReferent(mWeakSink);
 | |
|       if (!sink) {
 | |
|         mWeakSink = nullptr;
 | |
|       }
 | |
|     }
 | |
|     // Determine if it is safe to flush the sink notifications
 | |
|     // by determining if it safe to flush all the presshells.
 | |
|     if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
 | |
|       sink->FlushPendingNotifications(flushType);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Should we be flushing pending binding constructors in here?
 | |
| 
 | |
|   if (flushType <= FlushType::ContentAndNotify) {
 | |
|     // Nothing to do here
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If we have a parent we must flush the parent too to ensure that our
 | |
|   // container is reflowed if its size was changed.  But if it's not safe to
 | |
|   // flush ourselves, then don't flush the parent, since that can cause things
 | |
|   // like resizes of our frame's widget, which we can't handle while flushing
 | |
|   // is unsafe.
 | |
|   // Since media queries mean that a size change of our container can
 | |
|   // affect style, we need to promote a style flush on ourself to a
 | |
|   // layout flush on our parent, since we need our container to be the
 | |
|   // correct size to determine the correct style.
 | |
|   if (mParentDocument && IsSafeToFlush()) {
 | |
|     mozilla::ChangesToFlush parentFlush = aFlush;
 | |
|     if (flushType >= FlushType::Style) {
 | |
|       parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
 | |
|     }
 | |
|     mParentDocument->FlushPendingNotifications(parentFlush);
 | |
|   }
 | |
| 
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     shell->FlushPendingNotifications(aFlush);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool Copy(Document* aDocument, void* aData) {
 | |
|   auto* resources = static_cast<nsTArray<nsCOMPtr<Document>>*>(aData);
 | |
|   resources->AppendElement(aDocument);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::FlushExternalResources(FlushType aType) {
 | |
|   NS_ASSERTION(
 | |
|       aType >= FlushType::Style,
 | |
|       "should only need to flush for style or higher in external resources");
 | |
|   if (GetDisplayDocument()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsTArray<nsCOMPtr<Document>> resources;
 | |
|   EnumerateExternalResources(Copy, &resources);
 | |
| 
 | |
|   for (uint32_t i = 0; i < resources.Length(); i++) {
 | |
|     resources[i]->FlushPendingNotifications(aType);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::SetXMLDeclaration(const char16_t* aVersion,
 | |
|                                  const char16_t* aEncoding,
 | |
|                                  const int32_t aStandalone) {
 | |
|   if (!aVersion || *aVersion == '\0') {
 | |
|     mXMLDeclarationBits = 0;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
 | |
| 
 | |
|   if (aEncoding && *aEncoding != '\0') {
 | |
|     mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
 | |
|   }
 | |
| 
 | |
|   if (aStandalone == 1) {
 | |
|     mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
 | |
|                            XML_DECLARATION_BITS_STANDALONE_YES;
 | |
|   } else if (aStandalone == 0) {
 | |
|     mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
 | |
|                                  nsAString& aStandalone) {
 | |
|   aVersion.Truncate();
 | |
|   aEncoding.Truncate();
 | |
|   aStandalone.Truncate();
 | |
| 
 | |
|   if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // always until we start supporting 1.1 etc.
 | |
|   aVersion.AssignLiteral("1.0");
 | |
| 
 | |
|   if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
 | |
|     // This is what we have stored, not necessarily what was written
 | |
|     // in the original
 | |
|     GetCharacterSet(aEncoding);
 | |
|   }
 | |
| 
 | |
|   if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
 | |
|     if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
 | |
|       aStandalone.AssignLiteral("yes");
 | |
|     } else {
 | |
|       aStandalone.AssignLiteral("no");
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool Document::IsScriptEnabled() {
 | |
|   // If this document is sandboxed without 'allow-scripts'
 | |
|   // script is not enabled
 | |
|   if (HasScriptsBlockedBySandbox()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIScriptGlobalObject> globalObject =
 | |
|       do_QueryInterface(GetInnerWindow());
 | |
|   if (!globalObject || !globalObject->GetGlobalJSObject()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return xpc::Scriptability::Get(globalObject->GetGlobalJSObject()).Allowed();
 | |
| }
 | |
| 
 | |
| void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
 | |
|   PRTime modDate = 0;
 | |
|   nsresult rv;
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel;
 | |
|   rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (httpChannel) {
 | |
|     nsAutoCString tmp;
 | |
|     rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("last-modified"),
 | |
|                                         tmp);
 | |
| 
 | |
|     if (NS_SUCCEEDED(rv)) {
 | |
|       PRTime time;
 | |
|       PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
 | |
|       if (st == PR_SUCCESS) {
 | |
|         modDate = time;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // The misspelled key 'referer' is as per the HTTP spec
 | |
|     rv =
 | |
|         httpChannel->GetRequestHeader(NS_LITERAL_CSTRING("referer"), mReferrer);
 | |
| 
 | |
|     static const char* const headers[] = {
 | |
|         "default-style", "content-style-type", "content-language",
 | |
|         "content-disposition", "refresh", "x-dns-prefetch-control",
 | |
|         "x-frame-options", "referrer-policy",
 | |
|         // add more http headers if you need
 | |
|         // XXXbz don't add content-location support without reading bug
 | |
|         // 238654 and its dependencies/dups first.
 | |
|         0};
 | |
| 
 | |
|     nsAutoCString headerVal;
 | |
|     const char* const* name = headers;
 | |
|     while (*name) {
 | |
|       rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
 | |
|       if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
 | |
|         RefPtr<nsAtom> key = NS_Atomize(*name);
 | |
|         SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
 | |
|       }
 | |
|       ++name;
 | |
|     }
 | |
|   } else {
 | |
|     nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
 | |
|     if (fileChannel) {
 | |
|       nsCOMPtr<nsIFile> file;
 | |
|       fileChannel->GetFile(getter_AddRefs(file));
 | |
|       if (file) {
 | |
|         PRTime msecs;
 | |
|         rv = file->GetLastModifiedTime(&msecs);
 | |
| 
 | |
|         if (NS_SUCCEEDED(rv)) {
 | |
|           modDate = msecs * int64_t(PR_USEC_PER_MSEC);
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       nsAutoCString contentDisp;
 | |
|       rv = aChannel->GetContentDispositionHeader(contentDisp);
 | |
|       if (NS_SUCCEEDED(rv)) {
 | |
|         SetHeaderData(nsGkAtoms::headerContentDisposition,
 | |
|                       NS_ConvertASCIItoUTF16(contentDisp));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mLastModified.Truncate();
 | |
|   if (modDate != 0) {
 | |
|     GetFormattedTimeString(modDate, mLastModified);
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
 | |
|                                                nsAtom* aPrefix,
 | |
|                                                int32_t aNamespaceID,
 | |
|                                                const nsAString* aIs) {
 | |
| #ifdef DEBUG
 | |
|   nsAutoString qName;
 | |
|   if (aPrefix) {
 | |
|     aPrefix->ToString(qName);
 | |
|     qName.Append(':');
 | |
|   }
 | |
|   qName.Append(aName);
 | |
| 
 | |
|   // Note: "a:b:c" is a valid name in non-namespaces XML, and
 | |
|   // Document::CreateElement can call us with such a name and no prefix,
 | |
|   // which would cause an error if we just used true here.
 | |
|   bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
 | |
|   NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
 | |
|                "Don't pass invalid prefixes to Document::CreateElem, "
 | |
|                "check caller.");
 | |
| #endif
 | |
| 
 | |
|   RefPtr<mozilla::dom::NodeInfo> nodeInfo;
 | |
|   mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
 | |
|                                 getter_AddRefs(nodeInfo));
 | |
|   NS_ENSURE_TRUE(nodeInfo, nullptr);
 | |
| 
 | |
|   nsCOMPtr<Element> element;
 | |
|   nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
 | |
|                               NOT_FROM_PARSER, aIs);
 | |
|   return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
 | |
| }
 | |
| 
 | |
| bool Document::IsSafeToFlush() const {
 | |
|   nsIPresShell* shell = GetShell();
 | |
|   if (!shell) return true;
 | |
| 
 | |
|   return shell->IsSafeToFlush();
 | |
| }
 | |
| 
 | |
| void Document::Sanitize() {
 | |
|   // Sanitize the document by resetting all (current and former) password fields
 | |
|   // and any form fields with autocomplete=off to their default values.  We do
 | |
|   // this now, instead of when the presentation is restored, to offer some
 | |
|   // protection in case there is ever an exploit that allows a cached document
 | |
|   // to be accessed from a different document.
 | |
| 
 | |
|   // First locate all input elements, regardless of whether they are
 | |
|   // in a form, and reset the password and autocomplete=off elements.
 | |
| 
 | |
|   RefPtr<nsContentList> nodes =
 | |
|       GetElementsByTagName(NS_LITERAL_STRING("input"));
 | |
| 
 | |
|   nsAutoString value;
 | |
| 
 | |
|   uint32_t length = nodes->Length(true);
 | |
|   for (uint32_t i = 0; i < length; ++i) {
 | |
|     NS_ASSERTION(nodes->Item(i), "null item in node list!");
 | |
| 
 | |
|     RefPtr<HTMLInputElement> input =
 | |
|         HTMLInputElement::FromNodeOrNull(nodes->Item(i));
 | |
|     if (!input) continue;
 | |
| 
 | |
|     input->GetAttribute(NS_LITERAL_STRING("autocomplete"), value);
 | |
|     if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
 | |
|       input->Reset();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Now locate all _form_ elements that have autocomplete=off and reset them
 | |
|   nodes = GetElementsByTagName(NS_LITERAL_STRING("form"));
 | |
| 
 | |
|   length = nodes->Length(true);
 | |
|   for (uint32_t i = 0; i < length; ++i) {
 | |
|     NS_ASSERTION(nodes->Item(i), "null item in nodelist");
 | |
| 
 | |
|     HTMLFormElement* form = HTMLFormElement::FromNode(nodes->Item(i));
 | |
|     if (!form) continue;
 | |
| 
 | |
|     form->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, value);
 | |
|     if (value.LowerCaseEqualsLiteral("off")) form->Reset();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback, void* aData) {
 | |
|   if (!mSubDocuments) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // PLDHashTable::Iterator can't handle modifications while iterating so we
 | |
|   // copy all entries to an array first before calling any callbacks.
 | |
|   AutoTArray<nsCOMPtr<Document>, 8> subdocs;
 | |
|   for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
 | |
|     auto entry = static_cast<SubDocMapEntry*>(iter.Get());
 | |
|     Document* subdoc = entry->mSubDocument;
 | |
|     if (subdoc) {
 | |
|       subdocs.AppendElement(subdoc);
 | |
|     }
 | |
|   }
 | |
|   for (auto subdoc : subdocs) {
 | |
|     if (!aCallback(subdoc, aData)) {
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::CollectDescendantDocuments(
 | |
|     nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
 | |
|   if (!mSubDocuments) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
 | |
|     auto entry = static_cast<SubDocMapEntry*>(iter.Get());
 | |
|     const Document* subdoc = entry->mSubDocument;
 | |
|     if (subdoc) {
 | |
|       if (aCallback(subdoc)) {
 | |
|         aDescendants.AppendElement(entry->mSubDocument);
 | |
|       }
 | |
|       subdoc->CollectDescendantDocuments(aDescendants, aCallback);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG_bryner
 | |
| #  define DEBUG_PAGE_CACHE
 | |
| #endif
 | |
| 
 | |
| bool Document::CanSavePresentation(nsIRequest* aNewRequest) {
 | |
|   if (!IsBFCachingAllowed()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (EventHandlingSuppressed()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Do not allow suspended windows to be placed in the
 | |
|   // bfcache.  This method is also used to verify a document
 | |
|   // coming out of the bfcache is ok to restore, though.  So
 | |
|   // we only want to block suspend windows that aren't also
 | |
|   // frozen.
 | |
|   nsPIDOMWindowInner* win = GetInnerWindow();
 | |
|   if (win && win->IsSuspended() && !win->IsFrozen()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Check our event listener manager for unload/beforeunload listeners.
 | |
|   nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
 | |
|   if (piTarget) {
 | |
|     EventListenerManager* manager = piTarget->GetExistingListenerManager();
 | |
|     if (manager && manager->HasUnloadListeners()) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Check if we have pending network requests
 | |
|   nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
 | |
|   if (loadGroup) {
 | |
|     nsCOMPtr<nsISimpleEnumerator> requests;
 | |
|     loadGroup->GetRequests(getter_AddRefs(requests));
 | |
| 
 | |
|     bool hasMore = false;
 | |
| 
 | |
|     // We want to bail out if we have any requests other than aNewRequest (or
 | |
|     // in the case when aNewRequest is a part of a multipart response the base
 | |
|     // channel the multipart response is coming in on).
 | |
|     nsCOMPtr<nsIChannel> baseChannel;
 | |
|     nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
 | |
|     if (part) {
 | |
|       part->GetBaseChannel(getter_AddRefs(baseChannel));
 | |
|     }
 | |
| 
 | |
|     while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
 | |
|       nsCOMPtr<nsISupports> elem;
 | |
|       requests->GetNext(getter_AddRefs(elem));
 | |
| 
 | |
|       nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
 | |
|       if (request && request != aNewRequest && request != baseChannel) {
 | |
|         // Favicon loads don't need to block caching.
 | |
|         nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
 | |
|         if (channel) {
 | |
|           nsCOMPtr<nsILoadInfo> li;
 | |
|           channel->GetLoadInfo(getter_AddRefs(li));
 | |
|           if (li) {
 | |
|             if (li->InternalContentPolicyType() ==
 | |
|                 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
 | |
|               continue;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
| #ifdef DEBUG_PAGE_CACHE
 | |
|         nsAutoCString requestName, docSpec;
 | |
|         request->GetName(requestName);
 | |
|         if (mDocumentURI) mDocumentURI->GetSpec(docSpec);
 | |
| 
 | |
|         printf("document %s has request %s\n", docSpec.get(),
 | |
|                requestName.get());
 | |
| #endif
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Check if we have active GetUserMedia use
 | |
|   if (MediaManager::Exists() && win &&
 | |
|       MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_WEBRTC
 | |
|   // Check if we have active PeerConnections
 | |
|   if (win && win->HasActivePeerConnections()) {
 | |
|     return false;
 | |
|   }
 | |
| #endif  // MOZ_WEBRTC
 | |
| 
 | |
|   // Don't save presentations for documents containing EME content, so that
 | |
|   // CDMs reliably shutdown upon user navigation.
 | |
|   if (ContainsEMEContent()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Don't save presentations for documents containing MSE content, to
 | |
|   // reduce memory usage.
 | |
|   if (ContainsMSEContent()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (mSubDocuments) {
 | |
|     for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
 | |
|       auto entry = static_cast<SubDocMapEntry*>(iter.Get());
 | |
|       Document* subdoc = entry->mSubDocument;
 | |
| 
 | |
|       // The aIgnoreRequest we were passed is only for us, so don't pass it on.
 | |
|       bool canCache = subdoc ? subdoc->CanSavePresentation(nullptr) : false;
 | |
|       if (!canCache) {
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (win) {
 | |
|     auto* globalWindow = nsGlobalWindowInner::Cast(win);
 | |
| #ifdef MOZ_WEBSPEECH
 | |
|     if (globalWindow->HasActiveSpeechSynthesis()) {
 | |
|       return false;
 | |
|     }
 | |
| #endif
 | |
|     if (globalWindow->HasUsedVR()) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::Destroy() {
 | |
|   // The ContentViewer wants to release the document now.  So, tell our content
 | |
|   // to drop any references to the document so that it can be destroyed.
 | |
|   if (mIsGoingAway) return;
 | |
| 
 | |
|   mIsGoingAway = true;
 | |
| 
 | |
|   if (mDocumentL10n) {
 | |
|     mDocumentL10n->Destroy();
 | |
|   }
 | |
| 
 | |
|   ScriptLoader()->Destroy();
 | |
|   SetScriptGlobalObject(nullptr);
 | |
|   RemovedFromDocShell();
 | |
| 
 | |
|   bool oldVal = mInUnlinkOrDeletion;
 | |
|   mInUnlinkOrDeletion = true;
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   uint32_t oldChildCount = GetChildCount();
 | |
| #endif
 | |
| 
 | |
|   for (nsIContent* child = GetFirstChild(); child;
 | |
|        child = child->GetNextSibling()) {
 | |
|     child->DestroyContent();
 | |
|     MOZ_ASSERT(child->GetParentNode() == this);
 | |
|   }
 | |
|   MOZ_ASSERT(oldChildCount == GetChildCount());
 | |
| 
 | |
|   mInUnlinkOrDeletion = oldVal;
 | |
| 
 | |
|   mLayoutHistoryState = nullptr;
 | |
| 
 | |
|   if (mOriginalDocument) {
 | |
|     mOriginalDocument->mLatestStaticClone = nullptr;
 | |
|   }
 | |
| 
 | |
|   // Shut down our external resource map.  We might not need this for
 | |
|   // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
 | |
|   // tearing down all those frame trees right now is the right thing to do.
 | |
|   mExternalResourceMap.Shutdown();
 | |
| }
 | |
| 
 | |
| void Document::RemovedFromDocShell() {
 | |
|   if (mRemovedFromDocShell) return;
 | |
| 
 | |
|   mRemovedFromDocShell = true;
 | |
|   EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 | |
| 
 | |
|   for (nsIContent* child = GetFirstChild(); child;
 | |
|        child = child->GetNextSibling()) {
 | |
|     child->SaveSubtreeState();
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
 | |
|     const {
 | |
|   nsCOMPtr<nsILayoutHistoryState> state;
 | |
|   if (!mScriptGlobalObject) {
 | |
|     state = mLayoutHistoryState;
 | |
|   } else {
 | |
|     nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
 | |
|     if (docShell) {
 | |
|       docShell->GetLayoutHistoryState(getter_AddRefs(state));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return state.forget();
 | |
| }
 | |
| 
 | |
| void Document::EnsureOnloadBlocker() {
 | |
|   // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
 | |
|   // -- it's not ours.
 | |
|   if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
 | |
|     nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
 | |
|     if (loadGroup) {
 | |
|       // Check first to see if mOnloadBlocker is in the loadgroup.
 | |
|       nsCOMPtr<nsISimpleEnumerator> requests;
 | |
|       loadGroup->GetRequests(getter_AddRefs(requests));
 | |
| 
 | |
|       bool hasMore = false;
 | |
|       while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
 | |
|         nsCOMPtr<nsISupports> elem;
 | |
|         requests->GetNext(getter_AddRefs(elem));
 | |
|         nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
 | |
|         if (request && request == mOnloadBlocker) {
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Not in the loadgroup, so add it.
 | |
|       loadGroup->AddRequest(mOnloadBlocker, nullptr);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::AsyncBlockOnload() {
 | |
|   while (mAsyncOnloadBlockCount) {
 | |
|     --mAsyncOnloadBlockCount;
 | |
|     BlockOnload();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::BlockOnload() {
 | |
|   if (mDisplayDocument) {
 | |
|     mDisplayDocument->BlockOnload();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
 | |
|   // -- it's not ours.
 | |
|   if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
 | |
|     if (!nsContentUtils::IsSafeToRunScript()) {
 | |
|       // Because AddRequest may lead to OnStateChange calls in chrome,
 | |
|       // block onload only when there are no script blockers.
 | |
|       ++mAsyncOnloadBlockCount;
 | |
|       if (mAsyncOnloadBlockCount == 1) {
 | |
|         nsContentUtils::AddScriptRunner(NewRunnableMethod(
 | |
|             "Document::AsyncBlockOnload", this, &Document::AsyncBlockOnload));
 | |
|       }
 | |
|       return;
 | |
|     }
 | |
|     nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
 | |
|     if (loadGroup) {
 | |
|       loadGroup->AddRequest(mOnloadBlocker, nullptr);
 | |
|     }
 | |
|   }
 | |
|   ++mOnloadBlockCount;
 | |
| }
 | |
| 
 | |
| void Document::UnblockOnload(bool aFireSync) {
 | |
|   if (mDisplayDocument) {
 | |
|     mDisplayDocument->UnblockOnload(aFireSync);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mOnloadBlockCount == 0 && mAsyncOnloadBlockCount == 0) {
 | |
|     MOZ_ASSERT_UNREACHABLE(
 | |
|         "More UnblockOnload() calls than BlockOnload() "
 | |
|         "calls; dropping call");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   --mOnloadBlockCount;
 | |
| 
 | |
|   if (mOnloadBlockCount == 0) {
 | |
|     if (mScriptGlobalObject) {
 | |
|       // Only manipulate the loadgroup in this case, because if
 | |
|       // mScriptGlobalObject is null, it's not ours.
 | |
|       if (aFireSync && mAsyncOnloadBlockCount == 0) {
 | |
|         // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
 | |
|         ++mOnloadBlockCount;
 | |
|         DoUnblockOnload();
 | |
|       } else {
 | |
|         PostUnblockOnloadEvent();
 | |
|       }
 | |
|     } else if (mIsBeingUsedAsImage) {
 | |
|       // To correctly unblock onload for a document that contains an SVG
 | |
|       // image, we need to know when all of the SVG document's resources are
 | |
|       // done loading, in a way comparable to |window.onload|. We fire this
 | |
|       // event to indicate that the SVG should be considered fully loaded.
 | |
|       // Because scripting is disabled on SVG-as-image documents, this event
 | |
|       // is not accessible to content authors. (See bug 837315.)
 | |
|       RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
 | |
|           this, NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"), CanBubble::eNo,
 | |
|           ChromeOnlyDispatch::eNo);
 | |
|       asyncDispatcher->PostDOMEvent();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| class nsUnblockOnloadEvent : public Runnable {
 | |
|  public:
 | |
|   explicit nsUnblockOnloadEvent(Document* aDoc)
 | |
|       : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
 | |
|   NS_IMETHOD Run() override {
 | |
|     mDoc->DoUnblockOnload();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   RefPtr<Document> mDoc;
 | |
| };
 | |
| 
 | |
| void Document::PostUnblockOnloadEvent() {
 | |
|   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
|   nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
 | |
|   nsresult rv = Dispatch(TaskCategory::Other, evt.forget());
 | |
|   if (NS_SUCCEEDED(rv)) {
 | |
|     // Stabilize block count so we don't post more events while this one is up
 | |
|     ++mOnloadBlockCount;
 | |
|   } else {
 | |
|     NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::DoUnblockOnload() {
 | |
|   MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
 | |
|   MOZ_ASSERT(mOnloadBlockCount != 0,
 | |
|              "Shouldn't have a count of zero here, since we stabilized in "
 | |
|              "PostUnblockOnloadEvent");
 | |
| 
 | |
|   --mOnloadBlockCount;
 | |
| 
 | |
|   if (mOnloadBlockCount != 0) {
 | |
|     // We blocked again after the last unblock.  Nothing to do here.  We'll
 | |
|     // post a new event when we unblock again.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mAsyncOnloadBlockCount != 0) {
 | |
|     // We need to wait until the async onload block has been handled.
 | |
|     PostUnblockOnloadEvent();
 | |
|   }
 | |
| 
 | |
|   // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
 | |
|   // -- it's not ours.
 | |
|   if (mScriptGlobalObject) {
 | |
|     nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
 | |
|     if (loadGroup) {
 | |
|       loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
 | |
|   for (nsIFrame* f = aFrame; f;
 | |
|        f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
 | |
|     nsIContent* content = f->GetContent();
 | |
|     if (!content || content->IsInAnonymousSubtree()) continue;
 | |
| 
 | |
|     if (content->OwnerDoc() == this) {
 | |
|       return content;
 | |
|     }
 | |
|     // We must be in a subdocument so jump directly to the root frame.
 | |
|     // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
 | |
|     // the containing document.
 | |
|     f = f->PresContext()->GetPresShell()->GetRootFrame();
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
 | |
|                                       const nsAString& aType, bool aPersisted,
 | |
|                                       bool aOnlySystemGroup) {
 | |
|   if (!aDispatchTarget) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   PageTransitionEventInit init;
 | |
|   init.mBubbles = true;
 | |
|   init.mCancelable = true;
 | |
|   init.mPersisted = aPersisted;
 | |
| 
 | |
|   nsDocShell* docShell = mDocumentContainer.get();
 | |
|   init.mInFrameSwap = docShell && docShell->InFrameSwap();
 | |
| 
 | |
|   RefPtr<PageTransitionEvent> event =
 | |
|       PageTransitionEvent::Constructor(this, aType, init);
 | |
| 
 | |
|   event->SetTrusted(true);
 | |
|   event->SetTarget(this);
 | |
|   if (aOnlySystemGroup) {
 | |
|     event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
 | |
|   }
 | |
|   EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
 | |
|                                     nullptr);
 | |
| }
 | |
| 
 | |
| static bool NotifyPageShow(Document* aDocument, void* aData) {
 | |
|   const bool* aPersistedPtr = static_cast<const bool*>(aData);
 | |
|   aDocument->OnPageShow(*aPersistedPtr, nullptr);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
 | |
|                           bool aOnlySystemGroup) {
 | |
|   mVisible = true;
 | |
| 
 | |
|   EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 | |
|   EnumerateExternalResources(NotifyPageShow, &aPersisted);
 | |
| 
 | |
|   Element* root = GetRootElement();
 | |
|   if (aPersisted && root) {
 | |
|     // Send out notifications that our <link> elements are attached.
 | |
|     RefPtr<nsContentList> links =
 | |
|         NS_GetContentList(root, kNameSpaceID_XHTML, NS_LITERAL_STRING("link"));
 | |
| 
 | |
|     uint32_t linkCount = links->Length(true);
 | |
|     for (uint32_t i = 0; i < linkCount; ++i) {
 | |
|       static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // See Document
 | |
|   if (!aDispatchStartTarget) {
 | |
|     // Set mIsShowing before firing events, in case those event handlers
 | |
|     // move us around.
 | |
|     mIsShowing = true;
 | |
|   }
 | |
| 
 | |
|   if (mAnimationController) {
 | |
|     mAnimationController->OnPageShow();
 | |
|   }
 | |
| 
 | |
|   if (aPersisted) {
 | |
|     ImageTracker()->SetAnimatingState(true);
 | |
|   }
 | |
| 
 | |
|   UpdateVisibilityState();
 | |
| 
 | |
|   if (!mIsBeingUsedAsImage) {
 | |
|     // Dispatch observer notification to notify observers page is shown.
 | |
|     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
 | |
|     if (os) {
 | |
|       nsIPrincipal* principal = NodePrincipal();
 | |
|       os->NotifyObservers(ToSupports(this),
 | |
|                           nsContentUtils::IsSystemPrincipal(principal)
 | |
|                               ? "chrome-page-shown"
 | |
|                               : "content-page-shown",
 | |
|                           nullptr);
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<EventTarget> target = aDispatchStartTarget;
 | |
|     if (!target) {
 | |
|       target = do_QueryInterface(GetWindow());
 | |
|     }
 | |
|     DispatchPageTransition(target, NS_LITERAL_STRING("pageshow"), aPersisted,
 | |
|                            aOnlySystemGroup);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool NotifyPageHide(Document* aDocument, void* aData) {
 | |
|   const bool* aPersistedPtr = static_cast<const bool*>(aData);
 | |
|   aDocument->OnPageHide(*aPersistedPtr, nullptr);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static void DispatchFullscreenChange(Document* aDocument, nsINode* aTarget) {
 | |
|   if (nsPresContext* presContext = aDocument->GetPresContext()) {
 | |
|     auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
 | |
|         FullscreenEventType::Change, aDocument, aTarget);
 | |
|     presContext->RefreshDriver()->ScheduleFullscreenEvent(
 | |
|         std::move(pendingEvent));
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void ClearPendingFullscreenRequests(Document* aDoc);
 | |
| 
 | |
| static bool HasHttpScheme(nsIURI* aURI) {
 | |
|   bool isHttpish = false;
 | |
|   return aURI &&
 | |
|          ((NS_SUCCEEDED(aURI->SchemeIs("http", &isHttpish)) && isHttpish) ||
 | |
|           (NS_SUCCEEDED(aURI->SchemeIs("https", &isHttpish)) && isHttpish));
 | |
| }
 | |
| 
 | |
| void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
 | |
|                           bool aOnlySystemGroup) {
 | |
|   if (IsTopLevelContentDocument() && GetDocGroup() &&
 | |
|       Telemetry::CanRecordExtended()) {
 | |
|     TabGroup* tabGroup = mDocGroup->GetTabGroup();
 | |
| 
 | |
|     if (tabGroup) {
 | |
|       uint32_t active = tabGroup->Count(true /* aActiveOnly */);
 | |
|       uint32_t total = tabGroup->Count();
 | |
| 
 | |
|       if (HasHttpScheme(GetDocumentURI())) {
 | |
|         Telemetry::Accumulate(Telemetry::ACTIVE_HTTP_DOCGROUPS_PER_TABGROUP,
 | |
|                               active);
 | |
|         Telemetry::Accumulate(Telemetry::TOTAL_HTTP_DOCGROUPS_PER_TABGROUP,
 | |
|                               total);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Send out notifications that our <link> elements are detached,
 | |
|   // but only if this is not a full unload.
 | |
|   Element* root = GetRootElement();
 | |
|   if (aPersisted && root) {
 | |
|     RefPtr<nsContentList> links =
 | |
|         NS_GetContentList(root, kNameSpaceID_XHTML, NS_LITERAL_STRING("link"));
 | |
| 
 | |
|     uint32_t linkCount = links->Length(true);
 | |
|     for (uint32_t i = 0; i < linkCount; ++i) {
 | |
|       static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // See Document
 | |
|   if (!aDispatchStartTarget) {
 | |
|     // Set mIsShowing before firing events, in case those event handlers
 | |
|     // move us around.
 | |
|     mIsShowing = false;
 | |
|   }
 | |
| 
 | |
|   if (mAnimationController) {
 | |
|     mAnimationController->OnPageHide();
 | |
|   }
 | |
| 
 | |
|   // We do not stop the animations (bug 1024343)
 | |
|   // when the page is refreshing while being dragged out
 | |
|   nsDocShell* docShell = mDocumentContainer.get();
 | |
|   if (aPersisted && !(docShell && docShell->InFrameSwap())) {
 | |
|     ImageTracker()->SetAnimatingState(false);
 | |
|   }
 | |
| 
 | |
|   ExitPointerLock();
 | |
| 
 | |
|   if (!mIsBeingUsedAsImage) {
 | |
|     // Dispatch observer notification to notify observers page is hidden.
 | |
|     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
 | |
|     if (os) {
 | |
|       nsIPrincipal* principal = NodePrincipal();
 | |
|       os->NotifyObservers(ToSupports(this),
 | |
|                           nsContentUtils::IsSystemPrincipal(principal)
 | |
|                               ? "chrome-page-hidden"
 | |
|                               : "content-page-hidden",
 | |
|                           nullptr);
 | |
|     }
 | |
| 
 | |
|     // Now send out a PageHide event.
 | |
|     nsCOMPtr<EventTarget> target = aDispatchStartTarget;
 | |
|     if (!target) {
 | |
|       target = do_QueryInterface(GetWindow());
 | |
|     }
 | |
|     {
 | |
|       PageUnloadingEventTimeStamp timeStamp(this);
 | |
|       DispatchPageTransition(target, NS_LITERAL_STRING("pagehide"), aPersisted,
 | |
|                              aOnlySystemGroup);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mVisible = false;
 | |
| 
 | |
|   UpdateVisibilityState();
 | |
| 
 | |
|   EnumerateExternalResources(NotifyPageHide, &aPersisted);
 | |
|   EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 | |
| 
 | |
|   ClearPendingFullscreenRequests(this);
 | |
|   if (FullscreenStackTop()) {
 | |
|     // If this document was fullscreen, we should exit fullscreen in this
 | |
|     // doctree branch. This ensures that if the user navigates while in
 | |
|     // fullscreen mode we don't leave its still visible ancestor documents
 | |
|     // in fullscreen mode. So exit fullscreen in the document's fullscreen
 | |
|     // root document, as this will exit fullscreen in all the root's
 | |
|     // descendant documents. Note that documents are removed from the
 | |
|     // doctree by the time OnPageHide() is called, so we must store a
 | |
|     // reference to the root (in Document::mFullscreenRoot) since we can't
 | |
|     // just traverse the doctree to get the root.
 | |
|     Document::ExitFullscreenInDocTree(this);
 | |
| 
 | |
|     // Since the document is removed from the doctree before OnPageHide() is
 | |
|     // called, ExitFullscreen() can't traverse from the root down to *this*
 | |
|     // document, so we must manually call CleanupFullscreenState() below too.
 | |
|     // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
 | |
|     // so we *must* call it after ExitFullscreen(), not before.
 | |
|     // OnPageHide() is called in every hidden (i.e. descendant) document,
 | |
|     // so calling CleanupFullscreenState() here will ensure all hidden
 | |
|     // documents have their fullscreen state reset.
 | |
|     CleanupFullscreenState();
 | |
| 
 | |
|     // The fullscreenchange event is to be queued in the refresh driver,
 | |
|     // however a hidden page wouldn't trigger that again, so it makes no
 | |
|     // sense to dispatch such event here.
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::WillDispatchMutationEvent(nsINode* aTarget) {
 | |
|   NS_ASSERTION(
 | |
|       mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
 | |
|       "mSubtreeModifiedTargets not cleared after dispatching?");
 | |
|   ++mSubtreeModifiedDepth;
 | |
|   if (aTarget) {
 | |
|     // MayDispatchMutationEvent is often called just before this method,
 | |
|     // so it has already appended the node to mSubtreeModifiedTargets.
 | |
|     int32_t count = mSubtreeModifiedTargets.Count();
 | |
|     if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
 | |
|       mSubtreeModifiedTargets.AppendObject(aTarget);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::MutationEventDispatched(nsINode* aTarget) {
 | |
|   --mSubtreeModifiedDepth;
 | |
|   if (mSubtreeModifiedDepth == 0) {
 | |
|     int32_t count = mSubtreeModifiedTargets.Count();
 | |
|     if (!count) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     nsPIDOMWindowInner* window = GetInnerWindow();
 | |
|     if (window &&
 | |
|         !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
 | |
|       mSubtreeModifiedTargets.Clear();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     nsCOMArray<nsINode> realTargets;
 | |
|     for (int32_t i = 0; i < count; ++i) {
 | |
|       nsINode* possibleTarget = mSubtreeModifiedTargets[i];
 | |
|       nsCOMPtr<nsIContent> content = do_QueryInterface(possibleTarget);
 | |
|       if (content && content->ChromeOnlyAccess()) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       nsINode* commonAncestor = nullptr;
 | |
|       int32_t realTargetCount = realTargets.Count();
 | |
|       for (int32_t j = 0; j < realTargetCount; ++j) {
 | |
|         commonAncestor =
 | |
|             nsContentUtils::GetCommonAncestor(possibleTarget, realTargets[j]);
 | |
|         if (commonAncestor) {
 | |
|           realTargets.ReplaceObjectAt(commonAncestor, j);
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       if (!commonAncestor) {
 | |
|         realTargets.AppendObject(possibleTarget);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     mSubtreeModifiedTargets.Clear();
 | |
| 
 | |
|     int32_t realTargetCount = realTargets.Count();
 | |
|     for (int32_t k = 0; k < realTargetCount; ++k) {
 | |
|       InternalMutationEvent mutation(true, eLegacySubtreeModified);
 | |
|       (new AsyncEventDispatcher(realTargets[k], mutation))
 | |
|           ->RunDOMEventWhenSafe();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::DestroyElementMaps() {
 | |
| #ifdef DEBUG
 | |
|   mStyledLinksCleared = true;
 | |
| #endif
 | |
|   mStyledLinks.Clear();
 | |
|   mIdentifierMap.Clear();
 | |
|   mComposedShadowRoots.Clear();
 | |
|   mResponsiveContent.Clear();
 | |
|   IncrementExpandoGeneration(*this);
 | |
| }
 | |
| 
 | |
| void Document::RefreshLinkHrefs() {
 | |
|   // Get a list of all links we know about.  We will reset them, which will
 | |
|   // remove them from the document, so we need a copy of what is in the
 | |
|   // hashtable.
 | |
|   LinkArray linksToNotify(mStyledLinks.Count());
 | |
|   for (auto iter = mStyledLinks.ConstIter(); !iter.Done(); iter.Next()) {
 | |
|     linksToNotify.AppendElement(iter.Get()->GetKey());
 | |
|   }
 | |
| 
 | |
|   // Reset all of our styled links.
 | |
|   nsAutoScriptBlocker scriptBlocker;
 | |
|   for (LinkArray::size_type i = 0; i < linksToNotify.Length(); i++) {
 | |
|     linksToNotify[i]->ResetLinkState(true, linksToNotify[i]->ElementHasHref());
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult Document::CloneDocHelper(Document* clone) const {
 | |
|   clone->mIsStaticDocument = mCreatingStaticClone;
 | |
| 
 | |
|   // Init document
 | |
|   nsresult rv = clone->Init();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (mCreatingStaticClone) {
 | |
|     nsCOMPtr<nsILoadGroup> loadGroup;
 | |
| 
 | |
|     // |mDocumentContainer| is the container of the document that is being
 | |
|     // created and not the original container. See CreateStaticClone function().
 | |
|     nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
 | |
|     if (docLoader) {
 | |
|       docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
 | |
|     }
 | |
|     nsCOMPtr<nsIChannel> channel = GetChannel();
 | |
|     nsCOMPtr<nsIURI> uri;
 | |
|     if (channel) {
 | |
|       NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
 | |
|     } else {
 | |
|       uri = Document::GetDocumentURI();
 | |
|     }
 | |
|     clone->mChannel = channel;
 | |
|     if (uri) {
 | |
|       clone->ResetToURI(uri, loadGroup, NodePrincipal());
 | |
|     }
 | |
| 
 | |
|     clone->SetContainer(mDocumentContainer);
 | |
|   }
 | |
| 
 | |
|   // Now ensure that our clone has the same URI, base URI, and principal as us.
 | |
|   // We do this after the mCreatingStaticClone block above, because that block
 | |
|   // can set the base URI to an incorrect value in cases when base URI
 | |
|   // information came from the channel.  So we override explicitly, and do it
 | |
|   // for all these properties, in case ResetToURI messes with any of the rest of
 | |
|   // them.
 | |
|   clone->SetDocumentURI(Document::GetDocumentURI());
 | |
|   clone->SetChromeXHRDocURI(mChromeXHRDocURI);
 | |
|   clone->SetPrincipal(NodePrincipal());
 | |
|   clone->mDocumentBaseURI = mDocumentBaseURI;
 | |
|   clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
 | |
| 
 | |
|   bool hasHadScriptObject = true;
 | |
|   nsIScriptGlobalObject* scriptObject =
 | |
|       GetScriptHandlingObject(hasHadScriptObject);
 | |
|   NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
 | |
|   if (mCreatingStaticClone) {
 | |
|     // If we're doing a static clone (print, print preview), then we're going to
 | |
|     // be setting a scope object after the clone. It's better to set it only
 | |
|     // once, so we don't do that here. However, we do want to act as if there is
 | |
|     // a script handling object. So we set mHasHadScriptHandlingObject.
 | |
|     clone->mHasHadScriptHandlingObject = true;
 | |
|   } else if (scriptObject) {
 | |
|     clone->SetScriptHandlingObject(scriptObject);
 | |
|   } else {
 | |
|     clone->SetScopeObject(GetScopeObject());
 | |
|   }
 | |
|   // Make the clone a data document
 | |
|   clone->SetLoadedAsData(true);
 | |
| 
 | |
|   // Misc state
 | |
| 
 | |
|   // State from Document
 | |
|   clone->mCharacterSet = mCharacterSet;
 | |
|   clone->mCharacterSetSource = mCharacterSetSource;
 | |
|   clone->mCompatMode = mCompatMode;
 | |
|   clone->mBidiOptions = mBidiOptions;
 | |
|   clone->mContentLanguage = mContentLanguage;
 | |
|   clone->SetContentTypeInternal(GetContentTypeInternal());
 | |
|   clone->mSecurityInfo = mSecurityInfo;
 | |
| 
 | |
|   // State from Document
 | |
|   clone->mType = mType;
 | |
|   clone->mXMLDeclarationBits = mXMLDeclarationBits;
 | |
|   clone->mBaseTarget = mBaseTarget;
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| static bool SetLoadingInSubDocument(Document* aDocument, void* aData) {
 | |
|   aDocument->SetAncestorLoading(*(static_cast<bool*>(aData)));
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::SetAncestorLoading(bool aAncestorIsLoading) {
 | |
|   NotifyLoading(mAncestorIsLoading, aAncestorIsLoading, mReadyState,
 | |
|                 mReadyState);
 | |
|   mAncestorIsLoading = aAncestorIsLoading;
 | |
| }
 | |
| 
 | |
| void Document::NotifyLoading(const bool& aCurrentParentIsLoading,
 | |
|                              bool aNewParentIsLoading,
 | |
|                              const ReadyState& aCurrentState,
 | |
|                              ReadyState aNewState) {
 | |
|   // Mirror the top-level loading state down to all subdocuments
 | |
|   bool was_loading = aCurrentParentIsLoading ||
 | |
|                      aCurrentState == READYSTATE_LOADING ||
 | |
|                      aCurrentState == READYSTATE_INTERACTIVE;
 | |
|   bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
 | |
|                     aNewState == READYSTATE_INTERACTIVE;  // new value for state
 | |
|   bool set_load_state = was_loading != is_loading;
 | |
| 
 | |
|   if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
 | |
|     nsPIDOMWindowInner* inner = GetInnerWindow();
 | |
|     if (inner) {
 | |
|       inner->SetActiveLoadingState(is_loading);
 | |
|     }
 | |
|     EnumerateSubDocuments(SetLoadingInSubDocument, &is_loading);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::SetReadyStateInternal(ReadyState rs) {
 | |
|   if (rs == READYSTATE_UNINITIALIZED) {
 | |
|     // Transition back to uninitialized happens only to keep assertions happy
 | |
|     // right before readyState transitions to something else. Make this
 | |
|     // transition undetectable by Web content.
 | |
|     mReadyState = rs;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (READYSTATE_LOADING == rs) {
 | |
|     mLoadingTimeStamp = mozilla::TimeStamp::Now();
 | |
|   }
 | |
|   NotifyLoading(mAncestorIsLoading, mAncestorIsLoading, mReadyState, rs);
 | |
|   mReadyState = rs;
 | |
|   if (mTiming) {
 | |
|     switch (rs) {
 | |
|       case READYSTATE_LOADING:
 | |
|         mTiming->NotifyDOMLoading(Document::GetDocumentURI());
 | |
|         break;
 | |
|       case READYSTATE_INTERACTIVE:
 | |
|         mTiming->NotifyDOMInteractive(Document::GetDocumentURI());
 | |
|         break;
 | |
|       case READYSTATE_COMPLETE:
 | |
|         mTiming->NotifyDOMComplete(Document::GetDocumentURI());
 | |
|         break;
 | |
|       default:
 | |
|         NS_WARNING("Unexpected ReadyState value");
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
|   // At the time of loading start, we don't have timing object, record time.
 | |
| 
 | |
|   if (READYSTATE_INTERACTIVE == rs) {
 | |
|     if (nsContentUtils::IsSystemPrincipal(NodePrincipal())) {
 | |
|       Element* root = GetRootElement();
 | |
|       if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozpersist)) {
 | |
|         mXULPersist = new XULPersist(this);
 | |
|         mXULPersist->Init();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   RecordNavigationTiming(rs);
 | |
| 
 | |
|   RefPtr<AsyncEventDispatcher> asyncDispatcher =
 | |
|       new AsyncEventDispatcher(this, NS_LITERAL_STRING("readystatechange"),
 | |
|                                CanBubble::eNo, ChromeOnlyDispatch::eNo);
 | |
|   asyncDispatcher->RunDOMEventWhenSafe();
 | |
| }
 | |
| 
 | |
| void Document::GetReadyState(nsAString& aReadyState) const {
 | |
|   switch (mReadyState) {
 | |
|     case READYSTATE_LOADING:
 | |
|       aReadyState.AssignLiteral(u"loading");
 | |
|       break;
 | |
|     case READYSTATE_INTERACTIVE:
 | |
|       aReadyState.AssignLiteral(u"interactive");
 | |
|       break;
 | |
|     case READYSTATE_COMPLETE:
 | |
|       aReadyState.AssignLiteral(u"complete");
 | |
|       break;
 | |
|     default:
 | |
|       aReadyState.AssignLiteral(u"uninitialized");
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool SuppressEventHandlingInDocument(Document* aDocument, void* aData) {
 | |
|   aDocument->SuppressEventHandling(*static_cast<uint32_t*>(aData));
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::SuppressEventHandling(uint32_t aIncrease) {
 | |
|   mEventsSuppressed += aIncrease;
 | |
|   UpdateFrameRequestCallbackSchedulingState();
 | |
|   for (uint32_t i = 0; i < aIncrease; ++i) {
 | |
|     ScriptLoader()->AddExecuteBlocker();
 | |
|   }
 | |
| 
 | |
|   EnumerateSubDocuments(SuppressEventHandlingInDocument, &aIncrease);
 | |
| }
 | |
| 
 | |
| static void FireOrClearDelayedEvents(nsTArray<nsCOMPtr<Document>>& aDocuments,
 | |
|                                      bool aFireEvents) {
 | |
|   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
 | |
|   if (!fm) return;
 | |
| 
 | |
|   for (uint32_t i = 0; i < aDocuments.Length(); ++i) {
 | |
|     // NB: Don't bother trying to fire delayed events on documents that were
 | |
|     // closed before this event ran.
 | |
|     if (!aDocuments[i]->EventHandlingSuppressed()) {
 | |
|       fm->FireDelayedEvents(aDocuments[i]);
 | |
|       nsCOMPtr<nsIPresShell> shell = aDocuments[i]->GetShell();
 | |
|       if (shell) {
 | |
|         // Only fire events for active documents.
 | |
|         bool fire = aFireEvents && aDocuments[i]->GetInnerWindow() &&
 | |
|                     aDocuments[i]->GetInnerWindow()->IsCurrentInnerWindow();
 | |
|         shell->FireOrClearDelayedEvents(fire);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::PreloadPictureClosed() {
 | |
|   MOZ_ASSERT(mPreloadPictureDepth > 0);
 | |
|   mPreloadPictureDepth--;
 | |
|   if (mPreloadPictureDepth == 0) {
 | |
|     mPreloadPictureFoundSource.SetIsVoid(true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
 | |
|                                          const nsAString& aSizesAttr,
 | |
|                                          const nsAString& aTypeAttr,
 | |
|                                          const nsAString& aMediaAttr) {
 | |
|   // Nested pictures are not valid syntax, so while we'll eventually load them,
 | |
|   // it's not worth tracking sources mixed between nesting levels to preload
 | |
|   // them effectively.
 | |
|   if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
 | |
|     // <picture> selects the first matching source, so if this returns a URI we
 | |
|     // needn't consider new sources until a new <picture> is encountered.
 | |
|     bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
 | |
|         this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
 | |
|         aMediaAttr, mPreloadPictureFoundSource);
 | |
|     if (found && mPreloadPictureFoundSource.IsVoid()) {
 | |
|       // Found an empty source, which counts
 | |
|       mPreloadPictureFoundSource.SetIsVoid(false);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIURI> Document::ResolvePreloadImage(
 | |
|     nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
 | |
|     const nsAString& aSizesAttr, bool* aIsImgSet) {
 | |
|   nsString sourceURL;
 | |
|   bool isImgSet;
 | |
|   if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
 | |
|     // We're in a <picture> element and found a URI from a source previous to
 | |
|     // this image, use it.
 | |
|     sourceURL = mPreloadPictureFoundSource;
 | |
|     isImgSet = true;
 | |
|   } else {
 | |
|     // Otherwise try to use this <img> as a source
 | |
|     HTMLImageElement::SelectSourceForTagWithAttrs(
 | |
|         this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
 | |
|         VoidString(), sourceURL);
 | |
|     isImgSet = !aSrcsetAttr.IsEmpty();
 | |
|   }
 | |
| 
 | |
|   // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
 | |
|   if (sourceURL.IsEmpty()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Construct into URI using passed baseURI (the parser may know of base URI
 | |
|   // changes that have not reached us)
 | |
|   nsresult rv;
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
 | |
|                                                  this, aBaseURI);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   *aIsImgSet = isImgSet;
 | |
| 
 | |
|   // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
 | |
|   // this this <picture> share the same <sources> (though this is not valid per
 | |
|   // spec)
 | |
|   return uri.forget();
 | |
| }
 | |
| 
 | |
| void Document::MaybePreLoadImage(
 | |
|     nsIURI* uri, const nsAString& aCrossOriginAttr,
 | |
|     enum mozilla::net::ReferrerPolicy aReferrerPolicy, bool aIsImgSet) {
 | |
|   // Early exit if the img is already present in the img-cache
 | |
|   // which indicates that the "real" load has already started and
 | |
|   // that we shouldn't preload it.
 | |
|   if (nsContentUtils::IsImageInCache(uri, this)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
 | |
|                           nsContentUtils::CORSModeToLoadImageFlags(
 | |
|                               Element::StringToCORSMode(aCrossOriginAttr));
 | |
| 
 | |
|   nsContentPolicyType policyType =
 | |
|       aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
 | |
|                 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
 | |
| 
 | |
|   // Image not in cache - trigger preload
 | |
|   RefPtr<imgRequestProxy> request;
 | |
|   nsresult rv = nsContentUtils::LoadImage(
 | |
|       uri, static_cast<nsINode*>(this), this, NodePrincipal(), 0,
 | |
|       GetDocumentURIAsReferrer(),  // uri of document used as referrer
 | |
|       aReferrerPolicy,
 | |
|       nullptr,  // no observer
 | |
|       loadFlags, NS_LITERAL_STRING("img"), getter_AddRefs(request), policyType);
 | |
| 
 | |
|   // Pin image-reference to avoid evicting it from the img-cache before
 | |
|   // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
 | |
|   // unlink
 | |
|   if (NS_SUCCEEDED(rv)) {
 | |
|     mPreloadingImages.Put(uri, request.forget());
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
 | |
|   NS_MutateURI mutator(aOrigURI);
 | |
|   if (NS_FAILED(mutator.GetStatus())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
 | |
|   // which ignores the path and uses only the origin. The other is for the
 | |
|   // document mPreloadedPreconnects de-duplication hash. Anonymous vs
 | |
|   // non-Anonymous preconnects create different connections on the wire and
 | |
|   // therefore should not be considred duplicates of each other and we
 | |
|   // normalize the path before putting it in the hash to accomplish that.
 | |
| 
 | |
|   if (aCORSMode == CORS_ANONYMOUS) {
 | |
|     mutator.SetPathQueryRef(NS_LITERAL_CSTRING("/anonymous"));
 | |
|   } else {
 | |
|     mutator.SetPathQueryRef(NS_LITERAL_CSTRING("/"));
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   nsresult rv = mutator.Finalize(uri);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto entry = mPreloadedPreconnects.LookupForAdd(uri);
 | |
|   if (entry) {
 | |
|     return;  // we found an existing entry
 | |
|   }
 | |
|   entry.OrInsert([]() { return true; });
 | |
| 
 | |
|   nsCOMPtr<nsISpeculativeConnect> speculator(
 | |
|       do_QueryInterface(nsContentUtils::GetIOService()));
 | |
|   if (!speculator) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aCORSMode == CORS_ANONYMOUS) {
 | |
|     speculator->SpeculativeAnonymousConnect2(uri, NodePrincipal(), nullptr);
 | |
|   } else {
 | |
|     speculator->SpeculativeConnect2(uri, NodePrincipal(), nullptr);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::ForgetImagePreload(nsIURI* aURI) {
 | |
|   // Checking count is faster than hashing the URI in the common
 | |
|   // case of empty table.
 | |
|   if (mPreloadingImages.Count() != 0) {
 | |
|     nsCOMPtr<imgIRequest> req;
 | |
|     mPreloadingImages.Remove(aURI, getter_AddRefs(req));
 | |
|     if (req) {
 | |
|       // Make sure to cancel the request so imagelib knows it's gone.
 | |
|       req->CancelAndForgetObserver(NS_BINDING_ABORTED);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::UpdateDocumentStates(EventStates aChangedStates) {
 | |
|   if (aChangedStates.HasState(NS_DOCUMENT_STATE_RTL_LOCALE)) {
 | |
|     if (IsDocumentRightToLeft()) {
 | |
|       mDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
 | |
|     } else {
 | |
|       mDocumentState &= ~NS_DOCUMENT_STATE_RTL_LOCALE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aChangedStates.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
 | |
|     if (IsTopLevelWindowInactive()) {
 | |
|       mDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
 | |
|     } else {
 | |
|       mDocumentState &= ~NS_DOCUMENT_STATE_WINDOW_INACTIVE;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| /**
 | |
|  * Stub for LoadSheet(), since all we want is to get the sheet into
 | |
|  * the CSSLoader's style cache
 | |
|  */
 | |
| class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
 | |
|   ~StubCSSLoaderObserver() {}
 | |
| 
 | |
|  public:
 | |
|   NS_IMETHOD
 | |
|   StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
 | |
|   NS_DECL_ISUPPORTS
 | |
| };
 | |
| NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| void Document::PreloadStyle(
 | |
|     nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
 | |
|     const enum mozilla::net::ReferrerPolicy aReferrerPolicy,
 | |
|     const nsAString& aIntegrity) {
 | |
|   // The CSSLoader will retain this object after we return.
 | |
|   nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
 | |
| 
 | |
|   // Charset names are always ASCII.
 | |
|   CSSLoader()->LoadSheet(uri, true, NodePrincipal(), aEncoding, obs,
 | |
|                          Element::StringToCORSMode(aCrossOriginAttr),
 | |
|                          aReferrerPolicy, aIntegrity);
 | |
| }
 | |
| 
 | |
| nsresult Document::LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
 | |
|                                        RefPtr<mozilla::StyleSheet>* aSheet) {
 | |
|   css::SheetParsingMode mode =
 | |
|       isAgentSheet ? css::eAgentSheetFeatures : css::eAuthorSheetFeatures;
 | |
|   return CSSLoader()->LoadSheetSync(uri, mode, isAgentSheet, aSheet);
 | |
| }
 | |
| 
 | |
| class nsDelayedEventDispatcher : public Runnable {
 | |
|  public:
 | |
|   explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>& aDocuments)
 | |
|       : mozilla::Runnable("nsDelayedEventDispatcher") {
 | |
|     mDocuments.SwapElements(aDocuments);
 | |
|   }
 | |
|   virtual ~nsDelayedEventDispatcher() {}
 | |
| 
 | |
|   NS_IMETHOD Run() override {
 | |
|     FireOrClearDelayedEvents(mDocuments, true);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   nsTArray<nsCOMPtr<Document>> mDocuments;
 | |
| };
 | |
| 
 | |
| static bool GetAndUnsuppressSubDocuments(Document* aDocument, void* aData) {
 | |
|   if (aDocument->EventHandlingSuppressed() > 0) {
 | |
|     aDocument->DecreaseEventSuppression();
 | |
|     aDocument->ScriptLoader()->RemoveExecuteBlocker();
 | |
|   }
 | |
| 
 | |
|   auto* docs = static_cast<nsTArray<nsCOMPtr<Document>>*>(aData);
 | |
| 
 | |
|   docs->AppendElement(aDocument);
 | |
|   aDocument->EnumerateSubDocuments(GetAndUnsuppressSubDocuments, aData);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
 | |
|   nsTArray<nsCOMPtr<Document>> documents;
 | |
|   GetAndUnsuppressSubDocuments(this, &documents);
 | |
| 
 | |
|   if (aFireEvents) {
 | |
|     MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
|     nsCOMPtr<nsIRunnable> ded = new nsDelayedEventDispatcher(documents);
 | |
|     Dispatch(TaskCategory::Other, ded.forget());
 | |
|   } else {
 | |
|     FireOrClearDelayedEvents(documents, false);
 | |
|   }
 | |
| 
 | |
|   if (!EventHandlingSuppressed()) {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     nsTArray<RefPtr<net::ChannelEventQueue>> queues;
 | |
|     mSuspendedQueues.SwapElements(queues);
 | |
|     for (net::ChannelEventQueue* queue : queues) {
 | |
|       queue->Resume();
 | |
|     }
 | |
| 
 | |
|     // If there have been any events driven by the refresh driver which were
 | |
|     // delayed due to events being suppressed in this document, make sure there
 | |
|     // is a refresh scheduled soon so the events will run.
 | |
|     if (mHasDelayedRefreshEvent) {
 | |
|       mHasDelayedRefreshEvent = false;
 | |
| 
 | |
|       if (mPresShell) {
 | |
|         nsRefreshDriver* rd = mPresShell->GetPresContext()->RefreshDriver();
 | |
|         rd->RunDelayedEventsSoon();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(EventHandlingSuppressed());
 | |
|   mSuspendedQueues.AppendElement(aQueue);
 | |
| }
 | |
| 
 | |
| static bool SetSuppressedEventListenerInSubDocument(Document* aDocument,
 | |
|                                                     void* aData) {
 | |
|   aDocument->SetSuppressedEventListener(static_cast<EventListener*>(aData));
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::SetSuppressedEventListener(EventListener* aListener) {
 | |
|   mSuppressedEventListener = aListener;
 | |
|   EnumerateSubDocuments(SetSuppressedEventListenerInSubDocument, aListener);
 | |
| }
 | |
| 
 | |
| nsISupports* Document::GetCurrentContentSink() {
 | |
|   return mParser ? mParser->GetContentSink() : nullptr;
 | |
| }
 | |
| 
 | |
| Document* Document::GetTemplateContentsOwner() {
 | |
|   if (!mTemplateContentsOwner) {
 | |
|     bool hasHadScriptObject = true;
 | |
|     nsIScriptGlobalObject* scriptObject =
 | |
|         GetScriptHandlingObject(hasHadScriptObject);
 | |
| 
 | |
|     nsCOMPtr<Document> document;
 | |
|     nsresult rv = NS_NewDOMDocument(getter_AddRefs(document),
 | |
|                                     EmptyString(),  // aNamespaceURI
 | |
|                                     EmptyString(),  // aQualifiedName
 | |
|                                     nullptr,        // aDoctype
 | |
|                                     Document::GetDocumentURI(),
 | |
|                                     Document::GetDocBaseURI(), NodePrincipal(),
 | |
|                                     true,          // aLoadedAsData
 | |
|                                     scriptObject,  // aEventObject
 | |
|                                     DocumentFlavorHTML);
 | |
|     NS_ENSURE_SUCCESS(rv, nullptr);
 | |
| 
 | |
|     mTemplateContentsOwner = document;
 | |
|     NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
 | |
| 
 | |
|     if (!scriptObject) {
 | |
|       mTemplateContentsOwner->SetScopeObject(GetScopeObject());
 | |
|     }
 | |
| 
 | |
|     mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
 | |
| 
 | |
|     // Set |mTemplateContentsOwner| as the template contents owner of itself so
 | |
|     // that it is the template contents owner of nested template elements.
 | |
|     mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
 | |
|   }
 | |
| 
 | |
|   return mTemplateContentsOwner;
 | |
| }
 | |
| 
 | |
| static already_AddRefed<nsPIDOMWindowOuter> FindTopWindowForElement(
 | |
|     Element* element) {
 | |
|   Document* document = element->OwnerDoc();
 | |
|   if (!document) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> window = document->GetWindow();
 | |
|   if (!window) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Trying to find the top window (equivalent to window.top).
 | |
|   if (nsCOMPtr<nsPIDOMWindowOuter> top = window->GetTop()) {
 | |
|     window = top.forget();
 | |
|   }
 | |
|   return window.forget();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * nsAutoFocusEvent is used to dispatch a focus event for an
 | |
|  * nsGenericHTMLFormElement with the autofocus attribute enabled.
 | |
|  */
 | |
| class nsAutoFocusEvent : public Runnable {
 | |
|  public:
 | |
|   explicit nsAutoFocusEvent(already_AddRefed<Element>&& aElement,
 | |
|                             already_AddRefed<nsPIDOMWindowOuter>&& aTopWindow)
 | |
|       : mozilla::Runnable("nsAutoFocusEvent"),
 | |
|         mElement(aElement),
 | |
|         mTopWindow(aTopWindow) {}
 | |
| 
 | |
|   NS_IMETHOD Run() override {
 | |
|     nsCOMPtr<nsPIDOMWindowOuter> currentTopWindow =
 | |
|         FindTopWindowForElement(mElement);
 | |
|     if (currentTopWindow != mTopWindow) {
 | |
|       // The element's top window changed from when the event was queued.
 | |
|       // Don't take away focus from an unrelated window.
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     // Don't steal focus from the user.
 | |
|     if (mTopWindow->GetFocusedElement()) {
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     mozilla::ErrorResult rv;
 | |
|     mElement->Focus(rv);
 | |
|     return rv.StealNSResult();
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   nsCOMPtr<Element> mElement;
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> mTopWindow;
 | |
| };
 | |
| 
 | |
| void Document::SetAutoFocusElement(Element* aAutoFocusElement) {
 | |
|   if (mAutoFocusFired) {
 | |
|     // Too late.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mAutoFocusElement) {
 | |
|     // The spec disallows multiple autofocus elements, so we consider only the
 | |
|     // first one to preserve the old behavior.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mAutoFocusElement = do_GetWeakReference(aAutoFocusElement);
 | |
|   TriggerAutoFocus();
 | |
| }
 | |
| 
 | |
| void Document::TriggerAutoFocus() {
 | |
|   if (mAutoFocusFired) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mPresShell || !mPresShell->DidInitialize()) {
 | |
|     // Delay autofocus until frames are constructed so that we don't thrash
 | |
|     // style and layout calculations.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Element> autoFocusElement = do_QueryReferent(mAutoFocusElement);
 | |
|   if (autoFocusElement && autoFocusElement->OwnerDoc() == this) {
 | |
|     mAutoFocusFired = true;
 | |
| 
 | |
|     nsCOMPtr<nsPIDOMWindowOuter> topWindow =
 | |
|         FindTopWindowForElement(autoFocusElement);
 | |
|     if (!topWindow) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // NOTE: This may be removed in the future since the spec technically
 | |
|     // allows autofocus after load.
 | |
|     nsCOMPtr<Document> topDoc = topWindow->GetExtantDoc();
 | |
|     if (topDoc &&
 | |
|         topDoc->GetReadyStateEnum() == Document::READYSTATE_COMPLETE) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsIRunnable> event =
 | |
|         new nsAutoFocusEvent(autoFocusElement.forget(), topWindow.forget());
 | |
|     nsresult rv = NS_DispatchToCurrentThread(event.forget());
 | |
|     NS_ENSURE_SUCCESS_VOID(rv);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::SetScrollToRef(nsIURI* aDocumentURI) {
 | |
|   if (!aDocumentURI) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString ref;
 | |
| 
 | |
|   // Since all URI's that pass through here aren't URL's we can't
 | |
|   // rely on the nsIURI implementation for providing a way for
 | |
|   // finding the 'ref' part of the URI, we'll haveto revert to
 | |
|   // string routines for finding the data past '#'
 | |
| 
 | |
|   nsresult rv = aDocumentURI->GetSpec(ref);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     Unused << aDocumentURI->GetRef(mScrollToRef);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsReadingIterator<char> start, end;
 | |
| 
 | |
|   ref.BeginReading(start);
 | |
|   ref.EndReading(end);
 | |
| 
 | |
|   if (FindCharInReadable('#', start, end)) {
 | |
|     ++start;  // Skip over the '#'
 | |
| 
 | |
|     mScrollToRef = Substring(start, end);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::ScrollToRef() {
 | |
|   if (mScrolledToRefAlready) {
 | |
|     nsCOMPtr<nsIPresShell> shell = GetShell();
 | |
|     if (shell) {
 | |
|       shell->ScrollToAnchor();
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mScrollToRef.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIPresShell> shell = GetShell();
 | |
|   if (shell) {
 | |
|     nsresult rv = NS_ERROR_FAILURE;
 | |
|     // We assume that the bytes are in UTF-8, as it says in the spec:
 | |
|     // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
 | |
|     NS_ConvertUTF8toUTF16 ref(mScrollToRef);
 | |
|     // Check an empty string which might be caused by the UTF-8 conversion
 | |
|     if (!ref.IsEmpty()) {
 | |
|       // Note that GoToAnchor will handle flushing layout as needed.
 | |
|       rv = shell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
 | |
|     } else {
 | |
|       rv = NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     if (NS_FAILED(rv)) {
 | |
|       nsAutoCString buff;
 | |
|       const bool unescaped =
 | |
|           NS_UnescapeURL(mScrollToRef.BeginReading(), mScrollToRef.Length(),
 | |
|                          /*aFlags =*/0, buff);
 | |
| 
 | |
|       // This attempt is only necessary if characters were unescaped.
 | |
|       if (unescaped) {
 | |
|         NS_ConvertUTF8toUTF16 utf16Str(buff);
 | |
|         if (!utf16Str.IsEmpty()) {
 | |
|           rv = shell->GoToAnchor(utf16Str, mChangeScrollPosWhenScrollingToRef);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // If UTF-8 URI failed then try to assume the string as a
 | |
|       // document's charset.
 | |
|       if (NS_FAILED(rv)) {
 | |
|         const Encoding* encoding = GetDocumentCharacterSet();
 | |
|         rv = encoding->DecodeWithoutBOMHandling(unescaped ? buff : mScrollToRef,
 | |
|                                                 ref);
 | |
|         if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
 | |
|           rv = shell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     if (NS_SUCCEEDED(rv)) {
 | |
|       mScrolledToRefAlready = true;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::RegisterActivityObserver(nsISupports* aSupports) {
 | |
|   if (!mActivityObservers) {
 | |
|     mActivityObservers = new nsTHashtable<nsPtrHashKey<nsISupports>>();
 | |
|   }
 | |
|   mActivityObservers->PutEntry(aSupports);
 | |
| }
 | |
| 
 | |
| bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
 | |
|   if (!mActivityObservers) {
 | |
|     return false;
 | |
|   }
 | |
|   nsPtrHashKey<nsISupports>* entry = mActivityObservers->GetEntry(aSupports);
 | |
|   if (!entry) {
 | |
|     return false;
 | |
|   }
 | |
|   mActivityObservers->RemoveEntry(entry);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::EnumerateActivityObservers(
 | |
|     ActivityObserverEnumerator aEnumerator, void* aData) {
 | |
|   if (!mActivityObservers) return;
 | |
| 
 | |
|   for (auto iter = mActivityObservers->ConstIter(); !iter.Done(); iter.Next()) {
 | |
|     aEnumerator(iter.Get()->GetKey(), aData);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::RegisterPendingLinkUpdate(Link* aLink) {
 | |
|   if (aLink->HasPendingLinkUpdate()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   aLink->SetHasPendingLinkUpdate();
 | |
| 
 | |
|   if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
 | |
|     nsCOMPtr<nsIRunnable> event =
 | |
|         NewRunnableMethod("Document::FlushPendingLinkUpdatesFromRunnable", this,
 | |
|                           &Document::FlushPendingLinkUpdatesFromRunnable);
 | |
|     // Do this work in a second in the worst case.
 | |
|     nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
 | |
|                                                   EventQueuePriority::Idle);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       // If during shutdown posting a runnable doesn't succeed, we probably
 | |
|       // don't need to update link states.
 | |
|       return;
 | |
|     }
 | |
|     mHasLinksToUpdateRunnable = true;
 | |
|   }
 | |
| 
 | |
|   mLinksToUpdate.InfallibleAppend(aLink);
 | |
| }
 | |
| 
 | |
| void Document::FlushPendingLinkUpdatesFromRunnable() {
 | |
|   MOZ_ASSERT(mHasLinksToUpdateRunnable);
 | |
|   mHasLinksToUpdateRunnable = false;
 | |
|   FlushPendingLinkUpdates();
 | |
| }
 | |
| 
 | |
| void Document::FlushPendingLinkUpdates() {
 | |
|   if (mFlushingPendingLinkUpdates) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
 | |
|   mFlushingPendingLinkUpdates = true;
 | |
| 
 | |
|   while (!mLinksToUpdate.IsEmpty()) {
 | |
|     LinksToUpdateList links(std::move(mLinksToUpdate));
 | |
|     for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
 | |
|       Link* link = iter.Get();
 | |
|       Element* element = link->GetElement();
 | |
|       if (element->OwnerDoc() == this) {
 | |
|         link->ClearHasPendingLinkUpdate();
 | |
|         if (element->IsInComposedDoc()) {
 | |
|           element->UpdateLinkState(link->LinkState());
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<Document> Document::CreateStaticClone(
 | |
|     nsIDocShell* aCloneContainer) {
 | |
|   mCreatingStaticClone = true;
 | |
| 
 | |
|   // Make document use different container during cloning.
 | |
|   RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
 | |
|   SetContainer(static_cast<nsDocShell*>(aCloneContainer));
 | |
|   ErrorResult rv;
 | |
|   nsCOMPtr<nsINode> clonedNode = this->CloneNode(true, rv);
 | |
|   SetContainer(originalShell);
 | |
| 
 | |
|   nsCOMPtr<Document> clonedDoc;
 | |
|   if (rv.Failed()) {
 | |
|     // Don't return yet; we need to reset mCreatingStaticClone
 | |
|     rv.SuppressException();
 | |
|   } else {
 | |
|     clonedDoc = do_QueryInterface(clonedNode);
 | |
|     if (clonedDoc) {
 | |
|       if (IsStaticDocument()) {
 | |
|         clonedDoc->mOriginalDocument = mOriginalDocument;
 | |
|         mOriginalDocument->mLatestStaticClone = clonedDoc;
 | |
|       } else {
 | |
|         clonedDoc->mOriginalDocument = this;
 | |
|         mLatestStaticClone = clonedDoc;
 | |
|       }
 | |
| 
 | |
|       clonedDoc->mOriginalDocument->mStaticCloneCount++;
 | |
| 
 | |
|       size_t sheetsCount = SheetCount();
 | |
|       for (size_t i = 0; i < sheetsCount; ++i) {
 | |
|         RefPtr<StyleSheet> sheet = SheetAt(i);
 | |
|         if (sheet) {
 | |
|           if (sheet->IsApplicable()) {
 | |
|             RefPtr<StyleSheet> clonedSheet =
 | |
|                 sheet->Clone(nullptr, nullptr, clonedDoc, nullptr);
 | |
|             NS_WARNING_ASSERTION(clonedSheet,
 | |
|                                  "Cloning a stylesheet didn't work!");
 | |
|             if (clonedSheet) {
 | |
|               clonedDoc->AddStyleSheet(clonedSheet);
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
 | |
|         auto& sheets = mAdditionalSheets[additionalSheetType(t)];
 | |
|         for (StyleSheet* sheet : sheets) {
 | |
|           if (sheet->IsApplicable()) {
 | |
|             RefPtr<StyleSheet> clonedSheet =
 | |
|                 sheet->Clone(nullptr, nullptr, clonedDoc, nullptr);
 | |
|             NS_WARNING_ASSERTION(clonedSheet,
 | |
|                                  "Cloning a stylesheet didn't work!");
 | |
|             if (clonedSheet) {
 | |
|               clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
 | |
|                                                  clonedSheet);
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Font faces created with the JS API will not be reflected in the
 | |
|       // stylesheets and need to be copied over to the cloned document.
 | |
|       if (const FontFaceSet* set = GetFonts()) {
 | |
|         set->CopyNonRuleFacesTo(clonedDoc->Fonts());
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   mCreatingStaticClone = false;
 | |
|   return clonedDoc.forget();
 | |
| }
 | |
| 
 | |
| void Document::UnlinkOriginalDocumentIfStatic() {
 | |
|   if (IsStaticDocument() && mOriginalDocument) {
 | |
|     MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
 | |
|     mOriginalDocument->mStaticCloneCount--;
 | |
|     mOriginalDocument = nullptr;
 | |
|   }
 | |
|   MOZ_ASSERT(!mOriginalDocument);
 | |
| }
 | |
| 
 | |
| nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
 | |
|                                                 int32_t* aHandle) {
 | |
|   if (mFrameRequestCallbackCounter == INT32_MAX) {
 | |
|     // Can't increment without overflowing; bail out
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
|   int32_t newHandle = ++mFrameRequestCallbackCounter;
 | |
| 
 | |
|   DebugOnly<FrameRequest*> request =
 | |
|       mFrameRequestCallbacks.AppendElement(FrameRequest(aCallback, newHandle));
 | |
|   NS_ASSERTION(request, "This is supposed to be infallible!");
 | |
|   UpdateFrameRequestCallbackSchedulingState();
 | |
| 
 | |
|   *aHandle = newHandle;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void Document::CancelFrameRequestCallback(int32_t aHandle) {
 | |
|   // mFrameRequestCallbacks is stored sorted by handle
 | |
|   if (mFrameRequestCallbacks.RemoveElementSorted(aHandle)) {
 | |
|     UpdateFrameRequestCallbackSchedulingState();
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult Document::GetStateObject(nsIVariant** aState) {
 | |
|   // Get the document's current state object. This is the object backing both
 | |
|   // history.state and popStateEvent.state.
 | |
|   //
 | |
|   // mStateObjectContainer may be null; this just means that there's no
 | |
|   // current state object.
 | |
| 
 | |
|   if (!mStateObjectCached && mStateObjectContainer) {
 | |
|     AutoJSContext cx;
 | |
|     nsIGlobalObject* sgo = GetScopeObject();
 | |
|     NS_ENSURE_TRUE(sgo, NS_ERROR_UNEXPECTED);
 | |
|     JS::Rooted<JSObject*> global(cx, sgo->GetGlobalJSObject());
 | |
|     NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
 | |
|     JSAutoRealm ar(cx, global);
 | |
| 
 | |
|     mStateObjectContainer->DeserializeToVariant(
 | |
|         cx, getter_AddRefs(mStateObjectCached));
 | |
|   }
 | |
| 
 | |
|   NS_IF_ADDREF(*aState = mStateObjectCached);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
 | |
|   mTiming = aTiming;
 | |
|   if (!mLoadingTimeStamp.IsNull() && mTiming) {
 | |
|     mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(), mLoadingTimeStamp);
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsContentList* Document::ImageMapList() {
 | |
|   if (!mImageMaps) {
 | |
|     mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
 | |
|                                    nsGkAtoms::map);
 | |
|   }
 | |
| 
 | |
|   return mImageMaps;
 | |
| }
 | |
| 
 | |
| #define DEPRECATED_OPERATION(_op) #_op "Warning",
 | |
| static const char* kDeprecationWarnings[] = {
 | |
| #include "nsDeprecatedOperationList.h"
 | |
|     nullptr};
 | |
| #undef DEPRECATED_OPERATION
 | |
| 
 | |
| #define DOCUMENT_WARNING(_op) #_op "Warning",
 | |
| static const char* kDocumentWarnings[] = {
 | |
| #include "nsDocumentWarningList.h"
 | |
|     nullptr};
 | |
| #undef DOCUMENT_WARNING
 | |
| 
 | |
| static UseCounter OperationToUseCounter(
 | |
|     Document::DeprecatedOperations aOperation) {
 | |
|   switch (aOperation) {
 | |
| #define DEPRECATED_OPERATION(_op) \
 | |
|   case Document::e##_op:          \
 | |
|     return eUseCounter_##_op;
 | |
| #include "nsDeprecatedOperationList.h"
 | |
| #undef DEPRECATED_OPERATION
 | |
|     default:
 | |
|       MOZ_CRASH();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
 | |
|   return mDeprecationWarnedAbout[aOperation];
 | |
| }
 | |
| 
 | |
| void Document::WarnOnceAbout(DeprecatedOperations aOperation,
 | |
|                              bool asError /* = false */) const {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   if (HasWarnedAbout(aOperation)) {
 | |
|     return;
 | |
|   }
 | |
|   mDeprecationWarnedAbout[aOperation] = true;
 | |
|   // Don't count deprecated operations for about pages since those pages
 | |
|   // are almost in our control, and we always need to remove uses there
 | |
|   // before we remove the operation itself anyway.
 | |
|   if (!IsAboutPage()) {
 | |
|     const_cast<Document*>(this)->SetDocumentAndPageUseCounter(
 | |
|         OperationToUseCounter(aOperation));
 | |
|   }
 | |
|   uint32_t flags =
 | |
|       asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
 | |
|   nsContentUtils::ReportToConsole(flags, NS_LITERAL_CSTRING("DOM Core"), this,
 | |
|                                   nsContentUtils::eDOM_PROPERTIES,
 | |
|                                   kDeprecationWarnings[aOperation]);
 | |
| }
 | |
| 
 | |
| bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
 | |
|   return mDocWarningWarnedAbout[aWarning];
 | |
| }
 | |
| 
 | |
| void Document::WarnOnceAbout(DocumentWarnings aWarning,
 | |
|                              bool asError /* = false */,
 | |
|                              const char16_t** aParams /* = nullptr */,
 | |
|                              uint32_t aParamsLength /* = 0 */) const {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   if (HasWarnedAbout(aWarning)) {
 | |
|     return;
 | |
|   }
 | |
|   mDocWarningWarnedAbout[aWarning] = true;
 | |
|   uint32_t flags =
 | |
|       asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
 | |
|   nsContentUtils::ReportToConsole(flags, NS_LITERAL_CSTRING("DOM Core"), this,
 | |
|                                   nsContentUtils::eDOM_PROPERTIES,
 | |
|                                   kDocumentWarnings[aWarning], aParams,
 | |
|                                   aParamsLength);
 | |
| }
 | |
| 
 | |
| mozilla::dom::ImageTracker* Document::ImageTracker() {
 | |
|   if (!mImageTracker) {
 | |
|     mImageTracker = new mozilla::dom::ImageTracker;
 | |
|   }
 | |
|   return mImageTracker;
 | |
| }
 | |
| 
 | |
| static bool AllSubDocumentPluginEnum(Document* aDocument, void* userArg) {
 | |
|   nsTArray<nsIObjectLoadingContent*>* plugins =
 | |
|       reinterpret_cast<nsTArray<nsIObjectLoadingContent*>*>(userArg);
 | |
|   MOZ_ASSERT(plugins);
 | |
|   aDocument->GetPlugins(*plugins);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::GetPlugins(nsTArray<nsIObjectLoadingContent*>& aPlugins) {
 | |
|   aPlugins.SetCapacity(aPlugins.Length() + mPlugins.Count());
 | |
|   for (auto iter = mPlugins.ConstIter(); !iter.Done(); iter.Next()) {
 | |
|     aPlugins.AppendElement(iter.Get()->GetKey());
 | |
|   }
 | |
|   EnumerateSubDocuments(AllSubDocumentPluginEnum, &aPlugins);
 | |
| }
 | |
| 
 | |
| void Document::ScheduleSVGUseElementShadowTreeUpdate(
 | |
|     SVGUseElement& aUseElement) {
 | |
|   MOZ_ASSERT(aUseElement.IsInComposedDoc());
 | |
| 
 | |
|   mSVGUseElementsNeedingShadowTreeUpdate.PutEntry(&aUseElement);
 | |
| 
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     shell->EnsureStyleFlush();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::DoUpdateSVGUseElementShadowTrees() {
 | |
|   MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
 | |
|   nsTArray<RefPtr<SVGUseElement>> useElementsToUpdate;
 | |
| 
 | |
|   do {
 | |
|     useElementsToUpdate.Clear();
 | |
|     useElementsToUpdate.SetCapacity(
 | |
|         mSVGUseElementsNeedingShadowTreeUpdate.Count());
 | |
| 
 | |
|     {
 | |
|       for (auto iter = mSVGUseElementsNeedingShadowTreeUpdate.ConstIter();
 | |
|            !iter.Done(); iter.Next()) {
 | |
|         useElementsToUpdate.AppendElement(iter.Get()->GetKey());
 | |
|       }
 | |
|       mSVGUseElementsNeedingShadowTreeUpdate.Clear();
 | |
|     }
 | |
| 
 | |
|     for (auto& useElement : useElementsToUpdate) {
 | |
|       if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
 | |
|         // The element was in another <use> shadow tree which we processed
 | |
|         // already and also needed an update, and is removed from the document
 | |
|         // now, so nothing to do here.
 | |
|         MOZ_ASSERT(useElementsToUpdate.Length() > 1);
 | |
|         continue;
 | |
|       }
 | |
|       useElement->UpdateShadowTree();
 | |
|     }
 | |
|   } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
 | |
| }
 | |
| 
 | |
| void Document::NotifyMediaFeatureValuesChanged() {
 | |
|   for (auto iter = mResponsiveContent.ConstIter(); !iter.Done(); iter.Next()) {
 | |
|     RefPtr<HTMLImageElement> imageElement = iter.Get()->GetKey();
 | |
|     imageElement->MediaFeatureValuesChanged();
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<Touch> Document::CreateTouch(
 | |
|     nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
 | |
|     int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
 | |
|     int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
 | |
|     float aRotationAngle, float aForce) {
 | |
|   RefPtr<Touch> touch =
 | |
|       new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
 | |
|                 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
 | |
|   return touch.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<TouchList> Document::CreateTouchList() {
 | |
|   RefPtr<TouchList> retval = new TouchList(ToSupports(this));
 | |
|   return retval.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<TouchList> Document::CreateTouchList(
 | |
|     Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
 | |
|   RefPtr<TouchList> retval = new TouchList(ToSupports(this));
 | |
|   retval->Append(&aTouch);
 | |
|   for (uint32_t i = 0; i < aTouches.Length(); ++i) {
 | |
|     retval->Append(aTouches[i].get());
 | |
|   }
 | |
|   return retval.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<TouchList> Document::CreateTouchList(
 | |
|     const Sequence<OwningNonNull<Touch>>& aTouches) {
 | |
|   RefPtr<TouchList> retval = new TouchList(ToSupports(this));
 | |
|   for (uint32_t i = 0; i < aTouches.Length(); ++i) {
 | |
|     retval->Append(aTouches[i].get());
 | |
|   }
 | |
|   return retval.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
 | |
|     float aX, float aY) {
 | |
|   using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
 | |
| 
 | |
|   nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
 | |
|   nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
 | |
|   nsPoint pt(x, y);
 | |
| 
 | |
|   FlushPendingNotifications(FlushType::Layout);
 | |
| 
 | |
|   nsIPresShell* ps = GetShell();
 | |
|   if (!ps) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsIFrame* rootFrame = ps->GetRootFrame();
 | |
| 
 | |
|   // XUL docs, unlike HTML, have no frame tree until everything's done loading
 | |
|   if (!rootFrame) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
 | |
|       rootFrame, pt,
 | |
|       {FrameForPointOption::IgnorePaintSuppression,
 | |
|        FrameForPointOption::IgnoreCrossDoc});
 | |
|   if (!ptFrame) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // We require frame-relative coordinates for GetContentOffsetsFromPoint.
 | |
|   nsPoint aOffset;
 | |
|   nsCOMPtr<nsIWidget> widget = nsContentUtils::GetWidget(ps, &aOffset);
 | |
|   LayoutDeviceIntPoint refPoint = nsContentUtils::ToWidgetPoint(
 | |
|       CSSPoint(aX, aY), aOffset, GetPresContext());
 | |
|   nsPoint adjustedPoint =
 | |
|       nsLayoutUtils::GetEventCoordinatesRelativeTo(widget, refPoint, ptFrame);
 | |
| 
 | |
|   nsFrame::ContentOffsets offsets =
 | |
|       ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
 | |
| 
 | |
|   nsCOMPtr<nsIContent> node = offsets.content;
 | |
|   uint32_t offset = offsets.offset;
 | |
|   nsCOMPtr<nsIContent> anonNode = node;
 | |
|   bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
 | |
|   if (nodeIsAnonymous) {
 | |
|     node = ptFrame->GetContent();
 | |
|     nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
 | |
|     HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
 | |
|     nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
 | |
|     nsNumberControlFrame* numberFrame =
 | |
|         do_QueryFrame(nonanon->GetPrimaryFrame());
 | |
|     if (textFrame || numberFrame) {
 | |
|       // If the anonymous content node has a child, then we need to make sure
 | |
|       // that we get the appropriate child, as otherwise the offset may not be
 | |
|       // correct when we construct a range for it.
 | |
|       nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
 | |
|       if (firstChild) {
 | |
|         anonNode = firstChild;
 | |
|       }
 | |
| 
 | |
|       if (textArea) {
 | |
|         offset =
 | |
|             nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
 | |
|       }
 | |
| 
 | |
|       node = nonanon;
 | |
|     } else {
 | |
|       node = nullptr;
 | |
|       offset = 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
 | |
|   if (nodeIsAnonymous) {
 | |
|     aCaretPos->SetAnonymousContentNode(anonNode);
 | |
|   }
 | |
|   return aCaretPos.forget();
 | |
| }
 | |
| 
 | |
| bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
 | |
|   // We rely on correct frame information here, so need to flush frames.
 | |
|   FlushPendingNotifications(FlushType::Frames);
 | |
| 
 | |
|   // An element is potentially scrollable if all of the following conditions are
 | |
|   // true:
 | |
| 
 | |
|   // The element has an associated CSS layout box.
 | |
|   nsIFrame* bodyFrame = aBody->GetPrimaryFrame();
 | |
|   if (!bodyFrame) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // The element is not the HTML body element, or it is and the root element's
 | |
|   // used value of the overflow-x or overflow-y properties is not visible.
 | |
|   MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
 | |
|   nsIFrame* parentFrame = aBody->GetParent()->GetPrimaryFrame();
 | |
|   if (parentFrame &&
 | |
|       parentFrame->StyleDisplay()->mOverflowX == StyleOverflow::Visible &&
 | |
|       parentFrame->StyleDisplay()->mOverflowY == StyleOverflow::Visible) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // The element's used value of the overflow-x or overflow-y properties is not
 | |
|   // visible.
 | |
|   if (bodyFrame->StyleDisplay()->mOverflowX == StyleOverflow::Visible &&
 | |
|       bodyFrame->StyleDisplay()->mOverflowY == StyleOverflow::Visible) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| Element* Document::GetScrollingElement() {
 | |
|   // Keep this in sync with IsScrollingElement.
 | |
|   if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
 | |
|     RefPtr<HTMLBodyElement> body = GetBodyElement();
 | |
|     if (body && !IsPotentiallyScrollable(body)) {
 | |
|       return body;
 | |
|     }
 | |
| 
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return GetRootElement();
 | |
| }
 | |
| 
 | |
| bool Document::IsScrollingElement(Element* aElement) {
 | |
|   // Keep this in sync with GetScrollingElement.
 | |
|   MOZ_ASSERT(aElement);
 | |
| 
 | |
|   if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
 | |
|     return aElement == GetRootElement();
 | |
|   }
 | |
| 
 | |
|   // In the common case when aElement != body, avoid refcounting.
 | |
|   HTMLBodyElement* body = GetBodyElement();
 | |
|   if (aElement != body) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Now we know body is non-null, since aElement is not null.  It's the
 | |
|   // scrolling element for the document if it itself is not potentially
 | |
|   // scrollable.
 | |
|   RefPtr<HTMLBodyElement> strongBody(body);
 | |
|   return !IsPotentiallyScrollable(strongBody);
 | |
| }
 | |
| 
 | |
| class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
 | |
|  public:
 | |
|   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 | |
|   NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
 | |
| 
 | |
|   explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
 | |
|                                         const BlockParsingOptions& aOptions)
 | |
|       : mPromise(aPromise) {
 | |
|     nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
 | |
|     if (parser &&
 | |
|         (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
 | |
|       parser->BlockParser();
 | |
|       mParser = do_GetWeakReference(parser);
 | |
|       mDocument = aDocument;
 | |
|       mDocument->BlockOnload();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
 | |
|     MaybeUnblockParser();
 | |
| 
 | |
|     mPromise->MaybeResolve(aCx, aValue);
 | |
|   }
 | |
| 
 | |
|   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
 | |
|     MaybeUnblockParser();
 | |
| 
 | |
|     mPromise->MaybeReject(aCx, aValue);
 | |
|   }
 | |
| 
 | |
|  protected:
 | |
|   virtual ~UnblockParsingPromiseHandler() {
 | |
|     // If we're being cleaned up by the cycle collector, our mDocument reference
 | |
|     // may have been unlinked while our mParser weak reference is still alive.
 | |
|     if (mDocument) {
 | |
|       MaybeUnblockParser();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   void MaybeUnblockParser() {
 | |
|     nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
 | |
|     if (parser) {
 | |
|       MOZ_DIAGNOSTIC_ASSERT(mDocument);
 | |
|       nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
 | |
|       if (parser == docParser) {
 | |
|         parser->UnblockParser();
 | |
|         parser->ContinueInterruptedParsingAsync();
 | |
|         mDocument->UnblockOnload(false);
 | |
|       }
 | |
|     }
 | |
|     mParser = nullptr;
 | |
|     mDocument = nullptr;
 | |
|   }
 | |
| 
 | |
|   nsWeakPtr mParser;
 | |
|   RefPtr<Promise> mPromise;
 | |
|   RefPtr<Document> mDocument;
 | |
| };
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsISupports)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
 | |
| NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
 | |
| 
 | |
| already_AddRefed<Promise> Document::BlockParsing(
 | |
|     Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
 | |
|   RefPtr<Promise> resultPromise =
 | |
|       Promise::Create(aPromise.GetParentObject(), aRv);
 | |
|   if (aRv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<PromiseNativeHandler> promiseHandler =
 | |
|       new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
 | |
|   aPromise.AppendNativeHandler(promiseHandler);
 | |
| 
 | |
|   return resultPromise.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
 | |
|   if (mFailedChannel) {
 | |
|     nsCOMPtr<nsIURI> failedURI;
 | |
|     if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
 | |
|       return failedURI.forget();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
 | |
|   if (!uri) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return uri.forget();
 | |
| }
 | |
| 
 | |
| Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
 | |
|   if (!mReadyForIdle) {
 | |
|     nsIGlobalObject* global = GetScopeObject();
 | |
|     if (!global) {
 | |
|       aRv.Throw(NS_ERROR_NOT_AVAILABLE);
 | |
|       return nullptr;
 | |
|     }
 | |
| 
 | |
|     mReadyForIdle = Promise::Create(global, aRv);
 | |
|     if (aRv.Failed()) {
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return mReadyForIdle;
 | |
| }
 | |
| 
 | |
| void Document::MaybeResolveReadyForIdle() {
 | |
|   IgnoredErrorResult rv;
 | |
|   Promise* readyPromise = GetDocumentReadyForIdle(rv);
 | |
|   if (readyPromise) {
 | |
|     readyPromise->MaybeResolve(this);
 | |
|   }
 | |
| }
 | |
| 
 | |
| FeaturePolicy* Document::Policy() const {
 | |
|   // The policy is created when the document is initialized. We _must_ have a
 | |
|   // policy here even if the featurePolicy pref is off. If this assertion fails,
 | |
|   // it means that ::Policy() is called before ::StartDocumentLoad().
 | |
|   MOZ_ASSERT(mFeaturePolicy);
 | |
|   return mFeaturePolicy;
 | |
| }
 | |
| 
 | |
| nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
 | |
|   // Only chrome documents are allowed to use command dispatcher.
 | |
|   if (!nsContentUtils::IsChromeDoc(this)) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   if (!mCommandDispatcher) {
 | |
|     // Create our command dispatcher and hook it up.
 | |
|     mCommandDispatcher = new nsXULCommandDispatcher(this);
 | |
|   }
 | |
|   return mCommandDispatcher;
 | |
| }
 | |
| 
 | |
| void Document::InitializeXULBroadcastManager() {
 | |
|   if (mXULBroadcastManager) {
 | |
|     return;
 | |
|   }
 | |
|   mXULBroadcastManager = new XULBroadcastManager(this);
 | |
| }
 | |
| 
 | |
| static JSObject* GetScopeObjectOfNode(nsINode* node) {
 | |
|   MOZ_ASSERT(node, "Must not be called with null.");
 | |
| 
 | |
|   // Window root occasionally keeps alive a node of a document whose
 | |
|   // window is already dead. If in this brief period someone calls
 | |
|   // GetPopupNode and we return that node, we can end up creating a
 | |
|   // reflector for the node in the wrong global (the current global,
 | |
|   // not the document global, because we won't know what the document
 | |
|   // global is).  Returning an orphan node like that to JS would be a
 | |
|   // bug anyway, so to avoid this, let's do the same check as fetching
 | |
|   // GetParentObjet() on the document does to determine the scope and
 | |
|   // if it returns null let's just return null in XULDocument::GetPopupNode.
 | |
|   Document* doc = node->OwnerDoc();
 | |
|   MOZ_ASSERT(doc, "This should never happen.");
 | |
| 
 | |
|   nsIGlobalObject* global = doc->GetScopeObject();
 | |
|   return global ? global->GetGlobalJSObject() : nullptr;
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsPIWindowRoot> Document::GetWindowRoot() {
 | |
|   if (!mDocumentContainer) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   // XXX It's unclear why this can't just use GetWindow().
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> piWin = mDocumentContainer->GetWindow();
 | |
|   return piWin ? piWin->GetTopWindowRoot() : nullptr;
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsINode> Document::GetPopupNode() {
 | |
|   nsCOMPtr<nsINode> node;
 | |
|   nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
 | |
|   if (rootWin) {
 | |
|     node = rootWin->GetPopupNode();  // addref happens here
 | |
|   }
 | |
| 
 | |
|   if (!node) {
 | |
|     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
 | |
|     if (pm) {
 | |
|       node = pm->GetLastTriggerPopupNode(this);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (node && GetScopeObjectOfNode(node)) {
 | |
|     return node.forget();
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void Document::SetPopupNode(nsINode* aNode) {
 | |
|   nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
 | |
|   if (rootWin) {
 | |
|     rootWin->SetPopupNode(aNode);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Returns the rangeOffset element from the XUL Popup Manager. This is for
 | |
| // chrome callers only.
 | |
| nsINode* Document::GetPopupRangeParent(ErrorResult& aRv) {
 | |
|   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
 | |
|   if (!pm) {
 | |
|     aRv.Throw(NS_ERROR_FAILURE);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return pm->GetMouseLocationParent();
 | |
| }
 | |
| 
 | |
| // Returns the rangeOffset element from the XUL Popup Manager.
 | |
| int32_t Document::GetPopupRangeOffset(ErrorResult& aRv) {
 | |
|   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
 | |
|   if (!pm) {
 | |
|     aRv.Throw(NS_ERROR_FAILURE);
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   return pm->MouseLocationOffset();
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsINode> Document::GetTooltipNode() {
 | |
|   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
 | |
|   if (pm) {
 | |
|     nsCOMPtr<nsINode> node = pm->GetLastTriggerTooltipNode(this);
 | |
|     if (node) {
 | |
|       return node.forget();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| nsIHTMLCollection* Document::Children() {
 | |
|   if (!mChildrenCollection) {
 | |
|     mChildrenCollection =
 | |
|         new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
 | |
|                           nsGkAtoms::_asterisk, false);
 | |
|   }
 | |
| 
 | |
|   return mChildrenCollection;
 | |
| }
 | |
| 
 | |
| uint32_t Document::ChildElementCount() { return Children()->Length(); }
 | |
| 
 | |
| // Singleton class to manage the list of fullscreen documents which are the
 | |
| // root of a branch which contains fullscreen documents. We maintain this list
 | |
| // so that we can easily exit all windows from fullscreen when the user
 | |
| // presses the escape key.
 | |
| class FullscreenRoots {
 | |
|  public:
 | |
|   // Adds the root of given document to the manager. Calling this method
 | |
|   // with a document whose root is already contained has no effect.
 | |
|   static void Add(Document* aDoc);
 | |
| 
 | |
|   // Iterates over every root in the root list, and calls aFunction, passing
 | |
|   // each root once to aFunction. It is safe to call Add() and Remove() while
 | |
|   // iterating over the list (i.e. in aFunction). Documents that are removed
 | |
|   // from the manager during traversal are not traversed, and documents that
 | |
|   // are added to the manager during traversal are also not traversed.
 | |
|   static void ForEach(void (*aFunction)(Document* aDoc));
 | |
| 
 | |
|   // Removes the root of a specific document from the manager.
 | |
|   static void Remove(Document* aDoc);
 | |
| 
 | |
|   // Returns true if all roots added to the list have been removed.
 | |
|   static bool IsEmpty();
 | |
| 
 | |
|  private:
 | |
|   FullscreenRoots() { MOZ_COUNT_CTOR(FullscreenRoots); }
 | |
|   ~FullscreenRoots() { MOZ_COUNT_DTOR(FullscreenRoots); }
 | |
| 
 | |
|   enum { NotFound = uint32_t(-1) };
 | |
|   // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
 | |
|   static uint32_t Find(Document* aRoot);
 | |
| 
 | |
|   // Returns true if aRoot is in the list of fullscreen roots.
 | |
|   static bool Contains(Document* aRoot);
 | |
| 
 | |
|   // Singleton instance of the FullscreenRoots. This is instantiated when a
 | |
|   // root is added, and it is deleted when the last root is removed.
 | |
|   static FullscreenRoots* sInstance;
 | |
| 
 | |
|   // List of weak pointers to roots.
 | |
|   nsTArray<nsWeakPtr> mRoots;
 | |
| };
 | |
| 
 | |
| FullscreenRoots* FullscreenRoots::sInstance = nullptr;
 | |
| 
 | |
| /* static */
 | |
| void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
 | |
|   if (!sInstance) {
 | |
|     return;
 | |
|   }
 | |
|   // Create a copy of the roots array, and iterate over the copy. This is so
 | |
|   // that if an element is removed from mRoots we don't mess up our iteration.
 | |
|   nsTArray<nsWeakPtr> roots(sInstance->mRoots);
 | |
|   // Call aFunction on all entries.
 | |
|   for (uint32_t i = 0; i < roots.Length(); i++) {
 | |
|     nsCOMPtr<Document> root = do_QueryReferent(roots[i]);
 | |
|     // Check that the root isn't in the manager. This is so that new additions
 | |
|     // while we were running don't get traversed.
 | |
|     if (root && FullscreenRoots::Contains(root)) {
 | |
|       aFunction(root);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool FullscreenRoots::Contains(Document* aRoot) {
 | |
|   return FullscreenRoots::Find(aRoot) != NotFound;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void FullscreenRoots::Add(Document* aDoc) {
 | |
|   nsCOMPtr<Document> root = nsContentUtils::GetRootDocument(aDoc);
 | |
|   if (!FullscreenRoots::Contains(root)) {
 | |
|     if (!sInstance) {
 | |
|       sInstance = new FullscreenRoots();
 | |
|     }
 | |
|     sInstance->mRoots.AppendElement(do_GetWeakReference(root));
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| uint32_t FullscreenRoots::Find(Document* aRoot) {
 | |
|   if (!sInstance) {
 | |
|     return NotFound;
 | |
|   }
 | |
|   nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
 | |
|   for (uint32_t i = 0; i < roots.Length(); i++) {
 | |
|     nsCOMPtr<Document> otherRoot(do_QueryReferent(roots[i]));
 | |
|     if (otherRoot == aRoot) {
 | |
|       return i;
 | |
|     }
 | |
|   }
 | |
|   return NotFound;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void FullscreenRoots::Remove(Document* aDoc) {
 | |
|   nsCOMPtr<Document> root = nsContentUtils::GetRootDocument(aDoc);
 | |
|   uint32_t index = Find(root);
 | |
|   NS_ASSERTION(index != NotFound,
 | |
|                "Should only try to remove roots which are still added!");
 | |
|   if (index == NotFound || !sInstance) {
 | |
|     return;
 | |
|   }
 | |
|   sInstance->mRoots.RemoveElementAt(index);
 | |
|   if (sInstance->mRoots.IsEmpty()) {
 | |
|     delete sInstance;
 | |
|     sInstance = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool FullscreenRoots::IsEmpty() { return !sInstance; }
 | |
| 
 | |
| // Any fullscreen change waiting for the widget to finish transition
 | |
| // is queued here. This is declared static instead of a member of
 | |
| // Document because in the majority of time, there would be at most
 | |
| // one document requesting or exiting fullscreen. We shouldn't waste
 | |
| // the space to hold for it in every document.
 | |
| class PendingFullscreenChangeList {
 | |
|  public:
 | |
|   PendingFullscreenChangeList() = delete;
 | |
| 
 | |
|   template <typename T>
 | |
|   static void Add(UniquePtr<T> aChange) {
 | |
|     sList.insertBack(aChange.release());
 | |
|   }
 | |
| 
 | |
|   static const FullscreenChange* GetLast() { return sList.getLast(); }
 | |
| 
 | |
|   enum IteratorOption {
 | |
|     // When we are committing fullscreen changes or preparing for
 | |
|     // that, we generally want to iterate all requests in the same
 | |
|     // window with eDocumentsWithSameRoot option.
 | |
|     eDocumentsWithSameRoot,
 | |
|     // If we are removing a document from the tree, we would only
 | |
|     // want to remove the requests from the given document and its
 | |
|     // descendants. For that case, use eInclusiveDescendants.
 | |
|     eInclusiveDescendants
 | |
|   };
 | |
| 
 | |
|   template <typename T>
 | |
|   class Iterator {
 | |
|    public:
 | |
|     explicit Iterator(Document* aDoc, IteratorOption aOption)
 | |
|         : mCurrent(PendingFullscreenChangeList::sList.getFirst()),
 | |
|           mRootShellForIteration(aDoc->GetDocShell()) {
 | |
|       if (mCurrent) {
 | |
|         if (mRootShellForIteration && aOption == eDocumentsWithSameRoot) {
 | |
|           // Use a temporary to avoid undefined behavior from passing
 | |
|           // mRootShellForIteration.
 | |
|           nsCOMPtr<nsIDocShellTreeItem> root;
 | |
|           mRootShellForIteration->GetRootTreeItem(getter_AddRefs(root));
 | |
|           mRootShellForIteration = root.forget();
 | |
|         }
 | |
|         SkipToNextMatch();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     UniquePtr<T> TakeAndNext() {
 | |
|       auto thisChange = TakeAndNextInternal();
 | |
|       SkipToNextMatch();
 | |
|       return thisChange;
 | |
|     }
 | |
|     bool AtEnd() const { return mCurrent == nullptr; }
 | |
| 
 | |
|    private:
 | |
|     UniquePtr<T> TakeAndNextInternal() {
 | |
|       FullscreenChange* thisChange = mCurrent;
 | |
|       MOZ_ASSERT(thisChange->Type() == T::kType);
 | |
|       mCurrent = mCurrent->removeAndGetNext();
 | |
|       return WrapUnique(static_cast<T*>(thisChange));
 | |
|     }
 | |
|     void SkipToNextMatch() {
 | |
|       while (mCurrent) {
 | |
|         if (mCurrent->Type() == T::kType) {
 | |
|           nsCOMPtr<nsIDocShellTreeItem> docShell =
 | |
|               mCurrent->Document()->GetDocShell();
 | |
|           if (!docShell) {
 | |
|             // Always automatically drop fullscreen changes which are
 | |
|             // from a document detached from the doc shell.
 | |
|             UniquePtr<T> change = TakeAndNextInternal();
 | |
|             change->MayRejectPromise();
 | |
|             continue;
 | |
|           }
 | |
|           while (docShell && docShell != mRootShellForIteration) {
 | |
|             nsCOMPtr<nsIDocShellTreeItem> parent;
 | |
|             docShell->GetParent(getter_AddRefs(parent));
 | |
|             docShell = parent.forget();
 | |
|           }
 | |
|           if (docShell) {
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|         // The current one either don't have matched type, or isn't
 | |
|         // inside the given subtree, so skip this item.
 | |
|         mCurrent = mCurrent->getNext();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     FullscreenChange* mCurrent;
 | |
|     nsCOMPtr<nsIDocShellTreeItem> mRootShellForIteration;
 | |
|   };
 | |
| 
 | |
|  private:
 | |
|   static LinkedList<FullscreenChange> sList;
 | |
| };
 | |
| 
 | |
| /* static */ LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
 | |
| 
 | |
| Document* Document::GetFullscreenRoot() {
 | |
|   nsCOMPtr<Document> root = do_QueryReferent(mFullscreenRoot);
 | |
|   return root;
 | |
| }
 | |
| 
 | |
| void Document::SetFullscreenRoot(Document* aRoot) {
 | |
|   mFullscreenRoot = do_GetWeakReference(aRoot);
 | |
| }
 | |
| 
 | |
| already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
 | |
|   UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
 | |
|   RefPtr<Promise> promise = exit->GetPromise();
 | |
|   RestorePreviousFullscreenState(std::move(exit));
 | |
|   return promise.forget();
 | |
| }
 | |
| 
 | |
| static void AskWindowToExitFullscreen(Document* aDoc) {
 | |
|   if (XRE_GetProcessType() == GeckoProcessType_Content) {
 | |
|     nsContentUtils::DispatchEventOnlyToChrome(
 | |
|         aDoc, ToSupports(aDoc), NS_LITERAL_STRING("MozDOMFullscreen:Exit"),
 | |
|         CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
 | |
|   } else {
 | |
|     if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
 | |
|       win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| class nsCallExitFullscreen : public Runnable {
 | |
|  public:
 | |
|   explicit nsCallExitFullscreen(Document* aDoc)
 | |
|       : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
 | |
| 
 | |
|   NS_IMETHOD Run() final {
 | |
|     if (!mDoc) {
 | |
|       FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
 | |
|     } else {
 | |
|       AskWindowToExitFullscreen(mDoc);
 | |
|     }
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   nsCOMPtr<Document> mDoc;
 | |
| };
 | |
| 
 | |
| /* static */ void Document::AsyncExitFullscreen(Document* aDoc) {
 | |
|   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
|   nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
 | |
|   if (aDoc) {
 | |
|     aDoc->Dispatch(TaskCategory::Other, exit.forget());
 | |
|   } else {
 | |
|     NS_DispatchToCurrentThread(exit.forget());
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool CountFullscreenSubDocuments(Document* aDoc, void* aData) {
 | |
|   if (aDoc->FullscreenStackTop()) {
 | |
|     uint32_t* count = static_cast<uint32_t*>(aData);
 | |
|     (*count)++;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static uint32_t CountFullscreenSubDocuments(Document* aDoc) {
 | |
|   uint32_t count = 0;
 | |
|   aDoc->EnumerateSubDocuments(CountFullscreenSubDocuments, &count);
 | |
|   return count;
 | |
| }
 | |
| 
 | |
| bool Document::IsFullscreenLeaf() {
 | |
|   // A fullscreen leaf document is fullscreen, and has no fullscreen
 | |
|   // subdocuments.
 | |
|   if (!FullscreenStackTop()) {
 | |
|     return false;
 | |
|   }
 | |
|   return CountFullscreenSubDocuments(this) == 0;
 | |
| }
 | |
| 
 | |
| bool GetFullscreenLeaf(Document* aDoc, void* aData) {
 | |
|   if (aDoc->IsFullscreenLeaf()) {
 | |
|     Document** result = static_cast<Document**>(aData);
 | |
|     *result = aDoc;
 | |
|     return false;
 | |
|   }
 | |
|   if (aDoc->FullscreenStackTop()) {
 | |
|     aDoc->EnumerateSubDocuments(GetFullscreenLeaf, aData);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static Document* GetFullscreenLeaf(Document* aDoc) {
 | |
|   Document* leaf = nullptr;
 | |
|   GetFullscreenLeaf(aDoc, &leaf);
 | |
|   if (leaf) {
 | |
|     return leaf;
 | |
|   }
 | |
|   // Otherwise we could be either in a non-fullscreen doc tree, or we're
 | |
|   // below the fullscreen doc. Start the search from the root.
 | |
|   Document* root = nsContentUtils::GetRootDocument(aDoc);
 | |
|   // Check that the root is actually fullscreen so we don't waste time walking
 | |
|   // around its descendants.
 | |
|   if (!root->FullscreenStackTop()) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   GetFullscreenLeaf(root, &leaf);
 | |
|   return leaf;
 | |
| }
 | |
| 
 | |
| static bool ResetFullscreen(Document* aDocument, void* aData) {
 | |
|   if (Element* fsElement = aDocument->FullscreenStackTop()) {
 | |
|     NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
 | |
|                  "Should have at most 1 fullscreen subdocument.");
 | |
|     aDocument->CleanupFullscreenState();
 | |
|     NS_ASSERTION(!aDocument->FullscreenStackTop(), "Should reset fullscreen");
 | |
|     DispatchFullscreenChange(aDocument, fsElement);
 | |
|     aDocument->EnumerateSubDocuments(ResetFullscreen, nullptr);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| // Since Document::ExitFullscreenInDocTree() could be called from
 | |
| // Element::UnbindFromTree() where it is not safe to synchronously run
 | |
| // script. This runnable is the script part of that function.
 | |
| class ExitFullscreenScriptRunnable : public Runnable {
 | |
|  public:
 | |
|   explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
 | |
|       : mozilla::Runnable("ExitFullscreenScriptRunnable"),
 | |
|         mRoot(aRoot),
 | |
|         mLeaf(aLeaf) {}
 | |
| 
 | |
|   NS_IMETHOD Run() override {
 | |
|     // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
 | |
|     // document since we want this event to follow the same path that
 | |
|     // MozDOMFullscreen:Entered was dispatched.
 | |
|     nsContentUtils::DispatchEventOnlyToChrome(
 | |
|         mLeaf, ToSupports(mLeaf), NS_LITERAL_STRING("MozDOMFullscreen:Exited"),
 | |
|         CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
 | |
|     // Ensure the window exits fullscreen.
 | |
|     if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
 | |
|       win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
 | |
|                                  false);
 | |
|     }
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   nsCOMPtr<Document> mRoot;
 | |
|   nsCOMPtr<Document> mLeaf;
 | |
| };
 | |
| 
 | |
| /* static */ void Document::ExitFullscreenInDocTree(
 | |
|     Document* aMaybeNotARootDoc) {
 | |
|   MOZ_ASSERT(aMaybeNotARootDoc);
 | |
| 
 | |
|   // Unlock the pointer
 | |
|   UnlockPointer();
 | |
| 
 | |
|   // Resolve all promises which waiting for exit fullscreen.
 | |
|   PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
 | |
|       aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
 | |
|   while (!iter.AtEnd()) {
 | |
|     UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
 | |
|     exit->MayResolvePromise();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
 | |
|   if (!root || !root->FullscreenStackTop()) {
 | |
|     // If a document was detached before exiting from fullscreen, it is
 | |
|     // possible that the root had left fullscreen state. In this case,
 | |
|     // we would not get anything from the ResetFullscreen() call. Root's
 | |
|     // not being a fullscreen doc also means the widget should have
 | |
|     // exited fullscreen state. It means even if we do not return here,
 | |
|     // we would actually do nothing below except crashing ourselves via
 | |
|     // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
 | |
|     // document.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
 | |
|   // See ExitFullscreenScriptRunnable::Run for details. We have to
 | |
|   // record it here because we don't have such information after we
 | |
|   // reset the fullscreen state below.
 | |
|   Document* fullscreenLeaf = GetFullscreenLeaf(root);
 | |
| 
 | |
|   // Walk the tree of fullscreen documents, and reset their fullscreen state.
 | |
|   ResetFullscreen(root, nullptr);
 | |
| 
 | |
|   NS_ASSERTION(!root->FullscreenStackTop(),
 | |
|                "Fullscreen root should no longer be a fullscreen doc...");
 | |
| 
 | |
|   // Move the top-level window out of fullscreen mode.
 | |
|   FullscreenRoots::Remove(root);
 | |
| 
 | |
|   nsContentUtils::AddScriptRunner(
 | |
|       new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
 | |
| }
 | |
| 
 | |
| static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
 | |
|   RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
 | |
|       aDoc, NS_LITERAL_STRING("MozDOMFullscreen:NewOrigin"), CanBubble::eYes,
 | |
|       ChromeOnlyDispatch::eYes);
 | |
|   asyncDispatcher->PostDOMEvent();
 | |
| }
 | |
| 
 | |
| void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
 | |
|   NS_ASSERTION(!FullscreenStackTop() || !FullscreenRoots::IsEmpty(),
 | |
|                "Should have at least 1 fullscreen root when fullscreen!");
 | |
| 
 | |
|   if (!FullscreenStackTop() || !GetWindow() || FullscreenRoots::IsEmpty()) {
 | |
|     aExit->MayRejectPromise();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
 | |
|   AutoTArray<Element*, 8> exitElements;
 | |
| 
 | |
|   Document* doc = fullScreenDoc;
 | |
|   // Collect all subdocuments.
 | |
|   for (; doc != this; doc = doc->GetParentDocument()) {
 | |
|     Element* fsElement = doc->FullscreenStackTop();
 | |
|     MOZ_ASSERT(fsElement,
 | |
|                "Parent document of "
 | |
|                "a fullscreen document without fullscreen element?");
 | |
|     exitElements.AppendElement(fsElement);
 | |
|   }
 | |
|   MOZ_ASSERT(doc == this, "Must have reached this doc");
 | |
|   // Collect all ancestor documents which we are going to change.
 | |
|   for (; doc; doc = doc->GetParentDocument()) {
 | |
|     MOZ_ASSERT(!doc->mFullscreenStack.IsEmpty(),
 | |
|                "Ancestor of fullscreen document must also be in fullscreen");
 | |
|     Element* fsElement = doc->FullscreenStackTop();
 | |
|     if (doc != this) {
 | |
|       if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
 | |
|         if (iframe->FullscreenFlag()) {
 | |
|           // If this is an iframe, and it explicitly requested
 | |
|           // fullscreen, don't rollback it automatically.
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     exitElements.AppendElement(fsElement);
 | |
|     if (doc->mFullscreenStack.Length() > 1) {
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Document* lastDoc = exitElements.LastElement()->OwnerDoc();
 | |
|   if (!lastDoc->GetParentDocument() &&
 | |
|       lastDoc->mFullscreenStack.Length() == 1) {
 | |
|     // If we are fully exiting fullscreen, don't touch anything here,
 | |
|     // just wait for the window to get out from fullscreen first.
 | |
|     PendingFullscreenChangeList::Add(std::move(aExit));
 | |
|     AskWindowToExitFullscreen(this);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If fullscreen mode is updated the pointer should be unlocked
 | |
|   UnlockPointer();
 | |
|   // All documents listed in the array except the last one are going to
 | |
|   // completely exit from the fullscreen state.
 | |
|   for (auto i : IntegerRange(exitElements.Length() - 1)) {
 | |
|     exitElements[i]->OwnerDoc()->CleanupFullscreenState();
 | |
|   }
 | |
|   // The last document will either rollback one fullscreen element, or
 | |
|   // completely exit from the fullscreen state as well.
 | |
|   Document* newFullscreenDoc;
 | |
|   if (lastDoc->mFullscreenStack.Length() > 1) {
 | |
|     lastDoc->FullscreenStackPop();
 | |
|     newFullscreenDoc = lastDoc;
 | |
|   } else {
 | |
|     lastDoc->CleanupFullscreenState();
 | |
|     newFullscreenDoc = lastDoc->GetParentDocument();
 | |
|   }
 | |
|   // Dispatch the fullscreenchange event to all document listed. Note
 | |
|   // that the loop order is reversed so that events are dispatched in
 | |
|   // the tree order as indicated in the spec.
 | |
|   for (Element* e : Reversed(exitElements)) {
 | |
|     DispatchFullscreenChange(e->OwnerDoc(), e);
 | |
|   }
 | |
|   aExit->MayResolvePromise();
 | |
| 
 | |
|   MOZ_ASSERT(newFullscreenDoc,
 | |
|              "If we were going to exit from fullscreen on "
 | |
|              "all documents in this doctree, we should've asked the window to "
 | |
|              "exit first instead of reaching here.");
 | |
|   if (fullScreenDoc != newFullscreenDoc &&
 | |
|       !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
 | |
|     // We've popped so enough off the stack that we've rolled back to
 | |
|     // a fullscreen element in a parent document. If this document is
 | |
|     // cross origin, dispatch an event to chrome so it knows to show
 | |
|     // the warning UI.
 | |
|     DispatchFullscreenNewOriginEvent(newFullscreenDoc);
 | |
|   }
 | |
| }
 | |
| 
 | |
| class nsCallRequestFullscreen : public Runnable {
 | |
|  public:
 | |
|   explicit nsCallRequestFullscreen(UniquePtr<FullscreenRequest> aRequest)
 | |
|       : mozilla::Runnable("nsCallRequestFullscreen"),
 | |
|         mRequest(std::move(aRequest)) {}
 | |
| 
 | |
|   NS_IMETHOD Run() override {
 | |
|     Document* doc = mRequest->Document();
 | |
|     doc->RequestFullscreen(std::move(mRequest));
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   UniquePtr<FullscreenRequest> mRequest;
 | |
| };
 | |
| 
 | |
| void Document::AsyncRequestFullscreen(UniquePtr<FullscreenRequest> aRequest) {
 | |
|   // Request fullscreen asynchronously.
 | |
|   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
|   nsCOMPtr<nsIRunnable> event =
 | |
|       new nsCallRequestFullscreen(std::move(aRequest));
 | |
|   Dispatch(TaskCategory::Other, event.forget());
 | |
| }
 | |
| 
 | |
| static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
 | |
|   if (nsPresContext* presContext = aDoc->GetPresContext()) {
 | |
|     presContext->UpdateViewportScrollStylesOverride();
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void ClearFullscreenStateOnElement(Element* aElement) {
 | |
|   // Remove styles from existing top element.
 | |
|   EventStateManager::SetFullscreenState(aElement, false);
 | |
|   // Reset iframe fullscreen flag.
 | |
|   if (aElement->IsHTMLElement(nsGkAtoms::iframe)) {
 | |
|     static_cast<HTMLIFrameElement*>(aElement)->SetFullscreenFlag(false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::CleanupFullscreenState() {
 | |
|   // Iterate the fullscreen stack and clear the fullscreen states.
 | |
|   // Since we also need to clear the fullscreen-ancestor state, and
 | |
|   // currently fullscreen elements can only be placed in hierarchy
 | |
|   // order in the stack, reversely iterating the stack could be more
 | |
|   // efficient. NOTE that fullscreen-ancestor state would be removed
 | |
|   // in bug 1199529, and the elements may not in hierarchy order
 | |
|   // after bug 1195213.
 | |
|   for (nsWeakPtr& weakPtr : Reversed(mFullscreenStack)) {
 | |
|     if (nsCOMPtr<Element> element = do_QueryReferent(weakPtr)) {
 | |
|       ClearFullscreenStateOnElement(element);
 | |
|     }
 | |
|   }
 | |
|   mFullscreenStack.Clear();
 | |
|   mFullscreenRoot = nullptr;
 | |
| 
 | |
|   // Restore the zoom level that was in place prior to entering fullscreen.
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     if (shell->GetMobileViewportManager()) {
 | |
|       shell->SetResolutionAndScaleTo(mSavedResolution,
 | |
|                                      nsIPresShell::ChangeOrigin::eMainThread);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   UpdateViewportScrollbarOverrideForFullscreen(this);
 | |
| }
 | |
| 
 | |
| bool Document::FullscreenStackPush(Element* aElement) {
 | |
|   NS_ASSERTION(aElement, "Must pass non-null to FullscreenStackPush()");
 | |
|   Element* top = FullscreenStackTop();
 | |
|   if (top == aElement || !aElement) {
 | |
|     return false;
 | |
|   }
 | |
|   EventStateManager::SetFullscreenState(aElement, true);
 | |
|   mFullscreenStack.AppendElement(do_GetWeakReference(aElement));
 | |
|   NS_ASSERTION(FullscreenStackTop() == aElement, "Should match");
 | |
|   UpdateViewportScrollbarOverrideForFullscreen(this);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::FullscreenStackPop() {
 | |
|   if (mFullscreenStack.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   ClearFullscreenStateOnElement(FullscreenStackTop());
 | |
| 
 | |
|   // Remove top element. Note the remaining top element in the stack
 | |
|   // will not have fullscreen style bits set, so we will need to restore
 | |
|   // them on the new top element before returning.
 | |
|   uint32_t last = mFullscreenStack.Length() - 1;
 | |
|   mFullscreenStack.RemoveElementAt(last);
 | |
| 
 | |
|   // Pop from the stack null elements (references to elements which have
 | |
|   // been GC'd since they were added to the stack) and elements which are
 | |
|   // no longer in this document.
 | |
|   while (!mFullscreenStack.IsEmpty()) {
 | |
|     Element* element = FullscreenStackTop();
 | |
|     if (!element || !element->IsInUncomposedDoc() ||
 | |
|         element->OwnerDoc() != this) {
 | |
|       NS_ASSERTION(!element->State().HasState(NS_EVENT_STATE_FULLSCREEN),
 | |
|                    "Should have already removed fullscreen styles");
 | |
|       uint32_t last = mFullscreenStack.Length() - 1;
 | |
|       mFullscreenStack.RemoveElementAt(last);
 | |
|     } else {
 | |
|       // The top element of the stack is now an in-doc element. Return here.
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   UpdateViewportScrollbarOverrideForFullscreen(this);
 | |
| }
 | |
| 
 | |
| Element* Document::FullscreenStackTop() {
 | |
|   if (mFullscreenStack.IsEmpty()) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   uint32_t last = mFullscreenStack.Length() - 1;
 | |
|   nsCOMPtr<Element> element(do_QueryReferent(mFullscreenStack[last]));
 | |
|   NS_ASSERTION(element, "Should have fullscreen element!");
 | |
|   NS_ASSERTION(element->IsInComposedDoc(),
 | |
|                "Fullscreen element should be in doc");
 | |
|   NS_ASSERTION(element->OwnerDoc() == this,
 | |
|                "Fullscreen element should be in this doc");
 | |
|   return element;
 | |
| }
 | |
| 
 | |
| nsTArray<Element*> Document::GetFullscreenStack() const {
 | |
|   nsTArray<Element*> elements;
 | |
|   for (const nsWeakPtr& ptr : mFullscreenStack) {
 | |
|     if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
 | |
|       MOZ_ASSERT(elem->State().HasState(NS_EVENT_STATE_FULLSCREEN));
 | |
|       elements.AppendElement(elem);
 | |
|     }
 | |
|   }
 | |
|   return elements;
 | |
| }
 | |
| 
 | |
| // Returns true if aDoc is in the focused tab in the active window.
 | |
| static bool IsInActiveTab(Document* aDoc) {
 | |
|   nsCOMPtr<nsIDocShell> docshell = aDoc->GetDocShell();
 | |
|   if (!docshell) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   bool isActive = false;
 | |
|   docshell->GetIsActive(&isActive);
 | |
|   if (!isActive) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDocShellTreeItem> rootItem;
 | |
|   docshell->GetRootTreeItem(getter_AddRefs(rootItem));
 | |
|   if (!rootItem) {
 | |
|     return false;
 | |
|   }
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
 | |
|   if (!rootWin) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
 | |
|   if (!fm) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<mozIDOMWindowProxy> activeWindow;
 | |
|   fm->GetActiveWindow(getter_AddRefs(activeWindow));
 | |
|   if (!activeWindow) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return activeWindow == rootWin;
 | |
| }
 | |
| 
 | |
| nsresult Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
 | |
|   // Ensure the frame element is the fullscreen element in this document.
 | |
|   // If the frame element is already the fullscreen element in this document,
 | |
|   // this has no effect.
 | |
|   auto request = FullscreenRequest::CreateForRemote(aFrameElement);
 | |
|   RequestFullscreen(std::move(request));
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult Document::RemoteFrameFullscreenReverted() {
 | |
|   UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
 | |
|   RestorePreviousFullscreenState(std::move(exit));
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /* static */ bool Document::IsUnprefixedFullscreenEnabled(JSContext* aCx,
 | |
|                                                           JSObject* aObject) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   return nsContentUtils::IsSystemCaller(aCx) ||
 | |
|          nsContentUtils::IsUnprefixedFullscreenApiEnabled();
 | |
| }
 | |
| 
 | |
| static bool HasFullscreenSubDocument(Document* aDoc) {
 | |
|   uint32_t count = CountFullscreenSubDocuments(aDoc);
 | |
|   NS_ASSERTION(count <= 1,
 | |
|                "Fullscreen docs should have at most 1 fullscreen child!");
 | |
|   return count >= 1;
 | |
| }
 | |
| 
 | |
| // Returns nullptr if a request for Fullscreen API is currently enabled
 | |
| // in the given document. Returns a static string indicates the reason
 | |
| // why it is not enabled otherwise.
 | |
| static const char* GetFullscreenError(Document* aDoc, CallerType aCallerType) {
 | |
|   bool apiEnabled = nsContentUtils::IsFullscreenApiEnabled();
 | |
|   if (apiEnabled && aCallerType == CallerType::System) {
 | |
|     // Chrome code can always use the fullscreen API, provided it's not
 | |
|     // explicitly disabled.
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (!apiEnabled) {
 | |
|     return "FullscreenDeniedDisabled";
 | |
|   }
 | |
| 
 | |
|   if (!aDoc->IsVisible()) {
 | |
|     return "FullscreenDeniedHidden";
 | |
|   }
 | |
| 
 | |
|   // Ensure that all containing elements are <iframe> and have
 | |
|   // allowfullscreen attribute set.
 | |
|   nsCOMPtr<nsIDocShell> docShell(aDoc->GetDocShell());
 | |
|   if (!docShell || !docShell->GetFullscreenAllowed()) {
 | |
|     return "FullscreenDeniedContainerNotAllowed";
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| bool Document::FullscreenElementReadyCheck(const FullscreenRequest& aRequest) {
 | |
|   Element* elem = aRequest.Element();
 | |
|   // Strictly speaking, this isn't part of the fullscreen element ready
 | |
|   // check in the spec, but per steps in the spec, when an element which
 | |
|   // is already the fullscreen element requests fullscreen, nothing
 | |
|   // should change and no event should be dispatched, but we still need
 | |
|   // to resolve the returned promise.
 | |
|   if (elem == FullscreenStackTop()) {
 | |
|     aRequest.MayResolvePromise();
 | |
|     return false;
 | |
|   }
 | |
|   if (!elem->IsInComposedDoc()) {
 | |
|     aRequest.Reject("FullscreenDeniedNotInDocument");
 | |
|     return false;
 | |
|   }
 | |
|   if (elem->OwnerDoc() != this) {
 | |
|     aRequest.Reject("FullscreenDeniedMovedDocument");
 | |
|     return false;
 | |
|   }
 | |
|   if (!GetWindow()) {
 | |
|     aRequest.Reject("FullscreenDeniedLostWindow");
 | |
|     return false;
 | |
|   }
 | |
|   if (const char* msg = GetFullscreenError(this, aRequest.mCallerType)) {
 | |
|     aRequest.Reject(msg);
 | |
|     return false;
 | |
|   }
 | |
|   if (HasFullscreenSubDocument(this)) {
 | |
|     aRequest.Reject("FullscreenDeniedSubDocFullScreen");
 | |
|     return false;
 | |
|   }
 | |
|   // XXXsmaug Note, we don't follow the latest fullscreen spec here.
 | |
|   //         This whole check could be probably removed.
 | |
|   if (FullscreenStackTop() &&
 | |
|       !nsContentUtils::ContentIsHostIncludingDescendantOf(
 | |
|           elem, FullscreenStackTop())) {
 | |
|     // If this document is fullscreen, only grant fullscreen requests from
 | |
|     // a descendant of the current fullscreen element.
 | |
|     aRequest.Reject("FullscreenDeniedNotDescendant");
 | |
|     return false;
 | |
|   }
 | |
|   if (!nsContentUtils::IsChromeDoc(this) && !IsInActiveTab(this)) {
 | |
|     aRequest.Reject("FullscreenDeniedNotFocusedTab");
 | |
|     return false;
 | |
|   }
 | |
|   // Deny requests when a windowed plugin is focused.
 | |
|   nsFocusManager* fm = nsFocusManager::GetFocusManager();
 | |
|   if (!fm) {
 | |
|     NS_WARNING("Failed to retrieve focus manager in fullscreen request.");
 | |
|     aRequest.MayRejectPromise();
 | |
|     return false;
 | |
|   }
 | |
|   if (nsContentUtils::HasPluginWithUncontrolledEventDispatch(
 | |
|           fm->GetFocusedElement())) {
 | |
|     aRequest.Reject("FullscreenDeniedFocusedPlugin");
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
 | |
|   nsIDocShell* docShell = aDoc->GetDocShell();
 | |
|   if (!docShell) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   nsCOMPtr<nsIDocShellTreeItem> rootItem;
 | |
|   docShell->GetRootTreeItem(getter_AddRefs(rootItem));
 | |
|   return rootItem ? rootItem->GetWindow() : nullptr;
 | |
| }
 | |
| 
 | |
| static bool ShouldApplyFullscreenDirectly(Document* aDoc,
 | |
|                                           nsPIDOMWindowOuter* aRootWin) {
 | |
|   if (XRE_GetProcessType() == GeckoProcessType_Content) {
 | |
|     // If we are in the content process, we can apply the fullscreen
 | |
|     // state directly only if we have been in DOM fullscreen, because
 | |
|     // otherwise we always need to notify the chrome.
 | |
|     return !!nsContentUtils::GetRootDocument(aDoc)->GetFullscreenElement();
 | |
|   } else {
 | |
|     // If we are in the chrome process, and the window has not been in
 | |
|     // fullscreen, we certainly need to make that fullscreen first.
 | |
|     if (!aRootWin->GetFullScreen()) {
 | |
|       return false;
 | |
|     }
 | |
|     // The iterator not being at end indicates there is still some
 | |
|     // pending fullscreen request relates to this document. We have to
 | |
|     // push the request to the pending queue so requests are handled
 | |
|     // in the correct order.
 | |
|     PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
 | |
|         aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
 | |
|     if (!iter.AtEnd()) {
 | |
|       return false;
 | |
|     }
 | |
|     // We have to apply the fullscreen state directly in this case,
 | |
|     // because nsGlobalWindow::SetFullscreenInternal() will do nothing
 | |
|     // if it is already in fullscreen. If we do not apply the state but
 | |
|     // instead add it to the queue and wait for the window as normal,
 | |
|     // we would get stuck.
 | |
|     return true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest) {
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
 | |
|   if (!rootWin) {
 | |
|     aRequest->MayRejectPromise();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (ShouldApplyFullscreenDirectly(this, rootWin)) {
 | |
|     ApplyFullscreen(std::move(aRequest));
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Per spec only HTML, <svg>, and <math> should be allowed, but
 | |
|   // we also need to allow XUL elements right now.
 | |
|   Element* elem = aRequest->Element();
 | |
|   if (!elem->IsHTMLElement() && !elem->IsXULElement() &&
 | |
|       !elem->IsSVGElement(nsGkAtoms::svg) &&
 | |
|       !elem->IsMathMLElement(nsGkAtoms::math)) {
 | |
|     aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We don't need to check element ready before this point, because
 | |
|   // if we called ApplyFullscreen, it would check that for us.
 | |
|   if (!FullscreenElementReadyCheck(*aRequest)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   PendingFullscreenChangeList::Add(std::move(aRequest));
 | |
|   if (XRE_GetProcessType() == GeckoProcessType_Content) {
 | |
|     // If we are not the top level process, dispatch an event to make
 | |
|     // our parent process go fullscreen first.
 | |
|     nsContentUtils::DispatchEventOnlyToChrome(
 | |
|         this, ToSupports(this), NS_LITERAL_STRING("MozDOMFullscreen:Request"),
 | |
|         CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
 | |
|   } else {
 | |
|     // Make the window fullscreen.
 | |
|     rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */ bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
 | |
|   bool handled = false;
 | |
|   PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
 | |
|       aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
 | |
|   while (!iter.AtEnd()) {
 | |
|     UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
 | |
|     Document* doc = request->Document();
 | |
|     if (doc->ApplyFullscreen(std::move(request))) {
 | |
|       handled = true;
 | |
|     }
 | |
|   }
 | |
|   return handled;
 | |
| }
 | |
| 
 | |
| static void ClearPendingFullscreenRequests(Document* aDoc) {
 | |
|   PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
 | |
|       aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
 | |
|   while (!iter.AtEnd()) {
 | |
|     UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
 | |
|     request->MayRejectPromise();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
 | |
|   if (!FullscreenElementReadyCheck(*aRequest)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Stash a reference to any existing fullscreen doc, we'll use this later
 | |
|   // to detect if the origin which is fullscreen has changed.
 | |
|   nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
 | |
| 
 | |
|   // Stores a list of documents which we must dispatch "fullscreenchange"
 | |
|   // too. We're required by the spec to dispatch the events in root-to-leaf
 | |
|   // order, but we traverse the doctree in a leaf-to-root order, so we save
 | |
|   // references to the documents we must dispatch to so that we get the order
 | |
|   // as specified.
 | |
|   AutoTArray<Document*, 8> changed;
 | |
| 
 | |
|   // Remember the root document, so that if a fullscreen document is hidden
 | |
|   // we can reset fullscreen state in the remaining visible fullscreen
 | |
|   // documents.
 | |
|   Document* fullScreenRootDoc = nsContentUtils::GetRootDocument(this);
 | |
| 
 | |
|   // If a document is already in fullscreen, then unlock the mouse pointer
 | |
|   // before setting a new document to fullscreen
 | |
|   UnlockPointer();
 | |
| 
 | |
|   // Set the fullscreen element. This sets the fullscreen style on the
 | |
|   // element, and the fullscreen-ancestor styles on ancestors of the element
 | |
|   // in this document.
 | |
|   Element* elem = aRequest->Element();
 | |
|   DebugOnly<bool> x = FullscreenStackPush(elem);
 | |
|   NS_ASSERTION(x, "Fullscreen state of requesting doc should always change!");
 | |
|   // Set the iframe fullscreen flag.
 | |
|   if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
 | |
|     iframe->SetFullscreenFlag(true);
 | |
|   }
 | |
|   changed.AppendElement(this);
 | |
| 
 | |
|   // Propagate up the document hierarchy, setting the fullscreen element as
 | |
|   // the element's container in ancestor documents. This also sets the
 | |
|   // appropriate css styles as well. Note we don't propagate down the
 | |
|   // document hierarchy, the fullscreen element (or its container) is not
 | |
|   // visible there. Stop when we reach the root document.
 | |
|   Document* child = this;
 | |
|   while (true) {
 | |
|     child->SetFullscreenRoot(fullScreenRootDoc);
 | |
| 
 | |
|     // When entering fullscreen, reset the RCD's resolution to the intrinsic
 | |
|     // resolution, otherwise the fullscreen content could be sized larger than
 | |
|     // the screen (since fullscreen is implemented using position:fixed and
 | |
|     // fixed elements are sized to the layout viewport).
 | |
|     // This also ensures that things like video controls aren't zoomed in
 | |
|     // when in fullscreen mode.
 | |
|     if (nsIPresShell* shell = child->GetShell()) {
 | |
|       if (RefPtr<MobileViewportManager> manager =
 | |
|               shell->GetMobileViewportManager()) {
 | |
|         // Save the previous resolution so it can be restored.
 | |
|         child->mSavedResolution = shell->GetResolution();
 | |
|         shell->SetResolutionAndScaleTo(manager->ComputeIntrinsicResolution(),
 | |
|                                        nsIPresShell::ChangeOrigin::eMainThread);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
 | |
|                  "Fullscreen root should be set!");
 | |
|     if (child == fullScreenRootDoc) {
 | |
|       break;
 | |
|     }
 | |
|     Document* parent = child->GetParentDocument();
 | |
|     Element* element = parent->FindContentForSubDocument(child);
 | |
|     if (parent->FullscreenStackPush(element)) {
 | |
|       changed.AppendElement(parent);
 | |
|       child = parent;
 | |
|     } else {
 | |
|       // We've reached either the root, or a point in the doctree where the
 | |
|       // new fullscreen element container is the same as the previous
 | |
|       // fullscreen element's container. No more changes need to be made
 | |
|       // to the fullscreen stacks of documents further up the tree.
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   FullscreenRoots::Add(this);
 | |
| 
 | |
|   // If it is the first entry of the fullscreen, trigger an event so
 | |
|   // that the UI can response to this change, e.g. hide chrome, or
 | |
|   // notifying parent process to enter fullscreen. Note that chrome
 | |
|   // code may also want to listen to MozDOMFullscreen:NewOrigin event
 | |
|   // to pop up warning UI.
 | |
|   if (!previousFullscreenDoc) {
 | |
|     nsContentUtils::DispatchEventOnlyToChrome(
 | |
|         this, ToSupports(elem), NS_LITERAL_STRING("MozDOMFullscreen:Entered"),
 | |
|         CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
 | |
|   }
 | |
| 
 | |
|   // The origin which is fullscreen gets changed. Trigger an event so
 | |
|   // that the chrome knows to pop up a warning UI. Note that
 | |
|   // previousFullscreenDoc == nullptr upon first entry, so we always
 | |
|   // take this path on the first entry. Also note that, in a multi-
 | |
|   // process browser, the code in content process is responsible for
 | |
|   // sending message with the origin to its parent, and the parent
 | |
|   // shouldn't rely on this event itself.
 | |
|   if (aRequest->mShouldNotifyNewOrigin &&
 | |
|       !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
 | |
|     DispatchFullscreenNewOriginEvent(this);
 | |
|   }
 | |
| 
 | |
|   // Dispatch "fullscreenchange" events. Note that the loop order is
 | |
|   // reversed so that events are dispatched in the tree order as
 | |
|   // indicated in the spec.
 | |
|   for (Document* d : Reversed(changed)) {
 | |
|     DispatchFullscreenChange(d, d->FullscreenStackTop());
 | |
|   }
 | |
|   aRequest->MayResolvePromise();
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool Document::FullscreenEnabled(CallerType aCallerType) {
 | |
|   return !GetFullscreenError(this, aCallerType);
 | |
| }
 | |
| 
 | |
| void Document::SetOrientationPendingPromise(Promise* aPromise) {
 | |
|   mOrientationPendingPromise = aPromise;
 | |
| }
 | |
| 
 | |
| static void DispatchPointerLockChange(Document* aTarget) {
 | |
|   if (!aTarget) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<AsyncEventDispatcher> asyncDispatcher =
 | |
|       new AsyncEventDispatcher(aTarget, NS_LITERAL_STRING("pointerlockchange"),
 | |
|                                CanBubble::eYes, ChromeOnlyDispatch::eNo);
 | |
|   asyncDispatcher->PostDOMEvent();
 | |
| }
 | |
| 
 | |
| static void DispatchPointerLockError(Document* aTarget, const char* aMessage) {
 | |
|   if (!aTarget) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<AsyncEventDispatcher> asyncDispatcher =
 | |
|       new AsyncEventDispatcher(aTarget, NS_LITERAL_STRING("pointerlockerror"),
 | |
|                                CanBubble::eYes, ChromeOnlyDispatch::eNo);
 | |
|   asyncDispatcher->PostDOMEvent();
 | |
|   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
 | |
|                                   NS_LITERAL_CSTRING("DOM"), aTarget,
 | |
|                                   nsContentUtils::eDOM_PROPERTIES, aMessage);
 | |
| }
 | |
| 
 | |
| class PointerLockRequest final : public Runnable {
 | |
|  public:
 | |
|   PointerLockRequest(Element* aElement, bool aUserInputOrChromeCaller)
 | |
|       : mozilla::Runnable("PointerLockRequest"),
 | |
|         mElement(do_GetWeakReference(aElement)),
 | |
|         mDocument(do_GetWeakReference(aElement->OwnerDoc())),
 | |
|         mUserInputOrChromeCaller(aUserInputOrChromeCaller) {}
 | |
| 
 | |
|   NS_IMETHOD Run() final;
 | |
| 
 | |
|  private:
 | |
|   nsWeakPtr mElement;
 | |
|   nsWeakPtr mDocument;
 | |
|   bool mUserInputOrChromeCaller;
 | |
| };
 | |
| 
 | |
| static const char* GetPointerLockError(Element* aElement, Element* aCurrentLock,
 | |
|                                        bool aNoFocusCheck = false) {
 | |
|   // Check if pointer lock pref is enabled
 | |
|   if (!Preferences::GetBool("full-screen-api.pointer-lock.enabled")) {
 | |
|     return "PointerLockDeniedDisabled";
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Document> ownerDoc = aElement->OwnerDoc();
 | |
|   if (aCurrentLock && aCurrentLock->OwnerDoc() != ownerDoc) {
 | |
|     return "PointerLockDeniedInUse";
 | |
|   }
 | |
| 
 | |
|   if (!aElement->IsInComposedDoc()) {
 | |
|     return "PointerLockDeniedNotInDocument";
 | |
|   }
 | |
| 
 | |
|   if (ownerDoc->GetSandboxFlags() & SANDBOXED_POINTER_LOCK) {
 | |
|     return "PointerLockDeniedSandboxed";
 | |
|   }
 | |
| 
 | |
|   // Check if the element is in a document with a docshell.
 | |
|   if (!ownerDoc->GetContainer()) {
 | |
|     return "PointerLockDeniedHidden";
 | |
|   }
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> ownerWindow = ownerDoc->GetWindow();
 | |
|   if (!ownerWindow) {
 | |
|     return "PointerLockDeniedHidden";
 | |
|   }
 | |
|   nsCOMPtr<nsPIDOMWindowInner> ownerInnerWindow = ownerDoc->GetInnerWindow();
 | |
|   if (!ownerInnerWindow) {
 | |
|     return "PointerLockDeniedHidden";
 | |
|   }
 | |
|   if (ownerWindow->GetCurrentInnerWindow() != ownerInnerWindow) {
 | |
|     return "PointerLockDeniedHidden";
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> top = ownerWindow->GetScriptableTop();
 | |
|   if (!top || !top->GetExtantDoc() || top->GetExtantDoc()->Hidden()) {
 | |
|     return "PointerLockDeniedHidden";
 | |
|   }
 | |
| 
 | |
|   if (!aNoFocusCheck) {
 | |
|     mozilla::ErrorResult rv;
 | |
|     if (!top->GetExtantDoc()->HasFocus(rv)) {
 | |
|       return "PointerLockDeniedNotFocused";
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| static void ChangePointerLockedElement(Element* aElement, Document* aDocument,
 | |
|                                        Element* aPointerLockedElement) {
 | |
|   // aDocument here is not really necessary, as it is the uncomposed
 | |
|   // document of both aElement and aPointerLockedElement as far as one
 | |
|   // is not nullptr, and they wouldn't both be nullptr in any case.
 | |
|   // But since the caller of this function should have known what the
 | |
|   // document is, we just don't try to figure out what it should be.
 | |
|   MOZ_ASSERT(aDocument);
 | |
|   MOZ_ASSERT(aElement != aPointerLockedElement);
 | |
|   if (aPointerLockedElement) {
 | |
|     MOZ_ASSERT(aPointerLockedElement->GetComposedDoc() == aDocument);
 | |
|     aPointerLockedElement->ClearPointerLock();
 | |
|   }
 | |
|   if (aElement) {
 | |
|     MOZ_ASSERT(aElement->GetComposedDoc() == aDocument);
 | |
|     aElement->SetPointerLock();
 | |
|     EventStateManager::sPointerLockedElement = do_GetWeakReference(aElement);
 | |
|     EventStateManager::sPointerLockedDoc = do_GetWeakReference(aDocument);
 | |
|     NS_ASSERTION(EventStateManager::sPointerLockedElement &&
 | |
|                      EventStateManager::sPointerLockedDoc,
 | |
|                  "aElement and this should support weak references!");
 | |
|   } else {
 | |
|     EventStateManager::sPointerLockedElement = nullptr;
 | |
|     EventStateManager::sPointerLockedDoc = nullptr;
 | |
|   }
 | |
|   // Retarget all events to aElement via capture or
 | |
|   // stop retargeting if aElement is nullptr.
 | |
|   nsIPresShell::SetCapturingContent(aElement, CAPTURE_POINTERLOCK);
 | |
|   DispatchPointerLockChange(aDocument);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| PointerLockRequest::Run() {
 | |
|   nsCOMPtr<Element> e = do_QueryReferent(mElement);
 | |
|   nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
 | |
|   const char* error = nullptr;
 | |
|   if (!e || !doc || !e->GetComposedDoc()) {
 | |
|     error = "PointerLockDeniedNotInDocument";
 | |
|   } else if (e->GetComposedDoc() != doc) {
 | |
|     error = "PointerLockDeniedMovedDocument";
 | |
|   }
 | |
|   if (!error) {
 | |
|     nsCOMPtr<Element> pointerLockedElement =
 | |
|         do_QueryReferent(EventStateManager::sPointerLockedElement);
 | |
|     if (e == pointerLockedElement) {
 | |
|       DispatchPointerLockChange(doc);
 | |
|       return NS_OK;
 | |
|     }
 | |
|     // Note, we must bypass focus change, so pass true as the last parameter!
 | |
|     error = GetPointerLockError(e, pointerLockedElement, true);
 | |
|     // Another element in the same document is requesting pointer lock,
 | |
|     // just grant it without user input check.
 | |
|     if (!error && pointerLockedElement) {
 | |
|       ChangePointerLockedElement(e, doc, pointerLockedElement);
 | |
|       return NS_OK;
 | |
|     }
 | |
|   }
 | |
|   // If it is neither user input initiated, nor requested in fullscreen,
 | |
|   // it should be rejected.
 | |
|   if (!error && !mUserInputOrChromeCaller && !doc->GetFullscreenElement()) {
 | |
|     error = "PointerLockDeniedNotInputDriven";
 | |
|   }
 | |
|   if (!error && !doc->SetPointerLock(e, StyleCursorKind::None)) {
 | |
|     error = "PointerLockDeniedFailedToLock";
 | |
|   }
 | |
|   if (error) {
 | |
|     DispatchPointerLockError(doc, error);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   ChangePointerLockedElement(e, doc, nullptr);
 | |
|   nsContentUtils::DispatchEventOnlyToChrome(
 | |
|       doc, ToSupports(e), NS_LITERAL_STRING("MozDOMPointerLock:Entered"),
 | |
|       CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void Document::RequestPointerLock(Element* aElement, CallerType aCallerType) {
 | |
|   NS_ASSERTION(aElement,
 | |
|                "Must pass non-null element to Document::RequestPointerLock");
 | |
| 
 | |
|   nsCOMPtr<Element> pointerLockedElement =
 | |
|       do_QueryReferent(EventStateManager::sPointerLockedElement);
 | |
|   if (aElement == pointerLockedElement) {
 | |
|     DispatchPointerLockChange(this);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (const char* msg = GetPointerLockError(aElement, pointerLockedElement)) {
 | |
|     DispatchPointerLockError(this, msg);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   bool userInputOrSystemCaller = EventStateManager::IsHandlingUserInput() ||
 | |
|                                  aCallerType == CallerType::System;
 | |
|   nsCOMPtr<nsIRunnable> request =
 | |
|       new PointerLockRequest(aElement, userInputOrSystemCaller);
 | |
|   Dispatch(TaskCategory::Other, request.forget());
 | |
| }
 | |
| 
 | |
| bool Document::SetPointerLock(Element* aElement, StyleCursorKind aCursorStyle) {
 | |
|   MOZ_ASSERT(!aElement || aElement->OwnerDoc() == this,
 | |
|              "We should be either unlocking pointer (aElement is nullptr), "
 | |
|              "or locking pointer to an element in this document");
 | |
| #ifdef DEBUG
 | |
|   if (!aElement) {
 | |
|     nsCOMPtr<Document> pointerLockedDoc =
 | |
|         do_QueryReferent(EventStateManager::sPointerLockedDoc);
 | |
|     MOZ_ASSERT(pointerLockedDoc == this);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   nsIPresShell* shell = GetShell();
 | |
|   if (!shell) {
 | |
|     NS_WARNING("SetPointerLock(): No PresShell");
 | |
|     if (!aElement) {
 | |
|       // If we are unlocking pointer lock, but for some reason the doc
 | |
|       // has already detached from the presshell, just ask the event
 | |
|       // state manager to release the pointer.
 | |
|       EventStateManager::SetPointerLock(nullptr, nullptr);
 | |
|       return true;
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
|   nsPresContext* presContext = shell->GetPresContext();
 | |
|   if (!presContext) {
 | |
|     NS_WARNING("SetPointerLock(): Unable to get PresContext");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIWidget> widget;
 | |
|   nsIFrame* rootFrame = shell->GetRootFrame();
 | |
|   if (!NS_WARN_IF(!rootFrame)) {
 | |
|     widget = rootFrame->GetNearestWidget();
 | |
|     NS_WARNING_ASSERTION(widget,
 | |
|                          "SetPointerLock(): Unable to find widget in "
 | |
|                          "shell->GetRootFrame()->GetNearestWidget();");
 | |
|     if (aElement && !widget) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Hide the cursor and set pointer lock for future mouse events
 | |
|   RefPtr<EventStateManager> esm = presContext->EventStateManager();
 | |
|   esm->SetCursor(aCursorStyle, nullptr, Nothing(), widget, true);
 | |
|   EventStateManager::SetPointerLock(widget, aElement);
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::UnlockPointer(Document* aDoc) {
 | |
|   if (!EventStateManager::sIsPointerLocked) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Document> pointerLockedDoc =
 | |
|       do_QueryReferent(EventStateManager::sPointerLockedDoc);
 | |
|   if (!pointerLockedDoc || (aDoc && aDoc != pointerLockedDoc)) {
 | |
|     return;
 | |
|   }
 | |
|   if (!pointerLockedDoc->SetPointerLock(nullptr, StyleCursorKind::Auto)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Element> pointerLockedElement =
 | |
|       do_QueryReferent(EventStateManager::sPointerLockedElement);
 | |
|   ChangePointerLockedElement(nullptr, pointerLockedDoc, pointerLockedElement);
 | |
| 
 | |
|   RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
 | |
|       pointerLockedElement, NS_LITERAL_STRING("MozDOMPointerLock:Exited"),
 | |
|       CanBubble::eYes, ChromeOnlyDispatch::eYes);
 | |
|   asyncDispatcher->RunDOMEventWhenSafe();
 | |
| }
 | |
| 
 | |
| void Document::UpdateVisibilityState() {
 | |
|   dom::VisibilityState oldState = mVisibilityState;
 | |
|   mVisibilityState = ComputeVisibilityState();
 | |
|   if (oldState != mVisibilityState) {
 | |
|     nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
 | |
|                                          NS_LITERAL_STRING("visibilitychange"),
 | |
|                                          CanBubble::eYes, Cancelable::eNo);
 | |
|     EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 | |
|   }
 | |
| 
 | |
|   if (mVisibilityState == dom::VisibilityState::Visible) {
 | |
|     MaybeActiveMediaComponents();
 | |
|   }
 | |
| }
 | |
| 
 | |
| VisibilityState Document::ComputeVisibilityState() const {
 | |
|   // We have to check a few pieces of information here:
 | |
|   // 1)  Are we in bfcache (!IsVisible())?  If so, nothing else matters.
 | |
|   // 2)  Do we have an outer window?  If not, we're hidden.  Note that we don't
 | |
|   //     want to use GetWindow here because it does weird groveling for windows
 | |
|   //     in some cases.
 | |
|   // 3)  Is our outer window background?  If so, we're hidden.
 | |
|   // Otherwise, we're visible.
 | |
|   if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
 | |
|       mWindow->GetOuterWindow()->IsBackground()) {
 | |
|     return dom::VisibilityState::Hidden;
 | |
|   }
 | |
| 
 | |
|   return dom::VisibilityState::Visible;
 | |
| }
 | |
| 
 | |
| void Document::PostVisibilityUpdateEvent() {
 | |
|   nsCOMPtr<nsIRunnable> event =
 | |
|       NewRunnableMethod("Document::UpdateVisibilityState", this,
 | |
|                         &Document::UpdateVisibilityState);
 | |
|   Dispatch(TaskCategory::Other, event.forget());
 | |
| }
 | |
| 
 | |
| void Document::MaybeActiveMediaComponents() {
 | |
|   if (!mWindow) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   GetWindow()->MaybeActiveMediaComponents();
 | |
| }
 | |
| 
 | |
| void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
 | |
|   nsINode::AddSizeOfExcludingThis(aWindowSizes, &aWindowSizes.mDOMOtherSize);
 | |
| 
 | |
|   for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
 | |
|     AddSizeOfNodeTree(*kid, aWindowSizes);
 | |
|   }
 | |
| 
 | |
|   // IMPORTANT: for our ComputedValues measurements, we want to measure
 | |
|   // ComputedValues accessible from DOM elements before ComputedValues not
 | |
|   // accessible from DOM elements (i.e. accessible only from the frame tree).
 | |
|   //
 | |
|   // Therefore, the measurement of the Document superclass must happen after
 | |
|   // the measurement of DOM nodes (above), because Document contains the
 | |
|   // PresShell, which contains the frame tree.
 | |
|   if (mPresShell) {
 | |
|     mPresShell->AddSizeOfIncludingThis(aWindowSizes);
 | |
|   }
 | |
| 
 | |
|   aWindowSizes.mDOMOtherSize += mLangGroupFontPrefs.SizeOfExcludingThis(
 | |
|       aWindowSizes.mState.mMallocSizeOf);
 | |
| 
 | |
|   aWindowSizes.mPropertyTablesSize +=
 | |
|       mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
 | |
| 
 | |
|   if (EventListenerManager* elm = GetExistingListenerManager()) {
 | |
|     aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
 | |
|   }
 | |
| 
 | |
|   if (mNodeInfoManager) {
 | |
|     mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
 | |
|   }
 | |
| 
 | |
|   aWindowSizes.mDOMMediaQueryLists += mDOMMediaQueryLists.sizeOfExcludingThis(
 | |
|       aWindowSizes.mState.mMallocSizeOf);
 | |
| 
 | |
|   for (const MediaQueryList* mql : mDOMMediaQueryLists) {
 | |
|     aWindowSizes.mDOMMediaQueryLists +=
 | |
|         mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
 | |
|   }
 | |
| 
 | |
|   mContentBlockingLog.AddSizeOfExcludingThis(aWindowSizes);
 | |
| 
 | |
|   DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
 | |
| 
 | |
|   for (auto& sheetArray : mAdditionalSheets) {
 | |
|     AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
 | |
|   }
 | |
|   // Lumping in the loader with the style-sheets size is not ideal,
 | |
|   // but most of the things in there are in fact stylesheets, so it
 | |
|   // doesn't seem worthwhile to separate it out.
 | |
|   aWindowSizes.mLayoutStyleSheetsSize +=
 | |
|       CSSLoader()->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
 | |
| 
 | |
|   aWindowSizes.mDOMOtherSize += mAttrStyleSheet
 | |
|                                     ? mAttrStyleSheet->DOMSizeOfIncludingThis(
 | |
|                                           aWindowSizes.mState.mMallocSizeOf)
 | |
|                                     : 0;
 | |
| 
 | |
|   aWindowSizes.mDOMOtherSize += mStyledLinks.ShallowSizeOfExcludingThis(
 | |
|       aWindowSizes.mState.mMallocSizeOf);
 | |
| 
 | |
|   // Measurement of the following members may be added later if DMD finds it
 | |
|   // is worthwhile:
 | |
|   // - many!
 | |
| }
 | |
| 
 | |
| void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
 | |
|   aWindowSizes.mDOMOtherSize += aWindowSizes.mState.mMallocSizeOf(this);
 | |
|   DocAddSizeOfExcludingThis(aWindowSizes);
 | |
| }
 | |
| 
 | |
| void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
 | |
|                                       size_t* aNodeSize) const {
 | |
|   // This AddSizeOfExcludingThis() overrides the one from nsINode.  But
 | |
|   // nsDocuments can only appear at the top of the DOM tree, and we use the
 | |
|   // specialized DocAddSizeOfExcludingThis() in that case.  So this should never
 | |
|   // be called.
 | |
|   MOZ_CRASH();
 | |
| }
 | |
| 
 | |
| /* static */ void Document::AddSizeOfNodeTree(nsINode& aNode,
 | |
|                                               nsWindowSizes& aWindowSizes) {
 | |
|   size_t nodeSize = 0;
 | |
|   aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
 | |
| 
 | |
|   // This is where we transfer the nodeSize obtained from
 | |
|   // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
 | |
|   switch (aNode.NodeType()) {
 | |
|     case nsINode::ELEMENT_NODE:
 | |
|       aWindowSizes.mDOMElementNodesSize += nodeSize;
 | |
|       break;
 | |
|     case nsINode::TEXT_NODE:
 | |
|       aWindowSizes.mDOMTextNodesSize += nodeSize;
 | |
|       break;
 | |
|     case nsINode::CDATA_SECTION_NODE:
 | |
|       aWindowSizes.mDOMCDATANodesSize += nodeSize;
 | |
|       break;
 | |
|     case nsINode::COMMENT_NODE:
 | |
|       aWindowSizes.mDOMCommentNodesSize += nodeSize;
 | |
|       break;
 | |
|     default:
 | |
|       aWindowSizes.mDOMOtherSize += nodeSize;
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
 | |
|     aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
 | |
|   }
 | |
| 
 | |
|   if (aNode.IsContent()) {
 | |
|     nsTArray<nsIContent*> anonKids;
 | |
|     nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
 | |
|                                                   nsIContent::eAllChildren);
 | |
|     for (nsIContent* anonKid : anonKids) {
 | |
|       AddSizeOfNodeTree(*anonKid, aWindowSizes);
 | |
|     }
 | |
| 
 | |
|     if (auto* element = Element::FromNode(aNode)) {
 | |
|       if (ShadowRoot* shadow = element->GetShadowRoot()) {
 | |
|         AddSizeOfNodeTree(*shadow, aWindowSizes);
 | |
|       }
 | |
| 
 | |
|       for (nsXBLBinding* binding = element->GetXBLBinding(); binding;
 | |
|            binding = binding->GetBaseBinding()) {
 | |
|         if (nsIContent* anonContent = binding->GetAnonymousContent()) {
 | |
|           AddSizeOfNodeTree(*anonContent, aWindowSizes);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // NOTE(emilio): If you feel smart and want to change this function to use
 | |
|   // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
 | |
|   // sane way, and kids of <content> won't point to the parent, so we'd never
 | |
|   // find the root node where we should stop at.
 | |
|   for (nsIContent* kid = aNode.GetFirstChild(); kid;
 | |
|        kid = kid->GetNextSibling()) {
 | |
|     AddSizeOfNodeTree(*kid, aWindowSizes);
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
 | |
|                                                  ErrorResult& rv) {
 | |
|   nsCOMPtr<nsIScriptGlobalObject> global =
 | |
|       do_QueryInterface(aGlobal.GetAsSupports());
 | |
|   if (!global) {
 | |
|     rv.Throw(NS_ERROR_UNEXPECTED);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIScriptObjectPrincipal> prin =
 | |
|       do_QueryInterface(aGlobal.GetAsSupports());
 | |
|   if (!prin) {
 | |
|     rv.Throw(NS_ERROR_UNEXPECTED);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   NS_NewURI(getter_AddRefs(uri), "about:blank");
 | |
|   if (!uri) {
 | |
|     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Document> doc;
 | |
|   nsresult res = NS_NewDOMDocument(
 | |
|       getter_AddRefs(doc), VoidString(), EmptyString(), nullptr, uri, uri,
 | |
|       prin->GetPrincipal(), true, global, DocumentFlavorPlain);
 | |
|   if (NS_FAILED(res)) {
 | |
|     rv.Throw(res);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
 | |
| 
 | |
|   return doc.forget();
 | |
| }
 | |
| 
 | |
| XPathExpression* Document::CreateExpression(const nsAString& aExpression,
 | |
|                                             XPathNSResolver* aResolver,
 | |
|                                             ErrorResult& rv) {
 | |
|   return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
 | |
| }
 | |
| 
 | |
| nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
 | |
|   return XPathEvaluator()->CreateNSResolver(aNodeResolver);
 | |
| }
 | |
| 
 | |
| already_AddRefed<XPathResult> Document::Evaluate(
 | |
|     JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
 | |
|     XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
 | |
|     ErrorResult& rv) {
 | |
|   return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
 | |
|                                     aType, aResult, rv);
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIXULWindow> Document::GetXULWindowIfToplevelChrome() const {
 | |
|   nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
 | |
|   if (!item) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   nsCOMPtr<nsIDocShellTreeOwner> owner;
 | |
|   item->GetTreeOwner(getter_AddRefs(owner));
 | |
|   nsCOMPtr<nsIXULWindow> xulWin = do_GetInterface(owner);
 | |
|   if (!xulWin) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   nsCOMPtr<nsIDocShell> xulWinShell;
 | |
|   xulWin->GetDocShell(getter_AddRefs(xulWinShell));
 | |
|   if (!SameCOMIdentity(xulWinShell, item)) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return xulWin.forget();
 | |
| }
 | |
| 
 | |
| Document* Document::GetTopLevelContentDocument() {
 | |
|   Document* parent;
 | |
| 
 | |
|   if (!mLoadedAsData) {
 | |
|     parent = this;
 | |
|   } else {
 | |
|     nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
 | |
|     if (!window) {
 | |
|       return nullptr;
 | |
|     }
 | |
| 
 | |
|     parent = window->GetExtantDoc();
 | |
|     if (!parent) {
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   do {
 | |
|     if (parent->IsTopLevelContentDocument()) {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     // If we ever have a non-content parent before we hit a toplevel content
 | |
|     // parent, then we're never going to find one.  Just bail.
 | |
|     if (!parent->IsContentDocument()) {
 | |
|       return nullptr;
 | |
|     }
 | |
| 
 | |
|     parent = parent->GetParentDocument();
 | |
|   } while (parent);
 | |
| 
 | |
|   return parent;
 | |
| }
 | |
| 
 | |
| static bool MightBeChromeScheme(nsIURI* aURI) {
 | |
|   MOZ_ASSERT(aURI);
 | |
|   bool isChrome = true;
 | |
|   aURI->SchemeIs("chrome", &isChrome);
 | |
|   return isChrome;
 | |
| }
 | |
| 
 | |
| static bool MightBeAboutOrChromeScheme(nsIURI* aURI) {
 | |
|   MOZ_ASSERT(aURI);
 | |
|   bool isAbout = true;
 | |
|   aURI->SchemeIs("about", &isAbout);
 | |
|   return isAbout || MightBeChromeScheme(aURI);
 | |
| }
 | |
| 
 | |
| void Document::PropagateUseCounters(Document* aParentDocument) {
 | |
|   MOZ_ASSERT(this != aParentDocument);
 | |
| 
 | |
|   // Don't count chrome resources, even in the web content.
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   NodePrincipal()->GetURI(getter_AddRefs(uri));
 | |
|   if (!uri || MightBeChromeScheme(uri)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // What really matters here is that our use counters get propagated as
 | |
|   // high up in the content document hierarchy as possible.  So,
 | |
|   // starting with aParentDocument, we need to find the toplevel content
 | |
|   // document, and propagate our use counters into its
 | |
|   // mChildDocumentUseCounters.
 | |
|   Document* contentParent = aParentDocument->GetTopLevelContentDocument();
 | |
| 
 | |
|   if (!contentParent) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   contentParent->mChildDocumentUseCounters |= mUseCounters;
 | |
|   contentParent->mChildDocumentUseCounters |= mChildDocumentUseCounters;
 | |
| }
 | |
| 
 | |
| void Document::SetPageUseCounter(UseCounter aUseCounter) {
 | |
|   // We want to set the use counter on the "page" that owns us; the definition
 | |
|   // of "page" depends on what kind of document we are.  See the comments below
 | |
|   // for details.  In any event, checking all the conditions below is
 | |
|   // reasonably expensive, so we cache whether we've notified our owning page.
 | |
|   if (mNotifiedPageForUseCounter[aUseCounter]) {
 | |
|     return;
 | |
|   }
 | |
|   mNotifiedPageForUseCounter[aUseCounter] = true;
 | |
| 
 | |
|   if (mDisplayDocument) {
 | |
|     // If we are a resource document, we won't have a docshell and so we won't
 | |
|     // record any page use counters on this document.  Instead, we should
 | |
|     // forward it up to the document that loaded us.
 | |
|     MOZ_ASSERT(!mDocumentContainer);
 | |
|     mDisplayDocument->SetChildDocumentUseCounter(aUseCounter);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (IsBeingUsedAsImage()) {
 | |
|     // If this is an SVG image document, we also won't have a docshell.
 | |
|     MOZ_ASSERT(!mDocumentContainer);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We only care about use counters in content.  If we're already a toplevel
 | |
|   // content document, then we should have already set the use counter on
 | |
|   // ourselves, and we are done.
 | |
|   Document* contentParent = GetTopLevelContentDocument();
 | |
|   if (!contentParent) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (this == contentParent) {
 | |
|     MOZ_ASSERT(GetUseCounter(aUseCounter));
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   contentParent->SetChildDocumentUseCounter(aUseCounter);
 | |
| }
 | |
| 
 | |
| bool Document::HasScriptsBlockedBySandbox() {
 | |
|   return mSandboxFlags & SANDBOXED_SCRIPTS;
 | |
| }
 | |
| 
 | |
| bool Document::InlineScriptAllowedByCSP() {
 | |
|   // this function assumes the inline script is parser created
 | |
|   //  (e.g., before setting attribute(!) event handlers)
 | |
|   nsCOMPtr<nsIContentSecurityPolicy> csp;
 | |
|   nsresult rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
 | |
|   NS_ENSURE_SUCCESS(rv, true);
 | |
|   bool allowsInlineScript = true;
 | |
|   if (csp) {
 | |
|     nsresult rv = csp->GetAllowsInline(
 | |
|         nsIContentPolicy::TYPE_SCRIPT,
 | |
|         EmptyString(),  // aNonce
 | |
|         true,           // aParserCreated
 | |
|         nullptr,        // aTriggeringElement
 | |
|         nullptr,        // aCSPEventListener
 | |
|         EmptyString(),  // FIXME get script sample (bug 1314567)
 | |
|         0,              // aLineNumber
 | |
|         0,              // aColumnNumber
 | |
|         &allowsInlineScript);
 | |
|     NS_ENSURE_SUCCESS(rv, true);
 | |
|   }
 | |
|   return allowsInlineScript;
 | |
| }
 | |
| 
 | |
| static bool ReportExternalResourceUseCounters(Document* aDocument,
 | |
|                                               void* aData) {
 | |
|   const auto reportKind =
 | |
|       Document::UseCounterReportKind::eIncludeExternalResources;
 | |
|   aDocument->ReportUseCounters(reportKind);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::ReportUseCounters(UseCounterReportKind aKind) {
 | |
|   static const bool sDebugUseCounters = false;
 | |
|   if (mReportedUseCounters) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mReportedUseCounters = true;
 | |
| 
 | |
|   if (aKind == UseCounterReportKind::eIncludeExternalResources) {
 | |
|     EnumerateExternalResources(ReportExternalResourceUseCounters, nullptr);
 | |
|   }
 | |
| 
 | |
|   if (Telemetry::HistogramUseCounterCount > 0 &&
 | |
|       (IsContentDocument() || IsResourceDoc())) {
 | |
|     nsCOMPtr<nsIURI> uri;
 | |
|     NodePrincipal()->GetURI(getter_AddRefs(uri));
 | |
|     if (!uri || MightBeAboutOrChromeScheme(uri)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (sDebugUseCounters) {
 | |
|       nsCString spec = uri->GetSpecOrDefault();
 | |
| 
 | |
|       // URIs can be rather long for data documents, so truncate them to
 | |
|       // some reasonable length.
 | |
|       spec.Truncate(std::min(128U, spec.Length()));
 | |
|       printf("-- Use counters for %s --\n", spec.get());
 | |
|     }
 | |
| 
 | |
|     // We keep separate counts for individual documents and top-level
 | |
|     // pages to more accurately track how many web pages might break if
 | |
|     // certain features were removed.  Consider the case of a single
 | |
|     // HTML document with several SVG images and/or iframes with
 | |
|     // sub-documents of their own.  If we maintained a single set of use
 | |
|     // counters and all the sub-documents use a particular feature, then
 | |
|     // telemetry would indicate that we would be breaking N documents if
 | |
|     // that feature were removed.  Whereas with a document/top-level
 | |
|     // page split, we can see that N documents would be affected, but
 | |
|     // only a single web page would be affected.
 | |
| 
 | |
|     // The difference between the values of these two histograms and the
 | |
|     // related use counters below tell us how many pages did *not* use
 | |
|     // the feature in question.  For instance, if we see that a given
 | |
|     // session has destroyed 30 content documents, but a particular use
 | |
|     // counter shows only a count of 5, we can infer that the use
 | |
|     // counter was *not* used in 25 of those 30 documents.
 | |
|     //
 | |
|     // We do things this way, rather than accumulating a boolean flag
 | |
|     // for each use counter, to avoid sending histograms for features
 | |
|     // that don't get widely used.  Doing things in this fashion means
 | |
|     // smaller telemetry payloads and faster processing on the server
 | |
|     // side.
 | |
|     Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
 | |
|     if (IsTopLevelContentDocument()) {
 | |
|       Telemetry::Accumulate(Telemetry::TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED,
 | |
|                             1);
 | |
|     }
 | |
| 
 | |
|     for (int32_t c = 0; c < eUseCounter_Count; ++c) {
 | |
|       UseCounter uc = static_cast<UseCounter>(c);
 | |
| 
 | |
|       Telemetry::HistogramID id = static_cast<Telemetry::HistogramID>(
 | |
|           Telemetry::HistogramFirstUseCounter + uc * 2);
 | |
|       bool value = GetUseCounter(uc);
 | |
| 
 | |
|       if (value) {
 | |
|         if (sDebugUseCounters) {
 | |
|           const char* name = Telemetry::GetHistogramName(id);
 | |
|           if (name) {
 | |
|             printf("  %s", name);
 | |
|           } else {
 | |
|             printf("  #%d", id);
 | |
|           }
 | |
|           printf(": %d\n", value);
 | |
|         }
 | |
| 
 | |
|         Telemetry::Accumulate(id, 1);
 | |
|       }
 | |
| 
 | |
|       if (IsTopLevelContentDocument()) {
 | |
|         id = static_cast<Telemetry::HistogramID>(
 | |
|             Telemetry::HistogramFirstUseCounter + uc * 2 + 1);
 | |
|         value = GetUseCounter(uc) || GetChildDocumentUseCounter(uc);
 | |
| 
 | |
|         if (value) {
 | |
|           if (sDebugUseCounters) {
 | |
|             const char* name = Telemetry::GetHistogramName(id);
 | |
|             if (name) {
 | |
|               printf("  %s", name);
 | |
|             } else {
 | |
|               printf("  #%d", id);
 | |
|             }
 | |
|             printf(": %d\n", value);
 | |
|           }
 | |
| 
 | |
|           Telemetry::Accumulate(id, 1);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (IsTopLevelContentDocument()) {
 | |
|     using Telemetry::LABELS_HIDDEN_VIEWPORT_OVERFLOW_TYPE;
 | |
|     LABELS_HIDDEN_VIEWPORT_OVERFLOW_TYPE label;
 | |
|     switch (mViewportOverflowType) {
 | |
| #define CASE_OVERFLOW_TYPE(t_)                        \
 | |
|   case ViewportOverflowType::t_:                      \
 | |
|     label = LABELS_HIDDEN_VIEWPORT_OVERFLOW_TYPE::t_; \
 | |
|     break;
 | |
|       CASE_OVERFLOW_TYPE(NoOverflow)
 | |
|       CASE_OVERFLOW_TYPE(Desktop)
 | |
|       CASE_OVERFLOW_TYPE(ButNotMinScaleSize)
 | |
|       CASE_OVERFLOW_TYPE(MinScaleSize)
 | |
| #undef CASE_OVERFLOW_TYPE
 | |
|     }
 | |
|     Telemetry::AccumulateCategorical(label);
 | |
|   }
 | |
| 
 | |
|   if (IsTopLevelContentDocument()) {
 | |
|     CSSIntCoord adjustmentLength =
 | |
|         CSSPixel::FromAppUnits(mScrollAnchorAdjustmentLength).Rounded();
 | |
|     Telemetry::Accumulate(Telemetry::SCROLL_ANCHOR_ADJUSTMENT_LENGTH,
 | |
|                           adjustmentLength);
 | |
|     Telemetry::Accumulate(Telemetry::SCROLL_ANCHOR_ADJUSTMENT_COUNT,
 | |
|                           mScrollAnchorAdjustmentCount);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::UpdateIntersectionObservations() {
 | |
|   if (mIntersectionObservers.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   DOMHighResTimeStamp time = 0;
 | |
|   if (nsPIDOMWindowInner* window = GetInnerWindow()) {
 | |
|     Performance* perf = window->GetPerformance();
 | |
|     if (perf) {
 | |
|       time = perf->Now();
 | |
|     }
 | |
|   }
 | |
|   nsTArray<RefPtr<DOMIntersectionObserver>> observers(
 | |
|       mIntersectionObservers.Count());
 | |
|   for (auto iter = mIntersectionObservers.Iter(); !iter.Done(); iter.Next()) {
 | |
|     DOMIntersectionObserver* observer = iter.Get()->GetKey();
 | |
|     observers.AppendElement(observer);
 | |
|   }
 | |
|   for (const auto& observer : observers) {
 | |
|     if (observer) {
 | |
|       observer->Update(this, time);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::ScheduleIntersectionObserverNotification() {
 | |
|   if (mIntersectionObservers.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
|   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
|   nsCOMPtr<nsIRunnable> notification =
 | |
|       NewRunnableMethod("Document::NotifyIntersectionObservers", this,
 | |
|                         &Document::NotifyIntersectionObservers);
 | |
|   Dispatch(TaskCategory::Other, notification.forget());
 | |
| }
 | |
| 
 | |
| void Document::NotifyIntersectionObservers() {
 | |
|   nsTArray<RefPtr<DOMIntersectionObserver>> observers(
 | |
|       mIntersectionObservers.Count());
 | |
|   for (auto iter = mIntersectionObservers.Iter(); !iter.Done(); iter.Next()) {
 | |
|     DOMIntersectionObserver* observer = iter.Get()->GetKey();
 | |
|     observers.AppendElement(observer);
 | |
|   }
 | |
|   for (const auto& observer : observers) {
 | |
|     if (observer) {
 | |
|       observer->Notify();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool NotifyLayerManagerRecreatedCallback(Document* aDocument,
 | |
|                                                 void* aData) {
 | |
|   aDocument->NotifyLayerManagerRecreated();
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void Document::NotifyLayerManagerRecreated() {
 | |
|   EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 | |
|   EnumerateSubDocuments(NotifyLayerManagerRecreatedCallback, nullptr);
 | |
| }
 | |
| 
 | |
| XPathEvaluator* Document::XPathEvaluator() {
 | |
|   if (!mXPathEvaluator) {
 | |
|     mXPathEvaluator.reset(new dom::XPathEvaluator(this));
 | |
|   }
 | |
|   return mXPathEvaluator.get();
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
 | |
|   return mCachedEncoder.forget();
 | |
| }
 | |
| 
 | |
| void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
 | |
|   mCachedEncoder = aEncoder;
 | |
| }
 | |
| 
 | |
| void Document::SetContentTypeInternal(const nsACString& aType) {
 | |
|   if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
 | |
|       aType.EqualsLiteral("application/xhtml+xml")) {
 | |
|     mDefaultElementType = kNameSpaceID_XHTML;
 | |
|   }
 | |
| 
 | |
|   mCachedEncoder = nullptr;
 | |
|   mContentType = aType;
 | |
|   mContentTypeForWriteCalls = aType;
 | |
| }
 | |
| 
 | |
| nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
 | |
| 
 | |
| nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
 | |
| 
 | |
| void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
 | |
|   mStateObjectContainer = scContainer;
 | |
|   mStateObjectCached = nullptr;
 | |
| }
 | |
| 
 | |
| Document::DocumentTheme Document::GetDocumentLWTheme() {
 | |
|   if (mDocLWTheme == Doc_Theme_Uninitialized) {
 | |
|     mDocLWTheme = ThreadSafeGetDocumentLWTheme();
 | |
|   }
 | |
|   return mDocLWTheme;
 | |
| }
 | |
| 
 | |
| Document::DocumentTheme Document::ThreadSafeGetDocumentLWTheme() const {
 | |
|   if (!nsContentUtils::IsSystemPrincipal(NodePrincipal())) {
 | |
|     return Doc_Theme_None;
 | |
|   }
 | |
| 
 | |
|   if (mDocLWTheme != Doc_Theme_Uninitialized) {
 | |
|     return mDocLWTheme;
 | |
|   }
 | |
| 
 | |
|   DocumentTheme theme = Doc_Theme_None;  // No lightweight theme by default
 | |
|   Element* element = GetRootElement();
 | |
|   if (element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
 | |
|                                       nsGkAtoms::_true, eCaseMatters)) {
 | |
|     theme = Doc_Theme_Neutral;
 | |
|     nsAutoString lwTheme;
 | |
|     element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwthemetextcolor, lwTheme);
 | |
|     if (lwTheme.EqualsLiteral("dark")) {
 | |
|       theme = Doc_Theme_Dark;
 | |
|     } else if (lwTheme.EqualsLiteral("bright")) {
 | |
|       theme = Doc_Theme_Bright;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return theme;
 | |
| }
 | |
| 
 | |
| already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
 | |
|   RefPtr<mozilla::dom::NodeInfo> nodeInfo;
 | |
|   nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
 | |
|                                            ELEMENT_NODE);
 | |
|   MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
 | |
| 
 | |
|   nsCOMPtr<Element> element;
 | |
|   DebugOnly<nsresult> rv =
 | |
|       NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
 | |
|                         mozilla::dom::NOT_FROM_PARSER);
 | |
| 
 | |
|   MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
 | |
|   return element.forget();
 | |
| }
 | |
| 
 | |
| bool MarkDocumentTreeToBeInSyncOperation(Document* aDoc, void* aData) {
 | |
|   auto* documents = static_cast<nsTArray<nsCOMPtr<Document>>*>(aData);
 | |
|   if (aDoc) {
 | |
|     aDoc->SetIsInSyncOperation(true);
 | |
|     if (nsCOMPtr<nsPIDOMWindowInner> window = aDoc->GetInnerWindow()) {
 | |
|       window->TimeoutManager().BeginSyncOperation();
 | |
|     }
 | |
|     documents->AppendElement(aDoc);
 | |
|     aDoc->EnumerateSubDocuments(MarkDocumentTreeToBeInSyncOperation, aData);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc) {
 | |
|   mMicroTaskLevel = 0;
 | |
|   CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
 | |
|   if (ccjs) {
 | |
|     mMicroTaskLevel = ccjs->MicroTaskLevel();
 | |
|     ccjs->SetMicroTaskLevel(0);
 | |
|   }
 | |
|   if (aDoc) {
 | |
|     if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
 | |
|       if (nsCOMPtr<nsPIDOMWindowOuter> top = win->GetTop()) {
 | |
|         nsCOMPtr<Document> doc = top->GetExtantDoc();
 | |
|         MarkDocumentTreeToBeInSyncOperation(doc, &mDocuments);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsAutoSyncOperation::~nsAutoSyncOperation() {
 | |
|   for (RefPtr<Document>& doc : mDocuments) {
 | |
|     if (nsCOMPtr<nsPIDOMWindowInner> window = doc->GetInnerWindow()) {
 | |
|       window->TimeoutManager().EndSyncOperation();
 | |
|     }
 | |
|     doc->SetIsInSyncOperation(false);
 | |
|   }
 | |
|   CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
 | |
|   if (ccjs) {
 | |
|     ccjs->SetMicroTaskLevel(mMicroTaskLevel);
 | |
|   }
 | |
| }
 | |
| 
 | |
| gfxUserFontSet* Document::GetUserFontSet() {
 | |
|   if (!mFontFaceSet) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return mFontFaceSet->GetUserFontSet();
 | |
| }
 | |
| 
 | |
| void Document::FlushUserFontSet() {
 | |
|   if (!mFontFaceSetDirty) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mFontFaceSetDirty = false;
 | |
| 
 | |
|   if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
 | |
|     nsTArray<nsFontFaceRuleContainer> rules;
 | |
|     nsIPresShell* shell = GetShell();
 | |
|     if (shell && !shell->StyleSet()->AppendFontFaceRules(rules)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (!mFontFaceSet && !rules.IsEmpty()) {
 | |
|       nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
 | |
|       mFontFaceSet = new FontFaceSet(window, this);
 | |
|     }
 | |
| 
 | |
|     bool changed = false;
 | |
|     if (mFontFaceSet) {
 | |
|       changed = mFontFaceSet->UpdateRules(rules);
 | |
|     }
 | |
| 
 | |
|     // We need to enqueue a style change reflow (for later) to
 | |
|     // reflect that we're modifying @font-face rules.  (However,
 | |
|     // without a reflow, nothing will happen to start any downloads
 | |
|     // that are needed.)
 | |
|     if (changed && shell) {
 | |
|       if (nsPresContext* presContext = shell->GetPresContext()) {
 | |
|         presContext->UserFontSetUpdated();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Document::MarkUserFontSetDirty() {
 | |
|   if (mFontFaceSetDirty) {
 | |
|     return;
 | |
|   }
 | |
|   mFontFaceSetDirty = true;
 | |
|   if (nsIPresShell* shell = GetShell()) {
 | |
|     shell->EnsureStyleFlush();
 | |
|   }
 | |
| }
 | |
| 
 | |
| FontFaceSet* Document::Fonts() {
 | |
|   if (!mFontFaceSet) {
 | |
|     nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
 | |
|     mFontFaceSet = new FontFaceSet(window, this);
 | |
|     FlushUserFontSet();
 | |
|   }
 | |
|   return mFontFaceSet;
 | |
| }
 | |
| 
 | |
| void Document::ReportHasScrollLinkedEffect() {
 | |
|   if (mHasScrollLinkedEffect) {
 | |
|     // We already did this once for this document, don't do it again.
 | |
|     return;
 | |
|   }
 | |
|   mHasScrollLinkedEffect = true;
 | |
|   nsContentUtils::ReportToConsole(
 | |
|       nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Async Pan/Zoom"), this,
 | |
|       nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound2");
 | |
| }
 | |
| 
 | |
| void Document::SetUserHasInteracted() {
 | |
|   MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
 | |
|           ("Document %p has been interacted by user.", this));
 | |
| 
 | |
|   // We maybe need to update the user-interaction permission.
 | |
|   MaybeStoreUserInteractionAsPermission();
 | |
| 
 | |
|   if (mUserHasInteracted) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mUserHasInteracted = true;
 | |
| 
 | |
|   nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->GetLoadInfo() : nullptr;
 | |
|   if (loadInfo) {
 | |
|     loadInfo->SetDocumentHasUserInteracted(true);
 | |
|   }
 | |
| 
 | |
|   MaybeAllowStorageForOpenerAfterUserInteraction();
 | |
| }
 | |
| 
 | |
| BrowsingContext* Document::GetBrowsingContext() const {
 | |
|   nsPIDOMWindowOuter* outer = GetWindow();
 | |
|   return outer ? outer->GetBrowsingContext() : nullptr;
 | |
| }
 | |
| 
 | |
| void Document::NotifyUserGestureActivation() {
 | |
|   if (HasBeenUserGestureActivated()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<BrowsingContext> bc = GetBrowsingContext();
 | |
|   if (!bc) {
 | |
|     return;
 | |
|   }
 | |
|   bc->NotifyUserGestureActivation();
 | |
| }
 | |
| 
 | |
| bool Document::HasBeenUserGestureActivated() {
 | |
|   RefPtr<BrowsingContext> bc = GetBrowsingContext();
 | |
|   if (!bc) {
 | |
|     return false;
 | |
|   }
 | |
|   return bc->GetUserGestureActivation();
 | |
| }
 | |
| 
 | |
| void Document::MaybeNotifyAutoplayBlocked() {
 | |
|   Document* topLevelDoc = GetTopLevelContentDocument();
 | |
|   if (!topLevelDoc) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // This event is used to notify front-end side that we've blocked autoplay,
 | |
|   // so front-end side should show blocking icon as well.
 | |
|   RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
 | |
|       topLevelDoc, NS_LITERAL_STRING("GloballyAutoplayBlocked"),
 | |
|       CanBubble::eYes, ChromeOnlyDispatch::eYes);
 | |
|   asyncDispatcher->PostDOMEvent();
 | |
| }
 | |
| 
 | |
| void Document::ClearUserGestureActivation() {
 | |
|   if (!HasBeenUserGestureActivated()) {
 | |
|     return;
 | |
|   }
 | |
|   RefPtr<BrowsingContext> bc = GetBrowsingContext();
 | |
|   if (!bc) {
 | |
|     return;
 | |
|   }
 | |
|   bc->NotifyResetUserGestureActivation();
 | |
| }
 | |
| 
 | |
| void Document::SetDocTreeHadAudibleMedia() {
 | |
|   Document* topLevelDoc = GetTopLevelContentDocument();
 | |
|   if (!topLevelDoc) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   topLevelDoc->mDocTreeHadAudibleMedia = true;
 | |
| }
 | |
| 
 | |
| void Document::SetDocTreeHadPlayRevoked() {
 | |
|   Document* topLevelDoc = GetTopLevelContentDocument();
 | |
|   if (topLevelDoc) {
 | |
|     topLevelDoc->mDocTreeHadPlayRevoked = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| DocumentAutoplayPolicy Document::AutoplayPolicy() const {
 | |
|   return AutoplayPolicy::IsAllowedToPlay(*this);
 | |
| }
 | |
| 
 | |
| void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
 | |
|   if (StaticPrefs::network_cookie_cookieBehavior() !=
 | |
|       nsICookieService::BEHAVIOR_REJECT_TRACKER) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // This will probably change for project fission, but currently this document
 | |
|   // and the opener are on the same process. In the future, we should make this
 | |
|   // part async.
 | |
| 
 | |
|   nsPIDOMWindowInner* inner = GetInnerWindow();
 | |
|   if (NS_WARN_IF(!inner)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> outer = inner->GetOuterWindow();
 | |
|   if (NS_WARN_IF(!outer)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> outerOpener = outer->GetOpener();
 | |
|   if (!outerOpener) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsPIDOMWindowInner* openerInner = outerOpener->GetCurrentInnerWindow();
 | |
|   if (NS_WARN_IF(!openerInner)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Let's take the principal from the opener.
 | |
|   Document* openerDocument = openerInner->GetExtantDoc();
 | |
|   if (NS_WARN_IF(!openerDocument)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
 | |
|   if (NS_WARN_IF(!openerURI)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // No tracking resource.
 | |
|   if (!nsContentUtils::IsTrackingResourceWindow(inner)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If the opener is not a 3rd party and if this window is not a 3rd party, we
 | |
|   // should not continue.
 | |
|   if (!nsContentUtils::IsThirdPartyWindowOrChannel(inner, nullptr, openerURI) &&
 | |
|       !nsContentUtils::IsThirdPartyWindowOrChannel(openerInner, nullptr,
 | |
|                                                    nullptr)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We don't care when the asynchronous work finishes here.
 | |
|   Unused << AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(
 | |
|       NodePrincipal(), openerInner,
 | |
|       AntiTrackingCommon::eOpenerAfterUserInteraction);
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| // Documents can stay alive for days. We don't want to update the permission
 | |
| // value at any user-interaction, and, using a timer triggered any X seconds
 | |
| // should be good enough. 'X' is taken from
 | |
| // privacy.userInteraction.document.interval pref.
 | |
| //  We also want to store the user-interaction before shutting down, and, for
 | |
| //  this reason, this class implements nsIAsyncShutdownBlocker interface.
 | |
| class UserIntractionTimer final : public Runnable,
 | |
|                                   public nsITimerCallback,
 | |
|                                   public nsIAsyncShutdownBlocker {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS_INHERITED
 | |
| 
 | |
|   explicit UserIntractionTimer(Document* aDocument)
 | |
|       : Runnable("UserIntractionTimer"),
 | |
|         mPrincipal(aDocument->NodePrincipal()),
 | |
|         mDocument(do_GetWeakReference(aDocument)) {
 | |
|     static int32_t userInteractionTimerId = 0;
 | |
|     // Blocker names must be unique. Let's create it now because when needed,
 | |
|     // the document could be already gone.
 | |
|     mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
 | |
|                               ++userInteractionTimerId, aDocument);
 | |
|   }
 | |
| 
 | |
|   // Runnable interface
 | |
| 
 | |
|   NS_IMETHOD
 | |
|   Run() override {
 | |
|     uint32_t interval =
 | |
|         StaticPrefs::privacy_userInteraction_document_interval();
 | |
|     if (!interval) {
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     RefPtr<UserIntractionTimer> self = this;
 | |
|     auto raii =
 | |
|         MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
 | |
| 
 | |
|     nsresult rv = NS_NewTimerWithCallback(
 | |
|         getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT,
 | |
|         SystemGroup::EventTargetFor(TaskCategory::Other));
 | |
|     NS_ENSURE_SUCCESS(rv, NS_OK);
 | |
| 
 | |
|     nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
 | |
|     NS_ENSURE_TRUE(!!phase, NS_OK);
 | |
| 
 | |
|     rv = phase->AddBlocker(this, NS_LITERAL_STRING(__FILE__), __LINE__,
 | |
|                            NS_LITERAL_STRING("UserIntractionTimer shutdown"));
 | |
|     NS_ENSURE_SUCCESS(rv, NS_OK);
 | |
| 
 | |
|     raii.release();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // nsITimerCallback interface
 | |
| 
 | |
|   NS_IMETHOD
 | |
|   Notify(nsITimer* aTimer) override {
 | |
|     StoreUserInteraction();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
 | |
|   using nsINamed::GetName;
 | |
| #endif
 | |
| 
 | |
|   // nsIAsyncShutdownBlocker interface
 | |
| 
 | |
|   NS_IMETHOD
 | |
|   GetName(nsAString& aName) override {
 | |
|     aName = mBlockerName;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD
 | |
|   BlockShutdown(nsIAsyncShutdownClient* aClient) override {
 | |
|     CancelTimerAndStoreUserInteraction();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD
 | |
|   GetState(nsIPropertyBag**) override { return NS_OK; }
 | |
| 
 | |
|  private:
 | |
|   ~UserIntractionTimer() = default;
 | |
| 
 | |
|   void StoreUserInteraction() {
 | |
|     // Remove the shutting down blocker
 | |
|     nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
 | |
|     if (phase) {
 | |
|       phase->RemoveBlocker(this);
 | |
|     }
 | |
| 
 | |
|     // If the document is not gone, let's reset its timer flag.
 | |
|     nsCOMPtr<Document> document = do_QueryReferent(mDocument);
 | |
|     if (document) {
 | |
|       AntiTrackingCommon::StoreUserInteractionFor(mPrincipal);
 | |
|       document->ResetUserInteractionTimer();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void CancelTimerAndStoreUserInteraction() {
 | |
|     if (mTimer) {
 | |
|       mTimer->Cancel();
 | |
|       mTimer = nullptr;
 | |
|     }
 | |
| 
 | |
|     StoreUserInteraction();
 | |
|   }
 | |
| 
 | |
|   static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
 | |
|     nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown();
 | |
|     NS_ENSURE_TRUE(!!svc, nullptr);
 | |
| 
 | |
|     nsCOMPtr<nsIAsyncShutdownClient> phase;
 | |
|     nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
 | |
|     NS_ENSURE_SUCCESS(rv, nullptr);
 | |
| 
 | |
|     return phase.forget();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIPrincipal> mPrincipal;
 | |
|   nsWeakPtr mDocument;
 | |
| 
 | |
|   nsCOMPtr<nsITimer> mTimer;
 | |
| 
 | |
|   nsString mBlockerName;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS_INHERITED(UserIntractionTimer, Runnable, nsITimerCallback,
 | |
|                             nsIAsyncShutdownBlocker)
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| void Document::MaybeStoreUserInteractionAsPermission() {
 | |
|   // We care about user-interaction stored only for top-level documents.
 | |
|   if (GetSameTypeParentDocument()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mUserHasInteracted) {
 | |
|     // First interaction, let's store this info now.
 | |
|     AntiTrackingCommon::StoreUserInteractionFor(NodePrincipal());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mHasUserInteractionTimerScheduled) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIRunnable> task = new UserIntractionTimer(this);
 | |
|   nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
 | |
|                                                 EventQueuePriority::Idle);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // This value will be reset by the timer.
 | |
|   mHasUserInteractionTimerScheduled = true;
 | |
| }
 | |
| 
 | |
| void Document::ResetUserInteractionTimer() {
 | |
|   mHasUserInteractionTimerScheduled = false;
 | |
| }
 | |
| 
 | |
| bool Document::IsExtensionPage() const {
 | |
|   return Preferences::GetBool("media.autoplay.allow-extension-background-pages",
 | |
|                               true) &&
 | |
|          BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
 | |
| }
 | |
| 
 | |
| Document* Document::GetSameTypeParentDocument() {
 | |
|   nsCOMPtr<nsIDocShellTreeItem> current = GetDocShell();
 | |
|   if (!current) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDocShellTreeItem> parent;
 | |
|   current->GetSameTypeParent(getter_AddRefs(parent));
 | |
|   if (!parent) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return parent->GetDocument();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Retrieves the classification of the Flash plugins in the document based on
 | |
|  * the classification lists. For more information, see
 | |
|  * toolkit/components/url-classifier/flash-block-lists.rst
 | |
|  */
 | |
| FlashClassification Document::DocumentFlashClassification() {
 | |
|   // If neither pref is on, skip the null-principal and principal URI checks.
 | |
|   if (!StaticPrefs::plugins_http_https_only() &&
 | |
|       !StaticPrefs::plugins_flashBlock_enabled()) {
 | |
|     return FlashClassification::Unknown;
 | |
|   }
 | |
| 
 | |
|   if (NodePrincipal()->GetIsNullPrincipal()) {
 | |
|     return FlashClassification::Denied;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> classificationURI;
 | |
|   nsresult rv = NodePrincipal()->GetURI(getter_AddRefs(classificationURI));
 | |
|   if (NS_FAILED(rv) || !classificationURI) {
 | |
|     return FlashClassification::Denied;
 | |
|   }
 | |
| 
 | |
|   if (StaticPrefs::plugins_http_https_only()) {
 | |
|     // Only allow plugins for documents from an HTTP/HTTPS origin. This should
 | |
|     // allow dependent data: URIs to load plugins, but not:
 | |
|     // * chrome documents
 | |
|     // * "bare" data: loads
 | |
|     // * FTP/gopher/file
 | |
|     nsAutoCString scheme;
 | |
|     rv = classificationURI->GetScheme(scheme);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv)) ||
 | |
|         !(scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https"))) {
 | |
|       return FlashClassification::Denied;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If flash blocking is disabled, it is equivalent to all sites being
 | |
|   // on neither list.
 | |
|   if (!StaticPrefs::plugins_flashBlock_enabled()) {
 | |
|     return FlashClassification::Unknown;
 | |
|   }
 | |
| 
 | |
|   if (mFlashClassification == FlashClassification::Unknown) {
 | |
|     mFlashClassification = DocumentFlashClassificationInternal();
 | |
|   }
 | |
| 
 | |
|   return mFlashClassification;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Initializes |mIsThirdPartyForFlashClassifier| if necessary and returns its
 | |
|  * value. The value returned represents whether this document should be
 | |
|  * considered Third-Party.
 | |
|  *
 | |
|  * A top-level document cannot be a considered Third-Party; only subdocuments
 | |
|  * may. For a subdocument to be considered Third-Party, it must meet ANY ONE
 | |
|  * of the following requirements:
 | |
|  *  - The document's parent is Third-Party
 | |
|  *  - The document has a different scheme (http/https) than its parent document
 | |
|  *  - The document's domain and subdomain do not match those of its parent
 | |
|  *    document.
 | |
|  *
 | |
|  * If there is an error in determining whether the document is Third-Party,
 | |
|  * it will be assumed to be Third-Party for security reasons.
 | |
|  */
 | |
| bool Document::IsThirdPartyForFlashClassifier() {
 | |
|   if (mIsThirdPartyForFlashClassifier.isSome()) {
 | |
|     return mIsThirdPartyForFlashClassifier.value();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDocShellTreeItem> docshell = this->GetDocShell();
 | |
|   if (!docshell) {
 | |
|     mIsThirdPartyForFlashClassifier.emplace(true);
 | |
|     return mIsThirdPartyForFlashClassifier.value();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDocShellTreeItem> parent;
 | |
|   nsresult rv = docshell->GetSameTypeParent(getter_AddRefs(parent));
 | |
|   MOZ_ASSERT(NS_SUCCEEDED(rv),
 | |
|              "nsIDocShellTreeItem::GetSameTypeParent should never fail");
 | |
|   bool isTopLevel = !parent;
 | |
| 
 | |
|   if (isTopLevel) {
 | |
|     mIsThirdPartyForFlashClassifier.emplace(false);
 | |
|     return mIsThirdPartyForFlashClassifier.value();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Document> parentDocument = GetParentDocument();
 | |
|   if (!parentDocument) {
 | |
|     // Failure
 | |
|     mIsThirdPartyForFlashClassifier.emplace(true);
 | |
|     return mIsThirdPartyForFlashClassifier.value();
 | |
|   }
 | |
| 
 | |
|   if (parentDocument->IsThirdPartyForFlashClassifier()) {
 | |
|     mIsThirdPartyForFlashClassifier.emplace(true);
 | |
|     return mIsThirdPartyForFlashClassifier.value();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
 | |
|   nsCOMPtr<nsIPrincipal> parentPrincipal = parentDocument->GetPrincipal();
 | |
| 
 | |
|   bool principalsMatch = false;
 | |
|   rv = principal->Equals(parentPrincipal, &principalsMatch);
 | |
| 
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     // Failure
 | |
|     mIsThirdPartyForFlashClassifier.emplace(true);
 | |
|     return mIsThirdPartyForFlashClassifier.value();
 | |
|   }
 | |
| 
 | |
|   if (!principalsMatch) {
 | |
|     mIsThirdPartyForFlashClassifier.emplace(true);
 | |
|     return mIsThirdPartyForFlashClassifier.value();
 | |
|   }
 | |
| 
 | |
|   // Fall-through. Document is not a Third-Party Document.
 | |
|   mIsThirdPartyForFlashClassifier.emplace(false);
 | |
|   return mIsThirdPartyForFlashClassifier.value();
 | |
| }
 | |
| 
 | |
| FlashClassification Document::DocumentFlashClassificationInternal() {
 | |
|   FlashClassification classification = FlashClassification::Unknown;
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(GetChannel());
 | |
|   if (httpChannel) {
 | |
|     nsIHttpChannel::FlashPluginState state = nsIHttpChannel::FlashPluginUnknown;
 | |
|     httpChannel->GetFlashPluginState(&state);
 | |
| 
 | |
|     // Allow unknown children to inherit allowed status from parent, but do not
 | |
|     // allow denied children to do so.
 | |
| 
 | |
|     if (state == nsIHttpChannel::FlashPluginDeniedInSubdocuments &&
 | |
|         IsThirdPartyForFlashClassifier()) {
 | |
|       return FlashClassification::Denied;
 | |
|     }
 | |
| 
 | |
|     if (state == nsIHttpChannel::FlashPluginDenied) {
 | |
|       return FlashClassification::Denied;
 | |
|     }
 | |
| 
 | |
|     if (state == nsIHttpChannel::FlashPluginAllowed) {
 | |
|       classification = FlashClassification::Allowed;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (IsTopLevelContentDocument()) {
 | |
|     return classification;
 | |
|   }
 | |
| 
 | |
|   Document* parentDocument = GetParentDocument();
 | |
|   if (!parentDocument) {
 | |
|     return FlashClassification::Denied;
 | |
|   }
 | |
| 
 | |
|   FlashClassification parentClassification =
 | |
|       parentDocument->DocumentFlashClassification();
 | |
| 
 | |
|   if (parentClassification == FlashClassification::Denied) {
 | |
|     return FlashClassification::Denied;
 | |
|   }
 | |
| 
 | |
|   // Allow unknown children to inherit allowed status from parent, but
 | |
|   // do not allow denied children to do so.
 | |
|   if (classification == FlashClassification::Unknown &&
 | |
|       parentClassification == FlashClassification::Allowed) {
 | |
|     return FlashClassification::Allowed;
 | |
|   }
 | |
| 
 | |
|   return classification;
 | |
| }
 | |
| 
 | |
| void Document::ClearStaleServoData() {
 | |
|   DocumentStyleRootIterator iter(this);
 | |
|   while (Element* root = iter.GetNextStyleRoot()) {
 | |
|     RestyleManager::ClearServoDataFromSubtree(root);
 | |
|   }
 | |
| }
 | |
| 
 | |
| Selection* Document::GetSelection(ErrorResult& aRv) {
 | |
|   nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
 | |
|   if (!window) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (!window->IsCurrentInnerWindow()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
 | |
| }
 | |
| 
 | |
| already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
 | |
|     mozilla::ErrorResult& aRv) {
 | |
|   nsIGlobalObject* global = GetScopeObject();
 | |
|   if (!global) {
 | |
|     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<Promise> promise =
 | |
|       Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
 | |
|   if (aRv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (NodePrincipal()->GetIsNullPrincipal()) {
 | |
|     promise->MaybeResolve(false);
 | |
|     return promise.forget();
 | |
|   }
 | |
| 
 | |
|   if (IsTopLevelContentDocument()) {
 | |
|     promise->MaybeResolve(true);
 | |
|     return promise.forget();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Document> topLevelDoc = GetTopLevelContentDocument();
 | |
|   if (!topLevelDoc) {
 | |
|     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
 | |
|     return nullptr;
 | |
|   }
 | |
|   if (topLevelDoc->NodePrincipal()->Equals(NodePrincipal())) {
 | |
|     promise->MaybeResolve(true);
 | |
|     return promise.forget();
 | |
|   }
 | |
| 
 | |
|   nsPIDOMWindowInner* inner = GetInnerWindow();
 | |
|   nsGlobalWindowOuter* outer = nullptr;
 | |
|   if (inner) {
 | |
|     outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
 | |
|     promise->MaybeResolve(outer->HasStorageAccess());
 | |
|   } else {
 | |
|     promise->MaybeRejectWithUndefined();
 | |
|   }
 | |
|   return promise.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
 | |
|     mozilla::ErrorResult& aRv) {
 | |
|   nsIGlobalObject* global = GetScopeObject();
 | |
|   if (!global) {
 | |
|     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Propagate user input event handling to the resolve handler
 | |
|   RefPtr<Promise> promise =
 | |
|       Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
 | |
|   if (aRv.Failed()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Step 1. If the document already has been granted access, resolve.
 | |
|   nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
 | |
|   RefPtr<nsGlobalWindowOuter> outer;
 | |
|   if (inner) {
 | |
|     outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
 | |
|     if (outer->HasStorageAccess()) {
 | |
|       promise->MaybeResolveWithUndefined();
 | |
|       return promise.forget();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Step 2. If the document has a null origin, reject.
 | |
|   if (NodePrincipal()->GetIsNullPrincipal()) {
 | |
|     promise->MaybeRejectWithUndefined();
 | |
|     return promise.forget();
 | |
|   }
 | |
| 
 | |
|   // Only enforce third-party checks when there is a reason to enforce them.
 | |
|   if (StaticPrefs::network_cookie_cookieBehavior() !=
 | |
|       nsICookieService::BEHAVIOR_ACCEPT) {
 | |
|     // Step 3. If the document's frame is the main frame, resolve.
 | |
|     if (IsTopLevelContentDocument()) {
 | |
|       promise->MaybeResolveWithUndefined();
 | |
|       return promise.forget();
 | |
|     }
 | |
| 
 | |
|     // Step 4. If the sub frame's origin is equal to the main frame's, resolve.
 | |
|     nsCOMPtr<Document> topLevelDoc = GetTopLevelContentDocument();
 | |
|     if (!topLevelDoc) {
 | |
|       aRv.Throw(NS_ERROR_NOT_AVAILABLE);
 | |
|       return nullptr;
 | |
|     }
 | |
|     if (topLevelDoc->NodePrincipal()->Equals(NodePrincipal())) {
 | |
|       promise->MaybeResolveWithUndefined();
 | |
|       return promise.forget();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Step 5. If the sub frame is not sandboxed, skip to step 7.
 | |
|   // Step 6. If the sub frame doesn't have the token
 | |
|   //         "allow-storage-access-by-user-activation", reject.
 | |
|   if (StorageAccessSandboxed()) {
 | |
|     promise->MaybeRejectWithUndefined();
 | |
|     return promise.forget();
 | |
|   }
 | |
| 
 | |
|   // Step 7. If the sub frame's parent frame is not the top frame, reject.
 | |
|   Document* parent = GetParentDocument();
 | |
|   if (parent && !parent->IsTopLevelContentDocument()) {
 | |
|     promise->MaybeRejectWithUndefined();
 | |
|     return promise.forget();
 | |
|   }
 | |
| 
 | |
|   // Step 8. If the browser is not processing a user gesture, reject.
 | |
|   if (!EventStateManager::IsHandlingUserInput()) {
 | |
|     promise->MaybeRejectWithUndefined();
 | |
|     return promise.forget();
 | |
|   }
 | |
| 
 | |
|   // Step 9. Check any additional rules that the browser has.
 | |
|   //         Examples: Whitelists, blacklists, on-device classification,
 | |
|   //         user settings, anti-clickjacking heuristics, or prompting the
 | |
|   //         user for explicit permission. Reject if some rule is not fulfilled.
 | |
| 
 | |
|   if (nsContentUtils::IsInPrivateBrowsing(this)) {
 | |
|     // If the document is in PB mode, it doesn't have access to its persistent
 | |
|     // cookie jar, so reject the promise here.
 | |
|     promise->MaybeRejectWithUndefined();
 | |
|     return promise.forget();
 | |
|   }
 | |
| 
 | |
|   if (StaticPrefs::network_cookie_cookieBehavior() ==
 | |
|           nsICookieService::BEHAVIOR_REJECT_TRACKER &&
 | |
|       inner) {
 | |
|     // Only do something special for third-party tracking content.
 | |
|     if (nsContentUtils::StorageDisabledByAntiTracking(this, nullptr)) {
 | |
|       // Note: If this has returned true, the top-level document is guaranteed
 | |
|       // to not be on the Content Blocking allow list.
 | |
|       DebugOnly<bool> isOnAllowList = false;
 | |
|       // If we have a parent document, it has to be non-private since we
 | |
|       // verified earlier that our own document is non-private and a private
 | |
|       // document can never have a non-private document as its child.
 | |
|       MOZ_ASSERT_IF(parent, !nsContentUtils::IsInPrivateBrowsing(parent));
 | |
|       MOZ_ASSERT_IF(
 | |
|           NS_SUCCEEDED(AntiTrackingCommon::IsOnContentBlockingAllowList(
 | |
|               parent->GetDocumentURI(), false,
 | |
|               AntiTrackingCommon::eStorageChecks, isOnAllowList)),
 | |
|           !isOnAllowList);
 | |
| 
 | |
|       auto performFinalChecks = [inner]()
 | |
|           -> RefPtr<AntiTrackingCommon::StorageAccessFinalCheckPromise> {
 | |
|         RefPtr<AntiTrackingCommon::StorageAccessFinalCheckPromise::Private> p =
 | |
|             new AntiTrackingCommon::StorageAccessFinalCheckPromise::Private(
 | |
|                 __func__);
 | |
|         RefPtr<StorageAccessPermissionRequest> sapr =
 | |
|             StorageAccessPermissionRequest::Create(
 | |
|                 inner,
 | |
|                 // Allow
 | |
|                 [p] { p->Resolve(AntiTrackingCommon::eAllow, __func__); },
 | |
|                 // Allow auto grant
 | |
|                 [p] {
 | |
|                   p->Resolve(AntiTrackingCommon::eAllowAutoGrant, __func__);
 | |
|                 },
 | |
|                 // Allow on any site
 | |
|                 [p] {
 | |
|                   p->Resolve(AntiTrackingCommon::eAllowOnAnySite, __func__);
 | |
|                 },
 | |
|                 // Block
 | |
|                 [p] { p->Reject(false, __func__); });
 | |
| 
 | |
|         typedef ContentPermissionRequestBase::PromptResult PromptResult;
 | |
|         PromptResult pr = sapr->CheckPromptPrefs();
 | |
|         bool onAnySite = false;
 | |
|         if (pr == PromptResult::Pending) {
 | |
|           // Also check our custom pref for the "Allow on any site" case
 | |
|           if (Preferences::GetBool("dom.storage_access.prompt.testing",
 | |
|                                    false) &&
 | |
|               Preferences::GetBool(
 | |
|                   "dom.storage_access.prompt.testing.allowonanysite", false)) {
 | |
|             pr = PromptResult::Granted;
 | |
|             onAnySite = true;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         if (pr != PromptResult::Pending) {
 | |
|           MOZ_ASSERT_IF(pr != PromptResult::Granted,
 | |
|                         pr == PromptResult::Denied);
 | |
|           if (pr == PromptResult::Granted) {
 | |
|             return AntiTrackingCommon::StorageAccessFinalCheckPromise::
 | |
|                 CreateAndResolve(onAnySite ? AntiTrackingCommon::eAllowOnAnySite
 | |
|                                            : AntiTrackingCommon::eAllow,
 | |
|                                  __func__);
 | |
|           }
 | |
|           return AntiTrackingCommon::StorageAccessFinalCheckPromise::
 | |
|               CreateAndReject(false, __func__);
 | |
|         }
 | |
| 
 | |
|         sapr->RequestDelayedTask(
 | |
|             inner->EventTargetFor(TaskCategory::Other),
 | |
|             ContentPermissionRequestBase::DelayedTaskType::Request);
 | |
|         return p.forget();
 | |
|       };
 | |
|       AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(
 | |
|           NodePrincipal(), inner, AntiTrackingCommon::eStorageAccessAPI,
 | |
|           performFinalChecks)
 | |
|           ->Then(GetCurrentThreadSerialEventTarget(), __func__,
 | |
|                  [outer, promise] {
 | |
|                    // Step 10. Grant the document access to cookies and store
 | |
|                    // that fact for
 | |
|                    //          the purposes of future calls to
 | |
|                    //          hasStorageAccess() and requestStorageAccess().
 | |
|                    outer->SetHasStorageAccess(true);
 | |
|                    promise->MaybeResolveWithUndefined();
 | |
|                  },
 | |
|                  [outer, promise] {
 | |
|                    outer->SetHasStorageAccess(false);
 | |
|                    promise->MaybeRejectWithUndefined();
 | |
|                  });
 | |
| 
 | |
|       return promise.forget();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   outer->SetHasStorageAccess(true);
 | |
|   promise->MaybeResolveWithUndefined();
 | |
|   return promise.forget();
 | |
| }
 | |
| 
 | |
| void Document::RecordNavigationTiming(ReadyState aReadyState) {
 | |
|   if (!XRE_IsContentProcess()) {
 | |
|     return;
 | |
|   }
 | |
|   if (!IsTopLevelContentDocument()) {
 | |
|     return;
 | |
|   }
 | |
|   // If we dont have the timing yet (mostly because the doc is still loading),
 | |
|   // get it from docshell.
 | |
|   RefPtr<nsDOMNavigationTiming> timing = mTiming;
 | |
|   if (!timing) {
 | |
|     if (!mDocumentContainer) {
 | |
|       return;
 | |
|     }
 | |
|     timing = mDocumentContainer->GetNavigationTiming();
 | |
|     if (!timing) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
|   TimeStamp startTime = timing->GetNavigationStartTimeStamp();
 | |
|   switch (aReadyState) {
 | |
|     case READYSTATE_LOADING:
 | |
|       if (!mDOMLoadingSet) {
 | |
|         Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
 | |
|                                        startTime);
 | |
|         mDOMLoadingSet = true;
 | |
|       }
 | |
|       break;
 | |
|     case READYSTATE_INTERACTIVE:
 | |
|       if (!mDOMInteractiveSet) {
 | |
|         Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
 | |
|                                        startTime);
 | |
|         mDOMInteractiveSet = true;
 | |
|       }
 | |
|       break;
 | |
|     case READYSTATE_COMPLETE:
 | |
|       if (!mDOMCompleteSet) {
 | |
|         Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
 | |
|                                        startTime);
 | |
|         mDOMCompleteSet = true;
 | |
|       }
 | |
|       break;
 | |
|     default:
 | |
|       NS_WARNING("Unexpected ReadyState value");
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool Document::ModuleScriptsEnabled() {
 | |
|   static bool sEnabledForContent = false;
 | |
|   static bool sCachedPref = false;
 | |
|   if (!sCachedPref) {
 | |
|     sCachedPref = true;
 | |
|     Preferences::AddBoolVarCache(&sEnabledForContent,
 | |
|                                  "dom.moduleScripts.enabled", false);
 | |
|   }
 | |
| 
 | |
|   return nsContentUtils::IsChromeDoc(this) || sEnabledForContent;
 | |
| }
 | |
| 
 | |
| void Document::ReportShadowDOMUsage() {
 | |
|   if (mHasReportedShadowDOMUsage) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Document* topLevel = GetTopLevelContentDocument();
 | |
|   if (topLevel && !topLevel->mHasReportedShadowDOMUsage) {
 | |
|     topLevel->mHasReportedShadowDOMUsage = true;
 | |
|     nsString uri;
 | |
|     Unused << topLevel->GetDocumentURI(uri);
 | |
|     if (!uri.IsEmpty()) {
 | |
|       nsAutoString msg = NS_LITERAL_STRING("Shadow DOM used in [") + uri +
 | |
|                          NS_LITERAL_STRING("] or in some of its subdocuments.");
 | |
|       nsContentUtils::ReportToConsoleNonLocalized(
 | |
|           msg, nsIScriptError::infoFlag, NS_LITERAL_CSTRING("DOM"), topLevel);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mHasReportedShadowDOMUsage = true;
 | |
| }
 | |
| 
 | |
| bool Document::StorageAccessSandboxed() const {
 | |
|   return StaticPrefs::dom_storage_access_enabled() &&
 | |
|          (GetSandboxFlags() & SANDBOXED_STORAGE_ACCESS) != 0;
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsAtom> Document::GetContentLanguageAsAtomForStyle() const {
 | |
|   nsAutoString contentLang;
 | |
|   GetContentLanguage(contentLang);
 | |
|   contentLang.StripWhitespace();
 | |
| 
 | |
|   // Content-Language may be a comma-separated list of language codes,
 | |
|   // in which case the HTML5 spec says to treat it as unknown
 | |
|   if (!contentLang.IsEmpty() && !contentLang.Contains(char16_t(','))) {
 | |
|     return NS_Atomize(contentLang);
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsAtom> Document::GetLanguageForStyle() const {
 | |
|   RefPtr<nsAtom> lang = GetContentLanguageAsAtomForStyle();
 | |
|   if (!lang) {
 | |
|     lang = mLanguageFromCharset;
 | |
|   }
 | |
|   return lang.forget();
 | |
| }
 | |
| 
 | |
| const LangGroupFontPrefs* Document::GetFontPrefsForLang(
 | |
|     nsAtom* aLanguage, bool* aNeedsToCache) const {
 | |
|   nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
 | |
|   return StaticPresData::Get()->GetFontPrefsForLangHelper(
 | |
|       lang, &mLangGroupFontPrefs, aNeedsToCache);
 | |
| }
 | |
| 
 | |
| void Document::DoCacheAllKnownLangPrefs() {
 | |
|   MOZ_ASSERT(mFontGroupCacheDirty);
 | |
|   RefPtr<nsAtom> lang = GetLanguageForStyle();
 | |
|   GetFontPrefsForLang(lang.get());
 | |
|   GetFontPrefsForLang(nsGkAtoms::x_math);
 | |
|   // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
 | |
|   GetFontPrefsForLang(nsGkAtoms::Unicode);
 | |
|   for (auto iter = mLanguagesUsed.Iter(); !iter.Done(); iter.Next()) {
 | |
|     GetFontPrefsForLang(iter.Get()->GetKey());
 | |
|   }
 | |
|   mFontGroupCacheDirty = false;
 | |
| }
 | |
| 
 | |
| void Document::RecomputeLanguageFromCharset() {
 | |
|   nsLanguageAtomService* service = nsLanguageAtomService::GetService();
 | |
|   RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
 | |
|   if (language == nsGkAtoms::Unicode) {
 | |
|     language = service->GetLocaleLanguage();
 | |
|   }
 | |
| 
 | |
|   if (language == mLanguageFromCharset) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   ResetLangPrefs();
 | |
|   mLanguageFromCharset = language.forget();
 | |
| }
 | |
| 
 | |
| }  // namespace dom
 | |
| }  // namespace mozilla
 |