From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- dom/base/Document.cpp | 19019 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 19019 insertions(+) create mode 100644 dom/base/Document.cpp (limited to 'dom/base/Document.cpp') diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp new file mode 100644 index 0000000000..819cd8c11d --- /dev/null +++ b/dom/base/Document.cpp @@ -0,0 +1,19019 @@ +/* -*- 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/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/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/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), + 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), + mSubDocuments(nullptr), + mHeaderData(nullptr), + mServoRestyleRootDirtyBits(0), + mThrowOnDynamicMarkupInsertionCounter(0), + mIgnoreOpensDuringUnloadCounter(0), + mSavedResolution(1.0f), + mSavedResolutionBeforeMVM(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. + delete mSubDocuments; + mSubDocuments = nullptr; + + nsAutoScriptBlocker scriptBlocker; + + // Destroy link map now so we don't waste time removing + // links one by one + DestroyElementMaps(); + + // Invalidate cached array of child nodes + InvalidateChildNodes(); + + // We should not have child nodes when destructor is called, + // since child nodes keep their owner document alive. + MOZ_ASSERT(!HasChildren()); + + mCachedRootElement = nullptr; + + for (auto& sheets : mAdditionalSheets) { + UnlinkStyleSheets(sheets); + } + + if (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(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(mLastRememberedSizeObserver) + 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(mLastRememberedSizeObserver) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n) + 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; + } + + delete tmp->mSubDocuments; + 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. + delete mSubDocuments; + mSubDocuments = nullptr; + + bool oldVal = mInUnlinkOrDeletion; + mInUnlinkOrDeletion = true; + { // Scope for update + MOZ_AUTO_DOC_UPDATE(this, true); + + // Destroy link map now so we don't waste time removing + // links one by one + DestroyElementMaps(); + + // Invalidate cached array of child nodes + InvalidateChildNodes(); + + while (HasChildren()) { + nsMutationGuard::DidMutate(); + nsCOMPtr content = GetLastChild(); + nsIContent* previousSibling = content->GetPreviousSibling(); + DisconnectChild(content); + if (content == mCachedRootElement) { + // Immediately clear mCachedRootElement, now that it's been removed + // from mChildren, so that GetRootElement() will stop returning this + // now-stale value. + mCachedRootElement = nullptr; + } + MutationObservers::NotifyContentRemoved(this, content, previousSibling); + content->UnbindFromTree(); + } + MOZ_ASSERT(!mCachedRootElement, + "After removing all children, there should be no root elem"); + } + mInUnlinkOrDeletion = oldVal; +} + +void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup, + nsIPrincipal* aPrincipal, + nsIPrincipal* aPartitionedPrincipal) { + MOZ_ASSERT(aURI, "Null URI passed to ResetToURI"); + MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal); + + MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, + ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get())); + + mSecurityInfo = nullptr; + + nsCOMPtr group = do_QueryReferent(mDocumentLoadGroup); + if (!aLoadGroup || group != aLoadGroup) { + mDocumentLoadGroup = nullptr; + } + + DisconnectNodeTree(); + + // Reset our stylesheets + ResetStylesheetsToURI(aURI); + + // Release the listener manager + if (mListenerManager) { + mListenerManager->Disconnect(); + mListenerManager = nullptr; + } + + // Release the stylesheets list. + mDOMStyleSheets = nullptr; + + // Release our principal after tearing down the document, rather than before. + // This ensures that, during teardown, the document and the dying window + // (which already nulled out its document pointer and cached the principal) + // have matching principals. + SetPrincipals(nullptr, nullptr); + + // Clear the original URI so SetDocumentURI sets it. + mOriginalURI = nullptr; + + SetDocumentURI(aURI); + mChromeXHRDocURI = nullptr; + // If mDocumentBaseURI is null, Document::GetBaseURI() returns + // mDocumentURI. + mDocumentBaseURI = nullptr; + mChromeXHRDocBaseURI = nullptr; + + 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; + 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, + nsAString& aFormattedTimeString) { + PRExplodedTime prtime; + PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime); + // "MM/DD/YYYY hh:mm:ss" + char formatedTime[24]; + if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d", + prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year), + prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) { + CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString); + } else { + // If we for whatever reason failed to find the last modified time + // (or even the current time), fall back to what NS4.x returned. + aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00"); + } +} + +void Document::GetLastModified(nsAString& aLastModified) const { + if (!mLastModified.IsEmpty()) { + aLastModified.Assign(mLastModified); + } else { + GetFormattedTimeString(PR_Now(), aLastModified); + } +} + +static void IncrementExpandoGeneration(Document& aDoc) { + ++aDoc.mExpandoAndGeneration.generation; +} + +void Document::AddToNameTable(Element* aElement, nsAtom* aName) { + MOZ_ASSERT( + nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement), + "Only put elements that need to be exposed as document['name'] in " + "the named table."); + + IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName); + + // Null for out-of-memory + if (entry) { + if (!entry->HasNameElement() && + !entry->HasIdElementExposedAsHTMLDocumentProperty()) { + IncrementExpandoGeneration(*this); + } + entry->AddNameElement(this, aElement); + } +} + +void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) { + // Speed up document teardown + if (mIdentifierMap.Count() == 0) return; + + IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName); + if (!entry) // Could be false if the element was anonymous, hence never added + return; + + entry->RemoveNameElement(aElement); + if (!entry->HasNameElement() && + !entry->HasIdElementExposedAsHTMLDocumentProperty()) { + IncrementExpandoGeneration(*this); + } +} + +void Document::AddToIdTable(Element* aElement, nsAtom* aId) { + IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId); + + if (entry) { /* True except on OOM */ + if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) && + !entry->HasNameElement() && + !entry->HasIdElementExposedAsHTMLDocumentProperty()) { + IncrementExpandoGeneration(*this); + } + entry->AddIdElement(aElement); + } +} + +void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) { + NS_ASSERTION(aId, "huhwhatnow?"); + + // Speed up document teardown + if (mIdentifierMap.Count() == 0) { + return; + } + + IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId); + if (!entry) // Can be null for XML elements with changing ids. + return; + + entry->RemoveIdElement(aElement); + if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) && + !entry->HasNameElement() && + !entry->HasIdElementExposedAsHTMLDocumentProperty()) { + IncrementExpandoGeneration(*this); + } + if (entry->IsEmpty()) { + mIdentifierMap.RemoveEntry(entry); + } +} + +void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer, + bool aPreload) { + ReferrerPolicyEnum policy = + ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer); + // The empty string "" corresponds to no referrer policy, causing a fallback + // to a referrer policy defined elsewhere. + if (policy == ReferrerPolicy::_empty) { + return; + } + + MOZ_ASSERT(mReferrerInfo); + MOZ_ASSERT(mPreloadReferrerInfo); + + if (aPreload) { + mPreloadReferrerInfo = + static_cast((mPreloadReferrerInfo).get()) + ->CloneWithNewPolicy(policy); + } else { + 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