/* -*- 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 "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include #include #include #include #include #include #include #include #include #include #include "Attr.h" #include "AutoplayPolicy.h" #include "ErrorList.h" #include "ExpandedPrincipal.h" #include "MainThreadUtils.h" #include "MobileViewportManager.h" #include "NodeUbiReporting.h" #include "PLDHashTable.h" #include "StorageAccessPermissionRequest.h" #include "ThirdPartyUtil.h" #include "domstubs.h" #include "gfxPlatform.h" #include "imgIContainer.h" #include "imgLoader.h" #include "imgRequestProxy.h" #include "js/Value.h" #include "jsapi.h" #include "mozAutoDocUpdate.h" #include "mozIDOMWindow.h" #include "mozIThirdPartyUtil.h" #include "mozilla/AbstractTimelineMarker.h" #include "mozilla/AntiTrackingUtils.h" #include "mozilla/ArrayIterator.h" #include "mozilla/ArrayUtils.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/Base64.h" #include "mozilla/BasePrincipal.h" #include "mozilla/CSSEnabledState.h" #include "mozilla/ContentBlocking.h" #include "mozilla/ContentBlockingAllowList.h" #include "mozilla/ContentBlockingNotifier.h" #include "mozilla/ContentBlockingUserInteraction.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/DebugOnly.h" #include "mozilla/DocLoadingTimelineMarker.h" #include "mozilla/DocumentStyleRootIterator.h" #include "mozilla/EditorCommands.h" #include "mozilla/Encoding.h" #include "mozilla/ErrorResult.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventListenerManager.h" #include "mozilla/EventQueue.h" #include "mozilla/EventStateManager.h" #include "mozilla/ExtensionPolicyService.h" #include "mozilla/FullscreenChange.h" #include "mozilla/GlobalStyleSheetCache.h" #include "mozilla/HTMLEditor.h" #include "mozilla/HoldDropJSObjects.h" #include "mozilla/IdentifierMapEntry.h" #include "mozilla/InputTaskManager.h" #include "mozilla/IntegerRange.h" #include "mozilla/InternalMutationEvent.h" #include "mozilla/Likely.h" #include "mozilla/Logging.h" #include "mozilla/LookAndFeel.h" #include "mozilla/MacroForEach.h" #include "mozilla/MediaFeatureChange.h" #include "mozilla/MediaManager.h" #include "mozilla/MemoryReporting.h" #include "mozilla/NullPrincipal.h" #include "mozilla/OriginAttributes.h" #include "mozilla/OwningNonNull.h" #include "mozilla/PendingAnimationTracker.h" #include "mozilla/PendingFullscreenEvent.h" #include "mozilla/PermissionDelegateHandler.h" #include "mozilla/PermissionManager.h" #include "mozilla/Preferences.h" #include "mozilla/PreloadHashKey.h" #include "mozilla/PresShell.h" #include "mozilla/PresShellForwards.h" #include "mozilla/PresShellInlines.h" #include "mozilla/PseudoStyleType.h" #include "mozilla/RefCountType.h" #include "mozilla/RelativeTo.h" #include "mozilla/RestyleManager.h" #include "mozilla/ReverseIterator.h" #include "mozilla/SMILAnimationController.h" #include "mozilla/SMILTimeContainer.h" #include "mozilla/ScopeExit.h" #include "mozilla/Services.h" #include "mozilla/ServoCSSPropList.h" #include "mozilla/ServoStyleConsts.h" #include "mozilla/ServoStyleSet.h" #include "mozilla/ServoTypes.h" #include "mozilla/SizeOfState.h" #include "mozilla/Span.h" #include "mozilla/Sprintf.h" #include "mozilla/StaticAnalysisFunctions.h" #include "mozilla/StaticPrefs_apz.h" #include "mozilla/StaticPrefs_browser.h" #include "mozilla/StaticPrefs_docshell.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/StaticPrefs_full_screen_api.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/StaticPrefs_network.h" #include "mozilla/StaticPrefs_page_load.h" #include "mozilla/StaticPrefs_plugins.h" #include "mozilla/StaticPrefs_privacy.h" #include "mozilla/StaticPrefs_security.h" #include "mozilla/StaticPrefs_widget.h" #include "mozilla/StaticPresData.h" #include "mozilla/StorageAccess.h" #include "mozilla/StoragePrincipalHelper.h" #include "mozilla/StyleSheet.h" #include "mozilla/Telemetry.h" #include "mozilla/TelemetryHistogramEnums.h" #include "mozilla/TelemetryScalarEnums.h" #include "mozilla/TextControlElement.h" #include "mozilla/TextEditor.h" #include "mozilla/TimelineConsumers.h" #include "mozilla/TypedEnumBits.h" #include "mozilla/URLDecorationStripper.h" #include "mozilla/URLExtraData.h" #include "mozilla/Unused.h" #include "mozilla/css/ImageLoader.h" #include "mozilla/css/Loader.h" #include "mozilla/css/Rule.h" #include "mozilla/css/SheetParsingMode.h" #include "mozilla/dom/AnonymousContent.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/CDATASection.h" #include "mozilla/dom/CSPDictionariesBinding.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/ChromeObserver.h" #include "mozilla/dom/ClientInfo.h" #include "mozilla/dom/ClientState.h" #include "mozilla/dom/Comment.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/DOMImplementation.h" #include "mozilla/dom/DOMIntersectionObserver.h" #include "mozilla/dom/DOMStringList.h" #include "mozilla/dom/DocGroup.h" #include "mozilla/dom/DocumentBinding.h" #include "mozilla/dom/DocumentFragment.h" #include "mozilla/dom/DocumentL10n.h" #include "mozilla/dom/DocumentTimeline.h" #include "mozilla/dom/DocumentType.h" #include "mozilla/dom/ElementBinding.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/EventListenerBinding.h" #include "mozilla/dom/FailedCertSecurityInfoBinding.h" #include "mozilla/dom/FeaturePolicy.h" #include "mozilla/dom/FeaturePolicyUtils.h" #include "mozilla/dom/FontFaceSet.h" #include "mozilla/dom/FromParser.h" #include "mozilla/dom/HTMLAllCollection.h" #include "mozilla/dom/HTMLBodyElement.h" #include "mozilla/dom/HTMLCollectionBinding.h" #include "mozilla/dom/HTMLDialogElement.h" #include "mozilla/dom/HTMLFormElement.h" #include "mozilla/dom/HTMLIFrameElement.h" #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/dom/HTMLInputElement.h" #include "mozilla/dom/HTMLLinkElement.h" #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/dom/HTMLMetaElement.h" #include "mozilla/dom/HTMLSharedElement.h" #include "mozilla/dom/HTMLTextAreaElement.h" #include "mozilla/dom/ImageTracker.h" #include "mozilla/dom/Link.h" #include "mozilla/dom/MediaQueryList.h" #include "mozilla/dom/MediaSource.h" #include "mozilla/dom/MutationObservers.h" #include "mozilla/dom/NameSpaceConstants.h" #include "mozilla/dom/Navigator.h" #include "mozilla/dom/NetErrorInfoBinding.h" #include "mozilla/dom/NodeInfo.h" #include "mozilla/dom/NodeIterator.h" #include "mozilla/dom/PContentChild.h" #include "mozilla/dom/PWindowGlobalChild.h" #include "mozilla/dom/PageTransitionEvent.h" #include "mozilla/dom/PageTransitionEventBinding.h" #include "mozilla/dom/Performance.h" #include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/PostMessageEvent.h" #include "mozilla/dom/ProcessingInstruction.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/ResizeObserverController.h" #include "mozilla/dom/SVGElement.h" #include "mozilla/dom/SVGSVGElement.h" #include "mozilla/dom/SVGUseElement.h" #include "mozilla/dom/ScriptLoader.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/Selection.h" #include "mozilla/dom/ServiceWorkerContainer.h" #include "mozilla/dom/ServiceWorkerDescriptor.h" #include "mozilla/dom/ServiceWorkerManager.h" #include "mozilla/dom/ShadowIncludingTreeIterator.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h" #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h" #include "mozilla/dom/StyleSheetList.h" #include "mozilla/dom/TimeoutManager.h" #include "mozilla/dom/Touch.h" #include "mozilla/dom/TouchEvent.h" #include "mozilla/dom/TreeOrderedArray.h" #include "mozilla/dom/TreeOrderedArrayInlines.h" #include "mozilla/dom/TreeWalker.h" #include "mozilla/dom/URL.h" #include "mozilla/dom/UserActivation.h" #include "mozilla/dom/WindowBinding.h" #include "mozilla/dom/WindowContext.h" #include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/dom/WindowProxyHolder.h" #include "mozilla/dom/XPathEvaluator.h" #include "mozilla/dom/nsCSPContext.h" #include "mozilla/dom/nsCSPUtils.h" #include "mozilla/extensions/WebExtensionPolicy.h" #include "mozilla/fallible.h" #include "mozilla/gfx/BaseCoord.h" #include "mozilla/gfx/BaseSize.h" #include "mozilla/gfx/Coord.h" #include "mozilla/gfx/Point.h" #include "mozilla/gfx/ScaleFactor.h" #include "mozilla/intl/LocaleService.h" #include "mozilla/ipc/IdleSchedulerChild.h" #include "mozilla/ipc/MessageChannel.h" #include "mozilla/net/ChannelEventQueue.h" #include "mozilla/net/CookieJarSettings.h" #include "mozilla/net/NeckoChannelParams.h" #include "mozilla/net/RequestContextService.h" #include "nsAboutProtocolUtils.h" #include "nsAlgorithm.h" #include "nsAttrValue.h" #include "nsAttrValueInlines.h" #include "nsBaseHashtable.h" #include "nsBidiUtils.h" #include "nsCRT.h" #include "nsCSSPropertyID.h" #include "nsCSSProps.h" #include "nsCSSPseudoElements.h" #include "nsCanvasFrame.h" #include "nsCaseTreatment.h" #include "nsCharsetSource.h" #include "nsCommandManager.h" #include "nsCommandParams.h" #include "nsComponentManagerUtils.h" #include "nsContentCreatorFunctions.h" #include "nsContentList.h" #include "nsContentPermissionHelper.h" #include "nsContentSecurityUtils.h" #include "nsContentUtils.h" #include "nsCoord.h" #include "nsCycleCollectionNoteChild.h" #include "nsCycleCollectionTraversalCallback.h" #include "nsDOMAttributeMap.h" #include "nsDOMCaretPosition.h" #include "nsDOMNavigationTiming.h" #include "nsDOMString.h" #include "nsDeviceContext.h" #include "nsDocShell.h" #include "nsError.h" #include "nsEscape.h" #include "nsFocusManager.h" #include "nsFrameLoader.h" #include "nsFrameLoaderOwner.h" #include "nsGenericHTMLElement.h" #include "nsGlobalWindowInner.h" #include "nsGlobalWindowOuter.h" #include "nsHTMLCSSStyleSheet.h" #include "nsHTMLDocument.h" #include "nsHTMLStyleSheet.h" #include "nsHtml5Module.h" #include "nsHtml5Parser.h" #include "nsHtml5TreeOpExecutor.h" #include "nsIApplicationCache.h" #include "nsIAsyncShutdown.h" #include "nsIAuthPrompt.h" #include "nsIAuthPrompt2.h" #include "nsIBFCacheEntry.h" #include "nsIBaseWindow.h" #include "nsIBrowserChild.h" #include "nsIBrowserUsage.h" #include "nsICSSLoaderObserver.h" #include "nsICategoryManager.h" #include "nsICertOverrideService.h" #include "nsIContent.h" #include "nsIContentPolicy.h" #include "nsIContentSecurityPolicy.h" #include "nsIContentSink.h" #include "nsICookieJarSettings.h" #include "nsICookieService.h" #include "nsIDOMXULCommandDispatcher.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIDocumentActivity.h" #include "nsIDocumentEncoder.h" #include "nsIDocumentLoader.h" #include "nsIDocumentLoaderFactory.h" #include "nsIDocumentObserver.h" #include "nsIEditingSession.h" #include "nsIEditor.h" #include "nsIEffectiveTLDService.h" #include "nsIFile.h" #include "nsIFileChannel.h" #include "nsIFrame.h" #include "nsIGlobalObject.h" #include "nsIHTMLCollection.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIIOService.h" #include "nsIImageLoadingContent.h" #include "nsIInlineSpellChecker.h" #include "nsIInputStreamChannel.h" #include "nsIInterfaceRequestorUtils.h" #include "nsILayoutHistoryState.h" #include "nsIMultiPartChannel.h" #include "nsIMutationObserver.h" #include "nsINSSErrorsService.h" #include "nsINamed.h" #include "nsINodeList.h" #include "nsIObjectLoadingContent.h" #include "nsIObserverService.h" #include "nsIPermission.h" #include "nsIPrompt.h" #include "nsIPropertyBag2.h" #include "nsIReferrerInfo.h" #include "nsIRefreshURI.h" #include "nsIRequest.h" #include "nsIRequestContext.h" #include "nsIRunnable.h" #include "nsISHEntry.h" #include "nsIScriptElement.h" #include "nsIScriptError.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptSecurityManager.h" #include "nsISecurityConsoleMessage.h" #include "nsISelectionController.h" #include "nsISerialEventTarget.h" #include "nsISerializable.h" #include "nsISimpleEnumerator.h" #include "nsISiteSecurityService.h" #include "nsISocketProvider.h" #include "nsISpeculativeConnect.h" #include "nsIStructuredCloneContainer.h" #include "nsIThread.h" #include "nsITimedChannel.h" #include "nsITimer.h" #include "nsITransportSecurityInfo.h" #include "nsIURIMutator.h" #include "nsIVariant.h" #include "nsIWeakReference.h" #include "nsIWebNavigation.h" #include "nsIWidget.h" #include "nsIX509Cert.h" #include "nsIX509CertValidity.h" #include "nsIXMLContentSink.h" #include "nsIXULRuntime.h" #include "nsImageLoadingContent.h" #include "nsImportModule.h" #include "nsLanguageAtomService.h" #include "nsLayoutUtils.h" #include "nsNetCID.h" #include "nsNetUtil.h" #include "nsNodeInfoManager.h" #include "nsObjectLoadingContent.h" #include "nsPIDOMWindowInlines.h" #include "nsPIWindowRoot.h" #include "nsPoint.h" #include "nsPointerHashKeys.h" #include "nsPresContext.h" #include "nsQueryFrame.h" #include "nsQueryObject.h" #include "nsRange.h" #include "nsRect.h" #include "nsRefreshDriver.h" #include "nsSandboxFlags.h" #include "nsSerializationHelper.h" #include "nsServiceManagerUtils.h" #include "nsStringFlags.h" #include "nsStringIterator.h" #include "nsStyleSheetService.h" #include "nsStyleStruct.h" #include "nsTextNode.h" #include "nsUnicharUtils.h" #include "nsWrapperCache.h" #include "nsWrapperCacheInlines.h" #include "nsXPCOMCID.h" #include "nsXULAppAPI.h" #include "prthread.h" #include "prtime.h" #include "prtypes.h" #include "xpcpublic.h" // XXX Must be included after mozilla/Encoding.h #include "encoding_rs.h" #ifdef MOZ_XUL # include "mozilla/dom/XULBroadcastManager.h" # include "mozilla/dom/XULPersist.h" # include "nsIAppWindow.h" # include "nsXULPrototypeDocument.h" # include "nsXULCommandDispatcher.h" # include "nsXULPopupManager.h" # include "nsIDocShellTreeOwner.h" #endif #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) #define NS_MAX_DOCUMENT_WRITE_DEPTH 20 mozilla::LazyLogModule gPageCacheLog("PageCache"); mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer"); mozilla::LazyLogModule gUseCountersLog("UseCounters"); namespace mozilla { namespace dom { typedef nsTArray LinkArray; AutoTArray* Document::sLoadingForegroundTopLevelContentDocument = nullptr; static LazyLogModule gDocumentLeakPRLog("DocumentLeak"); static LazyLogModule gCspPRLog("CSP"); LazyLogModule gUserInteractionPRLog("UserInteraction"); static nsresult GetHttpChannelHelper(nsIChannel* aChannel, nsIHttpChannel** aHttpChannel) { nsCOMPtr httpChannel = do_QueryInterface(aChannel); if (httpChannel) { httpChannel.forget(aHttpChannel); return NS_OK; } nsCOMPtr multipart = do_QueryInterface(aChannel); if (!multipart) { *aHttpChannel = nullptr; return NS_OK; } nsCOMPtr 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::DependentAtomOrString* aKey) : mKey(aKey ? *aKey : nullptr) {} void IdentifierMapEntry::Traverse( nsCycleCollectionTraversalCallback* aCallback) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "mIdentifierMap mNameContentList"); aCallback->NoteXPCOMChild(static_cast(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; } void IdentifierMapEntry::AddContentChangeCallback( Document::IDTargetObserver aCallback, void* aData, bool aForImage) { if (!mChangeCallbacks) { mChangeCallbacks = MakeUnique>(); } 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(); } } } void IdentifierMapEntry::AddIdElement(Element* aElement) { MOZ_ASSERT(aElement, "Must have element"); MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?"); size_t index = mIdContentList.Insert(*aElement); if (index == 0) { Element* oldElement = mIdContentList->SafeElementAt(1); 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); } } void IdentifierMapEntry::ClearAndNotify() { Element* currentElement = mIdContentList->SafeElementAt(0); mIdContentList.Clear(); if (currentElement) { FireChangeCallbacks(currentElement, nullptr); } mNameContentList = nullptr; if (mImageElement) { SetImageElement(nullptr); } mChangeCallbacks = nullptr; } 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 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& aNames) override { AutoTArray 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 aGivenProto) override { return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto); } using nsBaseContentList::Item; private: virtual ~SimpleHTMLCollection() = default; }; 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() = default; NS_DECL_ISUPPORTS NS_DECL_NSIREQUEST private: ~OnloadBlocker() = default; }; 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::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { return GetTRRModeImpl(aTRRMode); } NS_IMETHODIMP OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) { return SetTRRModeImpl(aTRRMode); } NS_IMETHODIMP OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; } // ================================================================== namespace dom { ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {} Document* ExternalResourceMap::RequestResource( nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, 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"); MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo"); *aPendingLoad = nullptr; if (mHaveShutDown) { return nullptr; } // First, make sure we strip the ref from aURI. nsCOMPtr 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& loadEntry = mPendingLoads.GetOrInsert(clone); if (loadEntry) { RefPtr load(loadEntry); load.forget(aPendingLoad); return nullptr; } RefPtr load(new PendingLoad(aDisplayDocument)); loadEntry = load; if (NS_FAILED(load->StartLoad(clone, aReferrerInfo, 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(SubDocEnumFunc aCallback) { nsTArray> docs(mMap.Count()); for (const auto& entry : mMap) { if (Document* doc = entry.GetData()->mDocument) { docs.AppendElement(doc); } } for (auto& doc : docs) { if (aCallback(*doc) == CallState::Stop) { return; } } } 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 viewer = iter.UserData()->mViewer; if (viewer) { viewer->Hide(); } } } void ExternalResourceMap::ShowViewers() { for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) { nsCOMPtr viewer = iter.UserData()->mViewer; if (viewer) { viewer->Show(); } } } void TransferOverrideDPPX(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->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 load; mPendingLoads.Remove(aURI, getter_AddRefs(load)); nsresult rv = NS_OK; nsCOMPtr doc; if (aViewer) { doc = aViewer->GetDocument(); NS_ASSERTION(doc, "Must have a document"); 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), nullptr); 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) { if (nsPresContext* pc = doc->GetPresContext()) { pc->RecomputeBrowsingContextDependentData(); } TransferOverrideDPPX(aDisplayDocument, doc); TransferShowingState(aDisplayDocument, doc); } const nsTArray>& 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) { ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap(); if (map.HaveShutDown()) { return NS_BINDING_ABORTED; } nsCOMPtr viewer; nsCOMPtr 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); } nsresult ExternalResourceMap::PendingLoad::SetupViewer( nsIRequest* aRequest, nsIContentViewer** aViewer, nsILoadGroup** aLoadGroup) { MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest"); *aViewer = nullptr; *aLoadGroup = nullptr; nsCOMPtr chan(do_QueryInterface(aRequest)); NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED); nsCOMPtr 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 loadGroup; chan->GetLoadGroup(getter_AddRefs(loadGroup)); // Give this document its own loadgroup nsCOMPtr newLoadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID); NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY); newLoadGroup->SetLoadGroup(loadGroup); nsCOMPtr callbacks; loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); nsCOMPtr newCallbacks = new LoadgroupCallbacks(callbacks); newLoadGroup->SetNotificationCallbacks(newCallbacks); // This is some serious hackery cribbed from docshell nsCOMPtr 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 docLoaderFactory = do_GetService(contractId.get()); NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE); nsCOMPtr viewer; nsCOMPtr 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 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 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, 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, aStream, aOffset, aCount); } NS_IMETHODIMP ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { // mTargetListener might be null if SetupViewer or AddExternalResource failed if (mTargetListener) { nsCOMPtr listener; mTargetListener.swap(listener); return listener->OnStopRequest(aRequest, aStatus); } return NS_OK; } nsresult ExternalResourceMap::PendingLoad::StartLoad( nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) { MOZ_ASSERT(aURI, "Must have a URI"); MOZ_ASSERT(aRequestingNode, "Must have a node"); MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo"); nsCOMPtr loadGroup = aRequestingNode->OwnerDoc()->GetDocumentLoadGroup(); nsresult rv = NS_OK; nsCOMPtr channel; rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode, nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT, nsIContentPolicy::TYPE_OTHER, nullptr, // aPerformanceStorage loadGroup); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr httpChannel(do_QueryInterface(channel)); if (httpChannel) { rv = httpChannel->SetReferrerInfo(aReferrerInfo); 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(nsIBrowserChild))) { 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(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; } Document::FrameRequest::FrameRequest(FrameRequestCallback& aCallback, int32_t aHandle) : mCallback(&aCallback), mHandle(aHandle) { LogFrameRequestCallback::LogDispatch(mCallback); } Document::FrameRequest::~FrameRequest() = default; Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default; struct Document::MetaViewportElementAndData { RefPtr mElement; ViewportMetaData mData; bool operator==(const MetaViewportElementAndData& aOther) const { return mElement == aOther.mElement && mData == aOther.mData; } }; // ================================================================== // = // ================================================================== Document::InternalCommandDataHashtable* Document::sInternalCommandDataHashtable = nullptr; // static void Document::Shutdown() { if (sInternalCommandDataHashtable) { sInternalCommandDataHashtable->Clear(); delete sInternalCommandDataHashtable; sInternalCommandDataHashtable = nullptr; } } Document::Document(const char* aContentType) : nsINode(nullptr), DocumentOrShadowRoot(this), mBlockAllMixedContent(false), mBlockAllMixedContentPreloads(false), mUpgradeInsecureRequests(false), mUpgradeInsecurePreloads(false), mDontWarnAboutMutationEventsAndAllowSlowDOMMutations(false), mCharacterSet(WINDOWS_1252_ENCODING), mCharacterSetSource(0), mParentDocument(nullptr), mCachedRootElement(nullptr), mNodeInfoManager(nullptr), #ifdef DEBUG mStyledLinksCleared(false), #endif mBidiEnabled(false), mMayNeedFontPrefsUpdate(true), mMathMLEnabled(false), mIsInitialDocumentInWindow(false), mIgnoreDocGroupMismatches(false), mLoadedAsData(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), mHasPrintCallbacks(false), mInUnlinkOrDeletion(false), mHasHadScriptHandlingObject(false), mIsBeingUsedAsImage(false), mDocURISchemeIsChrome(false), mInChromeDocShell(false), mIsDevToolsDocument(false), mIsSyntheticDocument(false), mHasLinksToUpdateRunnable(false), mFlushingPendingLinkUpdates(false), mMayHaveDOMMutationObservers(false), mMayHaveAnimationObservers(false), mHasCSP(false), mHasUnsafeEvalCSP(false), mHasUnsafeInlineCSP(false), mHasCSPDeliveredThroughHeader(false), mBFCacheDisallowed(false), mHasHadDefaultView(false), mStyleSheetChangeEventsEnabled(false), mIsSrcdocDocument(false), mHasDisplayDocument(false), mFontFaceSetDirty(true), mDidFireDOMContentLoaded(true), mHasScrollLinkedEffect(false), mFrameRequestCallbacksScheduled(false), mIsTopLevelContentDocument(false), mIsContentDocument(false), mDidCallBeginLoad(false), mEncodingMenuDisabled(false), mLinksEnabled(true), mIsSVGGlyphsDocument(false), mInDestructor(false), mIsGoingAway(false), mInXBLUpdate(false), mNeedsReleaseAfterStackRefCntRelease(false), mStyleSetFilled(false), mQuirkSheetAdded(false), mContentEditableSheetAdded(false), mDesignModeSheetAdded(false), mSSApplicableStateNotificationPending(false), mMayHaveTitleElement(false), mDOMLoadingSet(false), mDOMInteractiveSet(false), mDOMCompleteSet(false), mAutoFocusFired(false), mScrolledToRefAlready(false), mChangeScrollPosWhenScrollingToRef(false), mDelayFrameLoaderInitialization(false), mSynchronousDOMContentLoaded(false), mMaybeServiceWorkerControlled(false), mAllowZoom(false), mValidScaleFloat(false), mValidMinScale(false), mValidMaxScale(false), mWidthStrEmpty(false), mParserAborted(false), mReportedDocumentUseCounters(false), mHasReportedShadowDOMUsage(false), mHasDelayedRefreshEvent(false), mLoadEventFiring(false), mSkipLoadEventAfterClose(false), mDisableCookieAccess(false), mDisableDocWrite(false), mTooDeepWriteRecursion(false), mPendingMaybeEditingStateChanged(false), mHasBeenEditable(false), mHasWarnedAboutZoom(false), mIsRunningExecCommand(false), mSetCompleteAfterDOMContentLoaded(false), mPendingFullscreenRequests(0), mXMLDeclarationBits(0), mOnloadBlockCount(0), mAsyncOnloadBlockCount(0), mWriteLevel(0), mContentEditableCount(0), mEditingState(EditingState::eOff), mCompatMode(eCompatibility_FullStandards), mReadyState(ReadyState::READYSTATE_UNINITIALIZED), mAncestorIsLoading(false), mVisibilityState(dom::VisibilityState::Hidden), mType(eUnknown), mDefaultElementType(0), mAllowXULXBL(eTriUnset), mSkipDTDSecurityChecks(false), 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), mUseCountersInitialized(false), mShouldReportUseCounters(false), mShouldSendPageUseCounters(false), mUserHasInteracted(false), mHasUserInteractionTimerScheduled(false), mStackRefCnt(0), mUpdateNestLevel(0), mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED), mViewportType(Unknown), mViewportFit(ViewportFitType::Auto), mSubDocuments(nullptr), mHeaderData(nullptr), mFlashClassification(FlashClassification::Unknown), mServoRestyleRootDirtyBits(0), mThrowOnDynamicMarkupInsertionCounter(0), mIgnoreOpensDuringUnloadCounter(0), mDocLWTheme(Doc_Theme_Uninitialized), mSavedResolution(1.0f), mSavedResolutionBeforeMVM(1.0f), mGeneration(0), mCachedTabSizeGeneration(0), mNextFormNumber(0), mNextControlNumber(0), mPreloadService(this) { MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this)); SetIsInDocument(); SetIsConnected(true); // Create these unconditionally, they will be used to warn about the `zoom` // property, even if use counters are disabled. mStyleUseCounters = Servo_UseCounters_Create().Consume(); 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(); mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr); mReferrerInfo = new dom::ReferrerInfo(nullptr); } #ifndef ANDROID // unused by GeckoView static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) { if (NS_WARN_IF(!aWin)) { return false; } nsIURI* uri = aWin->GetDocumentURI(); if (NS_WARN_IF(!uri)) { return false; } // getSpec is an expensive operation, hence we first check the scheme // to see if the caller is actually an about: page. if (!uri->SchemeIs("about")) { return false; } nsAutoCString aboutSpec; nsresult rv = NS_GetAboutModuleName(uri, aboutSpec); NS_ENSURE_SUCCESS(rv, false); return aboutSpec.EqualsASCII(aSpec); } #endif bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) { nsGlobalWindowInner* win = xpc::WindowOrNull(aObject); #ifdef ANDROID // GeckoView uses data URLs for error pages, so for now just check for any // error page return win && win->GetDocument() && win->GetDocument()->IsErrorPage(); #else return win && IsAboutErrorPage(win, "neterror"); #endif } already_AddRefed Document::AddCertException( bool aIsTemporary) { nsIGlobalObject* global = GetScopeObject(); if (!global) { return nullptr; } ErrorResult er; RefPtr promise = Promise::Create(global, er, Promise::ePropagateUserInteraction); if (er.Failed()) { return nullptr; } nsCOMPtr info; nsCOMPtr tsi; nsresult rv = NS_OK; if (NS_WARN_IF(!mFailedChannel)) { promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); return promise.forget(); } rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info)); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeReject(rv); return promise.forget(); } nsCOMPtr failedChannelURI; NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI)); if (!failedChannelURI) { promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); return promise.forget(); } nsCOMPtr innerURI = NS_GetInnermostURI(failedChannelURI); if (!innerURI) { promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); return promise.forget(); } nsAutoCString host; innerURI->GetAsciiHost(host); int32_t port; innerURI->GetPort(&port); tsi = do_QueryInterface(info); if (NS_WARN_IF(!tsi)) { promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); return promise.forget(); } bool isUntrusted = true; rv = tsi->GetIsUntrusted(&isUntrusted); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeReject(rv); return promise.forget(); } bool isDomainMismatch = true; rv = tsi->GetIsDomainMismatch(&isDomainMismatch); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeReject(rv); return promise.forget(); } bool isNotValidAtThisTime = true; rv = tsi->GetIsNotValidAtThisTime(&isNotValidAtThisTime); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeReject(rv); return promise.forget(); } nsCOMPtr cert; rv = tsi->GetServerCert(getter_AddRefs(cert)); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeReject(rv); return promise.forget(); } if (NS_WARN_IF(!cert)) { promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); return promise.forget(); } uint32_t flags = 0; if (isUntrusted) { flags |= nsICertOverrideService::ERROR_UNTRUSTED; } if (isDomainMismatch) { flags |= nsICertOverrideService::ERROR_MISMATCH; } if (isNotValidAtThisTime) { flags |= nsICertOverrideService::ERROR_TIME; } if (XRE_IsContentProcess()) { nsCOMPtr certSer = do_QueryInterface(cert); nsCString certSerialized; NS_SerializeToString(certSer, certSerialized); ContentChild* cc = ContentChild::GetSingleton(); MOZ_ASSERT(cc); cc->SendAddCertException(certSerialized, flags, host, port, aIsTemporary) ->Then(GetCurrentSerialEventTarget(), __func__, [promise](const mozilla::MozPromise< nsresult, mozilla::ipc::ResponseRejectReason, true>::ResolveOrRejectValue& aValue) { if (aValue.IsResolve()) { promise->MaybeResolve(aValue.ResolveValue()); } else { promise->MaybeRejectWithUndefined(); } }); return promise.forget(); } if (XRE_IsParentProcess()) { nsCOMPtr overrideService = do_GetService(NS_CERTOVERRIDE_CONTRACTID); if (!overrideService) { promise->MaybeReject(NS_ERROR_FAILURE); return promise.forget(); } rv = overrideService->RememberValidityOverride(host, port, cert, flags, aIsTemporary); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeReject(rv); return promise.forget(); } promise->MaybeResolveWithUndefined(); return promise.forget(); } promise->MaybeReject(NS_ERROR_FAILURE); return promise.forget(); } void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) { nsCOMPtr info; nsCOMPtr tsi; nsresult rv = NS_OK; if (NS_WARN_IF(!mFailedChannel)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info)); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } tsi = do_QueryInterface(info); if (NS_WARN_IF(!tsi)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } nsAutoString errorCodeString; rv = tsi->GetErrorCodeString(errorCodeString); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } aInfo.mErrorCodeString.Assign(errorCodeString); } bool Document::CallerIsTrustedAboutCertError(JSContext* aCx, JSObject* aObject) { nsGlobalWindowInner* win = xpc::WindowOrNull(aObject); #ifdef ANDROID // GeckoView uses data URLs for error pages, so for now just check for any // error page return win && win->GetDocument() && win->GetDocument()->IsErrorPage(); #else return win && IsAboutErrorPage(win, "certerror"); #endif } bool Document::IsErrorPage() const { nsCOMPtr loadInfo = mChannel ? mChannel->LoadInfo() : nullptr; return loadInfo && loadInfo->GetLoadErrorPage(); } void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo, ErrorResult& aRv) { nsCOMPtr info; nsCOMPtr tsi; nsresult rv = NS_OK; if (NS_WARN_IF(!mFailedChannel)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info)); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } tsi = do_QueryInterface(info); if (NS_WARN_IF(!tsi)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } nsAutoString errorCodeString; rv = tsi->GetErrorCodeString(errorCodeString); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } aInfo.mErrorCodeString.Assign(errorCodeString); rv = tsi->GetIsUntrusted(&aInfo.mIsUntrusted); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } rv = tsi->GetIsDomainMismatch(&aInfo.mIsDomainMismatch); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } rv = tsi->GetIsNotValidAtThisTime(&aInfo.mIsNotValidAtThisTime); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } nsCOMPtr cert; nsCOMPtr validity; rv = tsi->GetServerCert(getter_AddRefs(cert)); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } if (NS_WARN_IF(!cert)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } rv = cert->GetValidity(getter_AddRefs(validity)); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } if (NS_WARN_IF(!validity)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } PRTime validityResult; rv = validity->GetNotBefore(&validityResult); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC); rv = validity->GetNotAfter(&validityResult); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC); nsAutoString issuerCommonName; nsAutoString certChainPEMString; Sequence& certChainStrings = aInfo.mCertChainStrings.Construct(); int64_t maxValidity = std::numeric_limits::max(); int64_t minValidity = 0; PRTime notBefore, notAfter; nsTArray> failedCertArray; rv = tsi->GetFailedCertChain(failedCertArray); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } if (NS_WARN_IF(failedCertArray.IsEmpty())) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } for (const auto& certificate : failedCertArray) { rv = certificate->GetIssuerCommonName(issuerCommonName); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } rv = certificate->GetValidity(getter_AddRefs(validity)); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } if (NS_WARN_IF(!validity)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } rv = validity->GetNotBefore(¬Before); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } rv = validity->GetNotAfter(¬After); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } notBefore = std::max(minValidity, notBefore); notAfter = std::min(maxValidity, notAfter); nsTArray certArray; rv = certificate->GetRawDER(certArray); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } nsAutoString der64; rv = Base64Encode(reinterpret_cast(certArray.Elements()), certArray.Length(), der64); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } if (!certChainStrings.AppendElement(der64, fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } } aInfo.mIssuerCommonName.Assign(issuerCommonName); aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC); aInfo.mCertValidityRangeNotBefore = DOMTimeStamp(notBefore / PR_USEC_PER_MSEC); int32_t errorCode; rv = tsi->GetErrorCode(&errorCode); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } nsCOMPtr nsserr = do_GetService("@mozilla.org/nss_errors_service;1"); if (NS_WARN_IF(!nsserr)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } nsresult res; rv = nsserr->GetXPCOMFromNSSError(errorCode, &res); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } bool isPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(this); uint32_t flags = isPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0; OriginAttributes attrs; StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs); nsCOMPtr aURI; mFailedChannel->GetURI(getter_AddRefs(aURI)); if (XRE_IsContentProcess()) { ContentChild* cc = ContentChild::GetSingleton(); MOZ_ASSERT(cc); cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags, attrs, &aInfo.mHasHSTS); cc->SendIsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI, flags, attrs, &aInfo.mHasHPKP); } else { nsCOMPtr sss = do_GetService(NS_SSSERVICE_CONTRACTID); if (NS_WARN_IF(!sss)) { return; } Unused << NS_WARN_IF(NS_FAILED( sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags, attrs, nullptr, nullptr, &aInfo.mHasHSTS))); Unused << NS_WARN_IF(NS_FAILED( sss->IsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI, flags, attrs, nullptr, nullptr, &aInfo.mHasHPKP))); } } bool Document::AllowDeprecatedTls() { return Preferences::GetBool("security.tls.version.enable-deprecated", false); } void Document::SetAllowDeprecatedTls(bool value) { if (!IsErrorPage()) { return; } auto docShell = GetDocShell(); if (!docShell) { return; } auto child = BrowserChild::GetFrom(docShell); if (!child) { return; } child->SendSetAllowDeprecatedTls(value); } bool Document::IsAboutPage() const { nsCOMPtr principal = NodePrincipal(); return principal->SchemeIs("about"); } void Document::ConstructUbiNode(void* storage) { JS::ubi::Concrete::construct(storage, this); } void Document::LoadEventFired() { // Accumulate timing data located in each document's realm and report to // telemetry. AccumulateJSTelemetry(); // Collect page load timings AccumulatePageLoadTelemetry(); // Release the JS bytecode cache from its wait on the load event, and // potentially dispatch the encoding of the bytecode. if (ScriptLoader()) { ScriptLoader()->LoadEventFired(); } } static uint32_t CalcPercentage(TimeDuration aSubTimer, TimeDuration aTotalTimer) { return static_cast(100.0 * aSubTimer.ToMilliseconds() / aTotalTimer.ToMilliseconds()); } void Document::AccumulatePageLoadTelemetry() { // Interested only in top level documents for real websites that are in the // foreground. if (!ShouldIncludeInTelemetry(false) || !IsTopLevelContentDocument() || !GetNavigationTiming() || !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) { return; } if (!GetChannel()) { return; } nsCOMPtr timedChannel(do_QueryInterface(GetChannel())); if (!timedChannel) { return; } TimeStamp responseStart; timedChannel->GetResponseStart(&responseStart); TimeStamp navigationStart = GetNavigationTiming()->GetNavigationStartTimeStamp(); if (!responseStart || !navigationStart) { return; } nsCString http3Key; nsCOMPtr httpChannel = do_QueryInterface(GetChannel()); if (httpChannel) { uint32_t major; uint32_t minor; if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) { if (major == 3) { http3Key = "http3"_ns; } else if (major == 2) { bool supportHttp3 = false; if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) { supportHttp3 = false; } if (supportHttp3) { http3Key = "supports_http3"_ns; } } } } // First Contentful Paint if (TimeStamp firstContentfulPaint = GetNavigationTiming()->GetFirstContentfulPaintTimeStamp()) { Telemetry::AccumulateTimeDelta(Telemetry::PERF_FIRST_CONTENTFUL_PAINT_MS, navigationStart, firstContentfulPaint); if (!http3Key.IsEmpty()) { Telemetry::AccumulateTimeDelta( Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key, navigationStart, firstContentfulPaint); } Telemetry::AccumulateTimeDelta( Telemetry::PERF_FIRST_CONTENTFUL_PAINT_FROM_RESPONSESTART_MS, responseStart, firstContentfulPaint); } // DOM Content Loaded event if (TimeStamp dclEventStart = GetNavigationTiming()->GetDOMContentLoadedEventStartTimeStamp()) { Telemetry::AccumulateTimeDelta(Telemetry::PERF_DOM_CONTENT_LOADED_TIME_MS, navigationStart, dclEventStart); Telemetry::AccumulateTimeDelta( Telemetry::PERF_DOM_CONTENT_LOADED_TIME_FROM_RESPONSESTART_MS, responseStart, dclEventStart); } // Load event if (TimeStamp loadEventStart = GetNavigationTiming()->GetLoadEventStartTimeStamp()) { Telemetry::AccumulateTimeDelta(Telemetry::PERF_PAGE_LOAD_TIME_MS, navigationStart, loadEventStart); if (!http3Key.IsEmpty()) { Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS, http3Key, navigationStart, loadEventStart); } Telemetry::AccumulateTimeDelta( Telemetry::PERF_PAGE_LOAD_TIME_FROM_RESPONSESTART_MS, responseStart, loadEventStart); } } void Document::AccumulateJSTelemetry() { if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry(false)) { return; } if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) { return; } AutoJSContext cx; JSObject* globalObject = GetScopeObject()->GetGlobalJSObject(); JSAutoRealm ar(cx, globalObject); JS::JSTimers timers = JS::GetJSTimers(cx); TimeDuration totalExecutionTime = timers.executionTime; TimeDuration totalDelazificationTime = timers.delazificationTime; TimeDuration totalXDREncodingTime = timers.xdrEncodingTime; TimeDuration totalBaselineCompileTime = timers.baselineCompileTime; if (totalExecutionTime.IsZero()) { return; } if (!totalDelazificationTime.IsZero()) { Telemetry::Accumulate( Telemetry::JS_DELAZIFICATION_PROPORTION, CalcPercentage(totalDelazificationTime, totalExecutionTime)); } if (!totalXDREncodingTime.IsZero()) { Telemetry::Accumulate( Telemetry::JS_XDR_ENCODING_PROPORTION, CalcPercentage(totalXDREncodingTime, totalExecutionTime)); } if (!totalBaselineCompileTime.IsZero()) { Telemetry::Accumulate( Telemetry::JS_BASELINE_COMPILE_PROPORTION, CalcPercentage(totalBaselineCompileTime, totalExecutionTime)); } if (GetNavigationTiming()) { TimeStamp loadEventStart = GetNavigationTiming()->GetLoadEventStartTimeStamp(); TimeStamp navigationStart = GetNavigationTiming()->GetNavigationStartTimeStamp(); if (loadEventStart && navigationStart) { TimeDuration pageLoadTime = loadEventStart - navigationStart; Telemetry::Accumulate(Telemetry::JS_EXECUTION_PROPORTION, CalcPercentage(totalExecutionTime, pageLoadTime)); } } } 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()) { RemoveToplevelLoadingDocument(this); // don't report for about: pages if (!IsAboutPage()) { // 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); } 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; } } } } 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; nsAutoScriptBlocker scriptBlocker; // Destroy link map now so we don't waste time removing // links one by one DestroyElementMaps(); // 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) { UnlinkStyleSheets(sheets); } 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(); } if (mPermissionDelegateHandler) { mPermissionDelegateHandler->DropDocumentReference(); } delete mHeaderData; mPendingTitleChangeEvent.Revoke(); mPlugins.Clear(); MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(), "must not have media query lists left"); if (mNodeInfoManager) { mNodeInfoManager->DropDocumentReference(); } if (mDocGroup) { MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup()); mDocGroup->GetBrowsingContextGroup()->RemoveDocument(mDocGroup->GetKey(), 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, 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); LastRelease(); 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)", "(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; } 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(mStyleSheetSetList) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader) DocumentOrShadowRoot::Traverse(tmp, cb); for (auto& sheets : tmp->mAdditionalSheets) { tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[][i]", cb); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserver) 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) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll) // Traverse all our nsCOMArrays. 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(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*>(mql)->getNext()) { if (mql->HasListeners() && NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item"); cb.NoteXPCOMChild(mql); } } if (tmp->mResizeObserverController) { tmp->mResizeObserverController->Traverse(cb); } for (size_t i = 0; i < tmp->mMetaViewports.Length(); i++) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMetaViewports[i].mElement); } // XXX: This should be not needed once bug 1569185 lands. for (auto it = tmp->mL10nProtoElements.ConstIter(); !it.Done(); it.Next()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key"); cb.NoteXPCOMChild(it.Key()); CycleCollectionNoteChild(cb, it.UserData(), "mL10nProtoElements value"); } for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement); NS_IMPL_CYCLE_COLLECTION_TRAVERSE( mPendingFrameStaticClones[i].mStaticCloneOf); } 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 child = tmp->GetLastChild(); tmp->DisconnectChild(child); child->UnbindFromTree(); } tmp->UnlinkOriginalDocumentIfStatic(); tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer tmp->SetScriptGlobalObject(nullptr); for (auto& sheets : tmp->mAdditionalSheets) { tmp->UnlinkStyleSheets(sheets); } NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet) NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n) NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation) NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder) NS_IMPL_CYCLE_COLLECTION_UNLINK(mStateObjectCached) 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(mAnonymousContents) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager) NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll) NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo) if (tmp->IsTopLevelContentDocument()) { RemoveToplevelLoadingDocument(tmp); } tmp->mParentDocument = nullptr; NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages) NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers) if (tmp->mListenerManager) { tmp->mListenerManager->Disconnect(); tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER); tmp->mListenerManager = nullptr; } 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->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*>(mql)->getNext(); mql->Disconnect(); mql = next; } tmp->mPendingFrameStaticClones.Clear(); tmp->mInUnlinkOrDeletion = false; if (tmp->mResizeObserverController) { tmp->mResizeObserverController->Unlink(); } tmp->mMetaViewports.Clear(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements) NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE 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 // MutationObservers always notifies the first observer first, expecting the // first observer to be the document. slots->mMutationObservers.PrependElementUnlessExists( static_cast(this)); mOnloadBlocker = new OnloadBlocker(); mStyleImageLoader = new 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!"); mCSSLoader = new css::Loader(this); // Assume we're not quirky, until we know otherwise mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards); // 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 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 dom::FeaturePolicy(this); mFeaturePolicy->SetDefaultOrigin(NodePrincipal()); mStyleSet = MakeUnique(*this); mozilla::HoldJSObjects(this); return NS_OK; } void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); } void Document::RemoveAllPropertiesFor(nsINode* aNode) { PropertyTable().RemoveAllPropertiesFor(aNode); } bool Document::IsVisibleConsideringAncestors() const { const Document* parent = this; do { if (!parent->IsVisible()) { return false; } } while ((parent = parent->GetInProcessParentDocument())); return true; } void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) { nsCOMPtr uri; nsCOMPtr principal; nsCOMPtr partitionedPrincipal; if (aChannel) { // Note: this code is duplicated in PrototypeDocumentContentSink::Init and // nsScriptSecurityManager::GetChannelResultPrincipals. // Note: this should match the uri used for the OnNewURI call in // nsDocShell::CreateContentViewer. NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); nsIScriptSecurityManager* securityManager = nsContentUtils::GetSecurityManager(); if (securityManager) { securityManager->GetChannelResultPrincipals( aChannel, getter_AddRefs(principal), getter_AddRefs(partitionedPrincipal)); } } bool equal = principal->Equals(partitionedPrincipal); principal = MaybeDowngradePrincipal(principal); if (equal) { partitionedPrincipal = principal; } else { partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal); } ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal); // 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 bag = do_QueryInterface(aChannel); if (bag) { nsCOMPtr baseURI; bag->GetPropertyAsInterface(u"baseURI"_ns, NS_GET_IID(nsIURI), getter_AddRefs(baseURI)); if (baseURI) { mDocumentBaseURI = baseURI; mChromeXHRDocBaseURI = nullptr; } } mChannel = aChannel; } void Document::DisconnectNodeTree() { // Delete references to sub-documents and kill the subdocument map, // if any. This is not strictly needed, but makes the node tree // teardown a bit faster. delete mSubDocuments; mSubDocuments = nullptr; bool oldVal = mInUnlinkOrDeletion; mInUnlinkOrDeletion = true; { // Scope for update MOZ_AUTO_DOC_UPDATE(this, true); // Destroy link map now so we don't waste time removing // links one by one DestroyElementMaps(); // Invalidate cached array of child nodes InvalidateChildNodes(); while (HasChildren()) { nsMutationGuard::DidMutate(); nsCOMPtr 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; } MutationObservers::NotifyContentRemoved(this, content, previousSibling); content->UnbindFromTree(); } MOZ_ASSERT(!mCachedRootElement, "After removing all children, there should be no root elem"); } mInUnlinkOrDeletion = oldVal; } void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup, nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal) { MOZ_ASSERT(aURI, "Null URI passed to ResetToURI"); MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal); MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get())); mSecurityInfo = nullptr; nsCOMPtr group = do_QueryReferent(mDocumentLoadGroup); if (!aLoadGroup || group != aLoadGroup) { mDocumentLoadGroup = nullptr; } DisconnectNodeTree(); // 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. SetPrincipals(nullptr, 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; // Check if the current document is the top-level DevTools document. // For inner DevTools frames, mIsDevToolsDocument will be set when // calling SetDocumentParent. if (aURI && aURI->SchemeIs("about") && aURI->GetSpecOrDefault().EqualsLiteral("about:devtools-toolbox")) { mIsDevToolsDocument = true; } 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 rcsvc = net::RequestContextService::GetOrCreate(); if (rcsvc) { nsCOMPtr 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(""_ns); mContentLanguage.Truncate(); mBaseTarget.Truncate(); mXMLDeclarationBits = 0; // Now get our new principal if (aPrincipal) { SetPrincipals(aPrincipal, aPartitionedPrincipal); } else { nsIScriptSecurityManager* securityManager = nsContentUtils::GetSecurityManager(); if (securityManager) { nsCOMPtr loadContext(mDocumentContainer); if (!loadContext && aLoadGroup) { nsCOMPtr 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 principal; nsresult rv = securityManager->GetLoadContextContentPrincipal( mDocumentURI, loadContext, getter_AddRefs(principal)); if (NS_SUCCEEDED(rv)) { SetPrincipals(principal, principal); } } } if (mFontFaceSet) { mFontFaceSet->RefreshStandardFontLoadPrincipal(); } // Refresh the principal on the realm. if (nsPIDOMWindowInner* win = GetInnerWindow()) { nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal(); } } already_AddRefed 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()) { MOZ_DIAGNOSTIC_ASSERT(false, "Should never try to create a document with " "an expanded principal"); auto* expanded = basePrin->As(); return do_AddRef(expanded->AllowList().LastElement()); } if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) { // We basically want the parent document here, but because this is very // early in the load, GetInProcessParentDocument() returns null, so we use // the docshell hierarchy to get this information instead. if (RefPtr parent = mDocumentContainer->GetBrowsingContext()->GetParent()) { auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow()); if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) { nsCOMPtr nullPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1"); return nullPrincipal.forget(); } } } nsCOMPtr principal(aPrincipal); return principal.forget(); } size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) { nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); // lowest index first int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet); size_t count = mStyleSet->SheetCount(StyleOrigin::Author); size_t index = 0; for (; index < count; index++) { auto* sheet = mStyleSet->SheetAt(StyleOrigin::Author, index); MOZ_ASSERT(sheet); int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet); if (sheetDocIndex > newDocIndex) { break; } // If the sheet is not owned by the document it can be an author // sheet registered at nsStyleSheetService or an additional author // sheet on the document, which means the new // doc sheet should end up before it. if (sheetDocIndex < 0) { if (sheetService) { auto& authorSheets = *sheetService->AuthorStyleSheets(); if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) { break; } } if (sheet == GetFirstAdditionalAuthorSheet()) { break; } } } return index; } void Document::ResetStylesheetsToURI(nsIURI* aURI) { MOZ_ASSERT(aURI); ClearAdoptedStyleSheets(); auto ClearSheetList = [&](nsTArray>& aSheetList) { for (auto& sheet : Reversed(aSheetList)) { sheet->ClearAssociatedDocumentOrShadowRoot(); if (mStyleSetFilled) { mStyleSet->RemoveStyleSheet(*sheet); } } aSheetList.Clear(); }; ClearSheetList(mStyleSheets); for (auto& sheets : mAdditionalSheets) { ClearSheetList(sheets); } if (mStyleSetFilled) { if (auto* ss = nsStyleSheetService::GetInstance()) { for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) { MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot()); if (sheet->IsApplicable()) { mStyleSet->RemoveStyleSheet(*sheet); } } } } // 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(); } if (mStyleSetFilled) { FillStyleSetDocumentSheets(); if (mStyleSet->StyleSheetsHaveChanged()) { ApplicableStylesChanged(); } } } static void AppendSheetsToStyleSet( ServoStyleSet* aStyleSet, const nsTArray>& aSheets) { for (StyleSheet* sheet : Reversed(aSheets)) { aStyleSet->AppendStyleSheet(*sheet); } } void Document::FillStyleSetUserAndUASheets() { // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt // ordering. // The document will fill in the document sheets when we create the presshell auto* cache = GlobalStyleSheetCache::Singleton(); nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); MOZ_ASSERT(sheetService, "should never be creating a StyleSet after the style sheet " "service has gone"); for (StyleSheet* sheet : *sheetService->UserStyleSheets()) { mStyleSet->AppendStyleSheet(*sheet); } StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet() : cache->GetUserContentSheet(); if (sheet) { mStyleSet->AppendStyleSheet(*sheet); } mStyleSet->AppendStyleSheet(*cache->UASheet()); if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) { mStyleSet->AppendStyleSheet(*cache->MathMLSheet()); } if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) { mStyleSet->AppendStyleSheet(*cache->SVGSheet()); } mStyleSet->AppendStyleSheet(*cache->HTMLSheet()); if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) { mStyleSet->AppendStyleSheet(*cache->NoFramesSheet()); } if (nsLayoutUtils::ShouldUseNoScriptSheet(this)) { mStyleSet->AppendStyleSheet(*cache->NoScriptSheet()); } mStyleSet->AppendStyleSheet(*cache->CounterStylesSheet()); // Load the minimal XUL rules for scrollbars and a few other XUL things // that non-XUL (typically HTML) documents commonly use. mStyleSet->AppendStyleSheet(*cache->MinimalXULSheet()); // Only load the full XUL sheet if we'll need it. if (LoadsFullXULStyleSheetUpFront()) { mStyleSet->AppendStyleSheet(*cache->XULSheet()); } mStyleSet->AppendStyleSheet(*cache->FormsSheet()); mStyleSet->AppendStyleSheet(*cache->ScrollbarsSheet()); mStyleSet->AppendStyleSheet(*cache->PluginProblemSheet()); for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) { mStyleSet->AppendStyleSheet(*sheet); } MOZ_ASSERT(!mQuirkSheetAdded); if (NeedsQuirksSheet()) { mStyleSet->AppendStyleSheet(*cache->QuirkSheet()); mQuirkSheetAdded = true; } } void Document::FillStyleSet() { MOZ_ASSERT(!mStyleSetFilled); FillStyleSetUserAndUASheets(); FillStyleSetDocumentSheets(); mStyleSetFilled = true; } void Document::RemoveContentEditableStyleSheets() { MOZ_ASSERT(IsHTMLOrXHTML()); auto* cache = GlobalStyleSheetCache::Singleton(); bool changed = false; if (mDesignModeSheetAdded) { mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet()); mDesignModeSheetAdded = false; changed = true; } if (mContentEditableSheetAdded) { mStyleSet->RemoveStyleSheet(*cache->ContentEditableSheet()); mContentEditableSheetAdded = false; changed = true; } if (changed) { MOZ_ASSERT(mStyleSetFilled); ApplicableStylesChanged(); } } void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) { MOZ_ASSERT(IsHTMLOrXHTML()); MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled, "Caller should ensure we're being rendered"); auto* cache = GlobalStyleSheetCache::Singleton(); bool changed = false; if (!mContentEditableSheetAdded) { mStyleSet->AppendStyleSheet(*cache->ContentEditableSheet()); mContentEditableSheetAdded = true; changed = true; } if (mDesignModeSheetAdded != aDesignMode) { if (mDesignModeSheetAdded) { mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet()); } else { mStyleSet->AppendStyleSheet(*cache->DesignModeSheet()); } mDesignModeSheetAdded = !mDesignModeSheetAdded; changed = true; } if (changed) { ApplicableStylesChanged(); } } void Document::FillStyleSetDocumentSheets() { MOZ_ASSERT(mStyleSet->SheetCount(StyleOrigin::Author) == 0, "Style set already has document sheets?"); // Sheets are added in reverse order to avoid worst-case time complexity when // looking up the index of a sheet. // // Note that usually appending is faster (rebuilds less stuff in the // styleset), but in this case it doesn't matter since we're filling the // styleset from scratch anyway. for (StyleSheet* sheet : Reversed(mStyleSheets)) { if (sheet->IsApplicable()) { mStyleSet->AddDocStyleSheet(*sheet); } } EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) { if (aSheet.IsApplicable()) { mStyleSet->AddDocStyleSheet(aSheet); } }); nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) { mStyleSet->AppendStyleSheet(*sheet); } AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAgentSheet]); AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eUserSheet]); AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAuthorSheet]); } void Document::CompatibilityModeChanged() { MOZ_ASSERT(IsHTMLOrXHTML()); CSSLoader()->SetCompatibilityMode(mCompatMode); mStyleSet->CompatibilityModeChanged(); if (PresShell* presShell = GetPresShell()) { // Selectors may have become case-sensitive / case-insensitive, the stylist // has already performed the relevant invalidation. presShell->EnsureStyleFlush(); } if (!mStyleSetFilled) { MOZ_ASSERT(!mQuirkSheetAdded); return; } if (mQuirkSheetAdded == NeedsQuirksSheet()) { return; } auto* cache = GlobalStyleSheetCache::Singleton(); StyleSheet* sheet = cache->QuirkSheet(); if (mQuirkSheetAdded) { mStyleSet->RemoveStyleSheet(*sheet); } else { mStyleSet->AppendStyleSheet(*sheet); } mQuirkSheetAdded = !mQuirkSheetAdded; ApplicableStylesChanged(); } void Document::SetCompatibilityMode(nsCompatibility aMode) { NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards, "Bad compat mode for XHTML document!"); if (mCompatMode == aMode) { return; } mCompatMode = aMode; CompatibilityModeChanged(); } static void WarnIfSandboxIneffective(nsIDocShell* aDocShell, uint32_t aSandboxFlags, nsIChannel* aChannel) { // If the document permits allow-top-navigation and // allow-top-navigation-by-user-activation this will permit all top // navigation. if (aSandboxFlags != SANDBOXED_NONE && !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) && !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) { nsContentUtils::ReportToConsole( nsIScriptError::warningFlag, "Iframe Sandbox"_ns, aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES, "BothAllowTopNavigationAndUserActivationPresent"); } // 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)) { RefPtr bc = aDocShell->GetBrowsingContext(); MOZ_ASSERT(bc->IsInProcess()); RefPtr parentBC = bc->GetParent(); if (!parentBC || !parentBC->IsInProcess()) { // If parent document is not in process, then by construction it // cannot be same origin. return; } // Don't warn if our parent is not the top-level document. if (!parentBC->IsTopContent()) { return; } nsCOMPtr parentDocShell = parentBC->GetDocShell(); MOZ_ASSERT(parentDocShell); nsCOMPtr parentChannel; parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel)); if (!parentChannel) { return; } nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel); if (NS_FAILED(rv)) { return; } nsCOMPtr parentDocument = parentDocShell->GetDocument(); nsCOMPtr iframeUri; parentChannel->GetURI(getter_AddRefs(iframeUri)); nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Iframe Sandbox"_ns, parentDocument, nsContentUtils::eSECURITY_PROPERTIES, "BothAllowScriptsAndSameOriginPresent", nsTArray(), iframeUri); } } bool Document::IsSynthesized() { nsCOMPtr loadInfo = mChannel ? mChannel->LoadInfo() : nullptr; return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized(); } // static bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) { nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx); return principal && (principal->IsSystemPrincipal() || 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 uri; aChannel->GetURI(getter_AddRefs(uri)); MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p StartDocumentLoad %s", this, uri ? uri->GetSpecOrDefault().get() : "")); } 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 bag = do_QueryInterface(aChannel); if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns, 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 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); } // Store the security info for future use. mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); } // 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 docShell = do_QueryInterface(aContainer); // If this is an error page, don't inherit sandbox flags nsCOMPtr loadInfo = aChannel->LoadInfo(); if (docShell && !loadInfo->GetLoadErrorPage()) { mSandboxFlags = loadInfo->GetSandboxFlags(); WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel()); } // Set the opener policy for the top level content document. nsCOMPtr httpChan = do_QueryInterface(mChannel); nsILoadInfo::CrossOriginOpenerPolicy policy = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; if (IsTopLevelContentDocument() && httpChan && NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell && docShell->GetBrowsingContext()) { // Setting the opener policy on a discarded context has no effect. Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy); } // The CSP directives upgrade-insecure-requests as well as // block-all-mixed-content not only apply to the toplevel document, // but also to nested documents. The loadInfo of a subdocument // load already holds the correct flag, so let's just set it here // on the document. Please note that we set the appropriate preload // bits just for the sake of completeness here, because the preloader // does not reach into subdocuments. mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests(); mUpgradeInsecurePreloads = mUpgradeInsecureRequests; mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent(); mBlockAllMixedContentPreloads = mBlockAllMixedContent; // HTTPS-Only Mode flags // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all // sub-resources and sub-documents. mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); nsresult rv = InitReferrerInfo(aChannel); NS_ENSURE_SUCCESS(rv, rv); rv = InitCOEP(aChannel); NS_ENSURE_SUCCESS(rv, rv); // Check CSP navigate-to // We need to enforce the CSP of the document that initiated the load, // which is the CSP to inherit. nsCOMPtr cspToInherit = loadInfo->GetCspToInherit(); if (cspToInherit) { bool allowsNavigateTo = false; rv = cspToInherit->GetAllowsNavigateTo( mDocumentURI, loadInfo->GetIsFormSubmission(), !loadInfo->RedirectChain().IsEmpty(), /* aWasRedirected */ true, /* aEnforceWhitelist */ &allowsNavigateTo); NS_ENSURE_SUCCESS(rv, rv); if (!allowsNavigateTo) { aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION); return NS_OK; } } rv = InitCSP(aChannel); NS_ENSURE_SUCCESS(rv, rv); // Initialize FeaturePolicy rv = InitFeaturePolicy(aChannel); NS_ENSURE_SUCCESS(rv, rv); rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings)); NS_ENSURE_SUCCESS(rv, rv); // Generally XFO and CSP frame-ancestors is handled within // DocumentLoadListener. However, the DocumentLoadListener can not handle // object and embed. Until then we have to enforce it here (See Bug 1646899). nsContentPolicyType internalContentType = loadInfo->InternalContentPolicyType(); if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT || internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) { nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel); nsresult status; aChannel->GetStatus(&status); if (status == NS_ERROR_XFO_VIOLATION) { // stop! ERROR page! // But before we have to reset the principal of the document // because the onload() event fires before the error page // is displayed and we do not want the enclosing document // to access the contentDocument. RefPtr nullPrincipal = NullPrincipal::CreateWithInheritedAttributes(NodePrincipal()); // Before calling SetPrincipals() we should ensure that mFontFaceSet // and also GetInnerWindow() is still null at this point, before // we can fix Bug 1614735: Evaluate calls to SetPrincipal // within Document.cpp MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow()); SetPrincipals(nullPrincipal, nullPrincipal); } } return NS_OK; } nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; } void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; } nsIContentSecurityPolicy* Document::GetPreloadCsp() const { return mPreloadCSP; } void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) { mPreloadCSP = aPreloadCSP; } void Document::GetCspJSON(nsString& aJSON) { aJSON.Truncate(); if (!mCSP) { dom::CSPPolicies jsonPolicies; jsonPolicies.ToJSON(aJSON); return; } mCSP->ToJSON(aJSON); } void Document::SendToConsole(nsCOMArray& 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 if (mCSP) { // Set up 'block-all-mixed-content' if not already inherited // from the parent context or set by any other CSP. if (!mBlockAllMixedContent) { rv = mCSP->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 = mCSP->GetUpgradeInsecureRequests(&mUpgradeInsecureRequests); NS_ENSURE_SUCCESS_VOID(rv); } if (!mUpgradeInsecurePreloads) { mUpgradeInsecurePreloads = mUpgradeInsecureRequests; } // Update csp settings in the parent process if (auto* wgc = GetWindowGlobalChild()) { wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent, mUpgradeInsecureRequests); } } return; } // 2) apply settings from speculative csp if (mPreloadCSP) { if (!mBlockAllMixedContentPreloads) { rv = mPreloadCSP->GetBlockAllMixedContent(&mBlockAllMixedContentPreloads); NS_ENSURE_SUCCESS_VOID(rv); } if (!mUpgradeInsecurePreloads) { rv = mPreloadCSP->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; } // If this is a data document - no need to set CSP. if (mLoadedAsData) { return NS_OK; } // If this is an image, no need to set a CSP. Otherwise SVG images // served with a CSP might block internally applied inline styles. nsCOMPtr loadInfo = aChannel->LoadInfo(); if (loadInfo->GetExternalContentPolicyType() == ExtContentPolicy::TYPE_IMAGE) { return NS_OK; } MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?"); // If there is a CSP that needs to be inherited from whatever // global is considered the client of the document fetch then // we query it here from the loadinfo in case the newly created // document needs to inherit the CSP. See: // https://w3c.github.io/webappsec-csp/#initialize-document-csp if (CSP_ShouldResponseInheritCSP(aChannel)) { mCSP = loadInfo->GetCspToInherit(); } // If there is no CSP to inherit, then we create a new CSP here so // that history entries always have the right reference in case a // Meta CSP gets dynamically added after the history entry has // already been created. if (!mCSP) { mCSP = new nsCSPContext(); } // Always overwrite the requesting context of the CSP so that any new // 'self' keyword added to an inherited CSP translates correctly. nsresult rv = mCSP->SetRequestContextWithDocument(this); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsAutoCString tCspHeaderValue, tCspROHeaderValue; nsCOMPtr httpChannel; rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (httpChannel) { Unused << httpChannel->GetResponseHeader("content-security-policy"_ns, tCspHeaderValue); Unused << httpChannel->GetResponseHeader( "content-security-policy-report-only"_ns, tCspROHeaderValue); } NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue); NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue); // Check if this is a document from a WebExtension. nsCOMPtr principal = NodePrincipal(); auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy(); // If there's no CSP to apply, go ahead and return early if (!addonPolicy && cspHeaderValue.IsEmpty() && cspROHeaderValue.IsEmpty()) { if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) { nsCOMPtr 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)); // ----- if the doc is an addon, apply its CSP. if (addonPolicy) { mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false); mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false); // Bug 1548468: Move CSP off ExpandedPrincipal // Currently the LoadInfo holds the source of truth for every resource load // because LoadInfo::GetCSP() queries the CSP from an ExpandedPrincipal // (and not from the Client) if the load was triggered by an extension. auto* basePrin = BasePrincipal::Cast(principal); if (basePrin->Is()) { basePrin->As()->SetCsp(mCSP); } } // ----- if there's a full-strength CSP header, apply it. if (!cspHeaderValue.IsEmpty()) { mHasCSPDeliveredThroughHeader = true; rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false); NS_ENSURE_SUCCESS(rv, rv); } // ----- if there's a report-only CSP header, apply it. if (!cspROHeaderValue.IsEmpty()) { rv = CSP_AppendCSPFromHeader(mCSP, 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 = mCSP->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); // Skip setting the content blocking allowlist principal to NullPrincipal. // The principal is only used to enable/disable trackingprotection via // permission and can be shared with the top level sandboxed site. // See Bug 1654546. SetPrincipals(principal, principal); } ApplySettingsFromCSP(false); return NS_OK; } already_AddRefed Document::GetParentFeaturePolicy() { BrowsingContext* browsingContext = GetBrowsingContext(); if (!browsingContext) { return nullptr; } if (!browsingContext->IsContentSubframe()) { return nullptr; } HTMLIFrameElement* iframe = HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement()); if (iframe) { return do_AddRef(iframe->FeaturePolicy()); } if (XRE_IsParentProcess()) { return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy()); } WindowContext* windowContext = browsingContext->GetCurrentWindowContext(); if (!windowContext) { return nullptr; } WindowGlobalChild* child = windowContext->GetWindowGlobalChild(); if (!child) { return nullptr; } return do_AddRef(child->GetContainerFeaturePolicy()); } nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) { MOZ_ASSERT(mFeaturePolicy, "we should only call init once"); mFeaturePolicy->ResetDeclaredPolicy(); mFeaturePolicy->SetDefaultOrigin(NodePrincipal()); RefPtr parentPolicy = GetParentFeaturePolicy(); if (parentPolicy) { // Let's inherit the policy from the parent HTMLIFrameElement if it exists. mFeaturePolicy->InheritPolicy(parentPolicy); mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin()); } // 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 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("Feature-Policy"_ns, value); if (NS_SUCCEEDED(rv)) { mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value), NodePrincipal(), nullptr); } return NS_OK; } nsresult Document::InitReferrerInfo(nsIChannel* aChannel) { MOZ_ASSERT(mReferrerInfo); MOZ_ASSERT(mPreloadReferrerInfo); if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) { // The channel is loading `about:srcdoc`. Srcdoc loads should respond with // their parent's ReferrerInfo when asked for their ReferrerInfo, unless // they have an opaque origin. // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer if (BrowsingContext* bc = GetBrowsingContext()) { // At this point the document is not fully created and mParentDocument has // not been set yet, Document* parentDoc = bc->GetEmbedderElement() ? bc->GetEmbedderElement()->OwnerDoc() : nullptr; if (parentDoc) { mReferrerInfo = parentDoc->GetReferrerInfo(); mPreloadReferrerInfo = mReferrerInfo; return NS_OK; } MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(), "srcdoc without null principal as toplevel!"); } } nsCOMPtr httpChannel; nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!httpChannel) { return NS_OK; } nsCOMPtr referrerInfo = httpChannel->GetReferrerInfo(); if (referrerInfo) { mReferrerInfo = referrerInfo; } // Override policy if we get one from Referrerr-Policy header mozilla::dom::ReferrerPolicy policy = nsContentUtils::GetReferrerPolicyFromChannel(aChannel); mReferrerInfo = static_cast(mReferrerInfo.get()) ->CloneWithNewPolicy(policy); mPreloadReferrerInfo = mReferrerInfo; return NS_OK; } nsresult Document::InitCOEP(nsIChannel* aChannel) { nsCOMPtr httpChannel; nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); if (NS_FAILED(rv)) { return NS_OK; } nsCOMPtr intChannel = do_QueryInterface(httpChannel); if (!intChannel) { return NS_OK; } nsILoadInfo::CrossOriginEmbedderPolicy policy = nsILoadInfo::EMBEDDER_POLICY_NULL; if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(&policy))) { mEmbedderPolicy = Some(policy); } return NS_OK; } void Document::StopDocumentLoad() { if (mParser) { mParserAborted = true; mParser->Terminate(); } } void Document::SetDocumentURI(nsIURI* aURI) { nsCOMPtr oldBase = GetDocBaseURI(); mDocumentURI = aURI; nsIURI* newBase = GetDocBaseURI(); mDocURISchemeIsChrome = aURI && IsChromeURI(aURI); 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(); if (inner && inner->GetWindowGlobalChild()) { inner->GetWindowGlobalChild()->SetDocumentURI(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::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer, bool aPreload) { ReferrerPolicyEnum policy = ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer); // The empty string "" corresponds to no referrer policy, causing a fallback // to a referrer policy defined elsewhere. if (policy == ReferrerPolicy::_empty) { return; } MOZ_ASSERT(mReferrerInfo); MOZ_ASSERT(mPreloadReferrerInfo); if (aPreload) { mPreloadReferrerInfo = static_cast((mPreloadReferrerInfo).get()) ->CloneWithNewPolicy(policy); } else { mReferrerInfo = static_cast((mReferrerInfo).get()) ->CloneWithNewPolicy(policy); } } void Document::SetPrincipals(nsIPrincipal* aNewPrincipal, nsIPrincipal* aNewPartitionedPrincipal) { MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal); if (aNewPrincipal && mAllowDNSPrefetch && StaticPrefs::network_dns_disablePrefetchFromHTTPS()) { if (aNewPrincipal->SchemeIs("https")) { mAllowDNSPrefetch = false; } } mCSSLoader->DeregisterFromSheetCache(); mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal); mPartitionedPrincipal = aNewPartitionedPrincipal; mCSSLoader->RegisterInSheetCache(); #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 } #ifdef DEBUG void Document::AssertDocGroupMatchesKey() const { // Sanity check that we have an up-to-date and accurate docgroup // We only check if the principal when we can get the browsing context. if (!GetBrowsingContext()) { return; } if (mDocGroup) { nsAutoCString docGroupKey; // GetKey() can fail, e.g. after the TLD service has shut down. nsresult rv = mozilla::dom::DocGroup::GetKey( NodePrincipal(), CrossOriginIsolated(), docGroupKey); if (NS_SUCCEEDED(rv)) { MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey)); } } } #endif nsresult Document::Dispatch(TaskCategory aCategory, already_AddRefed&& 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(JSContext* aCx) const { JS::AutoFilename filename; uint32_t line = 0; uint32_t column = 0; if (!JS::DescribeScriptedCaller(aCx, &filename, &line, &column)) { return false; } return mTrackingScripts.Contains(nsDependentCString(filename.get())); } 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. auto* browsingContext = GetBrowsingContext(); if (browsingContext) { if (!browsingContext->GetAllowPlugins()) { 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::EnsureL10n() { if (!mDocumentL10n) { Element* elem = GetDocumentElement(); if (NS_WARN_IF(!elem)) { return; } bool isSync = elem->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nsync); mDocumentL10n = DocumentL10n::Create(this, isSync); MOZ_ASSERT(mDocumentL10n); } } bool Document::HasPendingInitialTranslation() { return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready; } DocumentL10n* Document::GetL10n() { return mDocumentL10n; } bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) { JS::Rooted object(aCx, aObject); nsCOMPtr callerPrincipal = nsContentUtils::SubjectPrincipal(aCx); nsGlobalWindowInner* win = xpc::WindowOrNull(object); bool allowed = false; callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr, &allowed); return allowed; } void Document::LocalizationLinkAdded(Element* aLinkElement) { if (!AllowsL10n()) { return; } EnsureL10n(); nsAutoString href; aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href); mDocumentL10n->AddResourceId(href); if (mReadyState >= READYSTATE_INTERACTIVE) { mDocumentL10n->Activate(true); mDocumentL10n->TriggerInitialTranslation(); } else { if (!mDocumentL10n->mBlockingLayout) { // Our initial translation is going to block layout start. Make sure // we don't fire the load event until after that stops happening and // layout has a chance to start. BlockOnload(); mDocumentL10n->mBlockingLayout = true; } } } void Document::LocalizationLinkRemoved(Element* aLinkElement) { if (!AllowsL10n()) { return; } if (mDocumentL10n) { nsAutoString href; aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href); uint32_t remaining = mDocumentL10n->RemoveResourceId(href); if (remaining == 0) { if (mDocumentL10n->mBlockingLayout) { mDocumentL10n->mBlockingLayout = false; UnblockOnload(/* aFireSync = */ false); } mDocumentL10n = nullptr; } } } /** * 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 , * In XHTML/HTML this is the end of . * * 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 (mDocumentL10n) { mDocumentL10n->Activate(false); } } void Document::OnParsingCompleted() { // 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->TriggerInitialTranslation(); } } void Document::InitialTranslationCompleted(bool aL10nCached) { if (mDocumentL10n && mDocumentL10n->mBlockingLayout) { // This means we blocked the load event in LocalizationLinkAdded. It's // important that the load blocker removal here be async, because our caller // will notify the content sink after us, and we want the content sync's // work to happen before the load event fires. mDocumentL10n->mBlockingLayout = false; UnblockOnload(/* aFireSync = */ false); } mL10nProtoElements.Clear(); nsXULPrototypeDocument* proto = GetPrototype(); if (proto) { proto->SetIsL10nCached(aL10nCached); } } bool Document::AllowsL10n() const { if (IsStaticDocument()) { // We don't allow l10n on static documents, because the nodes are already // cloned translated, and static docs don't get parsed so we never // TriggerInitialTranslation, etc, so a load blocker would keep hanging // forever. return false; } bool allowed = false; NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed); return allowed; } bool Document::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) { MOZ_ASSERT(NS_IsMainThread()); return nsContentUtils::IsSystemCaller(aCx) || StaticPrefs::dom_animations_api_core_enabled(); } bool Document::IsWebAnimationsEnabled(CallerType aCallerType) { MOZ_ASSERT(NS_IsMainThread()); return aCallerType == dom::CallerType::System || StaticPrefs::dom_animations_api_core_enabled(); } 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; } SVGSVGElement* Document::GetSVGRootElement() const { Element* root = GetRootElement(); if (!root || !root->IsSVGElement(nsGkAtoms::svg)) { return nullptr; } return static_cast(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 { nsFocusManager* fm = nsFocusManager::GetFocusManager(); if (!fm) { rv.Throw(NS_ERROR_NOT_AVAILABLE); return false; } // Is there a focused DOMWindow? nsCOMPtr 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->GetInProcessParentDocument()) { if (currentDoc == this) { // Yes, we are an ancestor return true; } } return false; } void Document::GetDesignMode(nsAString& aDesignMode) { if (HasFlag(NODE_IS_EDITABLE)) { aDesignMode.AssignLiteral("on"); } else { aDesignMode.AssignLiteral("off"); } } void Document::SetDesignMode(const nsAString& aDesignMode, nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) { SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv); } static void NotifyEditableStateChange(Document& aDoc) { #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED nsMutationGuard g; #endif for (nsIContent* node = aDoc.GetNextNode(&aDoc); node; node = node->GetNextNode(&aDoc)) { if (auto* element = Element::FromNode(node)) { element->UpdateState(true); } } MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0)); } void Document::SetDesignMode(const nsAString& aDesignMode, const Maybe& aSubjectPrincipal, ErrorResult& rv) { if (aSubjectPrincipal.isSome() && !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) { rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED); return; } bool editableMode = HasFlag(NODE_IS_EDITABLE); if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) { SetEditableFlag(!editableMode); // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic // state of all descendant elements of it. Update that now. NotifyEditableStateChange(*this); rv = EditingStateChanged(); } } nsCommandManager* Document::GetMidasCommandManager() { // check if we have it cached if (mMidasCommandManager) { return mMidasCommandManager; } nsPIDOMWindowOuter* window = GetWindow(); if (!window) { return nullptr; } nsIDocShell* docshell = window->GetDocShell(); if (!docshell) { return nullptr; } mMidasCommandManager = docshell->GetCommandManager(); return mMidasCommandManager; } // static void Document::EnsureInitializeInternalCommandDataHashtable() { if (sInternalCommandDataHashtable) { return; } sInternalCommandDataHashtable = new InternalCommandDataHashtable(); // clang-format off sInternalCommandDataHashtable->Put( u"bold"_ns, InternalCommandData( "cmd_bold", Command::FormatBold, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"italic"_ns, InternalCommandData( "cmd_italic", Command::FormatItalic, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"underline"_ns, InternalCommandData( "cmd_underline", Command::FormatUnderline, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"strikethrough"_ns, InternalCommandData( "cmd_strikethrough", Command::FormatStrikeThrough, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"subscript"_ns, InternalCommandData( "cmd_subscript", Command::FormatSubscript, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"superscript"_ns, InternalCommandData( "cmd_superscript", Command::FormatSuperscript, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"cut"_ns, InternalCommandData( "cmd_cut", Command::Cut, ExecCommandParam::Ignore, CutCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"copy"_ns, InternalCommandData( "cmd_copy", Command::Copy, ExecCommandParam::Ignore, CopyCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"paste"_ns, InternalCommandData( "cmd_paste", Command::Paste, ExecCommandParam::Ignore, PasteCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"delete"_ns, InternalCommandData( "cmd_deleteCharBackward", Command::DeleteCharBackward, ExecCommandParam::Ignore, DeleteCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"forwarddelete"_ns, InternalCommandData( "cmd_deleteCharForward", Command::DeleteCharForward, ExecCommandParam::Ignore, DeleteCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"selectall"_ns, InternalCommandData( "cmd_selectAll", Command::SelectAll, ExecCommandParam::Ignore, SelectAllCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"undo"_ns, InternalCommandData( "cmd_undo", Command::HistoryUndo, ExecCommandParam::Ignore, UndoCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"redo"_ns, InternalCommandData( "cmd_redo", Command::HistoryRedo, ExecCommandParam::Ignore, RedoCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"indent"_ns, InternalCommandData("cmd_indent", Command::FormatIndent, ExecCommandParam::Ignore, IndentCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"outdent"_ns, InternalCommandData( "cmd_outdent", Command::FormatOutdent, ExecCommandParam::Ignore, OutdentCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"backcolor"_ns, InternalCommandData( "cmd_highlight", Command::FormatBackColor, ExecCommandParam::String, HighlightColorStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"hilitecolor"_ns, InternalCommandData( "cmd_highlight", Command::FormatBackColor, ExecCommandParam::String, HighlightColorStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"forecolor"_ns, InternalCommandData( "cmd_fontColor", Command::FormatFontColor, ExecCommandParam::String, FontColorStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"fontname"_ns, InternalCommandData( "cmd_fontFace", Command::FormatFontName, ExecCommandParam::String, FontFaceStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"fontsize"_ns, InternalCommandData( "cmd_fontSize", Command::FormatFontSize, ExecCommandParam::String, FontSizeStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"increasefontsize"_ns, InternalCommandData( "cmd_increaseFont", Command::FormatIncreaseFontSize, ExecCommandParam::Ignore, IncreaseFontSizeCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"decreasefontsize"_ns, InternalCommandData( "cmd_decreaseFont", Command::FormatDecreaseFontSize, ExecCommandParam::Ignore, DecreaseFontSizeCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"inserthorizontalrule"_ns, InternalCommandData( "cmd_insertHR", Command::InsertHorizontalRule, ExecCommandParam::Ignore, InsertTagCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"createlink"_ns, InternalCommandData( "cmd_insertLinkNoUI", Command::InsertLink, ExecCommandParam::String, InsertTagCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"insertimage"_ns, InternalCommandData( "cmd_insertImageNoUI", Command::InsertImage, ExecCommandParam::String, InsertTagCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"inserthtml"_ns, InternalCommandData( "cmd_insertHTML", Command::InsertHTML, ExecCommandParam::String, InsertHTMLCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"inserttext"_ns, InternalCommandData( "cmd_insertText", Command::InsertText, ExecCommandParam::String, InsertPlaintextCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"gethtml"_ns, InternalCommandData( "cmd_getContents", Command::GetHTML, ExecCommandParam::Ignore, nullptr)); // Not defined in EditorCommands.h sInternalCommandDataHashtable->Put( u"justifyleft"_ns, InternalCommandData( "cmd_align", Command::FormatJustifyLeft, ExecCommandParam::Ignore, // Will be set to "left" AlignCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"justifyright"_ns, InternalCommandData( "cmd_align", Command::FormatJustifyRight, ExecCommandParam::Ignore, // Will be set to "right" AlignCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"justifycenter"_ns, InternalCommandData( "cmd_align", Command::FormatJustifyCenter, ExecCommandParam::Ignore, // Will be set to "center" AlignCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"justifyfull"_ns, InternalCommandData( "cmd_align", Command::FormatJustifyFull, ExecCommandParam::Ignore, // Will be set to "justify" AlignCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"removeformat"_ns, InternalCommandData( "cmd_removeStyles", Command::FormatRemove, ExecCommandParam::Ignore, RemoveStylesCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"unlink"_ns, InternalCommandData( "cmd_removeLinks", Command::FormatRemoveLink, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"insertorderedlist"_ns, InternalCommandData( "cmd_ol", Command::InsertOrderedList, ExecCommandParam::Ignore, ListCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"insertunorderedlist"_ns, InternalCommandData( "cmd_ul", Command::InsertUnorderedList, ExecCommandParam::Ignore, ListCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"insertparagraph"_ns, InternalCommandData( "cmd_insertParagraph", Command::InsertParagraph, ExecCommandParam::Ignore, InsertParagraphCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"insertlinebreak"_ns, InternalCommandData( "cmd_insertLineBreak", Command::InsertLineBreak, ExecCommandParam::Ignore, InsertLineBreakCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"formatblock"_ns, InternalCommandData( "cmd_paragraphState", Command::FormatBlock, ExecCommandParam::String, ParagraphStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"heading"_ns, InternalCommandData( "cmd_paragraphState", Command::FormatBlock, ExecCommandParam::String, ParagraphStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"styleWithCSS"_ns, InternalCommandData( "cmd_setDocumentUseCSS", Command::SetDocumentUseCSS, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"usecss"_ns, // Legacy command InternalCommandData( "cmd_setDocumentUseCSS", Command::SetDocumentUseCSS, ExecCommandParam::InvertedBoolean, SetDocumentStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"contentReadOnly"_ns, InternalCommandData( "cmd_setDocumentReadOnly", Command::SetDocumentReadOnly, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"readonly"_ns, // Legacy command InternalCommandData( "cmd_setDocumentReadOnly", Command::SetDocumentReadOnly, ExecCommandParam::InvertedBoolean, SetDocumentStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"insertBrOnReturn"_ns, InternalCommandData( "cmd_insertBrOnReturn", Command::SetDocumentInsertBROnEnterKeyPress, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"defaultParagraphSeparator"_ns, InternalCommandData( "cmd_defaultParagraphSeparator", Command::SetDocumentDefaultParagraphSeparator, ExecCommandParam::String, SetDocumentStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"enableObjectResizing"_ns, InternalCommandData( "cmd_enableObjectResizing", Command::ToggleObjectResizers, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"enableInlineTableEditing"_ns, InternalCommandData( "cmd_enableInlineTableEditing", Command::ToggleInlineTableEditor, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance)); sInternalCommandDataHashtable->Put( u"enableAbsolutePositionEditing"_ns, InternalCommandData( "cmd_enableAbsolutePositionEditing", Command::ToggleAbsolutePositionEditor, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance)); #if 0 // with empty string sInternalCommandDataHashtable->Put( u"justifynone"_ns, InternalCommandData( "cmd_align", Command::Undefined, ExecCommandParam::Ignore, nullptr)); // Not implemented yet. // REQUIRED SPECIAL REVIEW special review sInternalCommandDataHashtable->Put( u"saveas"_ns, InternalCommandData( "cmd_saveAs", Command::Undefined, ExecCommandParam::Boolean, nullptr)); // Not implemented yet. // REQUIRED SPECIAL REVIEW special review sInternalCommandDataHashtable->Put( u"print"_ns, InternalCommandData( "cmd_print", Command::Undefined, ExecCommandParam::Boolean, nullptr)); // Not implemented yet. #endif // #if 0 // clang-format on } Document::InternalCommandData Document::ConvertToInternalCommand( const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */, nsAString* aAdjustedValue /* = nullptr */) { MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty()); EnsureInitializeInternalCommandDataHashtable(); InternalCommandData commandData; if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) { return InternalCommandData(); } if (!aAdjustedValue) { // No further work to do return commandData; } switch (commandData.mExecCommandParam) { case ExecCommandParam::Ignore: // Just have to copy it, no checking switch (commandData.mCommand) { case Command::FormatJustifyLeft: aAdjustedValue->AssignLiteral("left"); break; case Command::FormatJustifyRight: aAdjustedValue->AssignLiteral("right"); break; case Command::FormatJustifyCenter: aAdjustedValue->AssignLiteral("center"); break; case Command::FormatJustifyFull: aAdjustedValue->AssignLiteral("justify"); break; default: MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) == EditorCommandParamType::None); break; } return commandData; case ExecCommandParam::Boolean: MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) & EditorCommandParamType::Bool)); // If this is a boolean value and it's not explicitly false (e.g. no // value). We default to "true" (see bug 301490). if (!aValue.LowerCaseEqualsLiteral("false")) { aAdjustedValue->AssignLiteral("true"); } else { aAdjustedValue->AssignLiteral("false"); } return commandData; case ExecCommandParam::InvertedBoolean: MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) & EditorCommandParamType::Bool)); // For old backwards commands we invert the check. if (aValue.LowerCaseEqualsLiteral("false")) { aAdjustedValue->AssignLiteral("true"); } else { aAdjustedValue->AssignLiteral("false"); } return commandData; case ExecCommandParam::String: MOZ_ASSERT(!!( EditorCommand::GetParamType(commandData.mCommand) & (EditorCommandParamType::String | EditorCommandParamType::CString))); switch (commandData.mCommand) { case Command::FormatBlock: { const char16_t* start = aValue.BeginReading(); const char16_t* end = aValue.EndReading(); if (start != end && *start == '<' && *(end - 1) == '>') { ++start; --end; } // XXX Should we reorder this array with actual usage? static const nsStaticAtom* kFormattableBlockTags[] = { // clang-format off nsGkAtoms::address, nsGkAtoms::blockquote, nsGkAtoms::dd, nsGkAtoms::div, nsGkAtoms::dl, nsGkAtoms::dt, nsGkAtoms::h1, nsGkAtoms::h2, nsGkAtoms::h3, nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6, nsGkAtoms::p, nsGkAtoms::pre, // clang-format on }; nsAutoString value(nsDependentSubstring(start, end)); ToLowerCase(value); const nsStaticAtom* valueAtom = NS_GetStaticAtom(value); for (const nsStaticAtom* kTag : kFormattableBlockTags) { if (valueAtom == kTag) { kTag->ToString(*aAdjustedValue); return commandData; } } return InternalCommandData(); } case Command::FormatFontSize: { // Per editing spec as of April 23, 2012, we need to reject the value // if it's not a valid floating-point number surrounded by optional // whitespace. Otherwise, we parse it as a legacy font size. For // now, we just parse as a legacy font size regardless (matching // WebKit) -- bug 747879. int32_t size = nsContentUtils::ParseLegacyFontSize(aValue); if (!size) { return InternalCommandData(); } MOZ_ASSERT(aAdjustedValue->IsEmpty()); aAdjustedValue->AppendInt(size); return commandData; } case Command::InsertImage: case Command::InsertLink: if (aValue.IsEmpty()) { // Invalid value, return false return InternalCommandData(); } aAdjustedValue->Assign(aValue); return commandData; case Command::SetDocumentDefaultParagraphSeparator: if (!aValue.LowerCaseEqualsLiteral("div") && !aValue.LowerCaseEqualsLiteral("p") && !aValue.LowerCaseEqualsLiteral("br")) { // Invalid value return InternalCommandData(); } aAdjustedValue->Assign(aValue); return commandData; default: aAdjustedValue->Assign(aValue); return commandData; } default: MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled"); return InternalCommandData(); } } bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI, const nsAString& aValue, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { // Only allow on HTML documents. if (!IsHTMLOrXHTML()) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_DOCUMENT_EXEC_COMMAND); return false; } // if they are requesting UI from us, let's fail since we have no UI if (aShowUI) { return false; } // If we're running an execCommand, we should just return false. // https://github.com/w3c/editing/issues/200#issuecomment-575241816 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() && mIsRunningExecCommand) { return false; } // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go() // this might add some ugly JS dependencies? nsAutoString adjustedValue; InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue); if (commandData.mCommand == Command::DoNothing) { return false; } // if editing is not on, bail if (commandData.IsAvailableOnlyWhenEditable() && !IsEditingOnAfterFlush()) { return false; } if (commandData.mCommand == Command::GetHTML) { aRv.Throw(NS_ERROR_FAILURE); return false; } // Do security check first. if (commandData.IsCutOrCopyCommand()) { if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) { // We have rejected the event due to it not being performed in an // input-driven context therefore, we report the error to the console. nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, this, nsContentUtils::eDOM_PROPERTIES, "ExecCommandCutCopyDeniedNotInputDriven"); return false; } } else if (commandData.IsPasteCommand()) { if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal, nsGkAtoms::clipboardRead)) { return false; } } // Next, consider context of command handling which is automatically resolved // by order of controllers in `nsCommandManager::GetControllerForCommand()`. // The order is: // 1. HTMLEditor for the document, if there is. // 2. TextEditor if there is an active element and it has TextEditor like // or