/* -*- 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 "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/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/ContentBlockingAllowList.h" #include "mozilla/ContentBlockingNotifier.h" #include "mozilla/ContentBlockingUserInteraction.h" #include "mozilla/ContentPrincipal.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/DebugOnly.h" #include "mozilla/ProfilerMarkers.h" #include "mozilla/AttributeStyles.h" #include "mozilla/DocumentStyleRootIterator.h" #include "mozilla/EditorBase.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/MappedDeclarationsBuilder.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/Maybe.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/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/SchedulerGroup.h" #include "mozilla/ScrollTimelineAnimationTracker.h" #include "mozilla/SMILAnimationController.h" #include "mozilla/SMILTimeContainer.h" #include "mozilla/ScopeExit.h" #include "mozilla/Components.h" #include "mozilla/SVGUtils.h" #include "mozilla/ServoStyleConsts.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_fission.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_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/TelemetryScalarEnums.h" #include "mozilla/TextControlElement.h" #include "mozilla/TextEditor.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/BlobURLProtocolHandler.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/CanvasRenderingContextHelper.h" #include "mozilla/dom/CDATASection.h" #include "mozilla/dom/CSPDictionariesBinding.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/CSSBinding.h" #include "mozilla/dom/CSSCustomPropertyRegisteredEvent.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/ErrorEvent.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/FragmentDirective.h" #include "mozilla/dom/fragmentdirectives_ffi_generated.h" #include "mozilla/dom/FromParser.h" #include "mozilla/dom/HighlightRegistry.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/InspectorUtils.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/nsHTTPSOnlyUtils.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/ResizeObserver.h" #include "mozilla/dom/RustTypes.h" #include "mozilla/dom/SVGElement.h" #include "mozilla/dom/SVGDocument.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/StyleSheetRemovedEvent.h" #include "mozilla/dom/StyleSheetRemovedEventBinding.h" #include "mozilla/dom/TimeoutManager.h" #include "mozilla/dom/ToggleEvent.h" #include "mozilla/dom/Touch.h" #include "mozilla/dom/TouchEvent.h" #include "mozilla/dom/TreeOrderedArrayInlines.h" #include "mozilla/dom/TreeWalker.h" #include "mozilla/dom/URL.h" #include "mozilla/dom/UseCounterMetrics.h" #include "mozilla/dom/UserActivation.h" #include "mozilla/dom/WakeLockJS.h" #include "mozilla/dom/WakeLockSentinel.h" #include "mozilla/dom/WindowBinding.h" #include "mozilla/dom/WindowContext.h" #include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/dom/WindowProxyHolder.h" #include "mozilla/dom/WorkerDocumentListener.h" #include "mozilla/dom/XPathEvaluator.h" #include "mozilla/dom/XPathExpression.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/glean/GleanMetrics.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 "nsCSSRendering.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 "nsDocShellLoadTypes.h" #include "nsEffectiveTLDService.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 "nsHTMLDocument.h" #include "nsHtml5Module.h" #include "nsHtml5Parser.h" #include "nsHtml5TreeOpExecutor.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 "nsIContentInlines.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 "nsIDNSService.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 "nsIPublicKeyPinningService.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 "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 "nsIHTMLContentSink.h" #include "nsIXULRuntime.h" #include "nsImageLoadingContent.h" #include "nsImportModule.h" #include "nsLanguageAtomService.h" #include "nsLayoutUtils.h" #include "nsMimeTypes.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 "nsStyleUtil.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" #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" #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 gSHIPBFCacheLog("SHIPBFCache"); mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer"); mozilla::LazyLogModule gUseCountersLog("UseCounters"); namespace mozilla { namespace dom { class Document::HeaderData { public: HeaderData(nsAtom* aField, const nsAString& aData) : mField(aField), mData(aData) {} ~HeaderData() { // Delete iteratively to avoid blowing up the stack, though it shouldn't // happen in practice. UniquePtr next = std::move(mNext); while (next) { next = std::move(next->mNext); } } RefPtr mField; nsString mData; UniquePtr mNext; }; 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->Iter(); !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() const { 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 dom::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::SetCanceledReason(const nsACString& aReason) { return SetCanceledReasonImpl(aReason); } NS_IMETHODIMP OnloadBlocker::GetCanceledReason(nsACString& aReason) { return GetCanceledReasonImpl(aReason); } NS_IMETHODIMP OnloadBlocker::CancelWithReason(nsresult aStatus, const nsACString& aReason) { return CancelWithReasonImpl(aStatus, aReason); } 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; } bool loadStartSucceeded = mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) { if (!loadEntry) { loadEntry.Insert(MakeRefPtr(aDisplayDocument)); if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo, aRequestingNode))) { return false; } } RefPtr load(loadEntry.Data()); load.forget(aPendingLoad); return true; }); if (!loadStartSucceeded) { // Make sure we don't thrash things by trying this load again, since // chances are it failed for good reasons (security check, etc). // This must be done outside the WithEntryHandle functor, as it accesses // mPendingLoads. AddExternalResource(clone, nullptr, nullptr, aDisplayDocument); } return nullptr; } void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) { nsTArray> docs(mMap.Count()); for (const auto& entry : mMap.Values()) { if (Document* doc = entry->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 (const auto& entry : mMap) { ExternalResourceMap::ExternalResource* resource = entry.GetWeak(); 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 (const auto& entry : mMap) { nsCOMPtr viewer = entry.GetData()->mViewer; if (viewer) { viewer->Hide(); } } } void ExternalResourceMap::ShowViewers() { for (const auto& entry : mMap) { nsCOMPtr viewer = entry.GetData()->mViewer; if (viewer) { viewer->Show(); } } } 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, nsIDocumentViewer* 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 = mMap.InsertOrUpdate(aURI, MakeUnique()).get(); newResource->mDocument = doc; newResource->mViewer = aViewer; newResource->mLoadGroup = aLoadGroup; if (doc) { if (nsPresContext* pc = doc->GetPresContext()) { pc->RecomputeBrowsingContextDependentData(); } 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, nsIDocumentViewer** 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) #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); 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::PendingFrameStaticClone::~PendingFrameStaticClone() = default; // ================================================================== // = // ================================================================== 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), mCharacterSet(WINDOWS_1252_ENCODING), mCharacterSetSource(0), mParentDocument(nullptr), mCachedRootElement(nullptr), mNodeInfoManager(nullptr), #ifdef DEBUG mStyledLinksCleared(false), #endif mCachedStateObjectValid(false), mBlockAllMixedContent(false), mBlockAllMixedContentPreloads(false), mUpgradeInsecureRequests(false), mUpgradeInsecurePreloads(false), mDevToolsWatchingDOMMutations(false), mBidiEnabled(false), mMayNeedFontPrefsUpdate(true), mMathMLEnabled(false), mIsInitialDocumentInWindow(false), mIsEverInitialDocumentInWindow(false), mIgnoreDocGroupMismatches(false), mLoadedAsData(false), mAddedToMemoryReportingAsDataDocument(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), mChromeRulesEnabled(false), mInChromeDocShell(false), mIsSyntheticDocument(false), mHasLinksToUpdateRunnable(false), mFlushingPendingLinkUpdates(false), mMayHaveDOMMutationObservers(false), mMayHaveAnimationObservers(false), mHasCSPDeliveredThroughHeader(false), mBFCacheDisallowed(false), mHasHadDefaultView(false), mStyleSheetChangeEventsEnabled(false), mDevToolsAnonymousAndShadowEventsEnabled(false), mIsSrcdocDocument(false), mHasDisplayDocument(false), mFontFaceSetDirty(true), mDidFireDOMContentLoaded(true), mFrameRequestCallbacksScheduled(false), mIsTopLevelContentDocument(false), mIsContentDocument(false), mDidCallBeginLoad(false), mEncodingMenuDisabled(false), mLinksEnabled(true), mIsSVGGlyphsDocument(false), mInDestructor(false), mIsGoingAway(false), mStyleSetFilled(false), mQuirkSheetAdded(false), mContentEditableSheetAdded(false), mDesignModeSheetAdded(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), mIsRunningExecCommandByContent(false), mIsRunningExecCommandByChromeOrAddon(false), mSetCompleteAfterDOMContentLoaded(false), mDidHitCompleteSheetCache(false), mUseCountersInitialized(false), mShouldReportUseCounters(false), mShouldSendPageUseCounters(false), mUserHasInteracted(false), mHasUserInteractionTimerScheduled(false), mShouldResistFingerprinting(false), mCloningForSVGUse(false), mAllowDeclarativeShadowRoots(false), mSuspendDOMNotifications(false), mXMLDeclarationBits(0), mOnloadBlockCount(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), mStaticCloneCount(0), mWindow(nullptr), mBFCacheEntry(nullptr), mInSyncOperationCount(0), mBlockDOMContentLoaded(0), mUpdateNestLevel(0), mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED), mViewportType(Unknown), mViewportFit(ViewportFitType::Auto), mHeaderData(nullptr), mServoRestyleRootDirtyBits(0), mThrowOnDynamicMarkupInsertionCounter(0), mIgnoreOpensDuringUnloadCounter(0), mSavedResolution(1.0f), mGeneration(0), mCachedTabSizeGeneration(0), mNextFormNumber(0), mNextControlNumber(0), mPreloadService(this), mShouldNotifyFetchSuccess(false), mShouldNotifyFormOrPasswordRemoved(false) { 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.reset(Servo_UseCounters_Create()); SetContentType(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 } bool Document::CallerIsTrustedAboutHttpsOnlyError(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, "httpsonlyerror"); #endif } already_AddRefed Document::AddCertException( bool aIsTemporary, ErrorResult& aError) { RefPtr promise = Promise::Create(GetScopeObject(), aError, Promise::ePropagateUserInteraction); if (aError.Failed()) { return nullptr; } nsresult rv = NS_OK; if (NS_WARN_IF(!mFailedChannel)) { promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 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); nsCOMPtr tsi; rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi)); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeReject(rv); return promise.forget(); } if (NS_WARN_IF(!tsi)) { promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 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(); } if (XRE_IsContentProcess()) { ContentChild* cc = ContentChild::GetSingleton(); MOZ_ASSERT(cc); OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef(); cc->SendAddCertException(cert, host, port, attrs, 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(); } OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef(); rv = overrideService->RememberValidityOverride(host, port, attrs, cert, 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::ReloadWithHttpsOnlyException() { if (WindowGlobalChild* wgc = GetWindowGlobalChild()) { wgc->SendReloadWithHttpsOnlyException(); } } void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) { nsresult rv = NS_OK; if (NS_WARN_IF(!mFailedChannel)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } nsCOMPtr tsi; rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi)); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } 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::CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject) { RefPtr principal = BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx)); if (!principal) { return false; } // We allow the privilege SSA to be called from system principal. if (principal->IsSystemPrincipal()) { return true; } // We only allow calling the privilege SSA from the content script of the // webcompat extension. if (auto* policy = principal->ContentScriptAddonPolicy()) { nsAutoString addonID; policy->GetId(addonID); return addonID.EqualsLiteral("webcompat@mozilla.org"); } return false; } bool Document::IsErrorPage() const { nsCOMPtr loadInfo = mChannel ? mChannel->LoadInfo() : nullptr; return loadInfo && loadInfo->GetLoadErrorPage(); } void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo, ErrorResult& aRv) { nsresult rv = NS_OK; if (NS_WARN_IF(!mFailedChannel)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } nsCOMPtr tsi; rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi)); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } 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); nsITransportSecurityInfo::OverridableErrorCategory errorCategory; rv = tsi->GetOverridableErrorCategory(&errorCategory); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } switch (errorCategory) { case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST: aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Trust_error; break; case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN: aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Domain_mismatch; break; case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME: aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Expired_or_not_yet_valid; break; default: aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Unset; break; } 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; } 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(aURI, attrs, &aInfo.mHasHSTS); } else { nsCOMPtr sss = do_GetService(NS_SSSERVICE_CONTRACTID); if (NS_WARN_IF(!sss)) { return; } Unused << NS_WARN_IF( NS_FAILED(sss->IsSecureURI(aURI, attrs, &aInfo.mHasHSTS))); } nsCOMPtr pkps = do_GetService(NS_PKPSERVICE_CONTRACTID); if (NS_WARN_IF(!pkps)) { return; } Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP))); } bool Document::IsAboutPage() const { return NodePrincipal()->SchemeIs("about"); } void Document::ConstructUbiNode(void* storage) { JS::ubi::Concrete::construct(storage, this); } void Document::LoadEventFired() { // Object used to collect some telemetry data so we don't need to query for it // twice. glean::perf::PageLoadExtra pageLoadEventData; // Accumulate timing data located in each document's realm and report to // telemetry. AccumulateJSTelemetry(pageLoadEventData); // Collect page load timings AccumulatePageLoadTelemetry(pageLoadEventData); // Record page load event RecordPageLoadEventTelemetry(pageLoadEventData); // Release the JS bytecode cache from its wait on the load event, and // potentially dispatch the encoding of the bytecode. if (ScriptLoader()) { ScriptLoader()->LoadEventFired(); } } void Document::RecordPageLoadEventTelemetry( glean::perf::PageLoadExtra& aEventTelemetryData) { // If the page load time is empty, then the content wasn't something we want // to report (i.e. not a top level document). if (!aEventTelemetryData.loadTime) { return; } MOZ_ASSERT(IsTopLevelContentDocument()); nsPIDOMWindowOuter* window = GetWindow(); if (!window) { return; } nsIDocShell* docshell = window->GetDocShell(); if (!docshell) { return; } nsAutoCString loadTypeStr; switch (docshell->GetLoadType()) { case LOAD_NORMAL: case LOAD_NORMAL_REPLACE: case LOAD_NORMAL_BYPASS_CACHE: case LOAD_NORMAL_BYPASS_PROXY: case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: loadTypeStr.Append("NORMAL"); break; case LOAD_HISTORY: loadTypeStr.Append("HISTORY"); break; case LOAD_RELOAD_NORMAL: case LOAD_RELOAD_BYPASS_CACHE: case LOAD_RELOAD_BYPASS_PROXY: case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: case LOAD_REFRESH: case LOAD_REFRESH_REPLACE: case LOAD_RELOAD_CHARSET_CHANGE: case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE: case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE: loadTypeStr.Append("RELOAD"); break; case LOAD_LINK: loadTypeStr.Append("LINK"); break; case LOAD_STOP_CONTENT: case LOAD_STOP_CONTENT_AND_REPLACE: loadTypeStr.Append("STOP"); break; case LOAD_ERROR_PAGE: loadTypeStr.Append("ERROR"); break; default: loadTypeStr.Append("OTHER"); break; } nsCOMPtr tldService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); if (tldService && mReferrerInfo && (docshell->GetLoadType() & nsIDocShell::LOAD_CMD_NORMAL)) { nsAutoCString currentBaseDomain, referrerBaseDomain; nsCOMPtr referrerURI = mReferrerInfo->GetComputedReferrer(); if (referrerURI) { auto result = NS_SUCCEEDED( tldService->GetBaseDomain(referrerURI, 0, referrerBaseDomain)); if (result) { bool sameOrigin = false; NodePrincipal()->IsSameOrigin(referrerURI, &sameOrigin); aEventTelemetryData.sameOriginNav = mozilla::Some(sameOrigin); } } } aEventTelemetryData.loadType = mozilla::Some(loadTypeStr); // Sending a glean ping must be done on the parent process. if (ContentChild* cc = ContentChild::GetSingleton()) { cc->SendRecordPageLoadEvent(aEventTelemetryData); } } void Document::AccumulatePageLoadTelemetry( glean::perf::PageLoadExtra& aEventTelemetryDataOut) { // Interested only in top level documents for real websites that are in the // foreground. if (!ShouldIncludeInTelemetry() || !IsTopLevelContentDocument() || !GetNavigationTiming() || !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) { return; } if (!GetChannel()) { return; } nsCOMPtr timedChannel(do_QueryInterface(GetChannel())); if (!timedChannel) { return; } // Default duration is 0, use this to check for bogus negative values. const TimeDuration zeroDuration; TimeStamp responseStart; timedChannel->GetResponseStart(&responseStart); TimeStamp redirectStart, redirectEnd; timedChannel->GetRedirectStart(&redirectStart); timedChannel->GetRedirectEnd(&redirectEnd); uint8_t redirectCount; timedChannel->GetRedirectCount(&redirectCount); if (redirectCount) { aEventTelemetryDataOut.redirectCount = mozilla::Some(static_cast(redirectCount)); } if (!redirectStart.IsNull() && !redirectEnd.IsNull()) { TimeDuration redirectTime = redirectEnd - redirectStart; if (redirectTime > zeroDuration) { aEventTelemetryDataOut.redirectTime = mozilla::Some(static_cast(redirectTime.ToMilliseconds())); } } TimeStamp dnsLookupStart, dnsLookupEnd; timedChannel->GetDomainLookupStart(&dnsLookupStart); timedChannel->GetDomainLookupEnd(&dnsLookupEnd); if (!dnsLookupStart.IsNull() && !dnsLookupEnd.IsNull()) { TimeDuration dnsLookupTime = dnsLookupEnd - dnsLookupStart; if (dnsLookupTime > zeroDuration) { aEventTelemetryDataOut.dnsLookupTime = mozilla::Some(static_cast(dnsLookupTime.ToMilliseconds())); } } TimeStamp navigationStart = GetNavigationTiming()->GetNavigationStartTimeStamp(); if (!responseStart || !navigationStart) { return; } nsAutoCString dnsKey("Native"); nsAutoCString http3Key; nsAutoCString http3WithPriorityKey; nsAutoCString earlyHintKey; nsCOMPtr httpChannel = do_QueryInterface(GetChannel()); if (httpChannel) { bool resolvedByTRR = false; Unused << httpChannel->GetIsResolvedByTRR(&resolvedByTRR); if (resolvedByTRR) { if (nsCOMPtr dns = do_GetService(NS_DNSSERVICE_CONTRACTID)) { dns->GetTRRDomainKey(dnsKey); } else { // Failed to get the DNS service. dnsKey = "(fail)"_ns; } aEventTelemetryDataOut.trrDomain = mozilla::Some(dnsKey); } uint32_t major; uint32_t minor; if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) { if (major == 3) { http3Key = "http3"_ns; nsCOMPtr httpChannel2 = do_QueryInterface(GetChannel()); nsCString header; if (httpChannel2 && NS_SUCCEEDED( httpChannel2->GetResponseHeader("priority"_ns, header)) && !header.IsEmpty()) { http3WithPriorityKey = "with_priority"_ns; } else { http3WithPriorityKey = "without_priority"_ns; } } else if (major == 2) { bool supportHttp3 = false; if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) { supportHttp3 = false; } if (supportHttp3) { http3Key = "supports_http3"_ns; } } aEventTelemetryDataOut.httpVer = mozilla::Some(major); } uint32_t earlyHintType = 0; Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType); if (earlyHintType & LinkStyle::ePRECONNECT) { earlyHintKey.Append("preconnect_"_ns); } if (earlyHintType & LinkStyle::ePRELOAD) { earlyHintKey.Append("preload_"_ns); earlyHintKey.Append(mPreloadService.GetEarlyHintUsed() ? "1"_ns : "0"_ns); } } TimeStamp asyncOpen; timedChannel->GetAsyncOpen(&asyncOpen); if (asyncOpen) { Telemetry::AccumulateTimeDelta(Telemetry::DNS_PERF_FIRST_BYTE_MS, dnsKey, asyncOpen, responseStart); } // First Contentful Composite if (TimeStamp firstContentfulComposite = GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) { glean::performance_pageload::fcp.AccumulateRawDuration( firstContentfulComposite - navigationStart); if (!http3Key.IsEmpty()) { Telemetry::AccumulateTimeDelta( Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key, navigationStart, firstContentfulComposite); } if (!http3WithPriorityKey.IsEmpty()) { Telemetry::AccumulateTimeDelta( Telemetry::H3P_PERF_FIRST_CONTENTFUL_PAINT_MS, http3WithPriorityKey, navigationStart, firstContentfulComposite); } if (!earlyHintKey.IsEmpty()) { Telemetry::AccumulateTimeDelta( Telemetry::EH_PERF_FIRST_CONTENTFUL_PAINT_MS, earlyHintKey, navigationStart, firstContentfulComposite); } Telemetry::AccumulateTimeDelta( Telemetry::DNS_PERF_FIRST_CONTENTFUL_PAINT_MS, dnsKey, navigationStart, firstContentfulComposite); glean::performance_pageload::fcp_responsestart.AccumulateRawDuration( firstContentfulComposite - responseStart); TimeDuration fcpTime = firstContentfulComposite - navigationStart; if (fcpTime > zeroDuration) { aEventTelemetryDataOut.fcpTime = mozilla::Some(static_cast(fcpTime.ToMilliseconds())); } } // Report the most up to date LCP time. For our histogram we actually report // this on page unload. if (TimeStamp lcpTime = GetNavigationTiming()->GetLargestContentfulRenderTimeStamp()) { aEventTelemetryDataOut.lcpTime = mozilla::Some( static_cast((lcpTime - navigationStart).ToMilliseconds())); } // Load event if (TimeStamp loadEventStart = GetNavigationTiming()->GetLoadEventStartTimeStamp()) { glean::performance_pageload::load_time.AccumulateRawDuration( loadEventStart - navigationStart); if (!http3Key.IsEmpty()) { Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS, http3Key, navigationStart, loadEventStart); } if (!http3WithPriorityKey.IsEmpty()) { Telemetry::AccumulateTimeDelta(Telemetry::H3P_PERF_PAGE_LOAD_TIME_MS, http3WithPriorityKey, navigationStart, loadEventStart); } if (!earlyHintKey.IsEmpty()) { Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_PAGE_LOAD_TIME_MS, earlyHintKey, navigationStart, loadEventStart); } glean::performance_pageload::load_time_responsestart.AccumulateRawDuration( loadEventStart - responseStart); TimeDuration responseTime = responseStart - navigationStart; if (responseTime > zeroDuration) { aEventTelemetryDataOut.responseTime = mozilla::Some(static_cast(responseTime.ToMilliseconds())); } TimeDuration loadTime = loadEventStart - navigationStart; if (loadTime > zeroDuration) { aEventTelemetryDataOut.loadTime = mozilla::Some(static_cast(loadTime.ToMilliseconds())); } } } void Document::AccumulateJSTelemetry( glean::perf::PageLoadExtra& aEventTelemetryDataOut) { if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry()) { return; } if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) { return; } AutoJSContext cx; JSObject* globalObject = GetScopeObject()->GetGlobalJSObject(); JSAutoRealm ar(cx, globalObject); JS::JSTimers timers = JS::GetJSTimers(cx); if (!timers.executionTime.IsZero()) { glean::javascript_pageload::execution_time.AccumulateRawDuration( timers.executionTime); aEventTelemetryDataOut.jsExecTime = mozilla::Some( static_cast(timers.executionTime.ToMilliseconds())); } if (!timers.delazificationTime.IsZero()) { glean::javascript_pageload::delazification_time.AccumulateRawDuration( timers.delazificationTime); } if (!timers.xdrEncodingTime.IsZero()) { glean::javascript_pageload::xdr_encode_time.AccumulateRawDuration( timers.xdrEncodingTime); } if (!timers.baselineCompileTime.IsZero()) { glean::javascript_pageload::baseline_compile_time.AccumulateRawDuration( timers.baselineCompileTime); } if (!timers.gcTime.IsZero()) { glean::javascript_pageload::gc_time.AccumulateRawDuration(timers.gcTime); } if (!timers.protectTime.IsZero()) { glean::javascript_pageload::protect_time.AccumulateRawDuration( timers.protectTime); } } 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()) { 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. 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 (mAttributeStyles) { mAttributeStyles->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(); } mHeaderData = nullptr; mPendingTitleChangeEvent.Revoke(); MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(), "must not have media query lists left"); if (mNodeInfoManager) { mNodeInfoManager->DropDocumentReference(); } if (mDocGroup) { MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup()); mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup); } UnlinkOriginalDocumentIfStatic(); UnregisterFromMemoryReportingForDataDocument(); } void Document::DropStyleSet() { mStyleSet = nullptr; } 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_END NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(Document) NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Document, LastRelease()) 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) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFragmentDirective) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry) // 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); if (tmp->mRadioGroupContainer) { RadioGroupContainer::Traverse(tmp->mRadioGroupContainer.get(), cb); } for (auto& sheets : tmp->mAdditionalSheets) { tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[][i]", cb); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadObserver) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementsObservedForLastRememberedSize) 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(mDocumentTimeline) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker) 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) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager) // Traverse all our nsCOMArrays. NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages) // 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(static_cast(mql)); } } // XXX: This should be not needed once bug 1569185 lands. for (const auto& entry : tmp->mL10nProtoElements) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key"); cb.NoteXPCOMChild(entry.GetKey()); CycleCollectionNoteChild(cb, entry.GetWeak(), "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); } for (auto& tableEntry : tmp->mActiveLocks) { ImplCycleCollectionTraverse(cb, *tableEntry.GetModifiableData(), "mActiveLocks entry", 0); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_CLASS(Document) NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject) NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document) tmp->mInUnlinkOrDeletion = true; tmp->SetStateObject(nullptr); // 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(mLazyLoadObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementsObservedForLastRememberedSize); NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet) NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFragmentDirective) NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry) 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(mDocumentTimeline) NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker) 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->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) { tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp, tmp->mDocGroup); } tmp->mDocGroup = nullptr; 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; } tmp->mSubDocuments = nullptr; NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager) MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled, "How did we get here without our presshell going away " "first?"); DocumentOrShadowRoot::Unlink(tmp); tmp->mRadioGroupContainer = nullptr; // 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->mActiveLocks.Clear(); tmp->mInUnlinkOrDeletion = false; tmp->UnregisterFromMemoryReportingForDataDocument(); 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(nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal) { if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) { return NS_ERROR_ALREADY_INITIALIZED; } // Force initialization. mOnloadBlocker = new OnloadBlocker(); mStyleImageLoader = new css::ImageLoader(this); mNodeInfoManager = new nsNodeInfoManager(this, aPrincipal); // 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()); if (aPrincipal) { SetPrincipals(aPrincipal, aPartitionedPrincipal); } else { RecomputeResistFingerprinting(); } return NS_OK; } void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); } void Document::RemoveAllPropertiesFor(nsINode* aNode) { PropertyTable().RemoveAllPropertiesFor(aNode); } void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) { nsCOMPtr uri; nsCOMPtr principal; nsCOMPtr partitionedPrincipal; if (aChannel) { mIsInPrivateBrowsing = NS_UsePrivateBrowsing(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::CreateDocumentViewer. 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; if (nsCOMPtr bag = do_QueryInterface(aChannel)) { if (nsCOMPtr baseURI = do_GetProperty(bag, u"baseURI"_ns)) { mDocumentBaseURI = baseURI.forget(); mChromeXHRDocBaseURI = nullptr; } } mChannel = aChannel; RecomputeResistFingerprinting(); } 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. 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; if (aLoadGroup) { nsCOMPtr callbacks; aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); if (callbacks) { nsCOMPtr loadContext = do_GetInterface(callbacks); if (loadContext) { // This is asserting that if we previously set mIsInPrivateBrowsing // to true from the channel in Document::Reset, that the loadContext // also believes it to be true. // MOZ_ASSERT(!mIsInPrivateBrowsing || // mIsInPrivateBrowsing == loadContext->UsePrivateBrowsing()); mIsInPrivateBrowsing = loadContext->UsePrivateBrowsing(); } } 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? SetContentType(""_ns); mContentLanguage = nullptr; 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 = NullPrincipal::CreateWithoutOriginAttributes(); return nullPrincipal.forget(); } } } nsCOMPtr principal(aPrincipal); return principal.forget(); } size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) { nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); ServoStyleSet& styleSet = EnsureStyleSet(); // lowest index first int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet); size_t count = styleSet.SheetCount(StyleOrigin::Author); size_t index = 0; for (; index < count; index++) { auto* sheet = styleSet.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(); ServoStyleSet& styleSet = EnsureStyleSet(); auto ClearSheetList = [&](nsTArray>& aSheetList) { for (auto& sheet : Reversed(aSheetList)) { sheet->ClearAssociatedDocumentOrShadowRoot(); if (mStyleSetFilled) { styleSet.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()) { styleSet.RemoveStyleSheet(*sheet); } } } } // Now reset our inline style and attribute sheets. if (mAttributeStyles) { mAttributeStyles->Reset(); mAttributeStyles->SetOwningDocument(this); } else { mAttributeStyles = new AttributeStyles(this); } if (mStyleSetFilled) { FillStyleSetDocumentSheets(); if (styleSet.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"); ServoStyleSet& styleSet = EnsureStyleSet(); for (StyleSheet* sheet : *sheetService->UserStyleSheets()) { styleSet.AppendStyleSheet(*sheet); } StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet() : cache->GetUserContentSheet(); if (sheet) { styleSet.AppendStyleSheet(*sheet); } styleSet.AppendStyleSheet(*cache->UASheet()); if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) { styleSet.AppendStyleSheet(*cache->MathMLSheet()); } if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) { styleSet.AppendStyleSheet(*cache->SVGSheet()); } styleSet.AppendStyleSheet(*cache->HTMLSheet()); if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) { styleSet.AppendStyleSheet(*cache->NoFramesSheet()); } styleSet.AppendStyleSheet(*cache->CounterStylesSheet()); // Only load the full XUL sheet if we'll need it. if (LoadsFullXULStyleSheetUpFront()) { styleSet.AppendStyleSheet(*cache->XULSheet()); } styleSet.AppendStyleSheet(*cache->FormsSheet()); styleSet.AppendStyleSheet(*cache->ScrollbarsSheet()); for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) { styleSet.AppendStyleSheet(*sheet); } MOZ_ASSERT(!mQuirkSheetAdded); if (NeedsQuirksSheet()) { styleSet.AppendStyleSheet(*cache->QuirkSheet()); mQuirkSheetAdded = true; } } void Document::FillStyleSet() { MOZ_ASSERT(!mStyleSetFilled); FillStyleSetUserAndUASheets(); FillStyleSetDocumentSheets(); mStyleSetFilled = true; } void Document::RemoveContentEditableStyleSheets() { MOZ_ASSERT(IsHTMLOrXHTML()); ServoStyleSet& styleSet = EnsureStyleSet(); auto* cache = GlobalStyleSheetCache::Singleton(); bool changed = false; if (mDesignModeSheetAdded) { styleSet.RemoveStyleSheet(*cache->DesignModeSheet()); mDesignModeSheetAdded = false; changed = true; } if (mContentEditableSheetAdded) { styleSet.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"); ServoStyleSet& styleSet = EnsureStyleSet(); auto* cache = GlobalStyleSheetCache::Singleton(); bool changed = false; if (!mContentEditableSheetAdded) { styleSet.AppendStyleSheet(*cache->ContentEditableSheet()); mContentEditableSheetAdded = true; changed = true; } if (mDesignModeSheetAdded != aDesignMode) { if (mDesignModeSheetAdded) { styleSet.RemoveStyleSheet(*cache->DesignModeSheet()); } else { styleSet.AppendStyleSheet(*cache->DesignModeSheet()); } mDesignModeSheetAdded = !mDesignModeSheetAdded; changed = true; } if (changed) { ApplicableStylesChanged(); } } void Document::FillStyleSetDocumentSheets() { ServoStyleSet& styleSet = EnsureStyleSet(); MOZ_ASSERT(styleSet.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()) { styleSet.AddDocStyleSheet(*sheet); } } EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) { if (aSheet.IsApplicable()) { styleSet.AddDocStyleSheet(aSheet); } }); nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) { styleSet.AppendStyleSheet(*sheet); } AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eAgentSheet]); AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eUserSheet]); AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eAuthorSheet]); } void Document::CompatibilityModeChanged() { MOZ_ASSERT(IsHTMLOrXHTML()); CSSLoader()->SetCompatibilityMode(mCompatMode); if (mStyleSet) { mStyleSet->CompatibilityModeChanged(); } if (!mStyleSetFilled) { MOZ_ASSERT(!mQuirkSheetAdded); return; } MOZ_ASSERT(mStyleSet); if (PresShell* presShell = GetPresShell()) { // Selectors may have become case-sensitive / case-insensitive, the stylist // has already performed the relevant invalidation. presShell->EnsureStyleFlush(); } 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(); // Trigger recomputation of the nsViewportInfo the next time it's queried. mViewportType = Unknown; } 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()); } static void CheckIsBadPolicy(nsILoadInfo::CrossOriginOpenerPolicy aPolicy, BrowsingContext* aContext, nsIChannel* aChannel) { #if defined(EARLY_BETA_OR_EARLIER) auto requireCORP = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP; if (aContext->GetOpenerPolicy() == aPolicy || (aContext->GetOpenerPolicy() != requireCORP && aPolicy != requireCORP)) { return; } nsCOMPtr uri; bool hasURI = NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(uri))); bool isViewSource = hasURI && uri->SchemeIs("view-source"); nsCString contentType; nsCOMPtr bag = do_QueryInterface(aChannel); bool isPDFJS = bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns, contentType)) && contentType.EqualsLiteral(APPLICATION_PDF); MOZ_DIAGNOSTIC_ASSERT(!isViewSource, "Bug 1834864: Assert due to view-source."); MOZ_DIAGNOSTIC_ASSERT(!isPDFJS, "Bug 1834864: Assert due to pdfjs."); MOZ_DIAGNOSTIC_ASSERT(aPolicy == requireCORP, "Assert due to clearing REQUIRE_CORP."); MOZ_DIAGNOSTIC_ASSERT(aContext->GetOpenerPolicy() == requireCORP, "Assert due to setting REQUIRE_CORP."); #endif // defined(EARLY_BETA_OR_EARLIER) } nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup, nsISupports* aContainer, nsIStreamListener** aDocListener, bool aReset) { 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; SetLoadedAsData(true, /* aConsiderForMemoryReporting */ 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); SetContentType(Substring(start, semicolon)); } RetrieveRelevantHeaders(aChannel); mChannel = aChannel; RecomputeResistFingerprinting(); 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()) { CheckIsBadPolicy(policy, docShell->GetBrowsingContext(), aChannel); // 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); // HACK: Calling EnsureIPCPoliciesRead() here will parse the CSP using the // context's current mSelfURI (which is still the previous mSelfURI), // bypassing some internal bugs with 'self' and iframe inheritance. // Not calling it here results in the mSelfURI being the current mSelfURI and // not the previous which breaks said inheritance. // https://bugzilla.mozilla.org/show_bug.cgi?id=1793560#ch-8 nsCOMPtr cspToInherit = loadInfo->GetCspToInherit(); if (cspToInherit) { cspToInherit->EnsureIPCPoliciesRead(); } 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; } void Document::SetLoadedAsData(bool aLoadedAsData, bool aConsiderForMemoryReporting) { mLoadedAsData = aLoadedAsData; if (aConsiderForMemoryReporting) { nsIGlobalObject* global = GetScopeObject(); if (global) { if (nsPIDOMWindowInner* window = global->GetAsInnerWindow()) { nsGlobalWindowInner::Cast(window) ->RegisterDataDocumentForMemoryReporting(this); } } } } 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) { bool block = false; rv = mCSP->GetBlockAllMixedContent(&block); NS_ENSURE_SUCCESS_VOID(rv); mBlockAllMixedContent = block; } 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) { bool upgrade = false; rv = mCSP->GetUpgradeInsecureRequests(&upgrade); NS_ENSURE_SUCCESS_VOID(rv); mUpgradeInsecureRequests = upgrade; } 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) { bool block = false; rv = mPreloadCSP->GetBlockAllMixedContent(&block); NS_ENSURE_SUCCESS_VOID(rv); mBlockAllMixedContent = block; } if (!mUpgradeInsecurePreloads) { bool upgrade = false; rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade); NS_ENSURE_SUCCESS_VOID(rv); mUpgradeInsecurePreloads = upgrade; } } } nsresult Document::InitCSP(nsIChannel* aChannel) { MOZ_ASSERT(!mScriptGlobalObject, "CSP must be initialized before mScriptGlobalObject is set!"); // 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 || loadInfo->GetExternalContentPolicyType() == ExtContentPolicy::TYPE_IMAGESET) { 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 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel); if (inheritedCSP) { 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 (!inheritedCSP && !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; } static Document* GetInProcessParentDocumentFrom(BrowsingContext* aContext) { BrowsingContext* parentContext = aContext->GetParent(); if (!parentContext) { return nullptr; } WindowContext* windowContext = parentContext->GetCurrentWindowContext(); if (!windowContext) { return nullptr; } return windowContext->GetDocument(); } 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()); } if (Document* parentDocument = GetInProcessParentDocumentFrom(browsingContext)) { return do_AddRef(parentDocument->FeaturePolicy()); } WindowContext* windowContext = browsingContext->GetCurrentWindowContext(); if (!windowContext) { return nullptr; } WindowGlobalChild* child = windowContext->GetWindowGlobalChild(); if (!child) { return nullptr; } return do_AddRef(child->GetContainerFeaturePolicy()); } void Document::InitFeaturePolicy() { MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created"); 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()); } } nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) { InitFeaturePolicy(); // 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; } void Document::EnsureNotEnteringAndExitFullscreen() { Document::ClearPendingFullscreenRequests(this); if (GetFullscreenElement()) { Document::AsyncExitFullscreen(this); } } void Document::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) { mReferrerInfo = aReferrerInfo; mCachedReferrerInfoForInternalCSSAndSVGResources = nullptr; mCachedURLData = nullptr; } 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) { SetReferrerInfo(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; } if (nsCOMPtr referrerInfo = httpChannel->GetReferrerInfo()) { SetReferrerInfo(referrerInfo); } // Override policy if we get one from Referrerr-Policy header mozilla::dom::ReferrerPolicy policy = nsContentUtils::GetReferrerPolicyFromChannel(aChannel); nsCOMPtr clone = static_cast(mReferrerInfo.get()) ->CloneWithNewPolicy(policy); SetReferrerInfo(clone); 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( mTrials.IsEnabled(OriginTrial::CoepCredentialless), &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; // This loosely implements §3.4.1 of Text Fragments // https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives // Unlike specified in the spec, the fragment directive is not stripped from // the URL in the session history entry. Instead it is removed when the URL is // set in the `Document`. Also, instead of storing the `uninvokedDirective` in // `Document` as mentioned in the spec, the extracted directives are moved to // the `FragmentDirective` object which deals with finding the ranges to // highlight in `ScrollToRef()`. // XXX(:jjaschke): This is only a temporary solution. // https://bugzil.la/1881429 is filed for revisiting this. nsTArray textDirectives; FragmentDirective::ParseAndRemoveFragmentDirectiveFromFragment( mDocumentURI, &textDirectives); FragmentDirective()->SetTextDirectives(std::move(textDirectives)); nsIURI* newBase = GetDocBaseURI(); mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(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) { mCachedURLData = nullptr; 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. if (WindowGlobalChild* wgc = GetWindowGlobalChild()) { wgc->SetDocumentURI(mDocumentURI); } } static void GetFormattedTimeString(PRTime aTime, bool aUniversal, nsAString& aFormattedTimeString) { PRExplodedTime prtime; PR_ExplodeTime(aTime, aUniversal ? PR_GMTParameters : 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(), ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC), 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 { nsCOMPtr clone = static_cast((mReferrerInfo).get()) ->CloneWithNewPolicy(policy); SetReferrerInfo(clone); } } 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; mCachedURLData = nullptr; mCSSLoader->RegisterInSheetCache(); RecomputeResistFingerprinting(); #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. // Note that we can be invoked during cycle collection, so we need to handle // the browsingcontext being partially unlinked - normally you shouldn't // null-check `Group()` as it shouldn't return nullptr. if (!GetBrowsingContext() || !GetBrowsingContext()->Group()) { return; } if (mDocGroup && mDocGroup->GetBrowsingContextGroup()) { MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup() == GetBrowsingContext()->Group()); // GetKey() can fail, e.g. after the TLD service has shut down. nsAutoCString docGroupKey; nsresult rv = mozilla::dom::DocGroup::GetKey( NodePrincipal(), GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated(), docGroupKey); if (NS_SUCCEEDED(rv)) { MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey)); } } } #endif nsresult Document::Dispatch(already_AddRefed&& aRunnable) const { return SchedulerGroup::Dispatch(std::move(aRunnable)); } void Document::NoteScriptTrackingStatus(const nsACString& aURL, bool aIsTracking) { if (aIsTracking) { mTrackingScripts.Insert(aURL); } else { MOZ_ASSERT(!mTrackingScripts.Contains(aURL)); } } bool Document::IsScriptTracking(JSContext* aCx) const { JS::AutoFilename filename; if (!JS::DescribeScriptedCaller(aCx, &filename)) { return false; } return mTrackingScripts.Contains(nsDependentCString(filename.get())); } void Document::GetContentType(nsAString& aContentType) { CopyUTF8toUTF16(GetContentTypeInternal(), aContentType); } void Document::SetContentType(const nsACString& aContentType) { if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None && aContentType.EqualsLiteral("application/xhtml+xml")) { mDefaultElementType = kNameSpaceID_XHTML; } mCachedEncoder = nullptr; mContentType = aContentType; } bool Document::HasPendingInitialTranslation() { return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready; } bool Document::HasPendingL10nMutations() const { return mDocumentL10n && mDocumentL10n->HasPendingMutations(); } 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; } nsAutoString href; aLinkElement->GetAttr(nsGkAtoms::href, href); if (!mDocumentL10n) { Element* elem = GetDocumentElement(); MOZ_DIAGNOSTIC_ASSERT(elem); bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync); mDocumentL10n = DocumentL10n::Create(this, isSync); if (NS_WARN_IF(!mDocumentL10n)) { return; } } mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href)); if (mReadyState >= READYSTATE_INTERACTIVE) { nsContentUtils::AddScriptRunner(NewRunnableMethod( "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n, &DocumentL10n::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(nsGkAtoms::href, href); uint32_t remaining = mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(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() { // XXX: This is a scaffolding for where we might inject prefetch // in bug 1717241. } 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) { RefPtr l10n = mDocumentL10n; l10n->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::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; } BrowsingContext* bc = GetBrowsingContext(); if (!bc) { return false; } if (!fm->IsInActiveWindow(bc)) { return false; } return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext()); } bool Document::ThisDocumentHasFocus() const { nsFocusManager* fm = nsFocusManager::GetFocusManager(); return fm && fm->GetFocusedWindow() && fm->GetFocusedWindow()->GetExtantDoc() == this; } void Document::GetDesignMode(nsAString& aDesignMode) { if (IsInDesignMode()) { 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->UpdateEditableState(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; } const bool editableMode = IsInDesignMode(); 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; } using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor; sInternalCommandDataHashtable = new InternalCommandDataHashtable(); // clang-format off sInternalCommandDataHashtable->InsertOrUpdate( u"bold"_ns, InternalCommandData( "cmd_bold", Command::FormatBold, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"italic"_ns, InternalCommandData( "cmd_italic", Command::FormatItalic, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"underline"_ns, InternalCommandData( "cmd_underline", Command::FormatUnderline, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"strikethrough"_ns, InternalCommandData( "cmd_strikethrough", Command::FormatStrikeThrough, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"subscript"_ns, InternalCommandData( "cmd_subscript", Command::FormatSubscript, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"superscript"_ns, InternalCommandData( "cmd_superscript", Command::FormatSuperscript, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"cut"_ns, InternalCommandData( "cmd_cut", Command::Cut, ExecCommandParam::Ignore, CutCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"copy"_ns, InternalCommandData( "cmd_copy", Command::Copy, ExecCommandParam::Ignore, CopyCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"paste"_ns, InternalCommandData( "cmd_paste", Command::Paste, ExecCommandParam::Ignore, PasteCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"delete"_ns, InternalCommandData( "cmd_deleteCharBackward", Command::DeleteCharBackward, ExecCommandParam::Ignore, DeleteCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"forwarddelete"_ns, InternalCommandData( "cmd_deleteCharForward", Command::DeleteCharForward, ExecCommandParam::Ignore, DeleteCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"selectall"_ns, InternalCommandData( "cmd_selectAll", Command::SelectAll, ExecCommandParam::Ignore, SelectAllCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"undo"_ns, InternalCommandData( "cmd_undo", Command::HistoryUndo, ExecCommandParam::Ignore, UndoCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"redo"_ns, InternalCommandData( "cmd_redo", Command::HistoryRedo, ExecCommandParam::Ignore, RedoCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"indent"_ns, InternalCommandData("cmd_indent", Command::FormatIndent, ExecCommandParam::Ignore, IndentCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"outdent"_ns, InternalCommandData( "cmd_outdent", Command::FormatOutdent, ExecCommandParam::Ignore, OutdentCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"backcolor"_ns, InternalCommandData( "cmd_highlight", Command::FormatBackColor, ExecCommandParam::String, HighlightColorStateCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"hilitecolor"_ns, InternalCommandData( "cmd_highlight", Command::FormatBackColor, ExecCommandParam::String, HighlightColorStateCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"forecolor"_ns, InternalCommandData( "cmd_fontColor", Command::FormatFontColor, ExecCommandParam::String, FontColorStateCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"fontname"_ns, InternalCommandData( "cmd_fontFace", Command::FormatFontName, ExecCommandParam::String, FontFaceStateCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"fontsize"_ns, InternalCommandData( "cmd_fontSize", Command::FormatFontSize, ExecCommandParam::String, FontSizeStateCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"inserthorizontalrule"_ns, InternalCommandData( "cmd_insertHR", Command::InsertHorizontalRule, ExecCommandParam::Ignore, InsertTagCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"createlink"_ns, InternalCommandData( "cmd_insertLinkNoUI", Command::InsertLink, ExecCommandParam::String, InsertTagCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"insertimage"_ns, InternalCommandData( "cmd_insertImageNoUI", Command::InsertImage, ExecCommandParam::String, InsertTagCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"inserthtml"_ns, InternalCommandData( "cmd_insertHTML", Command::InsertHTML, ExecCommandParam::String, InsertHTMLCommand::GetInstance, // TODO: Chromium inserts text content of the document fragment // created from the param. // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8 CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"inserttext"_ns, InternalCommandData( "cmd_insertText", Command::InsertText, ExecCommandParam::String, InsertPlaintextCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"justifyleft"_ns, InternalCommandData( "cmd_align", Command::FormatJustifyLeft, ExecCommandParam::Ignore, // Will be set to "left" AlignCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"justifyright"_ns, InternalCommandData( "cmd_align", Command::FormatJustifyRight, ExecCommandParam::Ignore, // Will be set to "right" AlignCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"justifycenter"_ns, InternalCommandData( "cmd_align", Command::FormatJustifyCenter, ExecCommandParam::Ignore, // Will be set to "center" AlignCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"justifyfull"_ns, InternalCommandData( "cmd_align", Command::FormatJustifyFull, ExecCommandParam::Ignore, // Will be set to "justify" AlignCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"removeformat"_ns, InternalCommandData( "cmd_removeStyles", Command::FormatRemove, ExecCommandParam::Ignore, RemoveStylesCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"unlink"_ns, InternalCommandData( "cmd_removeLinks", Command::FormatRemoveLink, ExecCommandParam::Ignore, StyleUpdatingCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"insertorderedlist"_ns, InternalCommandData( "cmd_ol", Command::InsertOrderedList, ExecCommandParam::Ignore, ListCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"insertunorderedlist"_ns, InternalCommandData( "cmd_ul", Command::InsertUnorderedList, ExecCommandParam::Ignore, ListCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"insertparagraph"_ns, InternalCommandData( "cmd_insertParagraph", Command::InsertParagraph, ExecCommandParam::Ignore, InsertParagraphCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"insertlinebreak"_ns, InternalCommandData( "cmd_insertLineBreak", Command::InsertLineBreak, ExecCommandParam::Ignore, InsertLineBreakCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"formatblock"_ns, InternalCommandData( "cmd_formatBlock", Command::FormatBlock, ExecCommandParam::String, FormatBlockStateCommand::GetInstance, CommandOnTextEditor::Disabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"styleWithCSS"_ns, InternalCommandData( "cmd_setDocumentUseCSS", Command::SetDocumentUseCSS, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance, CommandOnTextEditor::FallThrough)); sInternalCommandDataHashtable->InsertOrUpdate( u"usecss"_ns, // Legacy command InternalCommandData( "cmd_setDocumentUseCSS", Command::SetDocumentUseCSS, ExecCommandParam::InvertedBoolean, SetDocumentStateCommand::GetInstance, CommandOnTextEditor::FallThrough)); sInternalCommandDataHashtable->InsertOrUpdate( u"contentReadOnly"_ns, InternalCommandData( "cmd_setDocumentReadOnly", Command::SetDocumentReadOnly, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance, CommandOnTextEditor::Enabled)); sInternalCommandDataHashtable->InsertOrUpdate( u"insertBrOnReturn"_ns, InternalCommandData( "cmd_insertBrOnReturn", Command::SetDocumentInsertBROnEnterKeyPress, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance, CommandOnTextEditor::FallThrough)); sInternalCommandDataHashtable->InsertOrUpdate( u"defaultParagraphSeparator"_ns, InternalCommandData( "cmd_defaultParagraphSeparator", Command::SetDocumentDefaultParagraphSeparator, ExecCommandParam::String, SetDocumentStateCommand::GetInstance, CommandOnTextEditor::FallThrough)); sInternalCommandDataHashtable->InsertOrUpdate( u"enableObjectResizing"_ns, InternalCommandData( "cmd_enableObjectResizing", Command::ToggleObjectResizers, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance, CommandOnTextEditor::FallThrough)); sInternalCommandDataHashtable->InsertOrUpdate( u"enableInlineTableEditing"_ns, InternalCommandData( "cmd_enableInlineTableEditing", Command::ToggleInlineTableEditor, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance, CommandOnTextEditor::FallThrough)); sInternalCommandDataHashtable->InsertOrUpdate( u"enableAbsolutePositionEditing"_ns, InternalCommandData( "cmd_enableAbsolutePositionEditing", Command::ToggleAbsolutePositionEditor, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance, CommandOnTextEditor::FallThrough)); sInternalCommandDataHashtable->InsertOrUpdate( u"enableCompatibleJoinSplitDirection"_ns, InternalCommandData("cmd_enableCompatibleJoinSplitNodeDirection", Command::EnableCompatibleJoinSplitNodeDirection, ExecCommandParam::Boolean, SetDocumentStateCommand::GetInstance, CommandOnTextEditor::FallThrough)); #if 0 // with empty string sInternalCommandDataHashtable->InsertOrUpdate( u"justifynone"_ns, InternalCommandData( "cmd_align", Command::Undefined, ExecCommandParam::Ignore, nullptr, CommandOnTextEditor::Disabled)); // Not implemented yet. // REQUIRED SPECIAL REVIEW special review sInternalCommandDataHashtable->InsertOrUpdate( u"saveas"_ns, InternalCommandData( "cmd_saveAs", Command::Undefined, ExecCommandParam::Boolean, nullptr, CommandOnTextEditor::FallThrough)); // Not implemented yet. // REQUIRED SPECIAL REVIEW special review sInternalCommandDataHashtable->InsertOrUpdate( u"print"_ns, InternalCommandData( "cmd_print", Command::Undefined, ExecCommandParam::Boolean, nullptr, CommandOnTextEditor::FallThrough)); // 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(); } // Ignore if the command is disabled by a corresponding pref due to Gecko // specific. switch (commandData.mCommand) { case Command::SetDocumentReadOnly: if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() && aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) { return InternalCommandData(); } break; case Command::SetDocumentInsertBROnEnterKeyPress: MOZ_DIAGNOSTIC_ASSERT( aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn")); if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) { return InternalCommandData(); } break; default: break; } 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::article, nsGkAtoms::aside, nsGkAtoms::blockquote, nsGkAtoms::dd, nsGkAtoms::div, nsGkAtoms::dl, nsGkAtoms::dt, nsGkAtoms::footer, nsGkAtoms::h1, nsGkAtoms::h2, nsGkAtoms::h3, nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6, nsGkAtoms::header, nsGkAtoms::hgroup, nsGkAtoms::main, nsGkAtoms::nav, nsGkAtoms::p, nsGkAtoms::pre, nsGkAtoms::section, // 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(); } } Document::AutoEditorCommandTarget::AutoEditorCommandTarget( Document& aDocument, const InternalCommandData& aCommandData) : mCommandData(aCommandData) { // We'll retrieve an editor with current DOM tree and layout information. // However, JS may have already hidden or remove exposed root content of // the editor. Therefore, we need the latest layout information here. aDocument.FlushPendingNotifications(FlushType::Layout); if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) { mDoNothing = true; return; } if (nsPresContext* presContext = aDocument.GetPresContext()) { // Consider context of command handling which is automatically resolved // by order of controllers in `nsCommandManager::GetControllerForCommand()`. // The order is: // 1. TextEditor if there is an active element and it has TextEditor like // or