summaryrefslogtreecommitdiffstats
path: root/dom/base/Document.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/Document.cpp')
-rw-r--r--dom/base/Document.cpp18628
1 files changed, 18628 insertions, 0 deletions
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
new file mode 100644
index 0000000000..3a1575b7df
--- /dev/null
+++ b/dom/base/Document.cpp
@@ -0,0 +1,18628 @@
+/* -*- 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 <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <iterator>
+#include <limits>
+#include <type_traits>
+#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/AbstractTimelineMarker.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/ArrayIterator.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Base64.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/CSSEnabledState.h"
+#include "mozilla/ContentBlockingAllowList.h"
+#include "mozilla/ContentBlockingNotifier.h"
+#include "mozilla/ContentBlockingUserInteraction.h"
+#include "mozilla/ContentPrincipal.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/DocLoadingTimelineMarker.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/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/PendingAnimationTracker.h"
+#include "mozilla/PendingFullscreenEvent.h"
+#include "mozilla/PermissionDelegateHandler.h"
+#include "mozilla/PermissionManager.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PreloadHashKey.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellForwards.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/PseudoStyleType.h"
+#include "mozilla/RefCountType.h"
+#include "mozilla/RejectForeignAllowList.h"
+#include "mozilla/RelativeTo.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/ReverseIterator.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/ServoStyleSet.h"
+#include "mozilla/ServoTypes.h"
+#include "mozilla/SizeOfState.h"
+#include "mozilla/Span.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_docshell.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_editor.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/TelemetryHistogramEnums.h"
+#include "mozilla/TelemetryScalarEnums.h"
+#include "mozilla/TextControlElement.h"
+#include "mozilla/TextEditor.h"
+#include "mozilla/TimelineConsumers.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/URLDecorationStripper.h"
+#include "mozilla/URLExtraData.h"
+#include "mozilla/Unused.h"
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/css/Rule.h"
+#include "mozilla/css/SheetParsingMode.h"
+#include "mozilla/dom/AnonymousContent.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/CDATASection.h"
+#include "mozilla/dom/CSPDictionariesBinding.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ChromeObserver.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ClientState.h"
+#include "mozilla/dom/Comment.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/DOMImplementation.h"
+#include "mozilla/dom/DOMIntersectionObserver.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/DocumentBinding.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/DocumentL10n.h"
+#include "mozilla/dom/DocumentTimeline.h"
+#include "mozilla/dom/DocumentType.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventListenerBinding.h"
+#include "mozilla/dom/FailedCertSecurityInfoBinding.h"
+#include "mozilla/dom/FeaturePolicy.h"
+#include "mozilla/dom/FeaturePolicyUtils.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/dom/FromParser.h"
+#include "mozilla/dom/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/Link.h"
+#include "mozilla/dom/MediaQueryList.h"
+#include "mozilla/dom/MediaSource.h"
+#include "mozilla/dom/MutationObservers.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/NetErrorInfoBinding.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/NodeIterator.h"
+#include "mozilla/dom/PContentChild.h"
+#include "mozilla/dom/PWindowGlobalChild.h"
+#include "mozilla/dom/PageTransitionEvent.h"
+#include "mozilla/dom/PageTransitionEventBinding.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/PostMessageEvent.h"
+#include "mozilla/dom/ProcessingInstruction.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/ResizeObserverController.h"
+#include "mozilla/dom/SVGElement.h"
+#include "mozilla/dom/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/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/UserActivation.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "mozilla/dom/WorkerDocumentListener.h"
+#include "mozilla/dom/XPathEvaluator.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/extensions/WebExtensionPolicy.h"
+#include "mozilla/fallible.h"
+#include "mozilla/gfx/BaseCoord.h"
+#include "mozilla/gfx/BaseSize.h"
+#include "mozilla/gfx/Coord.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/ScaleFactor.h"
+#include "mozilla/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 "nsHTMLCSSStyleSheet.h"
+#include "nsHTMLDocument.h"
+#include "nsHTMLStyleSheet.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 "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<HeaderData> next = std::move(mNext);
+ while (next) {
+ next = std::move(next->mNext);
+ }
+ }
+
+ RefPtr<nsAtom> mField;
+ nsString mData;
+ UniquePtr<HeaderData> mNext;
+};
+
+using LinkArray = nsTArray<Link*>;
+
+AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
+ nullptr;
+
+static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
+static LazyLogModule gCspPRLog("CSP");
+LazyLogModule gUserInteractionPRLog("UserInteraction");
+
+static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
+ nsIHttpChannel** aHttpChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (httpChannel) {
+ httpChannel.forget(aHttpChannel);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
+ if (!multipart) {
+ *aHttpChannel = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIChannel> baseChannel;
+ nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ httpChannel = do_QueryInterface(baseChannel);
+ httpChannel.forget(aHttpChannel);
+
+ return NS_OK;
+}
+
+} // namespace dom
+
+#define NAME_NOT_VALID ((nsSimpleContentList*)1)
+
+IdentifierMapEntry::IdentifierMapEntry(
+ const IdentifierMapEntry::DependentAtomOrString* aKey)
+ : mKey(aKey ? *aKey : nullptr) {}
+
+void IdentifierMapEntry::Traverse(
+ nsCycleCollectionTraversalCallback* aCallback) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
+ "mIdentifierMap mNameContentList");
+ aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
+
+ if (mImageElement) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
+ "mIdentifierMap mImageElement element");
+ nsIContent* imageElement = mImageElement;
+ aCallback->NoteXPCOMChild(imageElement);
+ }
+}
+
+bool IdentifierMapEntry::IsEmpty() {
+ return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
+ !mImageElement;
+}
+
+bool IdentifierMapEntry::HasNameElement() const {
+ return mNameContentList && mNameContentList->Length() != 0;
+}
+
+void IdentifierMapEntry::AddContentChangeCallback(
+ Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
+ if (!mChangeCallbacks) {
+ mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
+ }
+
+ ChangeCallback cc = {aCallback, aData, aForImage};
+ mChangeCallbacks->PutEntry(cc);
+}
+
+void IdentifierMapEntry::RemoveContentChangeCallback(
+ Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
+ if (!mChangeCallbacks) return;
+ ChangeCallback cc = {aCallback, aData, aForImage};
+ mChangeCallbacks->RemoveEntry(cc);
+ if (mChangeCallbacks->Count() == 0) {
+ mChangeCallbacks = nullptr;
+ }
+}
+
+void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
+ Element* aNewElement,
+ bool aImageOnly) {
+ if (!mChangeCallbacks) return;
+
+ for (auto iter = mChangeCallbacks->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<nsAtom> name = NS_Atomize(aName);
+ for (uint32_t i = 0; i < mElements.Length(); i++) {
+ MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
+ Element* element = mElements[i]->AsElement();
+ if (element->GetID() == name ||
+ (element->HasName() &&
+ element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
+ aFound = true;
+ return element;
+ }
+ }
+ return nullptr;
+ }
+
+ virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
+ AutoTArray<nsAtom*, 8> atoms;
+ for (uint32_t i = 0; i < mElements.Length(); i++) {
+ MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
+ Element* element = mElements[i]->AsElement();
+
+ nsAtom* id = element->GetID();
+ MOZ_ASSERT(id != nsGkAtoms::_empty);
+ if (id && !atoms.Contains(id)) {
+ atoms.AppendElement(id);
+ }
+
+ if (element->HasName()) {
+ nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
+ MOZ_ASSERT(name && name != nsGkAtoms::_empty);
+ if (name && !atoms.Contains(name)) {
+ atoms.AppendElement(name);
+ }
+ }
+ }
+
+ nsString* names = aNames.AppendElements(atoms.Length());
+ for (uint32_t i = 0; i < atoms.Length(); i++) {
+ atoms[i]->ToString(names[i]);
+ }
+ }
+
+ virtual JSObject* GetWrapperPreserveColorInternal() override {
+ return nsWrapperCache::GetWrapperPreserveColor();
+ }
+ virtual void PreserveWrapperInternal(
+ nsISupports* aScriptObjectHolder) override {
+ nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
+ }
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ using nsBaseContentList::Item;
+
+ private:
+ virtual ~SimpleHTMLCollection() = 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<nsIURI> clone;
+ nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
+ if (NS_FAILED(rv) || !clone) {
+ return nullptr;
+ }
+
+ ExternalResource* resource;
+ mMap.Get(clone, &resource);
+ if (resource) {
+ return resource->mDocument;
+ }
+
+ bool loadStartSucceeded =
+ mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
+ if (!loadEntry) {
+ loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
+
+ if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
+ aRequestingNode))) {
+ return false;
+ }
+ }
+
+ RefPtr<PendingLoad> 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<RefPtr<Document>> 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<nsIContentViewer> viewer = entry.GetData()->mViewer;
+ if (viewer) {
+ viewer->Hide();
+ }
+ }
+}
+
+void ExternalResourceMap::ShowViewers() {
+ for (const auto& entry : mMap) {
+ nsCOMPtr<nsIContentViewer> 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,
+ nsIContentViewer* aViewer,
+ nsILoadGroup* aLoadGroup,
+ Document* aDisplayDocument) {
+ MOZ_ASSERT(aURI, "Unexpected call");
+ MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
+ "Must have both or neither");
+
+ RefPtr<PendingLoad> load;
+ mPendingLoads.Remove(aURI, getter_AddRefs(load));
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<Document> doc;
+ if (aViewer) {
+ doc = aViewer->GetDocument();
+ NS_ASSERTION(doc, "Must have a document");
+
+ 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<ExternalResource>()).get();
+
+ newResource->mDocument = doc;
+ newResource->mViewer = aViewer;
+ newResource->mLoadGroup = aLoadGroup;
+ if (doc) {
+ if (nsPresContext* pc = doc->GetPresContext()) {
+ pc->RecomputeBrowsingContextDependentData();
+ }
+ TransferShowingState(aDisplayDocument, doc);
+ }
+
+ const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
+ for (uint32_t i = 0; i < obs.Length(); ++i) {
+ obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
+ nullptr);
+ }
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
+ ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
+ if (map.HaveShutDown()) {
+ return NS_BINDING_ABORTED;
+ }
+
+ nsCOMPtr<nsIContentViewer> viewer;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsresult rv =
+ SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
+
+ // Make sure to do this no matter what
+ nsresult rv2 =
+ map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (NS_FAILED(rv2)) {
+ mTargetListener = nullptr;
+ return rv2;
+ }
+
+ return mTargetListener->OnStartRequest(aRequest);
+}
+
+nsresult ExternalResourceMap::PendingLoad::SetupViewer(
+ nsIRequest* aRequest, nsIContentViewer** aViewer,
+ nsILoadGroup** aLoadGroup) {
+ MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
+ *aViewer = nullptr;
+ *aLoadGroup = nullptr;
+
+ nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
+ NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
+ if (httpChannel) {
+ bool requestSucceeded;
+ if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
+ !requestSucceeded) {
+ // Bail out on this load, since it looks like we have an HTTP error page
+ return NS_BINDING_ABORTED;
+ }
+ }
+
+ nsAutoCString type;
+ chan->GetContentType(type);
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ chan->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ // Give this document its own loadgroup
+ nsCOMPtr<nsILoadGroup> newLoadGroup =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+ NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
+ newLoadGroup->SetLoadGroup(loadGroup);
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+
+ nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
+ new LoadgroupCallbacks(callbacks);
+ newLoadGroup->SetNotificationCallbacks(newCallbacks);
+
+ // This is some serious hackery cribbed from docshell
+ nsCOMPtr<nsICategoryManager> catMan =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
+ NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
+ nsCString contractId;
+ nsresult rv =
+ catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
+ do_GetService(contractId.get());
+ NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsIContentViewer> viewer;
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = docLoaderFactory->CreateInstance(
+ "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
+ getter_AddRefs(listener), getter_AddRefs(viewer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
+ if (!parser) {
+ /// We don't want to deal with the various fake documents yet
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // We can't handle HTML and other weird things here yet.
+ nsIContentSink* sink = parser->GetContentSink();
+ nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
+ if (!xmlSink) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ listener.swap(mTargetListener);
+ viewer.forget(aViewer);
+ newLoadGroup.forget(aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
+ 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<nsIStreamListener> 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<nsILoadGroup> loadGroup =
+ aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aPerformanceStorage
+ loadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> 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::SelectorCache::SelectorCache(nsIEventTarget* aEventTarget)
+ : nsExpirationTracker<SelectorCacheKey, 4>(1000, "Document::SelectorCache",
+ aEventTarget) {}
+
+Document::SelectorCache::~SelectorCache() { AgeAllGenerations(); }
+
+void Document::SelectorCache::NotifyExpired(SelectorCacheKey* aSelector) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aSelector);
+
+ // There is no guarantee that this method won't be re-entered when selector
+ // matching is ongoing because "memory-pressure" could be notified immediately
+ // when OOM happens according to the design of nsExpirationTracker.
+ // The perfect solution is to delete the |aSelector| and its
+ // RawServoSelectorList in mTable asynchronously.
+ // We remove these objects synchronously for now because NotifyExpired() will
+ // never be triggered by "memory-pressure" which is not implemented yet in
+ // the stage 2 of mozalloc_handle_oom().
+ // Once these objects are removed asynchronously, we should update the warning
+ // added in mozalloc_handle_oom() as well.
+ RemoveObject(aSelector);
+ mTable.Remove(aSelector->mKey);
+ delete aSelector;
+}
+
+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),
+ mIsDevToolsDocument(false),
+ mIsSyntheticDocument(false),
+ mHasLinksToUpdateRunnable(false),
+ mFlushingPendingLinkUpdates(false),
+ mMayHaveDOMMutationObservers(false),
+ mMayHaveAnimationObservers(false),
+ mHasCSP(false),
+ mHasUnsafeEvalCSP(false),
+ mHasUnsafeInlineCSP(false),
+ mHasCSPDeliveredThroughHeader(false),
+ mBFCacheDisallowed(false),
+ mHasHadDefaultView(false),
+ mStyleSheetChangeEventsEnabled(false),
+ 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),
+ mInXBLUpdate(false),
+ mStyleSetFilled(false),
+ mQuirkSheetAdded(false),
+ mContentEditableSheetAdded(false),
+ mDesignModeSheetAdded(false),
+ mSSApplicableStateNotificationPending(false),
+ mMayHaveTitleElement(false),
+ mDOMLoadingSet(false),
+ mDOMInteractiveSet(false),
+ mDOMCompleteSet(false),
+ mAutoFocusFired(false),
+ mScrolledToRefAlready(false),
+ mChangeScrollPosWhenScrollingToRef(false),
+ mDelayFrameLoaderInitialization(false),
+ mSynchronousDOMContentLoaded(false),
+ mMaybeServiceWorkerControlled(false),
+ mAllowZoom(false),
+ mValidScaleFloat(false),
+ mValidMinScale(false),
+ mValidMaxScale(false),
+ mWidthStrEmpty(false),
+ mParserAborted(false),
+ mReportedDocumentUseCounters(false),
+ mHasReportedShadowDOMUsage(false),
+ mHasDelayedRefreshEvent(false),
+ mLoadEventFiring(false),
+ mSkipLoadEventAfterClose(false),
+ mDisableCookieAccess(false),
+ mDisableDocWrite(false),
+ mTooDeepWriteRecursion(false),
+ mPendingMaybeEditingStateChanged(false),
+ mHasBeenEditable(false),
+ mHasWarnedAboutZoom(false),
+ mIsRunningExecCommand(false),
+ mSetCompleteAfterDOMContentLoaded(false),
+ mDidHitCompleteSheetCache(false),
+ mUseCountersInitialized(false),
+ mShouldReportUseCounters(false),
+ mShouldSendPageUseCounters(false),
+ mUserHasInteracted(false),
+ mHasUserInteractionTimerScheduled(false),
+ mShouldResistFingerprinting(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<mozilla::dom::Promise> Document::AddCertException(
+ bool aIsTemporary, ErrorResult& aError) {
+ RefPtr<Promise> 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<nsIURI> failedChannelURI;
+ NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
+ if (!failedChannelURI) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+
+ nsCOMPtr<nsIURI> 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<nsITransportSecurityInfo> 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<nsIX509Cert> 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<nsICertOverrideService> 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<nsITransportSecurityInfo> 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<BasePrincipal> 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<nsILoadInfo> 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<nsITransportSecurityInfo> 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<nsIX509Cert> cert;
+ nsCOMPtr<nsIX509CertValidity> 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<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
+ int64_t maxValidity = std::numeric_limits<int64_t>::max();
+ int64_t minValidity = 0;
+ PRTime notBefore, notAfter;
+ nsTArray<RefPtr<nsIX509Cert>> 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(&notBefore);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ rv = validity->GetNotAfter(&notAfter);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ notBefore = std::max(minValidity, notBefore);
+ notAfter = std::min(maxValidity, notAfter);
+ nsTArray<uint8_t> certArray;
+ rv = certificate->GetRawDER(certArray);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ nsAutoString der64;
+ rv = Base64Encode(reinterpret_cast<const char*>(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<nsINSSErrorsService> 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<nsIURI> aURI;
+ mFailedChannel->GetURI(getter_AddRefs(aURI));
+ if (XRE_IsContentProcess()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ cc->SendIsSecureURI(aURI, attrs, &aInfo.mHasHSTS);
+ } else {
+ nsCOMPtr<nsISiteSecurityService> 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<nsIPublicKeyPinningService> 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<Document>::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();
+ }
+}
+
+static uint32_t ConvertToUnsignedFromDouble(double aNumber) {
+ return aNumber < 0 ? 0 : static_cast<uint32_t>(aNumber);
+}
+
+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<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (tldService && mReferrerInfo &&
+ (docshell->GetLoadType() & nsIDocShell::LOAD_CMD_NORMAL)) {
+ nsAutoCString currentBaseDomain, referrerBaseDomain;
+ nsCOMPtr<nsIURI> 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(false) || !IsTopLevelContentDocument() ||
+ !GetNavigationTiming() ||
+ !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
+ return;
+ }
+
+ if (!GetChannel()) {
+ return;
+ }
+
+ nsCOMPtr<nsITimedChannel> 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<uint32_t>(redirectCount));
+ }
+
+ if (!redirectStart.IsNull() && !redirectEnd.IsNull()) {
+ TimeDuration redirectTime = redirectEnd - redirectStart;
+ if (redirectTime > zeroDuration) {
+ aEventTelemetryDataOut.redirectTime =
+ mozilla::Some(static_cast<uint32_t>(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<uint32_t>(dnsLookupTime.ToMilliseconds()));
+ }
+ }
+
+ TimeStamp navigationStart =
+ GetNavigationTiming()->GetNavigationStartTimeStamp();
+
+ if (!responseStart || !navigationStart) {
+ return;
+ }
+
+ nsAutoCString dnsKey("Native");
+ nsAutoCString http3Key;
+ nsAutoCString http3WithPriorityKey;
+ nsAutoCString earlyHintKey;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel =
+ do_QueryInterface(GetChannel());
+ if (httpChannel) {
+ bool resolvedByTRR = false;
+ Unused << httpChannel->GetIsResolvedByTRR(&resolvedByTRR);
+ if (resolvedByTRR) {
+ if (nsCOMPtr<nsIDNSService> 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<nsIHttpChannel> 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()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::PERF_FIRST_CONTENTFUL_PAINT_MS,
+ navigationStart, firstContentfulComposite);
+
+ 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);
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PERF_FIRST_CONTENTFUL_PAINT_FROM_RESPONSESTART_MS,
+ responseStart, firstContentfulComposite);
+
+ TimeDuration fcpTime = firstContentfulComposite - navigationStart;
+ if (fcpTime > zeroDuration) {
+ aEventTelemetryDataOut.fcpTime =
+ mozilla::Some(static_cast<uint32_t>(fcpTime.ToMilliseconds()));
+ }
+ }
+
+ // DOM Content Loaded event
+ if (TimeStamp dclEventStart =
+ GetNavigationTiming()->GetDOMContentLoadedEventStartTimeStamp()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::PERF_DOM_CONTENT_LOADED_TIME_MS,
+ navigationStart, dclEventStart);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PERF_DOM_CONTENT_LOADED_TIME_FROM_RESPONSESTART_MS,
+ responseStart, dclEventStart);
+ }
+
+ // Load event
+ if (TimeStamp loadEventStart =
+ GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::PERF_PAGE_LOAD_TIME_MS,
+ navigationStart, loadEventStart);
+ if (!http3Key.IsEmpty()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
+ http3Key, navigationStart, loadEventStart);
+ }
+
+ 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);
+ }
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PERF_PAGE_LOAD_TIME_FROM_RESPONSESTART_MS, responseStart,
+ loadEventStart);
+
+ TimeDuration responseTime = responseStart - navigationStart;
+ if (responseTime > zeroDuration) {
+ aEventTelemetryDataOut.responseTime =
+ mozilla::Some(static_cast<uint32_t>(responseTime.ToMilliseconds()));
+ }
+
+ TimeDuration loadTime = loadEventStart - navigationStart;
+ if (loadTime > zeroDuration) {
+ aEventTelemetryDataOut.loadTime =
+ mozilla::Some(static_cast<uint32_t>(loadTime.ToMilliseconds()));
+ }
+ }
+}
+
+void Document::AccumulateJSTelemetry(
+ glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
+ if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry(false)) {
+ return;
+ }
+
+ if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
+ return;
+ }
+
+ AutoJSContext cx;
+ JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
+ JSAutoRealm ar(cx, globalObject);
+ JS::JSTimers timers = JS::GetJSTimers(cx);
+
+ if (!timers.executionTime.IsZero()) {
+ Telemetry::Accumulate(
+ Telemetry::JS_PAGELOAD_EXECUTION_MS,
+ ConvertToUnsignedFromDouble(timers.executionTime.ToMilliseconds()));
+ aEventTelemetryDataOut.jsExecTime = mozilla::Some(
+ static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
+ }
+
+ if (!timers.delazificationTime.IsZero()) {
+ Telemetry::Accumulate(Telemetry::JS_PAGELOAD_DELAZIFICATION_MS,
+ ConvertToUnsignedFromDouble(
+ timers.delazificationTime.ToMilliseconds()));
+ }
+
+ if (!timers.xdrEncodingTime.IsZero()) {
+ Telemetry::Accumulate(
+ Telemetry::JS_PAGELOAD_XDR_ENCODING_MS,
+ ConvertToUnsignedFromDouble(timers.xdrEncodingTime.ToMilliseconds()));
+ }
+
+ if (!timers.baselineCompileTime.IsZero()) {
+ Telemetry::Accumulate(Telemetry::JS_PAGELOAD_BASELINE_COMPILE_MS,
+ ConvertToUnsignedFromDouble(
+ timers.baselineCompileTime.ToMilliseconds()));
+ }
+
+ if (!timers.gcTime.IsZero()) {
+ Telemetry::Accumulate(
+ Telemetry::JS_PAGELOAD_GC_MS,
+ ConvertToUnsignedFromDouble(timers.gcTime.ToMilliseconds()));
+ }
+
+ if (!timers.protectTime.IsZero()) {
+ Telemetry::Accumulate(
+ Telemetry::JS_PAGELOAD_PROTECT_MS,
+ ConvertToUnsignedFromDouble(timers.protectTime.ToMilliseconds()));
+ }
+}
+
+Document::~Document() {
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
+ MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
+ "Can't be top-level and a resource doc at the same time");
+
+ NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
+
+ if (IsTopLevelContentDocument()) {
+ RemoveToplevelLoadingDocument(this);
+
+ // don't report for about: pages
+ if (!IsAboutPage()) {
+ // record CSP telemetry on this document
+ if (mHasCSP) {
+ Accumulate(Telemetry::CSP_DOCUMENTS_COUNT, 1);
+ }
+ if (mHasUnsafeInlineCSP) {
+ Accumulate(Telemetry::CSP_UNSAFE_INLINE_DOCUMENTS_COUNT, 1);
+ }
+ if (mHasUnsafeEvalCSP) {
+ Accumulate(Telemetry::CSP_UNSAFE_EVAL_DOCUMENTS_COUNT, 1);
+ }
+
+ if (MOZ_UNLIKELY(mMathMLEnabled)) {
+ ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
+ }
+
+ if (IsHTMLDocument()) {
+ switch (GetCompatibilityMode()) {
+ case eCompatibility_FullStandards:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUIRKS_MODE::FullStandards);
+ break;
+ case eCompatibility_AlmostStandards:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
+ break;
+ case eCompatibility_NavQuirks:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
+ break;
+ }
+ }
+ }
+ }
+
+ mInDestructor = true;
+ mInUnlinkOrDeletion = true;
+
+ mozilla::DropJSObjects(this);
+
+ // Clear mObservers to keep it in sync with the mutationobserver list
+ mObservers.Clear();
+
+ mIntersectionObservers.Clear();
+
+ if (mStyleSheetSetList) {
+ mStyleSheetSetList->Disconnect();
+ }
+
+ if (mAnimationController) {
+ mAnimationController->Disconnect();
+ }
+
+ MOZ_ASSERT(mTimelines.isEmpty());
+
+ mParentDocument = nullptr;
+
+ // Kill the subdocument map, doing this will release its strong
+ // references, if any.
+ delete mSubDocuments;
+ mSubDocuments = nullptr;
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ // Destroy link map now so we don't waste time removing
+ // links one by one
+ DestroyElementMaps();
+
+ // Invalidate cached array of child nodes
+ InvalidateChildNodes();
+
+ // We should not have child nodes when destructor is called,
+ // since child nodes keep their owner document alive.
+ MOZ_ASSERT(!HasChildren());
+
+ mCachedRootElement = nullptr;
+
+ for (auto& sheets : mAdditionalSheets) {
+ UnlinkStyleSheets(sheets);
+ }
+
+ if (mAttrStyleSheet) {
+ mAttrStyleSheet->SetOwningDocument(nullptr);
+ }
+
+ if (mListenerManager) {
+ mListenerManager->Disconnect();
+ UnsetFlags(NODE_HAS_LISTENERMANAGER);
+ }
+
+ if (mScriptLoader) {
+ mScriptLoader->DropDocumentReference();
+ }
+
+ if (mCSSLoader) {
+ // Could be null here if Init() failed or if we have been unlinked.
+ mCSSLoader->DropDocumentReference();
+ }
+
+ if (mStyleImageLoader) {
+ mStyleImageLoader->DropDocumentReference();
+ }
+
+ if (mXULBroadcastManager) {
+ mXULBroadcastManager->DropDocumentReference();
+ }
+
+ if (mXULPersist) {
+ mXULPersist->DropDocumentReference();
+ }
+
+ if (mPermissionDelegateHandler) {
+ mPermissionDelegateHandler->DropDocumentReference();
+ }
+
+ 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();
+}
+
+NS_INTERFACE_TABLE_HEAD(Document)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE_BEGIN
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
+ NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
+ NS_INTERFACE_TABLE_ENTRY(Document, Document)
+ NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
+ NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
+ NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
+ NS_INTERFACE_TABLE_ENTRY(Document, nsIRadioGroupContainer)
+ NS_INTERFACE_TABLE_END
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(Document)
+NS_IMPL_MAIN_THREAD_ONLY_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);
+
+ for (auto& sheets : tmp->mAdditionalSheets) {
+ tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserverViewport)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRememberedSizeObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentVisibilityObserver)
+ 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(mPendingAnimationTracker)
+ 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<SubDocMapEntry*>(iter.Get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
+ cb.NoteXPCOMChild(entry->mKey);
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mSubDocuments entry->mSubDocument");
+ cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
+ }
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
+
+ // We own only the items in mDOMMediaQueryLists that have listeners;
+ // this reference is managed by their AddListener and RemoveListener
+ // methods.
+ for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
+ mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
+ if (mql->HasListeners() &&
+ NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
+ cb.NoteXPCOMChild(static_cast<EventTarget*>(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);
+ }
+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<nsIContent> child = tmp->GetLastChild();
+ tmp->DisconnectChild(child);
+ child->UnbindFromTree();
+ }
+
+ tmp->UnlinkOriginalDocumentIfStatic();
+
+ tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
+
+ tmp->SetScriptGlobalObject(nullptr);
+
+ for (auto& sheets : tmp->mAdditionalSheets) {
+ tmp->UnlinkStyleSheets(sheets);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserver)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserverViewport)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentVisibilityObserver)
+ 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(mPendingAnimationTracker)
+ 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);
+
+ // 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<LinkedListElement<MediaQueryList>*>(mql)->getNext();
+ mql->Disconnect();
+ mql = next;
+ }
+
+ tmp->mPendingFrameStaticClones.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() {
+ if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ // Force initialization.
+ mOnloadBlocker = new OnloadBlocker();
+ mStyleImageLoader = new css::ImageLoader(this);
+
+ mNodeInfoManager = new nsNodeInfoManager(this);
+
+ // 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<nsIGlobalObject> global =
+ xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+ NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
+ mScopeObject = do_GetWeakReference(global);
+ MOZ_ASSERT(mScopeObject);
+
+ mScriptLoader = new dom::ScriptLoader(this);
+
+ // we need to create a policy here so getting the policy within
+ // ::Policy() can *always* return a non null policy
+ mFeaturePolicy = new dom::FeaturePolicy(this);
+ mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
+
+ mStyleSet = MakeUnique<ServoStyleSet>(*this);
+
+ 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<nsIURI> uri;
+ nsCOMPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsIPrincipal> partitionedPrincipal;
+ if (aChannel) {
+ // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
+ // nsScriptSecurityManager::GetChannelResultPrincipals.
+ // Note: this should match the uri used for the OnNewURI call in
+ // nsDocShell::CreateContentViewer.
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+
+ nsIScriptSecurityManager* securityManager =
+ nsContentUtils::GetSecurityManager();
+ if (securityManager) {
+ securityManager->GetChannelResultPrincipals(
+ aChannel, getter_AddRefs(principal),
+ getter_AddRefs(partitionedPrincipal));
+ }
+ }
+
+ bool equal = principal->Equals(partitionedPrincipal);
+
+ principal = MaybeDowngradePrincipal(principal);
+ if (equal) {
+ partitionedPrincipal = principal;
+ } else {
+ partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
+ }
+
+ ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
+
+ // Note that, since mTiming does not change during a reset, the
+ // navigationStart time remains unchanged and therefore any future new
+ // timeline will have the same global clock time as the old one.
+ mDocumentTimeline = nullptr;
+
+ if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
+ if (nsCOMPtr<nsIURI> 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<nsIContent> content = GetLastChild();
+ nsIContent* previousSibling = content->GetPreviousSibling();
+ DisconnectChild(content);
+ if (content == mCachedRootElement) {
+ // Immediately clear mCachedRootElement, now that it's been removed
+ // from mChildren, so that GetRootElement() will stop returning this
+ // now-stale value.
+ mCachedRootElement = nullptr;
+ }
+ 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<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
+ if (!aLoadGroup || group != aLoadGroup) {
+ mDocumentLoadGroup = nullptr;
+ }
+
+ DisconnectNodeTree();
+
+ // Reset our stylesheets
+ ResetStylesheetsToURI(aURI);
+
+ // Release the listener manager
+ if (mListenerManager) {
+ mListenerManager->Disconnect();
+ mListenerManager = nullptr;
+ }
+
+ // Release the stylesheets list.
+ mDOMStyleSheets = nullptr;
+
+ // Release our principal after tearing down the document, rather than before.
+ // This ensures that, during teardown, the document and the dying window
+ // (which already nulled out its document pointer and cached the principal)
+ // have matching principals.
+ SetPrincipals(nullptr, nullptr);
+
+ // Clear the original URI so SetDocumentURI sets it.
+ mOriginalURI = nullptr;
+
+ SetDocumentURI(aURI);
+ mChromeXHRDocURI = nullptr;
+ // If mDocumentBaseURI is null, Document::GetBaseURI() returns
+ // mDocumentURI.
+ mDocumentBaseURI = nullptr;
+ mChromeXHRDocBaseURI = nullptr;
+
+ // Check if the current document is the top-level DevTools document.
+ // For inner DevTools frames, mIsDevToolsDocument will be set when
+ // calling SetDocumentParent.
+ if (aURI && aURI->SchemeIs("about") &&
+ aURI->GetSpecOrDefault().EqualsLiteral("about:devtools-toolbox")) {
+ mIsDevToolsDocument = true;
+ }
+
+ if (aLoadGroup) {
+ mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
+ // there was an assertion here that aLoadGroup was not null. This
+ // is no longer valid: nsDocShell::SetDocument does not create a
+ // load group, and it works just fine
+
+ // XXXbz what does "just fine" mean exactly? And given that there
+ // is no nsDocShell::SetDocument, what is this talking about?
+
+ if (IsContentDocument()) {
+ // Inform the associated request context about this load start so
+ // any of its internal load progress flags gets reset.
+ nsCOMPtr<nsIRequestContextService> rcsvc =
+ net::RequestContextService::GetOrCreate();
+ if (rcsvc) {
+ nsCOMPtr<nsIRequestContext> rc;
+ rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
+ if (rc) {
+ rc->BeginLoad();
+ }
+ }
+ }
+ }
+
+ mLastModified.Truncate();
+ // XXXbz I guess we're assuming that the caller will either pass in
+ // a channel with a useful type or call SetContentType?
+ SetContentType(""_ns);
+ mContentLanguage.Truncate();
+ mBaseTarget.Truncate();
+
+ mXMLDeclarationBits = 0;
+
+ // Now get our new principal
+ if (aPrincipal) {
+ SetPrincipals(aPrincipal, aPartitionedPrincipal);
+ } else {
+ nsIScriptSecurityManager* securityManager =
+ nsContentUtils::GetSecurityManager();
+ if (securityManager) {
+ nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
+
+ if (!loadContext && aLoadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ loadContext = do_GetInterface(cbs);
+ }
+
+ MOZ_ASSERT(loadContext,
+ "must have a load context or pass in an explicit principal");
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = securityManager->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<nsIPrincipal> Document::MaybeDowngradePrincipal(
+ nsIPrincipal* aPrincipal) {
+ if (!aPrincipal) {
+ return nullptr;
+ }
+
+ // We can't load a document with an expanded principal. If we're given one,
+ // automatically downgrade it to the last principal it subsumes (which is the
+ // extension principal, in the case of extension content scripts).
+ auto* basePrin = BasePrincipal::Cast(aPrincipal);
+ if (basePrin->Is<ExpandedPrincipal>()) {
+ MOZ_DIAGNOSTIC_ASSERT(false,
+ "Should never try to create a document with "
+ "an expanded principal");
+
+ auto* expanded = basePrin->As<ExpandedPrincipal>();
+ return do_AddRef(expanded->AllowList().LastElement());
+ }
+
+ if (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<BrowsingContext> parent =
+ mDocumentContainer->GetBrowsingContext()->GetParent()) {
+ auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
+ if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ return nullPrincipal.forget();
+ }
+ }
+ }
+ nsCOMPtr<nsIPrincipal> principal(aPrincipal);
+ return principal.forget();
+}
+
+size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
+ nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
+
+ // lowest index first
+ int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
+
+ size_t count = mStyleSet->SheetCount(StyleOrigin::Author);
+ size_t index = 0;
+ for (; index < count; index++) {
+ auto* sheet = mStyleSet->SheetAt(StyleOrigin::Author, index);
+ MOZ_ASSERT(sheet);
+ int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
+ if (sheetDocIndex > newDocIndex) {
+ break;
+ }
+
+ // If the sheet is not owned by the document it can be an author
+ // sheet registered at nsStyleSheetService or an additional author
+ // sheet on the document, which means the new
+ // doc sheet should end up before it.
+ if (sheetDocIndex < 0) {
+ if (sheetService) {
+ auto& authorSheets = *sheetService->AuthorStyleSheets();
+ if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
+ break;
+ }
+ }
+ if (sheet == GetFirstAdditionalAuthorSheet()) {
+ break;
+ }
+ }
+ }
+
+ return index;
+}
+
+void Document::ResetStylesheetsToURI(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+
+ ClearAdoptedStyleSheets();
+
+ auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
+ for (auto& sheet : Reversed(aSheetList)) {
+ sheet->ClearAssociatedDocumentOrShadowRoot();
+ if (mStyleSetFilled) {
+ mStyleSet->RemoveStyleSheet(*sheet);
+ }
+ }
+ aSheetList.Clear();
+ };
+ ClearSheetList(mStyleSheets);
+ for (auto& sheets : mAdditionalSheets) {
+ ClearSheetList(sheets);
+ }
+ if (mStyleSetFilled) {
+ if (auto* ss = nsStyleSheetService::GetInstance()) {
+ for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
+ MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
+ if (sheet->IsApplicable()) {
+ mStyleSet->RemoveStyleSheet(*sheet);
+ }
+ }
+ }
+ }
+
+ // Now reset our inline style and attribute sheets.
+ if (mAttrStyleSheet) {
+ mAttrStyleSheet->Reset();
+ mAttrStyleSheet->SetOwningDocument(this);
+ } else {
+ mAttrStyleSheet = new nsHTMLStyleSheet(this);
+ }
+
+ if (!mStyleAttrStyleSheet) {
+ mStyleAttrStyleSheet = new nsHTMLCSSStyleSheet();
+ }
+
+ if (mStyleSetFilled) {
+ FillStyleSetDocumentSheets();
+
+ if (mStyleSet->StyleSheetsHaveChanged()) {
+ ApplicableStylesChanged();
+ }
+ }
+}
+
+static void AppendSheetsToStyleSet(
+ ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
+ for (StyleSheet* sheet : Reversed(aSheets)) {
+ aStyleSet->AppendStyleSheet(*sheet);
+ }
+}
+
+void Document::FillStyleSetUserAndUASheets() {
+ // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
+ // ordering.
+
+ // The document will fill in the document sheets when we create the presshell
+ auto* cache = GlobalStyleSheetCache::Singleton();
+
+ nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
+ MOZ_ASSERT(sheetService,
+ "should never be creating a StyleSet after the style sheet "
+ "service has gone");
+
+ for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
+ mStyleSet->AppendStyleSheet(*sheet);
+ }
+
+ StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
+ : cache->GetUserContentSheet();
+ if (sheet) {
+ mStyleSet->AppendStyleSheet(*sheet);
+ }
+
+ mStyleSet->AppendStyleSheet(*cache->UASheet());
+
+ if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
+ mStyleSet->AppendStyleSheet(*cache->MathMLSheet());
+ }
+
+ if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
+ mStyleSet->AppendStyleSheet(*cache->SVGSheet());
+ }
+
+ mStyleSet->AppendStyleSheet(*cache->HTMLSheet());
+
+ if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
+ mStyleSet->AppendStyleSheet(*cache->NoFramesSheet());
+ }
+
+ mStyleSet->AppendStyleSheet(*cache->CounterStylesSheet());
+
+ // Only load the full XUL sheet if we'll need it.
+ if (LoadsFullXULStyleSheetUpFront()) {
+ mStyleSet->AppendStyleSheet(*cache->XULSheet());
+ }
+
+ mStyleSet->AppendStyleSheet(*cache->FormsSheet());
+ mStyleSet->AppendStyleSheet(*cache->ScrollbarsSheet());
+
+ for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
+ mStyleSet->AppendStyleSheet(*sheet);
+ }
+
+ MOZ_ASSERT(!mQuirkSheetAdded);
+ if (NeedsQuirksSheet()) {
+ mStyleSet->AppendStyleSheet(*cache->QuirkSheet());
+ mQuirkSheetAdded = true;
+ }
+}
+
+void Document::FillStyleSet() {
+ MOZ_ASSERT(!mStyleSetFilled);
+ FillStyleSetUserAndUASheets();
+ FillStyleSetDocumentSheets();
+ mStyleSetFilled = true;
+}
+
+void Document::RemoveContentEditableStyleSheets() {
+ MOZ_ASSERT(IsHTMLOrXHTML());
+
+ auto* cache = GlobalStyleSheetCache::Singleton();
+ bool changed = false;
+ if (mDesignModeSheetAdded) {
+ mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
+ mDesignModeSheetAdded = false;
+ changed = true;
+ }
+ if (mContentEditableSheetAdded) {
+ mStyleSet->RemoveStyleSheet(*cache->ContentEditableSheet());
+ mContentEditableSheetAdded = false;
+ changed = true;
+ }
+ if (changed) {
+ MOZ_ASSERT(mStyleSetFilled);
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
+ MOZ_ASSERT(IsHTMLOrXHTML());
+ MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
+ "Caller should ensure we're being rendered");
+
+ auto* cache = GlobalStyleSheetCache::Singleton();
+ bool changed = false;
+ if (!mContentEditableSheetAdded) {
+ mStyleSet->AppendStyleSheet(*cache->ContentEditableSheet());
+ mContentEditableSheetAdded = true;
+ changed = true;
+ }
+ if (mDesignModeSheetAdded != aDesignMode) {
+ if (mDesignModeSheetAdded) {
+ mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
+ } else {
+ mStyleSet->AppendStyleSheet(*cache->DesignModeSheet());
+ }
+ mDesignModeSheetAdded = !mDesignModeSheetAdded;
+ changed = true;
+ }
+ if (changed) {
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::FillStyleSetDocumentSheets() {
+ MOZ_ASSERT(mStyleSet->SheetCount(StyleOrigin::Author) == 0,
+ "Style set already has document sheets?");
+
+ // Sheets are added in reverse order to avoid worst-case time complexity when
+ // looking up the index of a sheet.
+ //
+ // Note that usually appending is faster (rebuilds less stuff in the
+ // styleset), but in this case it doesn't matter since we're filling the
+ // styleset from scratch anyway.
+ for (StyleSheet* sheet : Reversed(mStyleSheets)) {
+ if (sheet->IsApplicable()) {
+ mStyleSet->AddDocStyleSheet(*sheet);
+ }
+ }
+
+ EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
+ if (aSheet.IsApplicable()) {
+ mStyleSet->AddDocStyleSheet(aSheet);
+ }
+ });
+
+ nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
+ for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
+ mStyleSet->AppendStyleSheet(*sheet);
+ }
+
+ AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAgentSheet]);
+ AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eUserSheet]);
+ AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAuthorSheet]);
+}
+
+void Document::CompatibilityModeChanged() {
+ MOZ_ASSERT(IsHTMLOrXHTML());
+ CSSLoader()->SetCompatibilityMode(mCompatMode);
+ mStyleSet->CompatibilityModeChanged();
+ if (PresShell* presShell = GetPresShell()) {
+ // Selectors may have become case-sensitive / case-insensitive, the stylist
+ // has already performed the relevant invalidation.
+ presShell->EnsureStyleFlush();
+ }
+ if (!mStyleSetFilled) {
+ MOZ_ASSERT(!mQuirkSheetAdded);
+ return;
+ }
+ if (mQuirkSheetAdded == NeedsQuirksSheet()) {
+ return;
+ }
+ auto* cache = GlobalStyleSheetCache::Singleton();
+ StyleSheet* sheet = cache->QuirkSheet();
+ if (mQuirkSheetAdded) {
+ mStyleSet->RemoveStyleSheet(*sheet);
+ } else {
+ mStyleSet->AppendStyleSheet(*sheet);
+ }
+ mQuirkSheetAdded = !mQuirkSheetAdded;
+ ApplicableStylesChanged();
+}
+
+void Document::SetCompatibilityMode(nsCompatibility aMode) {
+ NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
+ "Bad compat mode for XHTML document!");
+
+ if (mCompatMode == aMode) {
+ return;
+ }
+ mCompatMode = aMode;
+ CompatibilityModeChanged();
+ // 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<BrowsingContext> bc = aDocShell->GetBrowsingContext();
+ MOZ_ASSERT(bc->IsInProcess());
+
+ RefPtr<BrowsingContext> 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<nsIDocShell> parentDocShell = parentBC->GetDocShell();
+ MOZ_ASSERT(parentDocShell);
+
+ nsCOMPtr<nsIChannel> parentChannel;
+ parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
+ if (!parentChannel) {
+ return;
+ }
+ nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
+ nsCOMPtr<nsIURI> iframeUri;
+ parentChannel->GetURI(getter_AddRefs(iframeUri));
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ "Iframe Sandbox"_ns, parentDocument,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ "BothAllowScriptsAndSameOriginPresent",
+ nsTArray<nsString>(), iframeUri);
+ }
+}
+
+bool Document::IsSynthesized() {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
+ return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
+}
+
+// static
+bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
+ nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
+ return principal && (principal->IsSystemPrincipal() ||
+ principal->GetIsAddonOrExpandedAddonPrincipal());
+}
+
+nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener** aDocListener,
+ bool aReset) {
+ if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
+ ("DOCUMENT %p StartDocumentLoad %s", this,
+ uri ? uri->GetSpecOrDefault().get() : ""));
+ }
+
+ MOZ_ASSERT(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<nsIPropertyBag2> 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<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
+ if (inStrmChan) {
+ bool isSrcdocChannel;
+ inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
+ if (isSrcdocChannel) {
+ mIsSrcdocDocument = true;
+ }
+ }
+
+ if (mChannel) {
+ nsLoadFlags loadFlags;
+ mChannel->GetLoadFlags(&loadFlags);
+ bool isDocument = false;
+ mChannel->GetIsDocument(&isDocument);
+ if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
+ IsSynthesized() && XRE_IsContentProcess()) {
+ ContentChild::UpdateCookieStatus(mChannel);
+ }
+
+ // 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<nsIDocShell> docShell = do_QueryInterface(aContainer);
+
+ // If this is an error page, don't inherit sandbox flags
+ nsCOMPtr<nsILoadInfo> 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<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
+ nsILoadInfo::CrossOriginOpenerPolicy policy =
+ nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+ if (IsTopLevelContentDocument() && httpChan &&
+ NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
+ docShell->GetBrowsingContext()) {
+ // Setting the opener policy on a discarded context has no effect.
+ Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
+ }
+
+ // The CSP directives upgrade-insecure-requests as well as
+ // block-all-mixed-content not only apply to the toplevel document,
+ // but also to nested documents. The loadInfo of a subdocument
+ // load already holds the correct flag, so let's just set it here
+ // on the document. Please note that we set the appropriate preload
+ // bits just for the sake of completeness here, because the preloader
+ // does not reach into subdocuments.
+ mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
+ mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
+ mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
+ mBlockAllMixedContentPreloads = mBlockAllMixedContent;
+
+ // HTTPS-Only Mode flags
+ // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
+ // sub-resources and sub-documents.
+ mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
+
+ nsresult rv = InitReferrerInfo(aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = InitCOEP(aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check CSP navigate-to
+ // We need to enforce the CSP of the document that initiated the load,
+ // which is the CSP to inherit.
+ nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
+ if (cspToInherit) {
+ bool allowsNavigateTo = false;
+ rv = cspToInherit->GetAllowsNavigateTo(
+ mDocumentURI, loadInfo->GetIsFormSubmission(),
+ !loadInfo->RedirectChain().IsEmpty(), /* aWasRedirected */
+ true, /* aEnforceWhitelist */
+ &allowsNavigateTo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!allowsNavigateTo) {
+ aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
+ return NS_OK;
+ }
+ }
+
+ rv = InitCSP(aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initialize FeaturePolicy
+ rv = InitFeaturePolicy(aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Generally XFO and CSP frame-ancestors is handled within
+ // DocumentLoadListener. However, the DocumentLoadListener can not handle
+ // object and embed. Until then we have to enforce it here (See Bug 1646899).
+ nsContentPolicyType internalContentType =
+ loadInfo->InternalContentPolicyType();
+ if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
+ internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
+ nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
+
+ nsresult status;
+ aChannel->GetStatus(&status);
+ if (status == NS_ERROR_XFO_VIOLATION) {
+ // stop! ERROR page!
+ // But before we have to reset the principal of the document
+ // because the onload() event fires before the error page
+ // is displayed and we do not want the enclosing document
+ // to access the contentDocument.
+ RefPtr<NullPrincipal> nullPrincipal =
+ 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->AsInnerWindow()) {
+ 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<nsISecurityConsoleMessage>& aMessages) {
+ for (uint32_t i = 0; i < aMessages.Length(); ++i) {
+ nsAutoString messageTag;
+ aMessages[i]->GetTag(messageTag);
+
+ nsAutoString category;
+ aMessages[i]->GetCategory(category);
+
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_ConvertUTF16toUTF8(category), this,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ NS_ConvertUTF16toUTF8(messageTag).get());
+ }
+}
+
+void Document::ApplySettingsFromCSP(bool aSpeculative) {
+ nsresult rv = NS_OK;
+ if (!aSpeculative) {
+ // 1) apply settings from regular CSP
+ 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<nsILoadInfo> 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<nsIHttpChannel> 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<nsIPrincipal> 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<nsIURI> chanURI;
+ aChannel->GetURI(getter_AddRefs(chanURI));
+ nsAutoCString aspec;
+ chanURI->GetAsciiSpec(aspec);
+ MOZ_LOG(gCspPRLog, LogLevel::Debug,
+ ("no CSP for document, %s", aspec.get()));
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_LOG(gCspPRLog, LogLevel::Debug,
+ ("Document is an add-on or CSP header specified %p", this));
+
+ // ----- 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<ExpandedPrincipal>()) {
+ basePrin->As<ExpandedPrincipal>()->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<dom::FeaturePolicy> 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<mozilla::dom::FeaturePolicy> 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<nsIHttpChannel> httpChannel;
+ nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!httpChannel) {
+ return NS_OK;
+ }
+
+ // query the policy from the header
+ nsAutoCString value;
+ rv = httpChannel->GetResponseHeader("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<nsIHttpChannel> httpChannel;
+ nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!httpChannel) {
+ return NS_OK;
+ }
+
+ if (nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo()) {
+ SetReferrerInfo(referrerInfo);
+ }
+
+ // Override policy if we get one from Referrerr-Policy header
+ mozilla::dom::ReferrerPolicy policy =
+ nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
+ nsCOMPtr<nsIReferrerInfo> clone =
+ static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
+ ->CloneWithNewPolicy(policy);
+ SetReferrerInfo(clone);
+ mPreloadReferrerInfo = mReferrerInfo;
+ return NS_OK;
+}
+
+nsresult Document::InitCOEP(nsIChannel* aChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> 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<nsIURI> 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<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
+ ->CloneWithNewPolicy(policy);
+ } else {
+ nsCOMPtr<nsIReferrerInfo> clone =
+ static_cast<mozilla::dom::ReferrerInfo*>((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();
+
+#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(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ // Note that this method may be called off the main thread.
+ if (mDocGroup) {
+ return mDocGroup->Dispatch(aCategory, std::move(aRunnable));
+ }
+ return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
+}
+
+nsISerialEventTarget* Document::EventTargetFor(TaskCategory aCategory) const {
+ if (mDocGroup) {
+ return mDocGroup->EventTargetFor(aCategory);
+ }
+ return DispatcherTrait::EventTargetFor(aCategory);
+}
+
+AbstractThread* Document::AbstractMainThreadFor(
+ mozilla::TaskCategory aCategory) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDocGroup) {
+ return mDocGroup->AbstractMainThreadFor(aCategory);
+ }
+ return DispatcherTrait::AbstractMainThreadFor(aCategory);
+}
+
+void Document::NoteScriptTrackingStatus(const nsACString& aURL,
+ bool aIsTracking) {
+ if (aIsTracking) {
+ mTrackingScripts.Insert(aURL);
+ } else {
+ MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
+ }
+}
+
+bool Document::IsScriptTracking(JSContext* aCx) const {
+ JS::AutoFilename filename;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ if (!JS::DescribeScriptedCaller(aCx, &filename, &line, &column)) {
+ return false;
+ }
+ return mTrackingScripts.Contains(nsDependentCString(filename.get()));
+}
+
+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::GetAllowPlugins() {
+ // First, we ask our docshell if it allows plugins.
+ auto* browsingContext = GetBrowsingContext();
+
+ if (browsingContext) {
+ if (!browsingContext->GetAllowPlugins()) {
+ return false;
+ }
+
+ // If the docshell allows plugins, we check whether
+ // we are sandboxed and plugins should not be allowed.
+ if (mSandboxFlags & SANDBOXED_PLUGINS) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+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<JSObject*> object(aCx, aObject);
+ nsCOMPtr<nsIPrincipal> 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(kNameSpaceID_None, 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(kNameSpaceID_None, 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 </linkset>,
+ * In XHTML/HTML this is the end of </head>.
+ *
+ * This milestone is used to allow for batch
+ * localization context I/O and building done
+ * once when all resources in the document have been
+ * collected.
+ */
+void Document::OnL10nResourceContainerParsed() {
+ // 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<DocumentL10n> 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::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return nsContentUtils::IsSystemCaller(aCx) ||
+ StaticPrefs::dom_animations_api_core_enabled();
+}
+
+bool Document::IsWebAnimationsEnabled(CallerType aCallerType) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return aCallerType == dom::CallerType::System ||
+ StaticPrefs::dom_animations_api_core_enabled();
+}
+
+bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
+ JSObject* /*unused*/
+) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return nsContentUtils::IsSystemCaller(aCx) ||
+ StaticPrefs::dom_animations_api_getAnimations_enabled();
+}
+
+bool Document::AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
+ JSObject* /*unused*/
+) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return nsContentUtils::IsSystemCaller(aCx) ||
+ StaticPrefs::dom_animations_api_implicit_keyframes_enabled();
+}
+
+bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
+ JSObject* /*unused*/
+) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return nsContentUtils::IsSystemCaller(aCx) ||
+ StaticPrefs::dom_animations_api_timelines_enabled();
+}
+
+DocumentTimeline* Document::Timeline() {
+ if (!mDocumentTimeline) {
+ mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
+ }
+
+ return mDocumentTimeline;
+}
+
+SVGSVGElement* Document::GetSVGRootElement() const {
+ Element* root = GetRootElement();
+ if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
+ return nullptr;
+ }
+ return static_cast<SVGSVGElement*>(root);
+}
+
+/* Return true if the document is in the focused top-level window, and is an
+ * ancestor of the focused DOMWindow. */
+bool Document::HasFocus(ErrorResult& rv) const {
+ 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->UpdateState(true);
+ }
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
+}
+
+void Document::SetDesignMode(const nsAString& aDesignMode,
+ const Maybe<nsIPrincipal*>& 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_paragraphState",
+ Command::FormatBlock,
+ ExecCommandParam::String,
+ ParagraphStateCommand::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::blockquote,
+ nsGkAtoms::dd,
+ nsGkAtoms::div,
+ nsGkAtoms::dl,
+ nsGkAtoms::dt,
+ nsGkAtoms::h1,
+ nsGkAtoms::h2,
+ nsGkAtoms::h3,
+ nsGkAtoms::h4,
+ nsGkAtoms::h5,
+ nsGkAtoms::h6,
+ nsGkAtoms::p,
+ nsGkAtoms::pre,
+ // clang-format on
+ };
+ nsAutoString value(nsDependentSubstring(start, end));
+ ToLowerCase(value);
+ const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
+ for (const nsStaticAtom* kTag : kFormattableBlockTags) {
+ if (valueAtom == kTag) {
+ kTag->ToString(*aAdjustedValue);
+ return commandData;
+ }
+ }
+ return InternalCommandData();
+ }
+ case Command::FormatFontSize: {
+ // Per editing spec as of April 23, 2012, we need to reject the value
+ // if it's not a valid floating-point number surrounded by optional
+ // whitespace. Otherwise, we parse it as a legacy font size. For
+ // now, we just parse as a legacy font size regardless (matching
+ // WebKit) -- bug 747879.
+ int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
+ if (!size) {
+ return InternalCommandData();
+ }
+ MOZ_ASSERT(aAdjustedValue->IsEmpty());
+ aAdjustedValue->AppendInt(size);
+ return commandData;
+ }
+ case Command::InsertImage:
+ case Command::InsertLink:
+ if (aValue.IsEmpty()) {
+ // Invalid value, return false
+ return InternalCommandData();
+ }
+ aAdjustedValue->Assign(aValue);
+ return commandData;
+ case Command::SetDocumentDefaultParagraphSeparator:
+ if (!aValue.LowerCaseEqualsLiteral("div") &&
+ !aValue.LowerCaseEqualsLiteral("p") &&
+ !aValue.LowerCaseEqualsLiteral("br")) {
+ // Invalid value
+ return InternalCommandData();
+ }
+ aAdjustedValue->Assign(aValue);
+ return commandData;
+ default:
+ aAdjustedValue->Assign(aValue);
+ return commandData;
+ }
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
+ return InternalCommandData();
+ }
+}
+
+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
+ // <input type="text"> or <textarea>.
+ // 2. HTMLEditor for the document, if there is.
+ // 3. Retarget to the DocShell or nsCommandManager as what we've done.
+ if (aCommandData.IsCutOrCopyCommand()) {
+ // Note that we used to use DocShell to handle `cut` and `copy` command
+ // for dispatching corresponding events for making possible web apps to
+ // implement their own editor without editable elements but supports
+ // standard shortcut keys, etc. In this case, we prefer to use active
+ // element's editor to keep same behavior.
+ mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
+ } else {
+ mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
+ mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
+ if (!mActiveEditor) {
+ mActiveEditor = mHTMLEditor;
+ }
+ }
+ }
+
+ // Then, retrieve editor command class instance which should handle it
+ // and can handle it now.
+ if (!mActiveEditor) {
+ // If the command is available without editor, we should redirect the
+ // command to focused descendant with DocShell.
+ if (aCommandData.IsAvailableOnlyWhenEditable()) {
+ mDoNothing = true;
+ return;
+ }
+ return;
+ }
+
+ // Otherwise, we should use EditorCommand instance (which is singleton
+ // instance) when it's enabled.
+ mEditorCommand = aCommandData.mGetEditorCommandFunc
+ ? aCommandData.mGetEditorCommandFunc()
+ : nullptr;
+ if (!mEditorCommand) {
+ mDoNothing = true;
+ mActiveEditor = nullptr;
+ mHTMLEditor = nullptr;
+ return;
+ }
+
+ if (IsCommandEnabled()) {
+ return;
+ }
+
+ // If the EditorCommand instance is disabled, we should do nothing if
+ // the command requires an editor.
+ if (aCommandData.IsAvailableOnlyWhenEditable()) {
+ // Do nothing if editor specific commands is disabled (bug 760052).
+ mDoNothing = true;
+ return;
+ }
+
+ // Otherwise, we should redirect it to focused descendant with DocShell.
+ mEditorCommand = nullptr;
+ mActiveEditor = nullptr;
+ mHTMLEditor = nullptr;
+}
+
+EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
+ using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
+ switch (mCommandData.mCommandOnTextEditor) {
+ case CommandOnTextEditor::Enabled:
+ return mActiveEditor;
+ case CommandOnTextEditor::Disabled:
+ return mActiveEditor && mActiveEditor->IsTextEditor()
+ ? nullptr
+ : mActiveEditor.get();
+ case CommandOnTextEditor::FallThrough:
+ return mHTMLEditor;
+ }
+ return nullptr;
+}
+
+bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
+ if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
+ // Make sure frames are up to date, since that can affect whether
+ // we're editable.
+ doc->FlushPendingNotifications(FlushType::Frames);
+ }
+ EditorBase* targetEditor = GetTargetEditor();
+ if (targetEditor && targetEditor->IsTextEditor()) {
+ // FYI: When `disabled` attribute is set, `TextEditor` treats it as
+ // "readonly" too.
+ return !targetEditor->IsReadonly();
+ }
+ return aDocument->IsEditingOn();
+}
+
+bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
+ EditorBase* targetEditor = GetTargetEditor();
+ if (!targetEditor) {
+ return false;
+ }
+ MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
+ return MOZ_KnownLive(mEditorCommand)
+ ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
+}
+
+nsresult Document::AutoEditorCommandTarget::DoCommand(
+ nsIPrincipal* aPrincipal) const {
+ MOZ_ASSERT(!DoNothing());
+ MOZ_ASSERT(mEditorCommand);
+ EditorBase* targetEditor = GetTargetEditor();
+ if (!targetEditor) {
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+ MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
+ return MOZ_KnownLive(mEditorCommand)
+ ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
+ aPrincipal);
+}
+
+template <typename ParamType>
+nsresult Document::AutoEditorCommandTarget::DoCommandParam(
+ const ParamType& aParam, nsIPrincipal* aPrincipal) const {
+ MOZ_ASSERT(!DoNothing());
+ MOZ_ASSERT(mEditorCommand);
+ EditorBase* targetEditor = GetTargetEditor();
+ if (!targetEditor) {
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+ MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
+ return MOZ_KnownLive(mEditorCommand)
+ ->DoCommandParam(mCommandData.mCommand, aParam,
+ MOZ_KnownLive(*targetEditor), aPrincipal);
+}
+
+nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
+ nsCommandParams& aParams) const {
+ MOZ_ASSERT(mEditorCommand);
+ EditorBase* targetEditor = GetTargetEditor();
+ if (!targetEditor) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
+ return MOZ_KnownLive(mEditorCommand)
+ ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
+ MOZ_KnownLive(targetEditor), nullptr);
+}
+
+bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
+ const nsAString& aValue,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "execCommand is only supported on HTML documents");
+ return false;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ // if they are requesting UI from us, let's fail since we have no UI
+ if (aShowUI) {
+ return false;
+ }
+
+ // If we're running an execCommand, we should just return false.
+ // https://github.com/w3c/editing/issues/200#issuecomment-575241816
+ if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
+ mIsRunningExecCommand) {
+ return false;
+ }
+
+ // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
+ // this might add some ugly JS dependencies?
+
+ nsAutoString adjustedValue;
+ InternalCommandData commandData =
+ ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
+ switch (commandData.mCommand) {
+ case Command::DoNothing:
+ return false;
+ case Command::SetDocumentReadOnly:
+ SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly);
+ break;
+ case Command::EnableCompatibleJoinSplitNodeDirection:
+ // We don't allow to take the legacy behavior back if the new one is
+ // enabled by default.
+ if (StaticPrefs::
+ editor_join_split_direction_compatible_with_the_other_browsers() &&
+ !adjustedValue.EqualsLiteral("true") &&
+ !aSubjectPrincipal.IsSystemPrincipal()) {
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Do security check first.
+ if (commandData.IsCutOrCopyCommand()) {
+ if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
+ // We have rejected the event due to it not being performed in an
+ // input-driven context therefore, we report the error to the console.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ this, nsContentUtils::eDOM_PROPERTIES,
+ "ExecCommandCutCopyDeniedNotInputDriven");
+ return false;
+ }
+ } else if (commandData.IsPasteCommand()) {
+ if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
+ nsGkAtoms::clipboardRead)) {
+ return false;
+ }
+ }
+
+ AutoRunningExecCommandMarker markRunningExecCommand(*this);
+
+ // Next, consider context of command handling which is automatically resolved
+ // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
+ AutoEditorCommandTarget editCommandTarget(*this, commandData);
+ if (commandData.IsAvailableOnlyWhenEditable() &&
+ !editCommandTarget.IsEditable(this)) {
+ return false;
+ }
+
+ if (editCommandTarget.DoNothing()) {
+ return false;
+ }
+
+ // If we cannot use EditorCommand instance directly, we need to handle the
+ // command with traditional path (i.e., with DocShell or nsCommandManager).
+ if (!editCommandTarget.IsEditor()) {
+ MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
+
+ // Special case clipboard write commands like Command::Cut and
+ // Command::Copy. For such commands, we need the behaviour from
+ // nsWindowRoot::GetControllers() which is to look at the focused element,
+ // and defer to a focused textbox's controller. The code past taken by
+ // other commands in ExecCommand() always uses the window directly, rather
+ // than deferring to the textbox, which is desireable for most editor
+ // commands, but not these commands (as those should allow copying out of
+ // embedded editors). This behaviour is invoked if we call DoCommand()
+ // directly on the docShell.
+ // XXX This means that we allow web app to pick up selected content in
+ // descendant document and write it into the clipboard when a
+ // descendant document has focus. However, Chromium does not allow
+ // this and this seems that it's not good behavior from point of view
+ // of security. We should treat this issue in another bug.
+ if (commandData.IsCutOrCopyCommand()) {
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (!docShell) {
+ return false;
+ }
+ nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
+ if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+ return false;
+ }
+ return NS_SUCCEEDED(rv);
+ }
+
+ // Otherwise (currently, only clipboard read commands like Command::Paste),
+ // we don't need to redirect the command to focused subdocument.
+ // Therefore, we should handle it with nsCommandManager as used to be.
+ // It may dispatch only preceding event of editing on non-editable element
+ // to make web apps possible to handle standard shortcut key, etc in
+ // their own editor.
+ RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
+ if (!commandManager) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ if (!window) {
+ return false;
+ }
+
+ // Return false for disabled commands (bug 760052)
+ if (!commandManager->IsCommandEnabled(
+ nsDependentCString(commandData.mXULCommandName), window)) {
+ return false;
+ }
+
+ MOZ_ASSERT(commandData.IsPasteCommand() ||
+ commandData.mCommand == Command::SelectAll);
+ nsresult rv =
+ commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
+ return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // Now, our target is fixed to the editor. So, we can use EditorCommand
+ // in EditorCommandTarget directly.
+
+ EditorCommandParamType paramType =
+ EditorCommand::GetParamType(commandData.mCommand);
+
+ // If we don't have meaningful parameter or the EditorCommand does not
+ // require additional parameter, we can use `DoCommand()`.
+ if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
+ MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
+ nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
+ return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
+ // "true" or "false" here. So, we can use `DoCommandParam()` which takes
+ // a `bool` value.
+ if (!!(paramType & EditorCommandParamType::Bool)) {
+ MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
+ adjustedValue.EqualsLiteral("false"));
+ nsresult rv = editCommandTarget.DoCommandParam(
+ Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
+ return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
+ // in this case. However, `paramType` may contain both `String` and
+ // `CString` but in such case, we should use `DoCommandParam()` which
+ // takes `nsAString`. So, we should check whether `paramType` contains
+ // `String` or not first.
+ if (!!(paramType & EditorCommandParamType::String)) {
+ MOZ_ASSERT(!adjustedValue.IsVoid());
+ nsresult rv =
+ editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
+ return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // Finally, `paramType` should have `CString`. We should use
+ // `DoCommandParam()` which takes `nsACString`.
+ if (!!(paramType & EditorCommandParamType::CString)) {
+ NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
+ MOZ_ASSERT(!utf8Value.IsVoid());
+ nsresult rv =
+ editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
+ return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ MOZ_ASSERT_UNREACHABLE(
+ "Not yet implemented to handle new EditorCommandParamType");
+ return false;
+}
+
+bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "queryCommandEnabled is only supported on HTML documents");
+ return false;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
+ switch (commandData.mCommand) {
+ case Command::DoNothing:
+ return false;
+ case Command::SetDocumentReadOnly:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
+ break;
+ case Command::SetDocumentInsertBROnEnterKeyPress:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
+ break;
+ default:
+ break;
+ }
+
+ // cut & copy are always allowed
+ if (commandData.IsCutOrCopyCommand()) {
+ return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
+ }
+
+ // Report false for restricted commands
+ if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
+ return false;
+ }
+
+ AutoEditorCommandTarget editCommandTarget(*this, commandData);
+ if (commandData.IsAvailableOnlyWhenEditable() &&
+ !editCommandTarget.IsEditable(this)) {
+ return false;
+ }
+
+ if (editCommandTarget.IsEditor()) {
+ return editCommandTarget.IsCommandEnabled();
+ }
+
+ // get command manager and dispatch command to our window if it's acceptable
+ RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
+ if (!commandManager) {
+ return false;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return false;
+ }
+
+ return commandManager->IsCommandEnabled(
+ nsDependentCString(commandData.mXULCommandName), window);
+}
+
+bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
+ ErrorResult& aRv) {
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "queryCommandIndeterm is only supported on HTML documents");
+ return false;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
+ if (commandData.mCommand == Command::DoNothing) {
+ return false;
+ }
+
+ AutoEditorCommandTarget editCommandTarget(*this, commandData);
+ if (commandData.IsAvailableOnlyWhenEditable() &&
+ !editCommandTarget.IsEditable(this)) {
+ return false;
+ }
+ RefPtr<nsCommandParams> params = new nsCommandParams();
+ if (editCommandTarget.IsEditor()) {
+ if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
+ return false;
+ }
+ } else {
+ // get command manager and dispatch command to our window if it's acceptable
+ RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
+ if (!commandManager) {
+ return false;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return false;
+ }
+
+ if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
+ window, params))) {
+ return false;
+ }
+ }
+
+ // If command does not have a state_mixed value, this call fails and sets
+ // retval to false. This is fine -- we want to return false in that case
+ // anyway (bug 738385), so we just don't throw regardless.
+ return params->GetBool("state_mixed");
+}
+
+bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
+ ErrorResult& aRv) {
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "queryCommandState is only supported on HTML documents");
+ return false;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
+ switch (commandData.mCommand) {
+ case Command::DoNothing:
+ return false;
+ case Command::SetDocumentReadOnly:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
+ break;
+ case Command::SetDocumentInsertBROnEnterKeyPress:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
+ break;
+ default:
+ break;
+ }
+
+ if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
+ // Per spec, state is supported for styleWithCSS but not useCSS, so we just
+ // return false always.
+ return false;
+ }
+
+ AutoEditorCommandTarget editCommandTarget(*this, commandData);
+ if (commandData.IsAvailableOnlyWhenEditable() &&
+ !editCommandTarget.IsEditable(this)) {
+ return false;
+ }
+ RefPtr<nsCommandParams> params = new nsCommandParams();
+ if (editCommandTarget.IsEditor()) {
+ if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
+ return false;
+ }
+ } else {
+ // get command manager and dispatch command to our window if it's acceptable
+ RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
+ if (!commandManager) {
+ return false;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return false;
+ }
+
+ if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
+ window, params))) {
+ return false;
+ }
+ }
+
+ // handle alignment as a special case (possibly other commands too?)
+ // Alignment is special because the external api is individual
+ // commands but internally we use cmd_align with different
+ // parameters. When getting the state of this command, we need to
+ // return the boolean for this particular alignment rather than the
+ // string of 'which alignment is this?'
+ switch (commandData.mCommand) {
+ case Command::FormatJustifyLeft: {
+ nsAutoCString currentValue;
+ nsresult rv = params->GetCString("state_attribute", currentValue);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ return currentValue.EqualsLiteral("left");
+ }
+ case Command::FormatJustifyRight: {
+ nsAutoCString currentValue;
+ nsresult rv = params->GetCString("state_attribute", currentValue);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ return currentValue.EqualsLiteral("right");
+ }
+ case Command::FormatJustifyCenter: {
+ nsAutoCString currentValue;
+ nsresult rv = params->GetCString("state_attribute", currentValue);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ return currentValue.EqualsLiteral("center");
+ }
+ case Command::FormatJustifyFull: {
+ nsAutoCString currentValue;
+ nsresult rv = params->GetCString("state_attribute", currentValue);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ return currentValue.EqualsLiteral("justify");
+ }
+ default:
+ break;
+ }
+
+ // If command does not have a state_all value, this call fails and sets
+ // retval to false. This is fine -- we want to return false in that case
+ // anyway (bug 738385), so we just succeed and return false regardless.
+ return params->GetBool("state_all");
+}
+
+bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
+ CallerType aCallerType, ErrorResult& aRv) {
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "queryCommandSupported is only supported on HTML documents");
+ return false;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
+ switch (commandData.mCommand) {
+ case Command::DoNothing:
+ return false;
+ case Command::SetDocumentReadOnly:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
+ break;
+ case Command::SetDocumentInsertBROnEnterKeyPress:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
+ break;
+ default:
+ break;
+ }
+
+ // Gecko technically supports all the clipboard commands including
+ // cut/copy/paste, but non-privileged content will be unable to call
+ // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
+ // may also be disallowed to be called from non-privileged content.
+ // For that reason, we report the support status of corresponding
+ // command accordingly.
+ if (aCallerType != CallerType::System) {
+ if (commandData.IsPasteCommand()) {
+ return false;
+ }
+ if (commandData.IsCutOrCopyCommand() &&
+ !StaticPrefs::dom_allow_cut_copy()) {
+ // XXXbz should we worry about correctly reporting "true" in the
+ // "restricted, but we're an addon with clipboardWrite permissions" case?
+ // See also nsContentUtils::IsCutCopyAllowed.
+ return false;
+ }
+ }
+
+ // aHTMLCommandName is supported if it can be converted to a Midas command
+ return true;
+}
+
+void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
+ nsAString& aValue, ErrorResult& aRv) {
+ aValue.Truncate();
+
+ // Only allow on HTML documents.
+ if (!IsHTMLOrXHTML()) {
+ aRv.ThrowInvalidStateError(
+ "queryCommandValue is only supported on HTML documents");
+ return;
+ }
+ // Otherwise, don't throw exception for compatibility with Chrome.
+
+ InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
+ switch (commandData.mCommand) {
+ case Command::DoNothing:
+ // Return empty string
+ return;
+ case Command::SetDocumentReadOnly:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
+ break;
+ case Command::SetDocumentInsertBROnEnterKeyPress:
+ SetUseCounter(
+ eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
+ break;
+ default:
+ break;
+ }
+
+ AutoEditorCommandTarget editCommandTarget(*this, commandData);
+ if (commandData.IsAvailableOnlyWhenEditable() &&
+ !editCommandTarget.IsEditable(this)) {
+ return;
+ }
+ RefPtr<nsCommandParams> params = new nsCommandParams();
+ if (editCommandTarget.IsEditor()) {
+ if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
+ return;
+ }
+
+ if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
+ return;
+ }
+ } else {
+ // get command manager and dispatch command to our window if it's acceptable
+ RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
+ if (!commandManager) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ if (!window) {
+ return;
+ }
+
+ if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
+ return;
+ }
+
+ if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
+ window, params))) {
+ return;
+ }
+ }
+
+ // If command does not have a state_attribute value, this call fails, and
+ // aValue will wind up being the empty string. This is fine -- we want to
+ // return "" in that case anyway (bug 738385), so we just return NS_OK
+ // regardless.
+ nsAutoCString result;
+ params->GetCString("state_attribute", result);
+ CopyUTF8toUTF16(result, aValue);
+}
+
+void Document::MaybeEditingStateChanged() {
+ if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
+ mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
+ if (nsContentUtils::IsSafeToRunScript()) {
+ EditingStateChanged();
+ } else if (!mInDestructor) {
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod("Document::MaybeEditingStateChanged", this,
+ &Document::MaybeEditingStateChanged));
+ }
+ }
+}
+
+void Document::NotifyFetchOrXHRSuccess() {
+ if (mShouldNotifyFetchSuccess) {
+ nsContentUtils::DispatchEventOnlyToChrome(
+ this, ToSupports(this), u"DOMDocFetchSuccess"_ns, CanBubble::eNo,
+ Cancelable::eNo, /* DefaultAction */ nullptr);
+ }
+}
+
+void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
+ mShouldNotifyFetchSuccess = aShouldNotify;
+}
+
+void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
+ mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
+}
+
+void Document::TearingDownEditor() {
+ if (IsEditingOn()) {
+ mEditingState = EditingState::eTearingDown;
+ if (IsHTMLOrXHTML()) {
+ RemoveContentEditableStyleSheets();
+ }
+ }
+}
+
+nsresult Document::TurnEditingOff() {
+ NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIDocShell* docshell = window->GetDocShell();
+ if (!docshell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isBeingDestroyed = false;
+ docshell->IsBeingDestroyed(&isBeingDestroyed);
+ if (isBeingDestroyed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIEditingSession> editSession;
+ nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // turn editing off
+ rv = editSession->TearDownEditorOnWindow(window);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEditingState = EditingState::eOff;
+
+ // Editor resets selection since it is being destroyed. But if focus is
+ // still into editable control, we have to initialize selection again.
+ if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
+ if (RefPtr<TextControlElement> textControlElement =
+ TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
+ if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
+ textEditor->ReinitializeSelection(*textControlElement);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
+ nsIDocShell* docShell = aWindow->GetDocShell();
+ if (!docShell) {
+ return false;
+ }
+ return docShell->GetPresShell() != nullptr;
+}
+
+HTMLEditor* Document::GetHTMLEditor() const {
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ return nullptr;
+ }
+
+ nsIDocShell* docshell = window->GetDocShell();
+ if (!docshell) {
+ return nullptr;
+ }
+
+ return docshell->GetHTMLEditor();
+}
+
+nsresult Document::EditingStateChanged() {
+ if (mRemovedFromDocShell) {
+ return NS_OK;
+ }
+
+ if (mEditingState == EditingState::eSettingUp ||
+ mEditingState == EditingState::eTearingDown) {
+ // XXX We shouldn't recurse
+ return NS_OK;
+ }
+
+ const bool designMode = IsInDesignMode();
+ EditingState newState =
+ designMode ? EditingState::eDesignMode
+ : (mContentEditableCount > 0 ? EditingState::eContentEditable
+ : EditingState::eOff);
+ if (mEditingState == newState) {
+ // No changes in editing mode.
+ return NS_OK;
+ }
+
+ const bool thisDocumentHasFocus = ThisDocumentHasFocus();
+ if (newState == EditingState::eOff) {
+ // Editing is being turned off.
+ nsAutoScriptBlocker scriptBlocker;
+ RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
+ NotifyEditableStateChange(*this);
+ nsresult rv = TurnEditingOff();
+ // If this document has focus and the editing state of this document
+ // becomes "off", it means that HTMLEditor won't handle any inputs nor
+ // modify the DOM tree. However, HTMLEditor may not receive `blur`
+ // event for this state change since this may occur without focus change.
+ // Therefore, let's notify HTMLEditor of this editing state change.
+ // Note that even if focusedElement is an editable text control element,
+ // it becomes not editable from HTMLEditor point of view since text
+ // control elements are manged by TextEditor.
+ RefPtr<Element> focusedElement =
+ nsFocusManager::GetFocusManager()
+ ? nsFocusManager::GetFocusManager()->GetFocusedElement()
+ : nullptr;
+ DebugOnly<nsresult> rvIgnored =
+ HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
+ htmlEditor, *this, focusedElement);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but "
+ "ignored");
+ return rv;
+ }
+
+ // Flush out style changes on our _parent_ document, if any, so that
+ // our check for a presshell won't get stale information.
+ if (mParentDocument) {
+ mParentDocument->FlushPendingNotifications(FlushType::Style);
+ }
+
+ // get editing session, make sure this is a strong reference so the
+ // window can't get deleted during the rest of this call.
+ const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ if (!window) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIDocShell* docshell = window->GetDocShell();
+ if (!docshell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // FlushPendingNotifications might destroy our docshell.
+ bool isBeingDestroyed = false;
+ docshell->IsBeingDestroyed(&isBeingDestroyed);
+ if (isBeingDestroyed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIEditingSession> editSession;
+ nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
+ if (htmlEditor) {
+ // We might already have an editor if it was set up for mail, let's see
+ // if this is actually the case.
+ uint32_t flags = 0;
+ htmlEditor->GetFlags(&flags);
+ if (flags & nsIEditor::eEditorMailMask) {
+ // We already have a mail editor, then we should not attempt to create
+ // another one.
+ return NS_OK;
+ }
+ }
+
+ if (!HasPresShell(window)) {
+ // We should not make the window editable or setup its editor.
+ // It's probably style=display:none.
+ return NS_OK;
+ }
+
+ bool makeWindowEditable = mEditingState == EditingState::eOff;
+ bool spellRecheckAll = false;
+ bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
+ htmlEditor = nullptr;
+
+ {
+ EditingState oldState = mEditingState;
+ nsAutoEditingState push(this, EditingState::eSettingUp);
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ // If we're entering the design mode from non-editable state, put the
+ // selection at the beginning of the document for compatibility reasons.
+ bool collapseSelectionAtBeginningOfDocument =
+ designMode && oldState == EditingState::eOff;
+ // However, mEditingState may be eOff even if there is some
+ // `contenteditable` area and selection has been initialized for it because
+ // mEditingState for `contenteditable` may have been scheduled to modify
+ // when safe. In such case, we should not reinitialize selection.
+ if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
+ Selection* selection =
+ presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
+ NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
+ if (selection && selection->RangeCount()) {
+ // Perhaps, we don't need to check whether the selection is in
+ // an editing host or not because all contents will be editable
+ // in designMode. (And we don't want to make this code so complicated
+ // because of legacy API.)
+ collapseSelectionAtBeginningOfDocument = false;
+ }
+ }
+
+ MOZ_ASSERT(mStyleSetFilled);
+
+ // Before making this window editable, we need to modify UA style sheet
+ // because new style may change whether focused element will be focusable
+ // or not.
+ if (IsHTMLOrXHTML()) {
+ AddContentEditableStyleSheetsToStyleSet(designMode);
+ }
+
+ if (designMode) {
+ // designMode is being turned on (overrides contentEditable).
+ spellRecheckAll = oldState == EditingState::eContentEditable;
+ }
+
+ // Adjust focused element with new style but blur event shouldn't be fired
+ // until mEditingState is modified with newState.
+ nsAutoScriptBlocker scriptBlocker;
+ if (designMode) {
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
+ window, nsFocusManager::eOnlyCurrentWindow,
+ getter_AddRefs(focusedWindow));
+ if (focusedContent) {
+ nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
+ bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable()
+ : !focusedContent->IsFocusable();
+ if (clearFocus) {
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ fm->ClearFocus(window);
+ // If we need to dispatch blur event, we should put off after
+ // modifying mEditingState since blur event handler may change
+ // designMode state again.
+ putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
+ }
+ }
+ }
+ }
+
+ if (makeWindowEditable) {
+ // Editing is being turned on (through designMode or contentEditable)
+ // Turn on editor.
+ // XXX This can cause flushing which can change the editing state, so make
+ // sure to avoid recursing.
+ rv = editSession->MakeWindowEditable(window, "html", false, false, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // XXX Need to call TearDownEditorOnWindow for all failures.
+ htmlEditor = docshell->GetHTMLEditor();
+ if (!htmlEditor) {
+ // Return NS_OK even though we've failed to create an editor here. This
+ // is so that the setter of designMode on non-HTML documents does not
+ // fail.
+ // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
+ // would detect that we can't support the mimetype if appropriate and
+ // would fall onto the eEditorErrorCantEditMimeType path.
+ return NS_OK;
+ }
+
+ if (collapseSelectionAtBeginningOfDocument) {
+ htmlEditor->BeginningOfDocument();
+ }
+
+ if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
+ nsContentUtils::AddScriptBlocker();
+ }
+ }
+
+ mEditingState = newState;
+ if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
+ nsContentUtils::RemoveScriptBlocker();
+ // If mEditingState is overwritten by another call and already disabled
+ // the editing, we shouldn't keep making window editable.
+ if (mEditingState == EditingState::eOff) {
+ return NS_OK;
+ }
+ }
+
+ if (makeWindowEditable) {
+ // TODO: We should do this earlier in this method.
+ // Previously, we called `ExecCommand` with `insertBrOnReturn` command
+ // whose argument is false here. Then, if it returns error, we
+ // stopped making it editable. However, after bug 1697078 fixed,
+ // `ExecCommand` returns error only when the document is not XHTML's
+ // nor HTML's. Therefore, we use same error handling for now.
+ if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
+ // Editor setup failed. Editing is not on after all.
+ // XXX Should we reset the editable flag on nodes?
+ editSession->TearDownEditorOnWindow(window);
+ mEditingState = EditingState::eOff;
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ // Set the editor to not insert <br> elements on return when in <p> elements
+ // by default.
+ htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
+ }
+
+ // Resync the editor's spellcheck state.
+ if (spellRecheckAll) {
+ nsCOMPtr<nsISelectionController> selectionController =
+ htmlEditor->GetSelectionController();
+ if (NS_WARN_IF(!selectionController)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
+ nsISelectionController::SELECTION_SPELLCHECK);
+ if (spellCheckSelection) {
+ spellCheckSelection->RemoveAllRanges(IgnoreErrors());
+ }
+ }
+ htmlEditor->SyncRealTimeSpell();
+
+ MaybeDispatchCheckKeyPressEventModelEvent();
+
+ // If this document keeps having focus and the HTMLEditor is in the design
+ // mode, it may not receive `focus` event for this editing state change since
+ // this may occur without a focus change. Therefore, let's notify HTMLEditor
+ // of this editing state change.
+ if (thisDocumentHasFocus && htmlEditor->IsInDesignMode() &&
+ ThisDocumentHasFocus()) {
+ DebugOnly<nsresult> rvIgnored =
+ htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, nullptr);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+ "HTMLEditor::FocusedElementOrDocumentBecomesEditable()"
+ " failed, but ignored");
+ }
+
+ return NS_OK;
+}
+
+// Helper class, used below in ChangeContentEditableCount().
+class DeferredContentEditableCountChangeEvent : public Runnable {
+ public:
+ DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
+ : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
+ mDoc(aDoc),
+ mElement(aElement) {}
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ if (mElement && mElement->OwnerDoc() == mDoc) {
+ RefPtr<Document> doc = std::move(mDoc);
+ RefPtr<Element> element = std::move(mElement);
+ doc->DeferredContentEditableCountChange(element);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<Document> mDoc;
+ RefPtr<Element> mElement;
+};
+
+void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
+ NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
+ "Trying to decrement too much.");
+
+ mContentEditableCount += aChange;
+
+ nsContentUtils::AddScriptRunner(
+ new DeferredContentEditableCountChangeEvent(this, aElement));
+}
+
+void Document::DeferredContentEditableCountChange(Element* aElement) {
+ const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ const bool elementHasFocus =
+ aElement && fm && fm->GetFocusedElement() == aElement;
+ if (elementHasFocus) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+ // When contenteditable of aElement is changed and HTMLEditor works with it
+ // or needs to start working with it, HTMLEditor may not receive `focus`
+ // event nor `blur` event because this may occur without a focus change.
+ // Therefore, we need to notify HTMLEditor of this contenteditable attribute
+ // change.
+ RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
+ if (aElement->HasFlag(NODE_IS_EDITABLE)) {
+ if (htmlEditor) {
+ DebugOnly<nsresult> rvIgnored =
+ htmlEditor->FocusedElementOrDocumentBecomesEditable(*this,
+ aElement);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
+ "ignored");
+ }
+ } else {
+ DebugOnly<nsresult> rvIgnored =
+ HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
+ htmlEditor, *this, aElement);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, "
+ "but ignored");
+ }
+ }
+
+ if (mParser ||
+ (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
+ return;
+ }
+
+ EditingState oldState = mEditingState;
+
+ nsresult rv = EditingStateChanged();
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (oldState == mEditingState &&
+ mEditingState == EditingState::eContentEditable) {
+ // We just changed the contentEditable state of a node, we need to reset
+ // the spellchecking state of that node.
+ if (aElement) {
+ if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
+ nsCOMPtr<nsIInlineSpellChecker> spellChecker;
+ rv = htmlEditor->GetInlineSpellChecker(false,
+ getter_AddRefs(spellChecker));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (spellChecker &&
+ aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) {
+ RefPtr<nsRange> range = nsRange::Create(aElement);
+ IgnoredErrorResult res;
+ range->SelectNode(*aElement, res);
+ if (res.Failed()) {
+ // The node might be detached from the document at this point,
+ // which would cause this call to fail. In this case, we can
+ // safely ignore the contenteditable count change.
+ return;
+ }
+
+ rv = spellChecker->SpellCheckRange(range);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+ }
+ }
+
+ // aElement causes creating new HTMLEditor and the element had and keep
+ // having focus, the HTMLEditor won't receive `focus` event. Therefore, we
+ // need to notify HTMLEditor of it becomes editable.
+ if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) &&
+ fm->GetFocusedElement() == aElement) {
+ if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
+ DebugOnly<nsresult> rvIgnored =
+ htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
+ "ignored");
+ }
+ }
+}
+
+void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
+ // Currently, we need to check only when we're becoming editable for
+ // contenteditable.
+ if (mEditingState != EditingState::eContentEditable) {
+ return;
+ }
+
+ if (mHasBeenEditable) {
+ return;
+ }
+ mHasBeenEditable = true;
+
+ // Dispatch "CheckKeyPressEventModel" event. That is handled only by
+ // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
+ // with proper keypress event for the active web app.
+ WidgetEvent checkEvent(true, eUnidentifiedEvent);
+ checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
+ checkEvent.mFlags.mCancelable = false;
+ checkEvent.mFlags.mBubbles = false;
+ checkEvent.mFlags.mOnlySystemGroupDispatch = true;
+ // Post the event rather than dispatching it synchronously because we need
+ // a call of SetKeyPressEventModel() before first key input. Therefore, we
+ // can avoid paying unnecessary runtime cost for most web apps.
+ (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
+}
+
+void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return;
+ }
+ presShell->SetKeyPressEventModel(aKeyPressEventModel);
+}
+
+TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
+
+void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
+ MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
+ MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
+ aFocusTime >= mLastFocusTime);
+ mLastFocusTime = aFocusTime;
+}
+
+void Document::GetReferrer(nsAString& aReferrer) const {
+ aReferrer.Truncate();
+ if (!mReferrerInfo) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
+ if (!referrer) {
+ return;
+ }
+
+ nsAutoCString uri;
+ nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ CopyUTF8toUTF16(uri, aReferrer);
+}
+
+void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
+ aCookie.Truncate(); // clear current cookie in case service fails;
+ // no cookie isn't an error condition.
+
+ if (mDisableCookieAccess) {
+ return;
+ }
+
+ // If the document's sandboxed origin flag is set, then reading cookies
+ // is prohibited.
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ aRv.ThrowSecurityError(
+ "Forbidden in a sandboxed document without the 'allow-same-origin' "
+ "flag.");
+ return;
+ }
+
+ StorageAccess storageAccess = CookieAllowedForDocument(this);
+ if (storageAccess == StorageAccess::eDeny) {
+ return;
+ }
+
+ if (ShouldPartitionStorage(storageAccess) &&
+ !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
+ return;
+ }
+
+ // If the document is a cookie-averse Document... return the empty string.
+ if (IsCookieAverse()) {
+ return;
+ }
+
+ // not having a cookie service isn't an error
+ nsCOMPtr<nsICookieService> service =
+ do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ if (service) {
+ nsAutoCString cookie;
+ service->GetCookieStringFromDocument(this, cookie);
+ // CopyUTF8toUTF16 doesn't handle error
+ // because it assumes that the input is valid.
+ UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
+ }
+}
+
+void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
+ if (mDisableCookieAccess) {
+ return;
+ }
+
+ // If the document's sandboxed origin flag is set, then setting cookies
+ // is prohibited.
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ aRv.ThrowSecurityError(
+ "Forbidden in a sandboxed document without the 'allow-same-origin' "
+ "flag.");
+ return;
+ }
+
+ StorageAccess storageAccess = CookieAllowedForDocument(this);
+ if (storageAccess == StorageAccess::eDeny) {
+ return;
+ }
+
+ if (ShouldPartitionStorage(storageAccess) &&
+ !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
+ return;
+ }
+
+ // If the document is a cookie-averse Document... do nothing.
+ if (IsCookieAverse()) {
+ return;
+ }
+
+ if (!mDocumentURI) {
+ return;
+ }
+
+ // not having a cookie service isn't an error
+ nsCOMPtr<nsICookieService> service =
+ do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ if (!service) {
+ return;
+ }
+
+ NS_ConvertUTF16toUTF8 cookie(aCookie);
+ nsresult rv = service->SetCookieStringFromDocument(this, cookie);
+
+ // No warning messages here.
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
+ nsString(aCookie).get());
+ }
+}
+
+ReferrerPolicy Document::GetReferrerPolicy() const {
+ return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
+ : ReferrerPolicy::_empty;
+}
+
+void Document::GetAlinkColor(nsAString& aAlinkColor) {
+ aAlinkColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetALink(aAlinkColor);
+ }
+}
+
+void Document::SetAlinkColor(const nsAString& aAlinkColor) {
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetALink(aAlinkColor);
+ }
+}
+
+void Document::GetLinkColor(nsAString& aLinkColor) {
+ aLinkColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetLink(aLinkColor);
+ }
+}
+
+void Document::SetLinkColor(const nsAString& aLinkColor) {
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetLink(aLinkColor);
+ }
+}
+
+void Document::GetVlinkColor(nsAString& aVlinkColor) {
+ aVlinkColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetVLink(aVlinkColor);
+ }
+}
+
+void Document::SetVlinkColor(const nsAString& aVlinkColor) {
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetVLink(aVlinkColor);
+ }
+}
+
+void Document::GetBgColor(nsAString& aBgColor) {
+ aBgColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetBgColor(aBgColor);
+ }
+}
+
+void Document::SetBgColor(const nsAString& aBgColor) {
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetBgColor(aBgColor);
+ }
+}
+
+void Document::GetFgColor(nsAString& aFgColor) {
+ aFgColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetText(aFgColor);
+ }
+}
+
+void Document::SetFgColor(const nsAString& aFgColor) {
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetText(aFgColor);
+ }
+}
+
+void Document::CaptureEvents() {
+ WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
+}
+
+void Document::ReleaseEvents() {
+ WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
+}
+
+HTMLAllCollection* Document::All() {
+ if (!mAll) {
+ mAll = new HTMLAllCollection(this);
+ }
+ return mAll;
+}
+
+nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
+ if (mIsSrcdocDocument) {
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
+ if (inStrmChan) {
+ return inStrmChan->GetSrcdocData(aSrcdocData);
+ }
+ }
+ aSrcdocData = VoidString();
+ return NS_OK;
+}
+
+Nullable<WindowProxyHolder> Document::GetDefaultView() const {
+ nsPIDOMWindowOuter* win = GetWindow();
+ if (!win) {
+ return nullptr;
+ }
+ return WindowProxyHolder(win->GetBrowsingContext());
+}
+
+nsIContent* Document::GetUnretargetedFocusedContent(
+ IncludeChromeOnly aIncludeChromeOnly) const {
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ if (!window) {
+ return nullptr;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
+ window, nsFocusManager::eOnlyCurrentWindow,
+ getter_AddRefs(focusedWindow));
+ if (!focusedContent) {
+ return nullptr;
+ }
+ // be safe and make sure the element is from this document
+ if (focusedContent->OwnerDoc() != this) {
+ return nullptr;
+ }
+ if (focusedContent->ChromeOnlyAccess() &&
+ aIncludeChromeOnly == IncludeChromeOnly::No) {
+ return focusedContent->FindFirstNonChromeOnlyAccessContent();
+ }
+ return focusedContent;
+}
+
+Element* Document::GetActiveElement() {
+ // Get the focused element.
+ Element* focusedElement = GetRetargetedFocusedElement();
+ if (focusedElement) {
+ return focusedElement;
+ }
+
+ // No focused element anywhere in this document. Try to get the BODY.
+ if (IsHTMLOrXHTML()) {
+ Element* bodyElement = AsHTMLDocument()->GetBody();
+ if (bodyElement) {
+ return bodyElement;
+ }
+ // Special case to handle the transition to XHTML from XUL documents
+ // where there currently isn't a body element, but we need to match the
+ // XUL behavior. This should be removed when bug 1540278 is resolved.
+ if (nsContentUtils::IsChromeDoc(this)) {
+ Element* docElement = GetDocumentElement();
+ if (docElement && docElement->IsXULElement()) {
+ return docElement;
+ }
+ }
+ // Because of IE compatibility, return null when html document doesn't have
+ // a body.
+ return nullptr;
+ }
+
+ // If we couldn't get a BODY, return the root element.
+ return GetDocumentElement();
+}
+
+Element* Document::GetCurrentScript() {
+ nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
+ return el;
+}
+
+void Document::ReleaseCapture() const {
+ // only release the capture if the caller can access it. This prevents a
+ // page from stopping a scrollbar grab for example.
+ nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
+ if (node && nsContentUtils::CanCallerAccess(node)) {
+ PresShell::ReleaseCapturingContent();
+ }
+}
+
+nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
+ if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
+ return mChromeXHRDocBaseURI;
+ }
+
+ return GetDocBaseURI();
+}
+
+void Document::SetBaseURI(nsIURI* aURI) {
+ if (!aURI && !mDocumentBaseURI) {
+ return;
+ }
+
+ // Don't do anything if the URI wasn't actually changed.
+ if (aURI && mDocumentBaseURI) {
+ bool equalBases = false;
+ mDocumentBaseURI->Equals(aURI, &equalBases);
+ if (equalBases) {
+ return;
+ }
+ }
+
+ mDocumentBaseURI = aURI;
+ mCachedURLData = nullptr;
+ RefreshLinkHrefs();
+}
+
+Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
+ const nsAString& aURI) {
+ RefPtr<nsIURI> resolvedURI;
+ MOZ_TRY(
+ NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
+ return OwningNonNull<nsIURI>(std::move(resolvedURI));
+}
+
+nsIReferrerInfo* Document::ReferrerInfoForInternalCSSAndSVGResources() {
+ if (!mCachedReferrerInfoForInternalCSSAndSVGResources) {
+ mCachedReferrerInfoForInternalCSSAndSVGResources =
+ ReferrerInfo::CreateForInternalCSSAndSVGResources(this);
+ }
+ return mCachedReferrerInfoForInternalCSSAndSVGResources;
+}
+
+URLExtraData* Document::DefaultStyleAttrURLData() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mCachedURLData) {
+ mCachedURLData = new URLExtraData(
+ GetDocBaseURI(), ReferrerInfoForInternalCSSAndSVGResources(),
+ NodePrincipal());
+ }
+ return mCachedURLData;
+}
+
+void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
+ if (mCharacterSet != aEncoding) {
+ mCharacterSet = aEncoding;
+ mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
+ RecomputeLanguageFromCharset();
+
+ if (nsPresContext* context = GetPresContext()) {
+ context->DocumentCharSetChanged(aEncoding);
+ }
+ }
+}
+
+void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
+ nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
+}
+
+void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
+ aData.Truncate();
+ const HeaderData* data = mHeaderData.get();
+ while (data) {
+ if (data->mField == aHeaderField) {
+ aData = data->mData;
+ break;
+ }
+ data = data->mNext.get();
+ }
+}
+
+void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
+ if (!aHeaderField) {
+ NS_ERROR("null headerField");
+ return;
+ }
+
+ if (!mHeaderData) {
+ if (!aData.IsEmpty()) { // don't bother storing empty string
+ mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
+ }
+ } else {
+ HeaderData* data = mHeaderData.get();
+ UniquePtr<HeaderData>* lastPtr = &mHeaderData;
+ bool found = false;
+ do { // look for existing and replace
+ if (data->mField == aHeaderField) {
+ if (!aData.IsEmpty()) {
+ data->mData.Assign(aData);
+ } else { // don't store empty string
+ // Note that data->mNext is moved to a temporary before the old value
+ // of *lastPtr is deleted.
+ *lastPtr = std::move(data->mNext);
+ }
+ found = true;
+
+ break;
+ }
+ lastPtr = &data->mNext;
+ data = lastPtr->get();
+ } while (data);
+
+ if (!aData.IsEmpty() && !found) {
+ // didn't find, append
+ *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
+ }
+ }
+
+ if (aHeaderField == nsGkAtoms::headerContentLanguage) {
+ CopyUTF16toUTF8(aData, mContentLanguage);
+ mMayNeedFontPrefsUpdate = true;
+ if (auto* presContext = GetPresContext()) {
+ presContext->ContentLanguageChanged();
+ }
+ }
+
+ if (aHeaderField == nsGkAtoms::origin_trial) {
+ mTrials.UpdateFromToken(aData, NodePrincipal());
+ if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) {
+ InitCOEP(mChannel);
+
+ // If we still don't have a WindowContext, WindowContext::OnNewDocument
+ // will take care of this.
+ if (WindowContext* ctx = GetWindowContext()) {
+ if (mEmbedderPolicy) {
+ Unused << ctx->SetEmbedderPolicy(mEmbedderPolicy.value());
+ }
+ }
+ }
+ }
+
+ if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
+ SetPreferredStyleSheetSet(aData);
+ }
+
+ if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
+ // We get into this code before we have a script global yet, so get to our
+ // container via mDocumentContainer.
+ if (mDocumentContainer) {
+ // Note: using mDocumentURI instead of mBaseURI here, for consistency
+ // (used to just use the current URI of our webnavigation, but that
+ // should really be the same thing). Note that this code can run
+ // before the current URI of the webnavigation has been updated, so we
+ // can't assert equality here.
+ mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
+ }
+ }
+
+ if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
+ mAllowDNSPrefetch) {
+ // Chromium treats any value other than 'on' (case insensitive) as 'off'.
+ mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
+ }
+
+ if (aHeaderField == nsGkAtoms::handheldFriendly) {
+ mViewportType = Unknown;
+ }
+}
+
+void Document::SetEarlyHints(
+ nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) {
+ mEarlyHints = std::move(aEarlyHints);
+}
+
+void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
+ NotNull<const Encoding*>& aEncoding,
+ nsHtml5TreeOpExecutor* aExecutor) {
+ if (aChannel) {
+ nsAutoCString charsetVal;
+ nsresult rv = aChannel->GetContentCharset(charsetVal);
+ if (NS_SUCCEEDED(rv)) {
+ const Encoding* preferred = Encoding::ForLabel(charsetVal);
+ if (preferred) {
+ if (aExecutor && preferred == REPLACEMENT_ENCODING) {
+ aExecutor->ComplainAboutBogusProtocolCharset(this, false);
+ }
+ aEncoding = WrapNotNull(preferred);
+ aCharsetSource = kCharsetFromChannel;
+ return;
+ } else if (aExecutor && !charsetVal.IsEmpty()) {
+ aExecutor->ComplainAboutBogusProtocolCharset(this, true);
+ }
+ }
+ }
+}
+
+static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
+#ifdef DEBUG
+ for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
+ const Element* element = Element::FromNode(node);
+ if (!element) {
+ continue;
+ }
+ MOZ_ASSERT(!element->HasServoData());
+ }
+#endif
+}
+
+already_AddRefed<PresShell> Document::CreatePresShell(
+ nsPresContext* aContext, nsViewManager* aViewManager) {
+ MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
+
+ NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
+
+ AssertNoStaleServoDataIn(*this);
+
+ RefPtr<PresShell> presShell = new PresShell(this);
+ // Note: we don't hold a ref to the shell (it holds a ref to us)
+ mPresShell = presShell;
+
+ if (!mStyleSetFilled) {
+ FillStyleSet();
+ }
+
+ presShell->Init(aContext, aViewManager);
+ if (RefPtr<class HighlightRegistry> highlightRegistry = mHighlightRegistry) {
+ highlightRegistry->AddHighlightSelectionsToFrameSelection();
+ }
+ // Gaining a shell causes changes in how media queries are evaluated, so
+ // invalidate that.
+ aContext->MediaFeatureValuesChanged(
+ {MediaFeatureChange::kAllChanges},
+ MediaFeatureChangePropagation::JustThisDocument);
+
+ // Make sure to never paint if we belong to an invisible DocShell.
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (docShell && docShell->IsInvisible()) {
+ presShell->SetNeverPainting(true);
+ }
+
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
+ ("DOCUMENT %p with PressShell %p and DocShell %p", this,
+ presShell.get(), docShell.get()));
+
+ mExternalResourceMap.ShowViewers();
+
+ UpdateFrameRequestCallbackSchedulingState();
+
+ if (mDocumentL10n) {
+ // In case we already accumulated mutations,
+ // we'll trigger the refresh driver now.
+ mDocumentL10n->OnCreatePresShell();
+ }
+
+ if (HasAutoFocusCandidates()) {
+ ScheduleFlushAutoFocusCandidates();
+ }
+ // Now that we have a shell, we might have @font-face rules (the presence of a
+ // shell may change which rules apply to us). We don't need to do anything
+ // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
+ // is ready to update we'll flush the font set.
+ MarkUserFontSetDirty();
+
+ // Take the author style disabled state from the top browsing cvontext.
+ // (PageStyleChild.sys.mjs ensures this is up to date.)
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
+ }
+
+ return presShell.forget();
+}
+
+void Document::UpdateFrameRequestCallbackSchedulingState(
+ PresShell* aOldPresShell) {
+ // If this condition changes to depend on some other variable, make sure to
+ // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
+ // that variable can change. Also consider if you should change
+ // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
+ // condition.
+ bool shouldBeScheduled =
+ WouldScheduleFrameRequestCallbacks() && !mFrameRequestManager.IsEmpty();
+ if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
+ // nothing to do
+ return;
+ }
+
+ PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
+ MOZ_RELEASE_ASSERT(presShell);
+
+ nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
+ if (shouldBeScheduled) {
+ rd->ScheduleFrameRequestCallbacks(this);
+ } else {
+ rd->RevokeFrameRequestCallbacks(this);
+ }
+
+ mFrameRequestCallbacksScheduled = shouldBeScheduled;
+}
+
+void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
+ MOZ_ASSERT(aCallbacks.IsEmpty());
+ mFrameRequestManager.Take(aCallbacks);
+ // No need to manually remove ourselves from the refresh driver; it will
+ // handle that part. But we do have to update our state.
+ mFrameRequestCallbacksScheduled = false;
+}
+
+bool Document::ShouldThrottleFrameRequests() const {
+ if (mStaticCloneCount > 0) {
+ // Even if we're not visible, a static clone may be, so run at full speed.
+ return false;
+ }
+
+ if (Hidden()) {
+ // We're not visible (probably in a background tab or the bf cache).
+ return true;
+ }
+
+ if (!mPresShell) {
+ // Can't do anything smarter. We don't run frame requests in documents
+ // without a pres shell anyways.
+ return false;
+ }
+
+ if (!mPresShell->IsActive()) {
+ // The pres shell is not active (we're an invisible OOP iframe or such), so
+ // throttle.
+ return true;
+ }
+
+ if (mPresShell->IsPaintingSuppressed()) {
+ // Historically we have throttled frame requests until we've painted at
+ // least once, so keep doing that.
+ return true;
+ }
+
+ Element* el = GetEmbedderElement();
+ if (!el) {
+ // If we're not in-process, our refresh driver is throttled separately (via
+ // PresShell::SetIsActive, so not much more we can do here.
+ return false;
+ }
+
+ if (!StaticPrefs::layout_throttle_in_process_iframes()) {
+ return false;
+ }
+
+ // Note that because we have to scroll this document into view at least once
+ // to unthrottle it, we will drop one requestAnimationFrame frame when a
+ // document that previously wasn't visible scrolls into view. This is
+ // acceptable / unlikely to be human-perceivable, though we could improve on
+ // it if needed by adding an intersection margin or something of that sort.
+ const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
+ *el->OwnerDoc(), /* aRoot = */ nullptr, /* aMargin = */ nullptr);
+ const IntersectionOutput output =
+ DOMIntersectionObserver::Intersect(input, *el);
+ return !output.Intersects();
+}
+
+void Document::DeletePresShell() {
+ mExternalResourceMap.HideViewers();
+ if (nsPresContext* presContext = mPresShell->GetPresContext()) {
+ presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
+ presContext->RefreshDriver()->CancelFlushAutoFocus(this);
+ }
+
+ // When our shell goes away, request that all our images be immediately
+ // discarded, so we don't carry around decoded image data for a document we
+ // no longer intend to paint.
+ ImageTracker()->RequestDiscardAll();
+
+ // Now that we no longer have a shell, we need to forget about any FontFace
+ // objects for @font-face rules that came from the style set. There's no need
+ // to call EnsureStyleFlush either, the shell is going away anyway, so there's
+ // no point on it.
+ MarkUserFontSetDirty();
+
+ if (mResizeObserverController) {
+ mResizeObserverController->ShellDetachedFromDocument();
+ }
+
+ if (IsEditingOn()) {
+ TurnEditingOff();
+ }
+
+ PresShell* oldPresShell = mPresShell;
+ mPresShell = nullptr;
+ UpdateFrameRequestCallbackSchedulingState(oldPresShell);
+
+ ClearStaleServoData();
+ AssertNoStaleServoDataIn(*this);
+
+ mStyleSet->ShellDetachedFromDocument();
+ mStyleSetFilled = false;
+ mQuirkSheetAdded = false;
+ mContentEditableSheetAdded = false;
+ mDesignModeSheetAdded = false;
+}
+
+void Document::DisallowBFCaching(uint32_t aStatus) {
+ NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
+ if (!mBFCacheDisallowed) {
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->SendUpdateBFCacheStatus(aStatus, 0);
+ }
+ }
+ mBFCacheDisallowed = true;
+}
+
+void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
+ MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
+
+ if (mPresShell) {
+ if (aEntry) {
+ mPresShell->StopObservingRefreshDriver();
+ } else if (mBFCacheEntry) {
+ mPresShell->StartObservingRefreshDriver();
+ }
+ }
+ mBFCacheEntry = aEntry;
+}
+
+bool Document::RemoveFromBFCacheSync() {
+ bool removed = false;
+ if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
+ entry->RemoveFromBFCacheSync();
+ removed = true;
+ } else if (!IsCurrentActiveDocument()) {
+ // In the old bfcache implementation while the new page is loading, but
+ // before nsIContentViewer.show() has been called, the previous page doesn't
+ // yet have nsIBFCacheEntry. However, the previous page isn't the current
+ // active document anymore.
+ DisallowBFCaching();
+ removed = true;
+ }
+
+ if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ if (bc->IsInBFCache()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ // IPC is asynchronous but the caller is supposed to check the return
+ // value. The reason for 'Sync' in the method name is that the old
+ // implementation may run scripts. There is Async variant in
+ // the old session history implementation for the cases where
+ // synchronous operation isn't safe.
+ cc->SendRemoveFromBFCache(bc->Top());
+ removed = true;
+ }
+ }
+ }
+ return removed;
+}
+
+static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
+ SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
+
+ NS_RELEASE(e->mKey);
+ if (e->mSubDocument) {
+ e->mSubDocument->SetParentDocument(nullptr);
+ NS_RELEASE(e->mSubDocument);
+ }
+}
+
+static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
+ SubDocMapEntry* e =
+ const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
+
+ e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
+ NS_ADDREF(e->mKey);
+
+ e->mSubDocument = nullptr;
+}
+
+nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
+ NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
+
+ if (!aSubDoc) {
+ // aSubDoc is nullptr, remove the mapping
+
+ if (mSubDocuments) {
+ mSubDocuments->Remove(aElement);
+ }
+ } else {
+ if (!mSubDocuments) {
+ // Create a new hashtable
+
+ static const PLDHashTableOps hash_table_ops = {
+ PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
+ PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
+
+ mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
+ }
+
+ // Add a mapping to the hash table
+ auto entry =
+ static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
+
+ if (!entry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (entry->mSubDocument) {
+ entry->mSubDocument->SetParentDocument(nullptr);
+
+ // Release the old sub document
+ NS_RELEASE(entry->mSubDocument);
+ }
+
+ entry->mSubDocument = aSubDoc;
+ NS_ADDREF(entry->mSubDocument);
+
+ aSubDoc->SetParentDocument(this);
+ }
+
+ return NS_OK;
+}
+
+Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
+ if (mSubDocuments && aContent->IsElement()) {
+ auto entry = static_cast<SubDocMapEntry*>(
+ mSubDocuments->Search(aContent->AsElement()));
+
+ if (entry) {
+ return entry->mSubDocument;
+ }
+ }
+
+ return nullptr;
+}
+
+Element* Document::GetEmbedderElement() const {
+ // We check if we're the active document in our BrowsingContext
+ // by comparing against its document, rather than checking if the
+ // WindowContext is cached, since mWindow may be null when we're
+ // called (such as in nsPresContext::Init).
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
+ }
+
+ return nullptr;
+}
+
+Element* Document::GetRootElement() const {
+ return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
+ ? mCachedRootElement
+ : GetRootElementInternal();
+}
+
+Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
+
+Element* Document::GetRootElementInternal() const {
+ // We invoke GetRootElement() immediately before the servo traversal, so we
+ // should always have a cache hit from Servo.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Loop backwards because any non-elements, such as doctypes and PIs
+ // are likely to appear before the root element.
+ for (nsIContent* child = GetLastChild(); child;
+ child = child->GetPreviousSibling()) {
+ if (Element* element = Element::FromNode(child)) {
+ const_cast<Document*>(this)->mCachedRootElement = element;
+ return element;
+ }
+ }
+
+ const_cast<Document*>(this)->mCachedRootElement = nullptr;
+ return nullptr;
+}
+
+void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
+ bool aNotify, ErrorResult& aRv) {
+ if (aKid->IsElement() && GetRootElement()) {
+ NS_WARNING("Inserting root element when we already have one");
+ aRv.ThrowHierarchyRequestError("There is already a root element.");
+ return;
+ }
+
+ nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
+}
+
+void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
+ Maybe<mozAutoDocUpdate> updateBatch;
+ if (aKid->IsElement()) {
+ updateBatch.emplace(this, aNotify);
+ // Destroy the link map up front before we mess with the child list.
+ DestroyElementMaps();
+ }
+
+ // Preemptively clear mCachedRootElement, since we may be about to remove it
+ // from our child list, and we don't want to return this maybe-obsolete value
+ // from any GetRootElement() calls that happen inside of RemoveChildNode().
+ // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
+ // GetRootElement() calls until after it's removed the child from mChildren.
+ // Any call before that point would restore this soon-to-be-obsolete cached
+ // answer, and our clearing here would be fruitless.)
+ mCachedRootElement = nullptr;
+ nsINode::RemoveChildNode(aKid, aNotify);
+ MOZ_ASSERT(mCachedRootElement != aKid,
+ "Stale pointer in mCachedRootElement, after we tried to clear it "
+ "(maybe somebody called GetRootElement() too early?)");
+}
+
+void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
+ if (mStyleSetFilled) {
+ mStyleSet->AddDocStyleSheet(aSheet);
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
+ mStyleSet->RecordShadowStyleChange(aShadowRoot);
+ ApplicableStylesChanged();
+}
+
+void Document::ApplicableStylesChanged() {
+ // TODO(emilio): if we decide to resolve style in display: none iframes, then
+ // we need to always track style changes and remove the mStyleSetFilled.
+ if (!mStyleSetFilled) {
+ return;
+ }
+
+ MarkUserFontSetDirty();
+ PresShell* ps = GetPresShell();
+ if (!ps) {
+ return;
+ }
+
+ ps->EnsureStyleFlush();
+ nsPresContext* pc = ps->GetPresContext();
+ if (!pc) {
+ return;
+ }
+
+ pc->MarkCounterStylesDirty();
+ pc->MarkFontFeatureValuesDirty();
+ pc->MarkFontPaletteValuesDirty();
+ pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
+}
+
+void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
+ if (mStyleSetFilled) {
+ mStyleSet->RemoveStyleSheet(aSheet);
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
+ DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
+
+ if (aSheet.IsApplicable()) {
+ AddStyleSheetToStyleSets(aSheet);
+ }
+}
+
+void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
+ const bool applicable = aSheet.IsApplicable();
+ // If we're actually in the document style sheet list
+ if (StyleOrderIndexOfSheet(aSheet) >= 0) {
+ if (applicable) {
+ AddStyleSheetToStyleSets(aSheet);
+ } else {
+ RemoveStyleSheetFromStyleSets(aSheet);
+ }
+ }
+
+ PostStyleSheetApplicableStateChangeEvent(aSheet);
+
+ if (!mSSApplicableStateNotificationPending) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(
+ "Document::NotifyStyleSheetApplicableStateChanged", this,
+ &Document::NotifyStyleSheetApplicableStateChanged);
+ mSSApplicableStateNotificationPending =
+ NS_SUCCEEDED(Dispatch(TaskCategory::Other, notification.forget()));
+ }
+}
+
+void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
+ if (!StyleSheetChangeEventsEnabled()) {
+ return;
+ }
+
+ StyleSheetApplicableStateChangeEventInit init;
+ init.mBubbles = true;
+ init.mCancelable = true;
+ init.mStylesheet = &aSheet;
+ init.mApplicable = aSheet.IsApplicable();
+
+ RefPtr<StyleSheetApplicableStateChangeEvent> event =
+ StyleSheetApplicableStateChangeEvent::Constructor(
+ this, u"StyleSheetApplicableStateChanged"_ns, init);
+ event->SetTrusted(true);
+ event->SetTarget(this);
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, event);
+ asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
+ asyncDispatcher->PostDOMEvent();
+}
+
+void Document::NotifyStyleSheetApplicableStateChanged() {
+ mSSApplicableStateNotificationPending = false;
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(
+ ToSupports(this), "style-sheet-applicable-state-changed", nullptr);
+ }
+}
+
+static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
+ nsIURI* aSheetURI) {
+ for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
+ bool bEqual;
+ nsIURI* uri = aSheets[i]->GetSheetURI();
+
+ if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
+ return i;
+ }
+
+ return -1;
+}
+
+nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
+ nsIURI* aSheetURI) {
+ MOZ_ASSERT(aSheetURI, "null arg");
+
+ // Checking if we have loaded this one already.
+ if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
+ return NS_ERROR_INVALID_ARG;
+
+ // Loading the sheet sync.
+ RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
+
+ css::SheetParsingMode parsingMode;
+ switch (aType) {
+ case Document::eAgentSheet:
+ parsingMode = css::eAgentSheetFeatures;
+ break;
+
+ case Document::eUserSheet:
+ parsingMode = css::eUserSheetFeatures;
+ break;
+
+ case Document::eAuthorSheet:
+ parsingMode = css::eAuthorSheetFeatures;
+ break;
+
+ default:
+ MOZ_CRASH("impossible value for aType");
+ }
+
+ auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
+ css::Loader::UseSystemPrincipal::Yes);
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+
+ RefPtr<StyleSheet> sheet = result.unwrap();
+
+ sheet->SetAssociatedDocumentOrShadowRoot(this);
+ MOZ_ASSERT(sheet->IsApplicable());
+
+ return AddAdditionalStyleSheet(aType, sheet);
+}
+
+nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
+ StyleSheet* aSheet) {
+ if (mAdditionalSheets[aType].Contains(aSheet)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!aSheet->IsApplicable()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mAdditionalSheets[aType].AppendElement(aSheet);
+
+ if (mStyleSetFilled) {
+ mStyleSet->AppendStyleSheet(*aSheet);
+ ApplicableStylesChanged();
+ }
+ return NS_OK;
+}
+
+void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
+ nsIURI* aSheetURI) {
+ MOZ_ASSERT(aSheetURI);
+
+ nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
+
+ int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
+ if (i >= 0) {
+ RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
+ sheets.RemoveElementAt(i);
+
+ if (!mIsGoingAway) {
+ MOZ_ASSERT(sheetRef->IsApplicable());
+ if (mStyleSetFilled) {
+ mStyleSet->RemoveStyleSheet(*sheetRef);
+ ApplicableStylesChanged();
+ }
+ }
+ sheetRef->ClearAssociatedDocumentOrShadowRoot();
+ }
+}
+
+nsIGlobalObject* Document::GetScopeObject() const {
+ nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
+ return scope;
+}
+
+DocGroup* Document::GetDocGroupOrCreate() {
+ if (!mDocGroup && GetBrowsingContext()) {
+ BrowsingContextGroup* group = GetBrowsingContext()->Group();
+ MOZ_ASSERT(group);
+
+ nsAutoCString docGroupKey;
+ nsresult rv = mozilla::dom::DocGroup::GetKey(
+ NodePrincipal(), group->IsPotentiallyCrossOriginIsolated(),
+ docGroupKey);
+ if (NS_SUCCEEDED(rv)) {
+ mDocGroup = group->AddDocument(docGroupKey, this);
+ }
+ }
+ return mDocGroup;
+}
+
+void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
+ mScopeObject = do_GetWeakReference(aGlobal);
+ if (aGlobal) {
+ mHasHadScriptHandlingObject = true;
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
+ if (!window) {
+ return;
+ }
+ BrowsingContextGroup* browsingContextGroup =
+ window->GetBrowsingContextGroup();
+
+ // We should already have the principal, and now that we have been added
+ // to a window, we should be able to join a DocGroup!
+ nsAutoCString docGroupKey;
+ nsresult rv = mozilla::dom::DocGroup::GetKey(
+ NodePrincipal(),
+ browsingContextGroup->IsPotentiallyCrossOriginIsolated(), docGroupKey);
+ if (mDocGroup) {
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
+ }
+ MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
+ browsingContextGroup);
+ } else {
+ mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
+
+ MOZ_ASSERT(mDocGroup);
+ }
+
+ MOZ_ASSERT_IF(
+ mNodeInfoManager->GetArenaAllocator(),
+ mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
+ }
+}
+
+bool Document::ContainsEMEContent() {
+ nsPIDOMWindowInner* win = GetInnerWindow();
+ // Note this case is different from checking just media elements in that
+ // it covers when we've created MediaKeys but not associated them with a
+ // media element.
+ return win && win->HasActiveMediaKeysInstance();
+}
+
+bool Document::ContainsMSEContent() {
+ bool containsMSE = false;
+
+ auto check = [&containsMSE](nsISupports* aSupports) {
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
+ if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
+ RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
+ if (ms) {
+ containsMSE = true;
+ }
+ }
+ };
+
+ EnumerateActivityObservers(check);
+ return containsMSE;
+}
+
+static void NotifyActivityChangedCallback(nsISupports* aSupports) {
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
+ if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
+ mediaElem->NotifyOwnerDocumentActivityChanged();
+ }
+ nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
+ do_QueryInterface(aSupports));
+ if (objectLoadingContent) {
+ nsObjectLoadingContent* olc =
+ static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
+ olc->NotifyOwnerDocumentActivityChanged();
+ }
+ nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
+ do_QueryInterface(aSupports));
+ if (objectDocumentActivity) {
+ objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
+ } else {
+ nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
+ do_QueryInterface(aSupports));
+ if (imageLoadingContent) {
+ auto ilc = static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
+ ilc->NotifyOwnerDocumentActivityChanged();
+ }
+ }
+}
+
+void Document::NotifyActivityChanged() {
+ EnumerateActivityObservers(NotifyActivityChangedCallback);
+}
+
+bool Document::IsTopLevelWindowInactive() const {
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ return !bc->GetIsActiveBrowserWindow();
+ }
+
+ return false;
+}
+
+void Document::SetContainer(nsDocShell* aContainer) {
+ if (aContainer) {
+ mDocumentContainer = aContainer;
+ } else {
+ mDocumentContainer = WeakPtr<nsDocShell>();
+ }
+
+ mInChromeDocShell =
+ aContainer && aContainer->GetBrowsingContext()->IsChrome();
+
+ NotifyActivityChanged();
+
+ // IsTopLevelWindowInactive depends on the docshell, so
+ // update the cached value now that it's available.
+ UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false);
+ if (!aContainer) {
+ return;
+ }
+
+ BrowsingContext* context = aContainer->GetBrowsingContext();
+ MOZ_ASSERT_IF(context && mDocGroup,
+ context->Group() == mDocGroup->GetBrowsingContextGroup());
+ if (context && context->IsContent()) {
+ SetIsTopLevelContentDocument(context->IsTopContent());
+ SetIsContentDocument(true);
+ } else {
+ SetIsTopLevelContentDocument(false);
+ SetIsContentDocument(false);
+ }
+}
+
+nsISupports* Document::GetContainer() const {
+ return static_cast<nsIDocShell*>(mDocumentContainer);
+}
+
+void Document::SetScriptGlobalObject(
+ nsIScriptGlobalObject* aScriptGlobalObject) {
+ MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
+ mAnimationController->IsPausedByType(
+ SMILTimeContainer::PAUSE_PAGEHIDE |
+ SMILTimeContainer::PAUSE_BEGIN),
+ "Clearing window pointer while animations are unpaused");
+
+ if (mScriptGlobalObject && !aScriptGlobalObject) {
+ // We're detaching from the window. We need to grab a pointer to
+ // our layout history state now.
+ mLayoutHistoryState = GetLayoutHistoryState();
+
+ // Also make sure to remove our onload blocker now if we haven't done it yet
+ if (mOnloadBlockCount != 0) {
+ nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
+ if (loadGroup) {
+ loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
+ }
+ }
+
+ if (GetController().isSome()) {
+ if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
+ loader->ClearCacheForControlledDocument(this);
+ }
+
+ // We may become controlled again if this document comes back out
+ // of bfcache. Clear our state to allow that to happen. Only
+ // clear this flag if we are actually controlled, though, so pages
+ // that were force reloaded don't become controlled when they
+ // come out of bfcache.
+ mMaybeServiceWorkerControlled = false;
+ }
+
+ if (GetWindowContext()) {
+ // The document is about to lose its window, so this is a good time to
+ // send our page use counters, while we still have access to our
+ // WindowContext.
+ //
+ // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
+ // catches some cases of documents losing their window that don't
+ // get in here.)
+ SendPageUseCounters();
+ }
+ }
+
+ // BlockOnload() might be called before mScriptGlobalObject is set.
+ // We may need to add the blocker once mScriptGlobalObject is set.
+ bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
+
+ mScriptGlobalObject = aScriptGlobalObject;
+
+ if (needOnloadBlocker) {
+ EnsureOnloadBlocker();
+ }
+
+ UpdateFrameRequestCallbackSchedulingState();
+
+ if (aScriptGlobalObject) {
+ // Go back to using the docshell for the layout history state
+ mLayoutHistoryState = nullptr;
+ SetScopeObject(aScriptGlobalObject);
+ mHasHadDefaultView = true;
+
+ if (mAllowDNSPrefetch) {
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (docShell) {
+#ifdef DEBUG
+ nsCOMPtr<nsIWebNavigation> webNav =
+ do_GetInterface(aScriptGlobalObject);
+ NS_ASSERTION(SameCOMIdentity(webNav, docShell),
+ "Unexpected container or script global?");
+#endif
+ bool allowDNSPrefetch;
+ docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
+ mAllowDNSPrefetch = allowDNSPrefetch;
+ }
+ }
+
+ // If we are set in a window that is already focused we should remember this
+ // as the time the document gained focus.
+ if (HasFocus(IgnoreErrors())) {
+ SetLastFocusTime(TimeStamp::Now());
+ }
+ }
+
+ // Remember the pointer to our window (or lack there of), to avoid
+ // having to QI every time it's asked for.
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
+ mWindow = window;
+
+ // Now that we know what our window is, we can flush the CSP errors to the
+ // Web Console. We are flushing all messages that occurred and were stored in
+ // the queue prior to this point.
+ if (mCSP) {
+ static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(GetChannel());
+ if (internalChannel) {
+ nsCOMArray<nsISecurityConsoleMessage> messages;
+ DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ SendToConsole(messages);
+ }
+
+ // Set our visibility state, but do not fire the event. This is correct
+ // because either we're coming out of bfcache (in which case IsVisible() will
+ // still test false at this point and no state change will happen) or we're
+ // doing the initial document load and don't want to fire the event for this
+ // change.
+ //
+ // When the visibility is changed, notify it to observers.
+ // Some observers need the notification, for example HTMLMediaElement uses
+ // it to update internal media resource allocation.
+ // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
+ // creation are already done before Document::SetScriptGlobalObject() call.
+ // MediaDecoder decides whether starting decoding is decided based on
+ // document's visibility. When the MediaDecoder is created,
+ // Document::SetScriptGlobalObject() is not yet called and document is
+ // hidden state. Therefore the MediaDecoder decides that decoding is
+ // not yet necessary. But soon after Document::SetScriptGlobalObject()
+ // call, the document becomes not hidden. At the time, MediaDecoder needs
+ // to know it and needs to start updating decoding.
+ UpdateVisibilityState(DispatchVisibilityChange::No);
+
+ // The global in the template contents owner document should be the same.
+ if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
+ mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
+ }
+
+ // Tell the script loader about the new global object.
+ if (mScriptLoader && !IsTemplateContentsOwner()) {
+ mScriptLoader->SetGlobalObject(mScriptGlobalObject);
+ }
+
+ if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
+ mScriptGlobalObject && GetChannel()) {
+ // If we are shift-reloaded, don't associate with a ServiceWorker.
+ if (mDocumentContainer->IsForceReloading()) {
+ NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
+ return;
+ }
+
+ mMaybeServiceWorkerControlled = true;
+ }
+}
+
+nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
+ MOZ_ASSERT(!mScriptGlobalObject,
+ "Do not call this when mScriptGlobalObject is set!");
+ if (mHasHadDefaultView) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
+ do_QueryReferent(mScopeObject);
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
+ if (win) {
+ nsPIDOMWindowOuter* outer = win->GetOuterWindow();
+ if (!outer || outer->GetCurrentInnerWindow() != win) {
+ NS_WARNING("Wrong inner/outer window combination!");
+ return nullptr;
+ }
+ }
+ return scriptHandlingObject;
+}
+void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
+ NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
+ "Wrong script object!");
+ if (aScriptObject) {
+ SetScopeObject(aScriptObject);
+ mHasHadDefaultView = false;
+ }
+}
+
+nsPIDOMWindowOuter* Document::GetWindowInternal() const {
+ MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
+ // Let's use mScriptGlobalObject. Even if the document is already removed from
+ // the docshell, the outer window might be still obtainable from the it.
+ nsCOMPtr<nsPIDOMWindowOuter> win;
+ if (mRemovedFromDocShell) {
+ // The docshell returns the outer window we are done.
+ nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
+ if (kungFuDeathGrip) {
+ win = kungFuDeathGrip->GetWindow();
+ }
+ } else {
+ if (nsCOMPtr<nsPIDOMWindowInner> inner =
+ do_QueryInterface(mScriptGlobalObject)) {
+ // mScriptGlobalObject is always the inner window, let's get the outer.
+ win = inner->GetOuterWindow();
+ }
+ }
+
+ return win;
+}
+
+bool Document::InternalAllowXULXBL() {
+ if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
+ mAllowXULXBL = eTriTrue;
+ return true;
+ }
+
+ mAllowXULXBL = eTriFalse;
+ return false;
+}
+
+// Note: We don't hold a reference to the document observer; we assume
+// that it has a live reference to the document.
+void Document::AddObserver(nsIDocumentObserver* aObserver) {
+ NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
+ "Observer already in the list");
+ mObservers.AppendElement(aObserver);
+ AddMutationObserver(aObserver);
+}
+
+bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
+ // If we're in the process of destroying the document (and we're
+ // informing the observers of the destruction), don't remove the
+ // observers from the list. This is not a big deal, since we
+ // don't hold a live reference to the observers.
+ if (!mInDestructor) {
+ RemoveMutationObserver(aObserver);
+ return mObservers.RemoveElement(aObserver);
+ }
+
+ return mObservers.Contains(aObserver);
+}
+
+void Document::BeginUpdate() {
+ ++mUpdateNestLevel;
+ nsContentUtils::AddScriptBlocker();
+ NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
+}
+
+void Document::EndUpdate() {
+ const bool reset = !mPendingMaybeEditingStateChanged;
+ mPendingMaybeEditingStateChanged = true;
+
+ NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
+
+ --mUpdateNestLevel;
+
+ nsContentUtils::RemoveScriptBlocker();
+
+ if (mXULBroadcastManager) {
+ mXULBroadcastManager->MaybeBroadcast();
+ }
+
+ if (reset) {
+ mPendingMaybeEditingStateChanged = false;
+ }
+ MaybeEditingStateChanged();
+}
+
+void Document::BeginLoad() {
+ if (IsEditingOn()) {
+ // Reset() blows away all event listeners in the document, and our
+ // editor relies heavily on those. Midas is turned on, to make it
+ // work, re-initialize it to give it a chance to add its event
+ // listeners again.
+
+ TurnEditingOff();
+ EditingStateChanged();
+ }
+
+ MOZ_ASSERT(!mDidCallBeginLoad);
+ mDidCallBeginLoad = true;
+
+ // Block onload here to prevent having to deal with blocking and
+ // unblocking it while we know the document is loading.
+ BlockOnload();
+ mDidFireDOMContentLoaded = false;
+ BlockDOMContentLoaded();
+
+ if (mScriptLoader) {
+ mScriptLoader->BeginDeferringScripts();
+ }
+
+ NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
+}
+
+void Document::MozSetImageElement(const nsAString& aImageElementId,
+ Element* aElement) {
+ if (aImageElementId.IsEmpty()) return;
+
+ // Hold a script blocker while calling SetImageElement since that can call
+ // out to id-observers
+ nsAutoScriptBlocker scriptBlocker;
+
+ IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
+ if (entry) {
+ entry->SetImageElement(aElement);
+ if (entry->IsEmpty()) {
+ mIdentifierMap.RemoveEntry(entry);
+ }
+ }
+}
+
+void Document::DispatchContentLoadedEvents() {
+ // If you add early returns from this method, make sure you're
+ // calling UnblockOnload properly.
+
+ // Unpin references to preloaded images
+ mPreloadingImages.Clear();
+
+ // DOM manipulation after content loaded should not care if the element
+ // came from the preloader.
+ mPreloadedPreconnects.Clear();
+
+ if (mTiming) {
+ mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
+ }
+
+ // Dispatch observer notification to notify observers document is interactive.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsIPrincipal* principal = NodePrincipal();
+ os->NotifyObservers(ToSupports(this),
+ principal->IsSystemPrincipal()
+ ? "chrome-document-interactive"
+ : "content-document-interactive",
+ nullptr);
+ }
+
+ // Fire a DOM event notifying listeners that this document has been
+ // loaded (excluding images and other loads initiated by this
+ // document).
+ nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
+ u"DOMContentLoaded"_ns, CanBubble::eYes,
+ Cancelable::eNo);
+
+ if (auto* const window = GetInnerWindow()) {
+ const RefPtr<ServiceWorkerContainer> serviceWorker =
+ window->Navigator()->ServiceWorker();
+
+ // This could cause queued messages from a service worker to get
+ // dispatched on serviceWorker.
+ serviceWorker->StartMessages();
+ }
+
+ if (MayStartLayout()) {
+ MaybeResolveReadyForIdle();
+ }
+
+ nsIDocShell* docShell = GetDocShell();
+
+ if (TimelineConsumers::HasConsumer(docShell)) {
+ TimelineConsumers::AddMarkerForDocShell(
+ docShell,
+ MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
+ }
+
+ if (mTiming) {
+ mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
+ }
+
+ // If this document is a [i]frame, fire a DOMFrameContentLoaded
+ // event on all parent documents notifying that the HTML (excluding
+ // other external files such as images and stylesheets) in a frame
+ // has finished loading.
+
+ // target_frame is the [i]frame element that will be used as the
+ // target for the event. It's the [i]frame whose content is done
+ // loading.
+ nsCOMPtr<Element> target_frame = GetEmbedderElement();
+
+ if (target_frame && target_frame->IsInComposedDoc()) {
+ nsCOMPtr<Document> parent = target_frame->OwnerDoc();
+ while (parent) {
+ RefPtr<Event> event;
+ if (parent) {
+ IgnoredErrorResult ignored;
+ event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
+ }
+
+ if (event) {
+ event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
+
+ event->SetTarget(target_frame);
+ event->SetTrusted(true);
+
+ // To dispatch this event we must manually call
+ // EventDispatcher::Dispatch() on the ancestor document since the
+ // target is not in the same document, so the event would never reach
+ // the ancestor document if we used the normal event
+ // dispatching code.
+
+ WidgetEvent* innerEvent = event->WidgetEventPtr();
+ if (innerEvent) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
+ // TODO: Bug 1506441
+ EventDispatcher::Dispatch(MOZ_KnownLive(ToSupports(parent)),
+ context, innerEvent, event, &status);
+ }
+ }
+ }
+
+ parent = parent->GetInProcessParentDocument();
+ }
+ }
+
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (inner) {
+ inner->NoteDOMContentLoaded();
+ }
+
+ // TODO
+ if (mMaybeServiceWorkerControlled) {
+ using mozilla::dom::ServiceWorkerManager;
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ Maybe<ClientInfo> clientInfo = GetClientInfo();
+ if (clientInfo.isSome()) {
+ swm->MaybeCheckNavigationUpdate(clientInfo.ref());
+ }
+ }
+ }
+
+ if (mSetCompleteAfterDOMContentLoaded) {
+ SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
+ mSetCompleteAfterDOMContentLoaded = false;
+ }
+
+ UnblockOnload(true);
+}
+
+void Document::EndLoad() {
+ bool turnOnEditing =
+ mParser && (IsInDesignMode() || mContentEditableCount > 0);
+
+#if defined(DEBUG)
+ // only assert if nothing stopped the load on purpose
+ if (!mParserAborted) {
+ nsContentSecurityUtils::AssertAboutPageHasCSP(this);
+ }
+#endif
+
+ // EndLoad may have been called without a matching call to BeginLoad, in the
+ // case of a failed parse (for example, due to timeout). In such a case, we
+ // still want to execute part of this code to do appropriate cleanup, but we
+ // gate part of it because it is intended to match 1-for-1 with calls to
+ // BeginLoad. We have an explicit flag bit for this purpose, since it's
+ // complicated and error prone to derive this condition from other related
+ // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
+
+ // Part 1: Code that always executes to cleanup end of parsing, whether
+ // that parsing was successful or not.
+
+ // Drop the ref to our parser, if any, but keep hold of the sink so that we
+ // can flush it from FlushPendingNotifications as needed. We might have to
+ // do that to get a StartLayout() to happen.
+ if (mParser) {
+ mWeakSink = do_GetWeakReference(mParser->GetContentSink());
+ mParser = nullptr;
+ }
+
+ // Update the attributes on the PerformanceNavigationTiming before notifying
+ // the onload observers.
+ if (nsPIDOMWindowInner* window = GetInnerWindow()) {
+ if (RefPtr<Performance> performance = window->GetPerformance()) {
+ performance->UpdateNavigationTimingEntry();
+ }
+ }
+
+ NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
+
+ // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
+
+ if (!mDidCallBeginLoad) {
+ return;
+ }
+ mDidCallBeginLoad = false;
+
+ UnblockDOMContentLoaded();
+
+ if (turnOnEditing) {
+ EditingStateChanged();
+ }
+
+ if (!GetWindow()) {
+ // This is a document that's not in a window. For example, this could be an
+ // XMLHttpRequest responseXML document, or a document created via DOMParser
+ // or DOMImplementation. We don't reach this code normally for such
+ // documents (which is not obviously correct), but can reach it via
+ // document.open()/document.close().
+ //
+ // Such documents don't fire load events, but per spec should set their
+ // readyState to "complete" when parsing and all loading of subresources is
+ // done. Parsing is done now, and documents not in a window don't load
+ // subresources, so just go ahead and mark ourselves as complete.
+ SetReadyStateInternal(Document::READYSTATE_COMPLETE,
+ /* updateTimingInformation = */ false);
+
+ // Reset mSkipLoadEventAfterClose just in case.
+ mSkipLoadEventAfterClose = false;
+ }
+}
+
+void Document::UnblockDOMContentLoaded() {
+ MOZ_ASSERT(mBlockDOMContentLoaded);
+ if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
+ return;
+ }
+
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
+ ("DOCUMENT %p UnblockDOMContentLoaded", this));
+
+ mDidFireDOMContentLoaded = true;
+ if (PresShell* presShell = GetPresShell()) {
+ presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
+ }
+
+ MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
+ if (!mSynchronousDOMContentLoaded) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
+ &Document::DispatchContentLoadedEvents);
+ Dispatch(TaskCategory::Other, ev.forget());
+ } else {
+ DispatchContentLoadedEvents();
+ }
+}
+
+void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
+ "Someone forgot a scriptblocker");
+ NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged,
+ (this, aElement, aStateMask));
+}
+
+void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
+ StyleRuleChangeKind) {
+ if (aSheet.IsApplicable()) {
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
+ if (aRule.IsIncompleteImportRule()) {
+ return;
+ }
+
+ if (aSheet.IsApplicable()) {
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
+ if (aSheet.IsApplicable()) {
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
+ if (aSheet.IsApplicable()) {
+ ApplicableStylesChanged();
+ }
+}
+
+static Element* GetCustomContentContainer(PresShell* aPresShell) {
+ if (!aPresShell || !aPresShell->GetCanvasFrame()) {
+ return nullptr;
+ }
+
+ return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
+}
+
+already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
+ Element& aElement, bool aForce, ErrorResult& aRv) {
+ // Clone the node to avoid returning a direct reference.
+ nsCOMPtr<nsINode> clone = aElement.CloneNode(true, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ PresShell* shell = GetPresShell();
+ if (aForce && !GetCustomContentContainer(shell)) {
+ FlushPendingNotifications(FlushType::Layout);
+ shell = GetPresShell();
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ auto anonContent =
+ MakeRefPtr<AnonymousContent>(clone.forget().downcast<Element>());
+
+ mAnonymousContents.AppendElement(anonContent);
+
+ if (Element* container = GetCustomContentContainer(shell)) {
+ container->AppendChildTo(&anonContent->ContentNode(), true, IgnoreErrors());
+ shell->GetCanvasFrame()->ShowCustomContentContainer();
+ }
+
+ return anonContent.forget();
+}
+
+static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
+ PresShell* aPresShell) {
+ RefPtr<Element> container = GetCustomContentContainer(aPresShell);
+ if (!container) {
+ return;
+ }
+ container->RemoveChild(aAnonContent.ContentNode(), IgnoreErrors());
+}
+
+void Document::RemoveAnonymousContent(AnonymousContent& aContent) {
+ nsAutoScriptBlocker scriptBlocker;
+
+ auto index = mAnonymousContents.IndexOf(&aContent);
+ if (index == mAnonymousContents.NoIndex) {
+ return;
+ }
+
+ mAnonymousContents.RemoveElementAt(index);
+ RemoveAnonContentFromCanvas(aContent, GetPresShell());
+
+ if (mAnonymousContents.IsEmpty() &&
+ GetCustomContentContainer(GetPresShell())) {
+ GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
+ }
+}
+
+Element* Document::GetAnonRootIfInAnonymousContentContainer(
+ nsINode* aNode) const {
+ if (!aNode->IsInNativeAnonymousSubtree()) {
+ return nullptr;
+ }
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell || !presShell->GetCanvasFrame()) {
+ return nullptr;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+ nsCOMPtr<Element> customContainer =
+ presShell->GetCanvasFrame()->GetCustomContentContainer();
+ if (!customContainer) {
+ return nullptr;
+ }
+
+ // An arbitrary number of elements can be inserted as children of the custom
+ // container frame. We want the one that was added that contains aNode, so
+ // we need to keep track of the last child separately using |child| here.
+ nsINode* child = aNode;
+ nsINode* parent = aNode->GetParentNode();
+ while (parent && parent->IsInNativeAnonymousSubtree()) {
+ if (parent == customContainer) {
+ return Element::FromNode(child);
+ }
+ child = parent;
+ parent = child->GetParentNode();
+ }
+ return nullptr;
+}
+
+Maybe<ClientInfo> Document::GetClientInfo() const {
+ if (const Document* orig = GetOriginalDocument()) {
+ if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
+ return info;
+ }
+ }
+
+ if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
+ return inner->GetClientInfo();
+ }
+
+ return Maybe<ClientInfo>();
+}
+
+Maybe<ClientState> Document::GetClientState() const {
+ if (const Document* orig = GetOriginalDocument()) {
+ if (Maybe<ClientState> state = orig->GetClientState()) {
+ return state;
+ }
+ }
+
+ if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
+ return inner->GetClientState();
+ }
+
+ return Maybe<ClientState>();
+}
+
+Maybe<ServiceWorkerDescriptor> Document::GetController() const {
+ if (const Document* orig = GetOriginalDocument()) {
+ if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
+ return controller;
+ }
+ }
+
+ if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
+ return inner->GetController();
+ }
+
+ return Maybe<ServiceWorkerDescriptor>();
+}
+
+//
+// Document interface
+//
+DocumentType* Document::GetDoctype() const {
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->NodeType() == DOCUMENT_TYPE_NODE) {
+ return static_cast<DocumentType*>(child);
+ }
+ }
+ return nullptr;
+}
+
+DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
+ if (!mDOMImplementation) {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), "about:blank");
+ if (!uri) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ bool hasHadScriptObject = true;
+ nsIScriptGlobalObject* scriptObject =
+ GetScriptHandlingObject(hasHadScriptObject);
+ if (!scriptObject && hasHadScriptObject) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ mDOMImplementation = new DOMImplementation(
+ this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
+ }
+
+ return mDOMImplementation;
+}
+
+bool IsLowercaseASCII(const nsAString& aValue) {
+ int32_t len = aValue.Length();
+ for (int32_t i = 0; i < len; ++i) {
+ char16_t c = aValue[i];
+ if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+already_AddRefed<Element> Document::CreateElement(
+ const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
+ ErrorResult& rv) {
+ rv = nsContentUtils::CheckQName(aTagName, false);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
+ nsAutoString lcTagName;
+ if (needsLowercase) {
+ nsContentUtils::ASCIIToLower(aTagName, lcTagName);
+ }
+
+ const nsString* is = nullptr;
+ PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
+ if (aOptions.IsElementCreationOptions()) {
+ const ElementCreationOptions& options =
+ aOptions.GetAsElementCreationOptions();
+
+ if (options.mIs.WasPassed()) {
+ is = &options.mIs.Value();
+ }
+
+ // Check 'pseudo' and throw an exception if it's not one allowed
+ // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
+ if (options.mPseudo.WasPassed()) {
+ Maybe<PseudoStyleType> type =
+ nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
+ if (!type || *type == PseudoStyleType::NotPseudo ||
+ !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
+ rv.ThrowNotSupportedError("Invalid pseudo-element");
+ return nullptr;
+ }
+ pseudoType = *type;
+ }
+ }
+
+ RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
+ nullptr, mDefaultElementType, is);
+
+ if (pseudoType != PseudoStyleType::NotPseudo) {
+ elem->SetPseudoElementType(pseudoType);
+ }
+
+ return elem.forget();
+}
+
+already_AddRefed<Element> Document::CreateElementNS(
+ const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
+ const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
+ mNodeInfoManager, ELEMENT_NODE,
+ getter_AddRefs(nodeInfo));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ const nsString* is = nullptr;
+ if (aOptions.IsElementCreationOptions()) {
+ const ElementCreationOptions& options =
+ aOptions.GetAsElementCreationOptions();
+ if (options.mIs.WasPassed()) {
+ is = &options.mIs.Value();
+ }
+ }
+
+ nsCOMPtr<Element> element;
+ rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
+ NOT_FROM_PARSER, is);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ return element.forget();
+}
+
+already_AddRefed<Element> Document::CreateXULElement(
+ const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
+ ErrorResult& aRv) {
+ aRv = nsContentUtils::CheckQName(aTagName, false);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ const nsString* is = nullptr;
+ if (aOptions.IsElementCreationOptions()) {
+ const ElementCreationOptions& options =
+ aOptions.GetAsElementCreationOptions();
+ if (options.mIs.WasPassed()) {
+ is = &options.mIs.Value();
+ }
+ }
+
+ RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
+ if (!elem) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+ return elem.forget();
+}
+
+already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
+ RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
+ return text.forget();
+}
+
+already_AddRefed<nsTextNode> Document::CreateTextNode(
+ const nsAString& aData) const {
+ RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
+ // Don't notify; this node is still being created.
+ text->SetText(aData, false);
+ return text.forget();
+}
+
+already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
+ RefPtr<DocumentFragment> frag =
+ new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
+ return frag.forget();
+}
+
+// Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
+already_AddRefed<dom::Comment> Document::CreateComment(
+ const nsAString& aData) const {
+ RefPtr<dom::Comment> comment =
+ new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
+
+ // Don't notify; this node is still being created.
+ comment->SetText(aData, false);
+ return comment.forget();
+}
+
+already_AddRefed<CDATASection> Document::CreateCDATASection(
+ const nsAString& aData, ErrorResult& rv) {
+ if (IsHTMLDocument()) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ if (FindInReadable(u"]]>"_ns, aData)) {
+ rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
+ return nullptr;
+ }
+
+ RefPtr<CDATASection> cdata =
+ new (mNodeInfoManager) CDATASection(mNodeInfoManager);
+
+ // Don't notify; this node is still being created.
+ cdata->SetText(aData, false);
+
+ return cdata.forget();
+}
+
+already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
+ const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
+ nsresult res = nsContentUtils::CheckQName(aTarget, false);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return nullptr;
+ }
+
+ if (FindInReadable(u"?>"_ns, aData)) {
+ rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
+ return nullptr;
+ }
+
+ RefPtr<ProcessingInstruction> pi =
+ NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
+
+ return pi.forget();
+}
+
+already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
+ ErrorResult& rv) {
+ if (!mNodeInfoManager) {
+ rv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+
+ nsresult res = nsContentUtils::CheckQName(aName, false);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return nullptr;
+ }
+
+ nsAutoString name;
+ if (IsHTMLDocument()) {
+ nsContentUtils::ASCIIToLower(aName, name);
+ } else {
+ name = aName;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
+ ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return nullptr;
+ }
+
+ RefPtr<Attr> attribute =
+ new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
+ return attribute.forget();
+}
+
+already_AddRefed<Attr> Document::CreateAttributeNS(
+ const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
+ ErrorResult& rv) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
+ mNodeInfoManager, ATTRIBUTE_NODE,
+ getter_AddRefs(nodeInfo));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<Attr> attribute =
+ new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
+ return attribute.forget();
+}
+
+void Document::ResolveScheduledSVGPresAttrs() {
+ for (SVGElement* svg : mLazySVGPresElements) {
+ svg->UpdateContentDeclarationBlock();
+ }
+ mLazySVGPresElements.Clear();
+}
+
+already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
+ const {
+ RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
+
+ const nsTArray<nsWeakPtr> blockedNodes = mBlockedNodesByClassifier.Clone();
+
+ for (unsigned long i = 0; i < blockedNodes.Length(); i++) {
+ nsWeakPtr weakNode = blockedNodes[i];
+ nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode);
+ // Consider only nodes to which we have managed to get strong references.
+ // Coping with nullptrs since it's expected for nodes to disappear when
+ // nobody else is referring to them.
+ if (node) {
+ list->AppendElement(node);
+ }
+ }
+
+ return list.forget();
+}
+
+void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
+ aSheetSet.Truncate();
+
+ // Look through our sheets, find the selected set title
+ size_t count = SheetCount();
+ nsAutoString title;
+ for (size_t index = 0; index < count; index++) {
+ StyleSheet* sheet = SheetAt(index);
+ NS_ASSERTION(sheet, "Null sheet in sheet list!");
+
+ if (sheet->Disabled()) {
+ // Disabled sheets don't affect the currently selected set
+ continue;
+ }
+
+ sheet->GetTitle(title);
+
+ if (aSheetSet.IsEmpty()) {
+ aSheetSet = title;
+ } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
+ // Sheets from multiple sets enabled; return null string, per spec.
+ SetDOMStringToNull(aSheetSet);
+ return;
+ }
+ }
+}
+
+void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
+ if (DOMStringIsNull(aSheetSet)) {
+ return;
+ }
+
+ // Must update mLastStyleSheetSet before doing anything else with stylesheets
+ // or CSSLoaders.
+ mLastStyleSheetSet = aSheetSet;
+ EnableStyleSheetsForSetInternal(aSheetSet, true);
+}
+
+void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
+ mPreferredStyleSheetSet = aSheetSet;
+ // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
+ // spec.
+ if (DOMStringIsNull(mLastStyleSheetSet)) {
+ // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
+ // per spec. The idea here is that we're changing our preferred set and
+ // that shouldn't change the value of lastStyleSheetSet. Also, we're
+ // using the Internal version so we can update the CSSLoader and not have
+ // to worry about null strings.
+ EnableStyleSheetsForSetInternal(aSheetSet, true);
+ }
+}
+
+DOMStringList* Document::StyleSheetSets() {
+ if (!mStyleSheetSetList) {
+ mStyleSheetSetList = new DOMStyleSheetSetList(this);
+ }
+ return mStyleSheetSetList;
+}
+
+void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
+ // Per spec, passing in null is a no-op.
+ if (!DOMStringIsNull(aSheetSet)) {
+ // Note: must make sure to not change the CSSLoader's preferred sheet --
+ // that value should be equal to either our lastStyleSheetSet (if that's
+ // non-null) or to our preferredStyleSheetSet. And this method doesn't
+ // change either of those.
+ EnableStyleSheetsForSetInternal(aSheetSet, false);
+ }
+}
+
+void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
+ bool aUpdateCSSLoader) {
+ size_t count = SheetCount();
+ nsAutoString title;
+ for (size_t index = 0; index < count; index++) {
+ StyleSheet* sheet = SheetAt(index);
+ NS_ASSERTION(sheet, "Null sheet in sheet list!");
+
+ sheet->GetTitle(title);
+ if (!title.IsEmpty()) {
+ sheet->SetEnabled(title.Equals(aSheetSet));
+ }
+ }
+ if (aUpdateCSSLoader) {
+ CSSLoader()->DocumentStyleSheetSetChanged();
+ }
+ if (mStyleSet->StyleSheetsHaveChanged()) {
+ ApplicableStylesChanged();
+ }
+}
+
+void Document::GetCharacterSet(nsAString& aCharacterSet) const {
+ nsAutoCString charset;
+ GetDocumentCharacterSet()->Name(charset);
+ CopyASCIItoUTF16(charset, aCharacterSet);
+}
+
+already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
+ ErrorResult& rv) const {
+ nsINode* imported = &aNode;
+
+ switch (imported->NodeType()) {
+ case DOCUMENT_NODE: {
+ break;
+ }
+ case DOCUMENT_FRAGMENT_NODE:
+ case ATTRIBUTE_NODE:
+ case ELEMENT_NODE:
+ case PROCESSING_INSTRUCTION_NODE:
+ case TEXT_NODE:
+ case CDATA_SECTION_NODE:
+ case COMMENT_NODE:
+ case DOCUMENT_TYPE_NODE: {
+ return imported->Clone(aDeep, mNodeInfoManager, rv);
+ }
+ default: {
+ NS_WARNING("Don't know how to clone this nodetype for importNode.");
+ }
+ }
+
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+}
+
+already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
+ return nsRange::Create(this, 0, this, 0, rv);
+}
+
+already_AddRefed<NodeIterator> Document::CreateNodeIterator(
+ nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
+ ErrorResult& rv) const {
+ RefPtr<NodeIterator> iterator =
+ new NodeIterator(&aRoot, aWhatToShow, aFilter);
+ return iterator.forget();
+}
+
+already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
+ uint32_t aWhatToShow,
+ NodeFilter* aFilter,
+ ErrorResult& rv) const {
+ RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
+ return walker.forget();
+}
+
+already_AddRefed<Location> Document::GetLocation() const {
+ nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
+
+ if (!w) {
+ return nullptr;
+ }
+
+ return do_AddRef(w->Location());
+}
+
+already_AddRefed<nsIURI> Document::GetDomainURI() {
+ nsIPrincipal* principal = NodePrincipal();
+
+ nsCOMPtr<nsIURI> uri;
+ principal->GetDomain(getter_AddRefs(uri));
+ if (uri) {
+ return uri.forget();
+ }
+ auto* basePrin = BasePrincipal::Cast(principal);
+ basePrin->GetURI(getter_AddRefs(uri));
+ return uri.forget();
+}
+
+void Document::GetDomain(nsAString& aDomain) {
+ nsCOMPtr<nsIURI> uri = GetDomainURI();
+
+ if (!uri) {
+ aDomain.Truncate();
+ return;
+ }
+
+ nsAutoCString hostName;
+ nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(hostName, aDomain);
+ } else {
+ // If we can't get the host from the URI (e.g. about:, javascript:,
+ // etc), just return an empty string.
+ aDomain.Truncate();
+ }
+}
+
+void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
+ if (!GetBrowsingContext()) {
+ // If our browsing context is null; disallow setting domain
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (mSandboxFlags & SANDBOXED_DOMAIN) {
+ // We're sandboxed; disallow setting domain
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (aDomain.IsEmpty()) {
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = GetDomainURI();
+ if (!uri) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Check new domain - must be a superdomain of the current host
+ // For example, a page from foo.bar.com may set domain to bar.com,
+ // but not to ar.com, baz.com, or fi.foo.bar.com.
+
+ nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
+ if (!newURI) {
+ // Error: illegal domain
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated()) {
+ WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
+ MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->SendSetDocumentDomain(newURI);
+ }
+}
+
+already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
+ const nsACString& aHostString) {
+ if (aHostString.IsEmpty()) {
+ return nullptr;
+ }
+
+ // Create new URI
+ nsCOMPtr<nsIURI> uri = GetDomainURI();
+ if (!uri) {
+ return nullptr;
+ }
+
+ nsresult rv;
+ rv = NS_MutateURI(uri)
+ .SetUserPass(""_ns)
+ .SetPort(-1) // we want to reset the port number if needed.
+ .SetHostPort(aHostString)
+ .Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return uri.forget();
+}
+
+already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
+ const nsAString& aNewDomain, nsIURI* aOrigHost) {
+ if (NS_WARN_IF(!aOrigHost)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> newURI =
+ CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
+ if (!newURI) {
+ // Error: failed to parse input domain
+ return nullptr;
+ }
+
+ if (!IsValidDomain(aOrigHost, newURI)) {
+ // Error: illegal domain
+ return nullptr;
+ }
+
+ nsAutoCString domain;
+ if (NS_FAILED(newURI->GetAsciiHost(domain))) {
+ return nullptr;
+ }
+
+ return CreateInheritingURIForHost(domain);
+}
+
+/* static */
+bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
+ // Check new domain - must be a superdomain of the current host
+ // For example, a page from foo.bar.com may set domain to bar.com,
+ // but not to ar.com, baz.com, or fi.foo.bar.com.
+ nsAutoCString current;
+ nsAutoCString domain;
+ if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
+ current.Truncate();
+ }
+ if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
+ domain.Truncate();
+ }
+
+ bool ok = current.Equals(domain);
+ if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
+ current.CharAt(current.Length() - domain.Length() - 1) == '.') {
+ // We're golden if the new domain is the current page's base domain or a
+ // subdomain of it.
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ return false;
+ }
+
+ nsAutoCString currentBaseDomain;
+ ok = NS_SUCCEEDED(
+ tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
+ NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
+ (domain.Length() >= currentBaseDomain.Length()),
+ "uh-oh! slight optimization wasn't valid somehow!");
+ ok = ok && domain.Length() >= currentBaseDomain.Length();
+ }
+
+ return ok;
+}
+
+Element* Document::GetHtmlElement() const {
+ Element* rootElement = GetRootElement();
+ if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
+ return rootElement;
+ return nullptr;
+}
+
+Element* Document::GetHtmlChildElement(nsAtom* aTag) {
+ Element* html = GetHtmlElement();
+ if (!html) return nullptr;
+
+ // Look for the element with aTag inside html. This needs to run
+ // forwards to find the first such element.
+ for (nsIContent* child = html->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(aTag)) return child->AsElement();
+ }
+ return nullptr;
+}
+
+nsGenericHTMLElement* Document::GetBody() {
+ Element* html = GetHtmlElement();
+ if (!html) {
+ return nullptr;
+ }
+
+ for (nsIContent* child = html->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::body) ||
+ child->IsHTMLElement(nsGkAtoms::frameset)) {
+ return static_cast<nsGenericHTMLElement*>(child);
+ }
+ }
+
+ return nullptr;
+}
+
+void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
+ nsCOMPtr<Element> root = GetRootElement();
+
+ // The body element must be either a body tag or a frameset tag. And we must
+ // have a root element to be able to add kids to it.
+ if (!newBody ||
+ !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
+ rv.ThrowHierarchyRequestError(
+ "The new body must be either a body tag or frameset tag.");
+ return;
+ }
+
+ if (!root) {
+ rv.ThrowHierarchyRequestError("No root element.");
+ return;
+ }
+
+ // Use DOM methods so that we pass through the appropriate security checks.
+ nsCOMPtr<Element> currentBody = GetBody();
+ if (currentBody) {
+ root->ReplaceChild(*newBody, *currentBody, rv);
+ } else {
+ root->AppendChild(*newBody, rv);
+ }
+}
+
+HTMLSharedElement* Document::GetHead() {
+ return static_cast<HTMLSharedElement*>(GetHeadElement());
+}
+
+Element* Document::GetTitleElement() {
+ // mMayHaveTitleElement will have been set to true if any HTML or SVG
+ // <title> element has been bound to this document. So if it's false,
+ // we know there is nothing to do here. This avoids us having to search
+ // the whole DOM if someone calls document.title on a large document
+ // without a title.
+ if (!mMayHaveTitleElement) {
+ return nullptr;
+ }
+
+ Element* root = GetRootElement();
+ if (root && root->IsSVGElement(nsGkAtoms::svg)) {
+ // In SVG, the document's title must be a child
+ for (nsIContent* child = root->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsSVGElement(nsGkAtoms::title)) {
+ return child->AsElement();
+ }
+ }
+ return nullptr;
+ }
+
+ // We check the HTML namespace even for non-HTML documents, except SVG. This
+ // matches the spec and the behavior of all tested browsers.
+ for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
+ if (node->IsHTMLElement(nsGkAtoms::title)) {
+ return node->AsElement();
+ }
+ }
+ return nullptr;
+}
+
+void Document::GetTitle(nsAString& aTitle) {
+ aTitle.Truncate();
+
+ Element* rootElement = GetRootElement();
+ if (!rootElement) {
+ return;
+ }
+
+ if (rootElement->IsXULElement()) {
+ rootElement->GetAttr(nsGkAtoms::title, aTitle);
+ } else if (Element* title = GetTitleElement()) {
+ nsContentUtils::GetNodeTextContent(title, false, aTitle);
+ } else {
+ return;
+ }
+
+ aTitle.CompressWhitespace();
+}
+
+void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
+ Element* rootElement = GetRootElement();
+ if (!rootElement) {
+ return;
+ }
+
+ if (rootElement->IsXULElement()) {
+ aRv =
+ rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
+ return;
+ }
+
+ Maybe<mozAutoDocUpdate> updateBatch;
+ nsCOMPtr<Element> title = GetTitleElement();
+ if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
+ if (!title) {
+ // Batch updates so that mutation events don't change "the title
+ // element" under us
+ updateBatch.emplace(this, true);
+ RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
+ nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
+ NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
+ NOT_FROM_PARSER);
+ if (!title) {
+ return;
+ }
+ rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
+ IgnoreErrors());
+ }
+ } else if (rootElement->IsHTMLElement()) {
+ if (!title) {
+ // Batch updates so that mutation events don't change "the title
+ // element" under us
+ updateBatch.emplace(this, true);
+ Element* head = GetHeadElement();
+ if (!head) {
+ return;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> titleInfo;
+ titleInfo = mNodeInfoManager->GetNodeInfo(
+ nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
+ title = NS_NewHTMLTitleElement(titleInfo.forget());
+ if (!title) {
+ return;
+ }
+
+ head->AppendChildTo(title, true, IgnoreErrors());
+ }
+ } else {
+ return;
+ }
+
+ aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
+}
+
+void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
+ NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
+ "Setting a title while unlinking or destroying the element?");
+ if (mInUnlinkOrDeletion) {
+ return;
+ }
+
+ if (aBoundTitleElement) {
+ mMayHaveTitleElement = true;
+ }
+ if (mPendingTitleChangeEvent.IsPending()) {
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ RefPtr<nsRunnableMethod<Document, void, false>> event =
+ NewNonOwningRunnableMethod("Document::DoNotifyPossibleTitleChange", this,
+ &Document::DoNotifyPossibleTitleChange);
+ if (NS_WARN_IF(NS_FAILED(Dispatch(TaskCategory::Other, do_AddRef(event))))) {
+ return;
+ }
+ mPendingTitleChangeEvent = std::move(event);
+}
+
+void Document::DoNotifyPossibleTitleChange() {
+ if (!mPendingTitleChangeEvent.IsPending()) {
+ return;
+ }
+ // Make sure the pending runnable method is cleared.
+ mPendingTitleChangeEvent.Revoke();
+ mHaveFiredTitleChange = true;
+
+ nsAutoString title;
+ GetTitle(title);
+
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ nsCOMPtr<nsISupports> container =
+ presShell->GetPresContext()->GetContainerWeak();
+ if (container) {
+ if (nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container)) {
+ docShellWin->SetTitle(title);
+ }
+ }
+ }
+
+ if (WindowGlobalChild* child = GetWindowGlobalChild()) {
+ child->SendUpdateDocumentTitle(title);
+ }
+
+ // Fire a DOM event for the title change.
+ nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
+ u"DOMTitleChanged"_ns, CanBubble::eYes,
+ Cancelable::eYes);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
+ }
+}
+
+already_AddRefed<MediaQueryList> Document::MatchMedia(
+ const nsACString& aMediaQueryList, CallerType aCallerType) {
+ RefPtr<MediaQueryList> result =
+ new MediaQueryList(this, aMediaQueryList, aCallerType);
+
+ mDOMMediaQueryLists.insertBack(result);
+
+ return result.forget();
+}
+
+void Document::SetMayStartLayout(bool aMayStartLayout) {
+ mMayStartLayout = aMayStartLayout;
+ if (MayStartLayout()) {
+ // Before starting layout, check whether we're a toplevel chrome
+ // window. If we are, setup some state so that we don't have to restyle
+ // the whole tree after StartLayout.
+ if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
+ // We're the chrome document!
+ win->BeforeStartLayout();
+ }
+ ReadyState state = GetReadyStateEnum();
+ if (state >= READYSTATE_INTERACTIVE) {
+ // DOMContentLoaded has fired already.
+ MaybeResolveReadyForIdle();
+ }
+ }
+
+ MaybeEditingStateChanged();
+}
+
+nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
+ mInitializableFrameLoaders.RemoveElement(aLoader);
+ // Don't even try to initialize.
+ if (mInDestructor) {
+ NS_WARNING(
+ "Trying to initialize a frame loader while"
+ "document is being deleted");
+ return NS_ERROR_FAILURE;
+ }
+
+ mInitializableFrameLoaders.AppendElement(aLoader);
+ if (!mFrameLoaderRunner) {
+ mFrameLoaderRunner =
+ NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
+ &Document::MaybeInitializeFinalizeFrameLoaders);
+ NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
+ nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
+ }
+ return NS_OK;
+}
+
+nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
+ nsIRunnable* aFinalizer) {
+ mInitializableFrameLoaders.RemoveElement(aLoader);
+ if (mInDestructor) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LogRunnable::LogDispatch(aFinalizer);
+ mFrameLoaderFinalizers.AppendElement(aFinalizer);
+ if (!mFrameLoaderRunner) {
+ mFrameLoaderRunner =
+ NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
+ &Document::MaybeInitializeFinalizeFrameLoaders);
+ NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
+ nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
+ }
+ return NS_OK;
+}
+
+void Document::MaybeInitializeFinalizeFrameLoaders() {
+ if (mDelayFrameLoaderInitialization) {
+ // This method will be recalled when !mDelayFrameLoaderInitialization.
+ mFrameLoaderRunner = nullptr;
+ return;
+ }
+
+ // We're not in an update, but it is not safe to run scripts, so
+ // postpone frameloader initialization and finalization.
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ if (!mInDestructor && !mFrameLoaderRunner &&
+ (mInitializableFrameLoaders.Length() ||
+ mFrameLoaderFinalizers.Length())) {
+ mFrameLoaderRunner = NewRunnableMethod(
+ "Document::MaybeInitializeFinalizeFrameLoaders", this,
+ &Document::MaybeInitializeFinalizeFrameLoaders);
+ nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
+ }
+ return;
+ }
+ mFrameLoaderRunner = nullptr;
+
+ // Don't use a temporary array for mInitializableFrameLoaders, because
+ // loading a frame may cause some other frameloader to be removed from the
+ // array. But be careful to keep the loader alive when starting the load!
+ while (mInitializableFrameLoaders.Length()) {
+ RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
+ mInitializableFrameLoaders.RemoveElementAt(0);
+ NS_ASSERTION(loader, "null frameloader in the array?");
+ loader->ReallyStartLoading();
+ }
+
+ uint32_t length = mFrameLoaderFinalizers.Length();
+ if (length > 0) {
+ nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
+ std::move(mFrameLoaderFinalizers);
+ for (uint32_t i = 0; i < length; ++i) {
+ LogRunnable::Run run(finalizers[i]);
+ finalizers[i]->Run();
+ }
+ }
+}
+
+void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
+ uint32_t length = mInitializableFrameLoaders.Length();
+ for (uint32_t i = 0; i < length; ++i) {
+ if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
+ mInitializableFrameLoaders.RemoveElementAt(i);
+ return;
+ }
+ }
+}
+
+void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
+ mPrototypeDocument = aPrototype;
+ mSynchronousDOMContentLoaded = true;
+}
+
+nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
+ return GetPermissionDelegateHandler();
+}
+
+Document* Document::RequestExternalResource(
+ nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
+ ExternalResourceLoad** aPendingLoad) {
+ MOZ_ASSERT(aURI, "Must have a URI");
+ MOZ_ASSERT(aRequestingNode, "Must have a node");
+ MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
+ if (mDisplayDocument) {
+ return mDisplayDocument->RequestExternalResource(
+ aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
+ }
+
+ return mExternalResourceMap.RequestResource(
+ aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
+}
+
+void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
+ mExternalResourceMap.EnumerateResources(aCallback);
+}
+
+SMILAnimationController* Document::GetAnimationController() {
+ // We create the animation controller lazily because most documents won't want
+ // one and only SVG documents and the like will call this
+ if (mAnimationController) return mAnimationController;
+ // Refuse to create an Animation Controller for data documents.
+ if (mLoadedAsData) return nullptr;
+
+ mAnimationController = new SMILAnimationController(this);
+
+ // If there's a presContext then check the animation mode and pause if
+ // necessary.
+ nsPresContext* context = GetPresContext();
+ if (mAnimationController && context &&
+ context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
+ mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
+ }
+
+ // If we're hidden (or being hidden), notify the newly-created animation
+ // controller. (Skip this check for SVG-as-an-image documents, though,
+ // because they don't get OnPageShow / OnPageHide calls).
+ if (!mIsShowing && !mIsBeingUsedAsImage) {
+ mAnimationController->OnPageHide();
+ }
+
+ return mAnimationController;
+}
+
+PendingAnimationTracker* Document::GetOrCreatePendingAnimationTracker() {
+ if (!mPendingAnimationTracker) {
+ mPendingAnimationTracker = new PendingAnimationTracker(this);
+ }
+
+ return mPendingAnimationTracker;
+}
+
+ScrollTimelineAnimationTracker*
+Document::GetOrCreateScrollTimelineAnimationTracker() {
+ if (!mScrollTimelineAnimationTracker) {
+ mScrollTimelineAnimationTracker = new ScrollTimelineAnimationTracker(this);
+ }
+
+ return mScrollTimelineAnimationTracker;
+}
+
+/**
+ * Retrieve the "direction" property of the document.
+ *
+ * @lina 01/09/2001
+ */
+void Document::GetDir(nsAString& aDirection) const {
+ aDirection.Truncate();
+ Element* rootElement = GetHtmlElement();
+ if (rootElement) {
+ static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
+ }
+}
+
+/**
+ * Set the "direction" property of the document.
+ *
+ * @lina 01/09/2001
+ */
+void Document::SetDir(const nsAString& aDirection) {
+ Element* rootElement = GetHtmlElement();
+ if (rootElement) {
+ rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
+ }
+}
+
+nsIHTMLCollection* Document::Images() {
+ if (!mImages) {
+ mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
+ nsGkAtoms::img);
+ }
+ return mImages;
+}
+
+nsIHTMLCollection* Document::Embeds() {
+ if (!mEmbeds) {
+ mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
+ nsGkAtoms::embed);
+ }
+ return mEmbeds;
+}
+
+static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
+ void* aData) {
+ return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
+ aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href);
+}
+
+nsIHTMLCollection* Document::Links() {
+ if (!mLinks) {
+ mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
+ }
+ return mLinks;
+}
+
+nsIHTMLCollection* Document::Forms() {
+ if (!mForms) {
+ // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
+ mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
+ nsGkAtoms::form);
+ }
+
+ return mForms;
+}
+
+nsIHTMLCollection* Document::Scripts() {
+ if (!mScripts) {
+ mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
+ nsGkAtoms::script);
+ }
+ return mScripts;
+}
+
+nsIHTMLCollection* Document::Applets() {
+ if (!mApplets) {
+ mApplets = new nsEmptyContentList(this);
+ }
+ return mApplets;
+}
+
+static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
+ void* aData) {
+ return aElement->IsHTMLElement(nsGkAtoms::a) &&
+ aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
+}
+
+nsIHTMLCollection* Document::Anchors() {
+ if (!mAnchors) {
+ mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
+ }
+ return mAnchors;
+}
+
+mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
+ const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
+ ErrorResult& rv) {
+ MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
+ "XOW should have caught this!");
+
+ nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
+ if (!window) {
+ rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return nullptr;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> outer =
+ nsPIDOMWindowOuter::GetFromCurrentInner(window);
+ if (!outer) {
+ rv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+ RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
+ RefPtr<BrowsingContext> newBC;
+ rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
+ if (!newBC) {
+ return nullptr;
+ }
+ return WindowProxyHolder(std::move(newBC));
+}
+
+Document* Document::Open(const Optional<nsAString>& /* unused */,
+ const Optional<nsAString>& /* unused */,
+ ErrorResult& aError) {
+ // Implements
+ // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
+
+ MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
+ "XOW should have caught this!");
+
+ // Step 1 -- throw if we're an XML document.
+ if (!IsHTMLDocument() || mDisableDocWrite) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Step 2 -- throw if dynamic markup insertion should throw.
+ if (ShouldThrowOnDynamicMarkupInsertion()) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Step 3 -- get the entry document, so we can use it for security checks.
+ nsCOMPtr<Document> callerDoc = GetEntryDocument();
+ if (!callerDoc) {
+ // If we're called from C++ or in some other way without an originating
+ // document we can't do a document.open w/o changing the principal of the
+ // document to something like about:blank (as that's the only sane thing to
+ // do when we don't know the origin of this call), and since we can't
+ // change the principals of a document for security reasons we'll have to
+ // refuse to go ahead with this call.
+
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // Step 4 -- make sure we're same-origin (not just same origin-domain) with
+ // the entry document.
+ if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // Step 5 -- if we have an active parser with a nonzero script nesting level,
+ // just no-op.
+ if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
+ return this;
+ }
+
+ // Step 6 -- check for open() during unload. Per spec, this is just a check
+ // of the ignore-opens-during-unload counter, but our unload event code
+ // doesn't affect that counter yet (unlike pagehide and beforeunload, which
+ // do), so we check for unload directly.
+ if (ShouldIgnoreOpens()) {
+ return this;
+ }
+
+ RefPtr<nsDocShell> shell(mDocumentContainer);
+ if (shell) {
+ bool inUnload;
+ shell->GetIsInUnload(&inUnload);
+ if (inUnload) {
+ return this;
+ }
+ }
+
+ // At this point we know this is a valid-enough document.open() call
+ // and not a no-op. Increment our use counter.
+ SetUseCounter(eUseCounter_custom_DocumentOpen);
+
+ // Step 7 -- stop existing navigation of our browsing context (and all other
+ // loads it's doing) if we're the active document of our browsing context.
+ // Note that we do not want to stop anything if there is no existing
+ // navigation.
+ if (shell && IsCurrentActiveDocument() &&
+ shell->GetIsAttemptingToNavigate()) {
+ shell->Stop(nsIWebNavigation::STOP_NETWORK);
+
+ // The Stop call may have cancelled the onload blocker request or
+ // prevented it from getting added, so we need to make sure it gets added
+ // to the document again otherwise the document could have a non-zero
+ // onload block count without the onload blocker request being in the
+ // loadgroup.
+ EnsureOnloadBlocker();
+ }
+
+ // Step 8 -- clear event listeners out of our DOM tree
+ for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
+ if (EventListenerManager* elm = node->GetExistingListenerManager()) {
+ elm->RemoveAllListeners();
+ }
+ }
+
+ // Step 9 -- clear event listeners from our window, if we have one.
+ //
+ // Note that we explicitly want the inner window, and only if we're its
+ // document. We want to do this (per spec) even when we're not the "active
+ // document", so we can't go through GetWindow(), because it might forward to
+ // the wrong inner.
+ if (nsPIDOMWindowInner* win = GetInnerWindow()) {
+ if (win->GetExtantDoc() == this) {
+ if (EventListenerManager* elm =
+ nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
+ elm->RemoveAllListeners();
+ }
+ }
+ }
+
+ // If we have a parser that has a zero script nesting level, we need to
+ // properly terminate it. We do that after we've removed all the event
+ // listeners (so termination won't trigger event listeners if it does
+ // something to the DOM), but before we remove all elements from the document
+ // (so if termination does modify the DOM in some way we will just blow it
+ // away immediately. See the similar code in WriteCommon that handles the
+ // !IsInsertionPointDefined() case and should stay in sync with this code.
+ if (mParser) {
+ MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
+ "Why didn't we take the early return?");
+ // Make sure we don't re-enter.
+ IgnoreOpensDuringUnload ignoreOpenGuard(this);
+ mParser->Terminate();
+ MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
+ }
+
+ // Step 10 -- remove all our DOM kids without firing any mutation events.
+ {
+ // We want to ignore any recursive calls to Open() that happen while
+ // disconnecting the node tree. The spec doesn't say to do this, but the
+ // spec also doesn't envision unload events on subframes firing while we do
+ // this, while all browsers fire them in practice. See
+ // <https://github.com/whatwg/html/issues/4611>.
+ IgnoreOpensDuringUnload ignoreOpenGuard(this);
+ DisconnectNodeTree();
+ }
+
+ // Step 11 -- if we're the current document in our docshell, do the
+ // equivalent of pushState() with the new URL we should have.
+ if (shell && IsCurrentActiveDocument()) {
+ nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
+ if (callerDoc != this) {
+ nsCOMPtr<nsIURI> noFragmentURI;
+ nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+ newURI = std::move(noFragmentURI);
+ }
+
+ // UpdateURLAndHistory might do various member-setting, so make sure we're
+ // holding strong refs to all the refcounted args on the stack. We can
+ // assume that our caller is holding on to "this" already.
+ nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
+ bool equalURIs;
+ nsresult rv = currentURI->Equals(newURI, &equalURIs);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+ nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
+ rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
+ /* aReplace = */ true, currentURI,
+ equalURIs);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ // And use the security info of the caller document as well, since
+ // it's the thing providing our data.
+ mSecurityInfo = callerDoc->GetSecurityInfo();
+
+ // This is not mentioned in the spec, but I think that's a spec bug. See
+ // <https://github.com/whatwg/html/issues/4299>. In any case, since our
+ // URL may be changing away from about:blank here, we really want to unset
+ // this flag no matter what, since only about:blank can be an initial
+ // document.
+ SetIsInitialDocument(false);
+
+ // And let our docloader know that it will need to track our load event.
+ nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
+ }
+
+ // Per spec nothing happens with our URI in other cases, though note
+ // <https://github.com/whatwg/html/issues/4286>.
+
+ // Note that we don't need to do anything here with base URIs per spec.
+ // That said, this might be assuming that we implement
+ // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
+ // correctly, which we don't right now for the about:blank case.
+
+ // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
+ mSkipLoadEventAfterClose = mLoadEventFiring;
+
+ // Preliminary to steps 13-16. Set our ready state to uninitialized before
+ // we do anything else, so we can then proceed to later ready state levels.
+ SetReadyStateInternal(READYSTATE_UNINITIALIZED,
+ /* updateTimingInformation = */ false);
+ // Reset a flag that affects readyState behavior.
+ mSetCompleteAfterDOMContentLoaded = false;
+
+ // Step 13 -- set our compat mode to standards.
+ SetCompatibilityMode(eCompatibility_FullStandards);
+
+ // Step 14 -- create a new parser associated with document. This also does
+ // step 16 implicitly.
+ mParserAborted = false;
+ RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
+ mParser = parser;
+ parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
+ nsresult rv = parser->StartExecutor();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ // Clear out our form control state, because the state of controls
+ // in the pre-open() document should not affect the state of
+ // controls that are now going to be written.
+ mLayoutHistoryState = nullptr;
+
+ if (shell) {
+ // Prepare the docshell and the document viewer for the impending
+ // out-of-band document.write()
+ shell->PrepareForNewContentModel();
+
+ nsCOMPtr<nsIContentViewer> cv;
+ shell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ cv->LoadStart(this);
+ }
+ }
+
+ // Step 15.
+ SetReadyStateInternal(Document::READYSTATE_LOADING,
+ /* updateTimingInformation = */ false);
+
+ // Step 16 happened with step 14 above.
+
+ // Step 17.
+ return this;
+}
+
+void Document::Close(ErrorResult& rv) {
+ if (!IsHTMLDocument()) {
+ // No calling document.close() on XHTML!
+
+ rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (ShouldThrowOnDynamicMarkupInsertion()) {
+ rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (!mParser || !mParser->IsScriptCreated()) {
+ return;
+ }
+
+ ++mWriteLevel;
+ rv = (static_cast<nsHtml5Parser*>(mParser.get()))
+ ->Parse(u""_ns, nullptr, true);
+ --mWriteLevel;
+}
+
+void Document::WriteCommon(const Sequence<nsString>& aText,
+ bool aNewlineTerminate, mozilla::ErrorResult& rv) {
+ // Fast path the common case
+ if (aText.Length() == 1) {
+ WriteCommon(aText[0], aNewlineTerminate, rv);
+ } else {
+ // XXXbz it would be nice if we could pass all the strings to the parser
+ // without having to do all this copying and then ask it to start
+ // parsing....
+ nsString text;
+ for (size_t i = 0; i < aText.Length(); ++i) {
+ text.Append(aText[i]);
+ }
+ WriteCommon(text, aNewlineTerminate, rv);
+ }
+}
+
+void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
+ ErrorResult& aRv) {
+#ifdef DEBUG
+ {
+ // Assert that we do not use or accidentally introduce doc.write()
+ // in system privileged context or in any of our about: pages.
+ nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
+ bool isAboutOrPrivContext = principal->IsSystemPrincipal();
+ if (!isAboutOrPrivContext) {
+ if (principal->SchemeIs("about")) {
+ // about:blank inherits the security contetext and this assertion
+ // is only meant for actual about: pages.
+ nsAutoCString host;
+ principal->GetHost(host);
+ isAboutOrPrivContext = !host.EqualsLiteral("blank");
+ }
+ }
+ // Some automated tests use an empty string to kick off some parsing
+ // mechansims, but they do not do any harm since they use an empty string.
+ MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
+ "do not use doc.write in privileged context!");
+ }
+#endif
+
+ mTooDeepWriteRecursion =
+ (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
+ if (NS_WARN_IF(mTooDeepWriteRecursion)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ if (!IsHTMLDocument() || mDisableDocWrite) {
+ // No calling document.write*() on XHTML!
+
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (ShouldThrowOnDynamicMarkupInsertion()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (mParserAborted) {
+ // Hixie says aborting the parser doesn't undefine the insertion point.
+ // However, since we null out mParser in that case, we track the
+ // theoretically defined insertion point using mParserAborted.
+ return;
+ }
+
+ // Implement Step 4.1 of:
+ // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
+ if (ShouldIgnoreOpens()) {
+ return;
+ }
+
+ void* key = GenerateParserKey();
+ if (mParser && !mParser->IsInsertionPointDefined()) {
+ if (mIgnoreDestructiveWritesCounter) {
+ // Instead of implying a call to document.open(), ignore the call.
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "DOM Events"_ns, this,
+ nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
+ return;
+ }
+ // The spec doesn't tell us to ignore opens from here, but we need to
+ // ensure opens are ignored here. See similar code in Open() that handles
+ // the case of an existing parser which is not currently running script and
+ // should stay in sync with this code.
+ IgnoreOpensDuringUnload ignoreOpenGuard(this);
+ mParser->Terminate();
+ MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
+ }
+
+ if (!mParser) {
+ if (mIgnoreDestructiveWritesCounter) {
+ // Instead of implying a call to document.open(), ignore the call.
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "DOM Events"_ns, this,
+ nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
+ return;
+ }
+
+ Open({}, {}, aRv);
+
+ // If Open() fails, or if it didn't create a parser (as it won't
+ // if the user chose to not discard the current document through
+ // onbeforeunload), don't write anything.
+ if (aRv.Failed() || !mParser) {
+ return;
+ }
+ }
+
+ static constexpr auto new_line = u"\n"_ns;
+
+ ++mWriteLevel;
+
+ // This could be done with less code, but for performance reasons it
+ // makes sense to have the code for two separate Parse() calls here
+ // since the concatenation of strings costs more than we like. And
+ // why pay that price when we don't need to?
+ if (aNewlineTerminate) {
+ aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
+ ->Parse(aText + new_line, key, false);
+ } else {
+ aRv =
+ (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
+ }
+
+ --mWriteLevel;
+
+ mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
+}
+
+void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
+ WriteCommon(aText, false, rv);
+}
+
+void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
+ WriteCommon(aText, true, rv);
+}
+
+void* Document::GenerateParserKey(void) {
+ if (!mScriptLoader) {
+ // If we don't have a script loader, then the parser probably isn't parsing
+ // anything anyway, so just return null.
+ return nullptr;
+ }
+
+ // The script loader provides us with the currently executing script element,
+ // which is guaranteed to be unique per script.
+ nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
+ if (script && mParser && mParser->IsScriptCreated()) {
+ nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
+ if (creatorParser != mParser) {
+ // Make scripts that aren't inserted by the active parser of this document
+ // participate in the context of the script that document.open()ed
+ // this document.
+ return nullptr;
+ }
+ }
+ return script;
+}
+
+/* static */
+bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
+ nsAtom* aAtom, void* aData) {
+ MOZ_ASSERT(aElement, "Must have element to work with!");
+
+ if (!aElement->HasName()) {
+ return false;
+ }
+
+ nsString* elementName = static_cast<nsString*>(aData);
+ return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
+ aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
+ eCaseMatters);
+}
+
+/* static */
+void* Document::UseExistingNameString(nsINode* aRootNode,
+ const nsString* aName) {
+ return const_cast<nsString*>(aName);
+}
+
+nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
+ if (mDocumentURI) {
+ nsAutoCString uri;
+ nsresult rv = mDocumentURI->GetSpec(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF8toUTF16(uri, aDocumentURI);
+ } else {
+ aDocumentURI.Truncate();
+ }
+
+ return NS_OK;
+}
+
+// Alias of above
+nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
+
+void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
+ CallerType aCallerType,
+ ErrorResult& aRv) const {
+ if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
+ aRv = GetDocumentURI(aDocumentURI);
+ return;
+ }
+
+ nsAutoCString uri;
+ nsresult res = mChromeXHRDocURI->GetSpec(uri);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ CopyUTF8toUTF16(uri, aDocumentURI);
+}
+
+nsIURI* Document::GetDocumentURIObject() const {
+ if (!mChromeXHRDocURI) {
+ return GetDocumentURI();
+ }
+
+ return mChromeXHRDocURI;
+}
+
+void Document::GetCompatMode(nsString& aCompatMode) const {
+ NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
+ mCompatMode == eCompatibility_AlmostStandards ||
+ mCompatMode == eCompatibility_FullStandards,
+ "mCompatMode is neither quirks nor strict for this document");
+
+ if (mCompatMode == eCompatibility_NavQuirks) {
+ aCompatMode.AssignLiteral("BackCompat");
+ } else {
+ aCompatMode.AssignLiteral("CSS1Compat");
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
+
+void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
+ if (Element* element = Element::FromNode(aNode)) {
+ if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
+ while (true) {
+ RefPtr<Attr> attr;
+ {
+ // Use an iterator to get an arbitrary attribute from the
+ // cache. The iterator must be destroyed before any other
+ // operations on mAttributeCache, to avoid hash table
+ // assertions.
+ auto iter = map->mAttributeCache.ConstIter();
+ if (iter.Done()) {
+ break;
+ }
+ attr = iter.UserData();
+ }
+
+ BlastSubtreeToPieces(attr);
+
+ mozilla::DebugOnly<nsresult> rv =
+ element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
+ attr->NodeInfo()->NameAtom(), false);
+
+ // XXX Should we abort here?
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
+ }
+ }
+
+ if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
+ BlastSubtreeToPieces(shadow);
+ element->UnattachShadow();
+ }
+ }
+
+ while (aNode->HasChildren()) {
+ nsIContent* node = aNode->GetFirstChild();
+ BlastSubtreeToPieces(node);
+ aNode->RemoveChildNode(node, false);
+ }
+}
+
+namespace mozilla::dom {
+
+nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) {
+ OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
+
+ // Scope firing mutation events so that we don't carry any state that
+ // might be stale
+ {
+ if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
+ nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
+ }
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ switch (adoptedNode->NodeType()) {
+ case ATTRIBUTE_NODE: {
+ // Remove from ownerElement.
+ OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
+
+ nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement();
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ if (ownerElement) {
+ OwningNonNull<Attr> newAttr =
+ ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ break;
+ }
+ case DOCUMENT_FRAGMENT_NODE: {
+ if (adoptedNode->IsShadowRoot()) {
+ rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
+ return nullptr;
+ }
+ [[fallthrough]];
+ }
+ case ELEMENT_NODE:
+ case PROCESSING_INSTRUCTION_NODE:
+ case TEXT_NODE:
+ case CDATA_SECTION_NODE:
+ case COMMENT_NODE:
+ case DOCUMENT_TYPE_NODE: {
+ // Don't allow adopting a node's anonymous subtree out from under it.
+ if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ // We don't want to adopt an element into its own contentDocument or into
+ // a descendant contentDocument, so we check if the frameElement of this
+ // document or any of its parents is the adopted node or one of its
+ // descendants.
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ while (bc) {
+ nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
+ if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
+ rv.ThrowHierarchyRequestError(
+ "Trying to adopt a node into its own contentDocument or a "
+ "descendant contentDocument.");
+ return nullptr;
+ }
+
+ if (XRE_IsParentProcess()) {
+ bc = bc->Canonical()->GetParentCrossChromeBoundary();
+ } else {
+ bc = bc->GetParent();
+ }
+ }
+
+ // Remove from parent.
+ nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
+ if (parent) {
+ parent->RemoveChildNode(adoptedNode->AsContent(), true);
+ } else {
+ MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
+ }
+
+ break;
+ }
+ case DOCUMENT_NODE: {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+ default: {
+ NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
+
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
+ bool sameDocument = oldDocument == this;
+
+ AutoJSContext cx;
+ JS::Rooted<JSObject*> newScope(cx, nullptr);
+ if (!sameDocument) {
+ newScope = GetWrapper();
+ if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
+ // Make sure cx is in a semi-sane compartment before we call WrapNative.
+ // It's kind of irrelevant, given that we're passing aAllowWrapping =
+ // false, and documents should always insist on being wrapped in an
+ // canonical scope. But we try to pass something sane anyway.
+ JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
+ JSAutoRealm ar(cx, globalObject);
+ JS::Rooted<JS::Value> v(cx);
+ rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
+ /* aAllowWrapping = */ false);
+ if (rv.Failed()) return nullptr;
+ newScope = &v.toObject();
+ }
+ }
+
+ adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
+ if (rv.Failed()) {
+ // Disconnect all nodes from their parents, since some have the old document
+ // as their ownerDocument and some have this as their ownerDocument.
+ nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
+ "Should still be in the document we just got adopted into");
+
+ return adoptedNode;
+}
+
+bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
+
+static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
+ const nsString& aScaleString) {
+ // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
+ if (aScaleString.EqualsLiteral("device-width") ||
+ aScaleString.EqualsLiteral("device-height")) {
+ return Some(LayoutDeviceToScreenScale(10.0f));
+ } else if (aScaleString.EqualsLiteral("yes")) {
+ return Some(LayoutDeviceToScreenScale(1.0f));
+ } else if (aScaleString.EqualsLiteral("no")) {
+ return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
+ } else if (aScaleString.IsEmpty()) {
+ return Nothing();
+ }
+
+ nsresult scaleErrorCode;
+ float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
+ if (NS_FAILED(scaleErrorCode)) {
+ return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
+ }
+
+ if (scale < 0) {
+ return Nothing();
+ }
+ return Some(clamped(LayoutDeviceToScreenScale(scale), ViewportMinScale(),
+ ViewportMaxScale()));
+}
+
+void Document::ParseScalesInViewportMetaData(
+ const ViewportMetaData& aViewportMetaData) {
+ Maybe<LayoutDeviceToScreenScale> scale;
+
+ scale = ParseScaleString(aViewportMetaData.mInitialScale);
+ mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
+ mValidScaleFloat = scale.isSome();
+
+ scale = ParseScaleString(aViewportMetaData.mMaximumScale);
+ // Chrome uses '5' for the fallback value of maximum-scale, we might
+ // consider matching it in future.
+ // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
+ mScaleMaxFloat = scale.valueOr(ViewportMaxScale());
+ mValidMaxScale = scale.isSome();
+
+ scale = ParseScaleString(aViewportMetaData.mMinimumScale);
+ mScaleMinFloat = scale.valueOr(ViewportMinScale());
+ mValidMinScale = scale.isSome();
+
+ // Resolve min-zoom and max-zoom values.
+ // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
+ if (mValidMaxScale && mValidMinScale) {
+ mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
+ }
+}
+
+void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
+ const nsAString& aHeightString,
+ bool aHasValidScale) {
+ // The width and height properties
+ // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
+ //
+ // The width and height viewport <META> properties are translated into width
+ // and height descriptors, setting the min-width/min-height value to
+ // extend-to-zoom and the max-width/max-height value to the length from the
+ // viewport <META> property as follows:
+ //
+ // 1. Non-negative number values are translated to pixel lengths, clamped to
+ // the range: [1px, 10000px]
+ // 2. Negative number values are dropped
+ // 3. device-width and device-height translate to 100vw and 100vh respectively
+ // 4. Other keywords and unknown values are also dropped
+ mMinWidth = nsViewportInfo::kAuto;
+ mMaxWidth = nsViewportInfo::kAuto;
+ if (!aWidthString.IsEmpty()) {
+ mMinWidth = nsViewportInfo::kExtendToZoom;
+ if (aWidthString.EqualsLiteral("device-width")) {
+ mMaxWidth = nsViewportInfo::kDeviceSize;
+ } else {
+ nsresult widthErrorCode;
+ mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
+ if (NS_FAILED(widthErrorCode)) {
+ mMaxWidth = nsViewportInfo::kAuto;
+ } else if (mMaxWidth >= 0.0f) {
+ mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
+ } else {
+ mMaxWidth = nsViewportInfo::kAuto;
+ }
+ }
+ } else if (aHasValidScale) {
+ if (aHeightString.IsEmpty()) {
+ mMinWidth = nsViewportInfo::kExtendToZoom;
+ mMaxWidth = nsViewportInfo::kExtendToZoom;
+ }
+ } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
+ mMinWidth = nsViewportInfo::kExtendToZoom;
+ mMaxWidth = nsViewportInfo::kDeviceSize;
+ }
+
+ mMinHeight = nsViewportInfo::kAuto;
+ mMaxHeight = nsViewportInfo::kAuto;
+ if (!aHeightString.IsEmpty()) {
+ mMinHeight = nsViewportInfo::kExtendToZoom;
+ if (aHeightString.EqualsLiteral("device-height")) {
+ mMaxHeight = nsViewportInfo::kDeviceSize;
+ } else {
+ nsresult heightErrorCode;
+ mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
+ if (NS_FAILED(heightErrorCode)) {
+ mMaxHeight = nsViewportInfo::kAuto;
+ } else if (mMaxHeight >= 0.0f) {
+ mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
+ } else {
+ mMaxHeight = nsViewportInfo::kAuto;
+ }
+ }
+ }
+}
+
+nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
+ MOZ_ASSERT(mPresShell);
+
+ // Compute the CSS-to-LayoutDevice pixel scale as the product of the
+ // widget scale and the full zoom.
+ nsPresContext* context = mPresShell->GetPresContext();
+ // When querying the full zoom, get it from the device context rather than
+ // directly from the pres context, because the device context's value can
+ // include an adjustment necessary to keep the number of app units per device
+ // pixel an integer, and we want the adjusted value.
+ float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
+ fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
+ CSSToLayoutDeviceScale layoutDeviceScale =
+ context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
+
+ CSSToScreenScale defaultScale =
+ layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
+
+ // Special behaviour for desktop mode, provided we are not on an about: page,
+ // or fullscreen.
+ const bool fullscreen = Fullscreen();
+ auto* bc = GetBrowsingContext();
+ if (bc && bc->ForceDesktopViewport() && !IsAboutPage() && !fullscreen) {
+ CSSCoord viewportWidth =
+ StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
+ CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
+ float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
+ CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
+ ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
+ return nsViewportInfo(fakeDesktopSize, scaleToFit,
+ nsViewportInfo::ZoomFlag::AllowZoom,
+ nsViewportInfo::ZoomBehaviour::Mobile);
+ }
+
+ // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
+ if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
+ return nsViewportInfo(aDisplaySize, defaultScale,
+ nsLayoutUtils::AllowZoomingForDocument(this)
+ ? nsViewportInfo::ZoomFlag::AllowZoom
+ : nsViewportInfo::ZoomFlag::DisallowZoom,
+ StaticPrefs::apz_allow_zooming_out()
+ ? nsViewportInfo::ZoomBehaviour::Mobile
+ : nsViewportInfo::ZoomBehaviour::Desktop);
+ }
+
+ // In cases where the width of the CSS viewport is less than or equal to the
+ // width of the display (i.e. width <= device-width) then we disable
+ // double-tap-to-zoom behaviour. See bug 941995 for details.
+
+ switch (mViewportType) {
+ case DisplayWidthHeight:
+ return nsViewportInfo(aDisplaySize, defaultScale,
+ nsViewportInfo::ZoomFlag::AllowZoom,
+ nsViewportInfo::ZoomBehaviour::Mobile);
+ case Unknown: {
+ // We might early exit if the viewport is empty. Even if we don't,
+ // at the end of this case we'll note that it was empty. Later, when
+ // we're using the cached values, this will trigger alternate code paths.
+ if (!mLastModifiedViewportMetaData) {
+ // If the docType specifies that we are on a site optimized for mobile,
+ // then we want to return specially crafted defaults for the viewport
+ // info.
+ if (RefPtr<DocumentType> docType = GetDoctype()) {
+ nsAutoString docId;
+ docType->GetPublicId(docId);
+ if ((docId.Find(u"WAP") != -1) || (docId.Find(u"Mobile") != -1) ||
+ (docId.Find(u"WML") != -1)) {
+ // We're making an assumption that the docType can't change here
+ mViewportType = DisplayWidthHeight;
+ return nsViewportInfo(aDisplaySize, defaultScale,
+ nsViewportInfo::ZoomFlag::AllowZoom,
+ nsViewportInfo::ZoomBehaviour::Mobile);
+ }
+ }
+
+ nsAutoString handheldFriendly;
+ GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
+ if (handheldFriendly.EqualsLiteral("true")) {
+ mViewportType = DisplayWidthHeight;
+ return nsViewportInfo(aDisplaySize, defaultScale,
+ nsViewportInfo::ZoomFlag::AllowZoom,
+ nsViewportInfo::ZoomBehaviour::Mobile);
+ }
+ }
+
+ ViewportMetaData metaData = GetViewportMetaData();
+
+ // Parse initial-scale, minimum-scale and maximum-scale.
+ ParseScalesInViewportMetaData(metaData);
+
+ // Parse width and height properties
+ // This function sets m{Min,Max}{Width,Height}.
+ ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
+ mValidScaleFloat);
+
+ mAllowZoom = true;
+ if ((metaData.mUserScalable.EqualsLiteral("0")) ||
+ (metaData.mUserScalable.EqualsLiteral("no")) ||
+ (metaData.mUserScalable.EqualsLiteral("false"))) {
+ mAllowZoom = false;
+ }
+
+ // Resolve viewport-fit value.
+ // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
+ mViewportFit = ViewportFitType::Auto;
+ if (!metaData.mViewportFit.IsEmpty()) {
+ if (metaData.mViewportFit.EqualsLiteral("contain")) {
+ mViewportFit = ViewportFitType::Contain;
+ } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
+ mViewportFit = ViewportFitType::Cover;
+ }
+ }
+
+ mWidthStrEmpty = metaData.mWidth.IsEmpty();
+
+ mViewportType = Specified;
+ [[fallthrough]];
+ }
+ case Specified:
+ default:
+ LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
+ LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
+ bool effectiveValidMaxScale = mValidMaxScale;
+
+ nsViewportInfo::ZoomFlag effectiveZoomFlag =
+ mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
+ : nsViewportInfo::ZoomFlag::DisallowZoom;
+ if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
+ // If the pref to force user-scalable is enabled, we ignore the values
+ // from the meta-viewport tag for these properties and just assume they
+ // allow the page to be scalable. Note in particular that this code is
+ // in the "Specified" branch of the enclosing switch statement, so that
+ // calls to GetViewportInfo always use the latest value of the
+ // browser_ui_zoom_force_user_scalable pref. Other codepaths that
+ // return nsViewportInfo instances are all consistent with
+ // browser_ui_zoom_force_user_scalable() already.
+ effectiveMinScale = ViewportMinScale();
+ effectiveMaxScale = ViewportMaxScale();
+ effectiveValidMaxScale = true;
+ effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
+ }
+
+ // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
+ auto ComputeExtendZoom = [&]() -> float {
+ if (mValidScaleFloat && effectiveValidMaxScale) {
+ return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
+ }
+ if (mValidScaleFloat) {
+ return mScaleFloat.scale;
+ }
+ if (effectiveValidMaxScale) {
+ return effectiveMaxScale.scale;
+ }
+ return nsViewportInfo::kAuto;
+ };
+
+ // Resolving 'extend-to-zoom'
+ // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
+ float extendZoom = ComputeExtendZoom();
+
+ CSSCoord minWidth = mMinWidth;
+ CSSCoord maxWidth = mMaxWidth;
+ CSSCoord minHeight = mMinHeight;
+ CSSCoord maxHeight = mMaxHeight;
+
+ // aDisplaySize is in screen pixels; convert them to CSS pixels for the
+ // viewport size. We need to use this scaled size for any clamping of
+ // width or height.
+ CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
+
+ // Our min and max width and height values are mostly as specified by
+ // the viewport declaration, but we make an exception for max width.
+ // Max width, if auto, and if there's no initial-scale, will be set
+ // to a default size. This is to support legacy site design with no
+ // viewport declaration, and to do that using the same scheme as
+ // Chrome does, in order to maintain web compatibility. Since the
+ // default size has a complicated calculation, we fixup the maxWidth
+ // value after setting it, above.
+ if (maxWidth == nsViewportInfo::kAuto && !mValidScaleFloat) {
+ if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
+ bc->InRDMPane()) {
+ // If RDM and touch simulation are active, then use the simulated
+ // screen width to accommodate for cases where the screen width is
+ // larger than the desktop viewport default.
+ maxWidth = nsViewportInfo::Max(
+ displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
+ } else {
+ maxWidth = StaticPrefs::browser_viewport_desktopWidth();
+ }
+ // Divide by fullZoom to stretch CSS pixel size of viewport in order
+ // to keep device pixel size unchanged after full zoom applied.
+ // See bug 974242.
+ maxWidth /= fullZoom;
+
+ // We set minWidth to ExtendToZoom, which will cause our later width
+ // calculation to expand to maxWidth, if scale restrictions allow it.
+ minWidth = nsViewportInfo::kExtendToZoom;
+ }
+
+ // Resolve device-width and device-height first.
+ if (maxWidth == nsViewportInfo::kDeviceSize) {
+ maxWidth = displaySize.width;
+ }
+ if (maxHeight == nsViewportInfo::kDeviceSize) {
+ maxHeight = displaySize.height;
+ }
+ if (extendZoom == nsViewportInfo::kAuto) {
+ if (maxWidth == nsViewportInfo::kExtendToZoom) {
+ maxWidth = nsViewportInfo::kAuto;
+ }
+ if (maxHeight == nsViewportInfo::kExtendToZoom) {
+ maxHeight = nsViewportInfo::kAuto;
+ }
+ if (minWidth == nsViewportInfo::kExtendToZoom) {
+ minWidth = maxWidth;
+ }
+ if (minHeight == nsViewportInfo::kExtendToZoom) {
+ minHeight = maxHeight;
+ }
+ } else {
+ CSSSize extendSize = displaySize / extendZoom;
+ if (maxWidth == nsViewportInfo::kExtendToZoom) {
+ maxWidth = extendSize.width;
+ }
+ if (maxHeight == nsViewportInfo::kExtendToZoom) {
+ maxHeight = extendSize.height;
+ }
+ if (minWidth == nsViewportInfo::kExtendToZoom) {
+ minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
+ }
+ if (minHeight == nsViewportInfo::kExtendToZoom) {
+ minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
+ }
+ }
+
+ // Resolve initial width and height from min/max descriptors
+ // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
+ CSSCoord width = nsViewportInfo::kAuto;
+ if (minWidth != nsViewportInfo::kAuto ||
+ maxWidth != nsViewportInfo::kAuto) {
+ width = nsViewportInfo::Max(
+ minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
+ }
+ CSSCoord height = nsViewportInfo::kAuto;
+ if (minHeight != nsViewportInfo::kAuto ||
+ maxHeight != nsViewportInfo::kAuto) {
+ height = nsViewportInfo::Max(
+ minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
+ }
+
+ // Resolve width value
+ // https://drafts.csswg.org/css-device-adapt/#resolve-width
+ if (width == nsViewportInfo::kAuto) {
+ if (height == nsViewportInfo::kAuto || aDisplaySize.height == 0) {
+ width = displaySize.width;
+ } else {
+ width = height * aDisplaySize.width / aDisplaySize.height;
+ }
+ }
+
+ // Resolve height value
+ // https://drafts.csswg.org/css-device-adapt/#resolve-height
+ if (height == nsViewportInfo::kAuto) {
+ if (aDisplaySize.width == 0) {
+ height = displaySize.height;
+ } else {
+ height = width * aDisplaySize.height / aDisplaySize.width;
+ }
+ }
+ MOZ_ASSERT(width != nsViewportInfo::kAuto &&
+ height != nsViewportInfo::kAuto);
+
+ CSSSize size(width, height);
+
+ CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
+ CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
+ CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
+
+ nsViewportInfo::AutoSizeFlag sizeFlag =
+ nsViewportInfo::AutoSizeFlag::FixedSize;
+ if (mMaxWidth == nsViewportInfo::kDeviceSize ||
+ (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::kDeviceSize ||
+ mScaleFloat.scale == 1.0f)) ||
+ (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::kAuto &&
+ mMaxHeight < 0)) {
+ sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
+ }
+
+ // FIXME: Resolving width and height should be done above 'Resolve width
+ // value' and 'Resolve height value'.
+ if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
+ size = displaySize;
+ }
+
+ // The purpose of clamping the viewport width to a minimum size is to
+ // prevent page authors from setting it to a ridiculously small value.
+ // If the page is actually being rendered in a very small area (as might
+ // happen in e.g. Android 8's picture-in-picture mode), we don't want to
+ // prevent the viewport from taking on that size.
+ CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
+
+ size.width = clamped(size.width, effectiveMinSize.width,
+ float(kViewportMaxSize.width));
+
+ // Also recalculate the default zoom, if it wasn't specified in the
+ // metadata, and the width is specified.
+ if (!mValidScaleFloat && !mWidthStrEmpty) {
+ CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
+ scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
+ }
+
+ size.height = clamped(size.height, effectiveMinSize.height,
+ float(kViewportMaxSize.height));
+
+ // In cases of user-scalable=no, if we have a positive scale, clamp it to
+ // min and max, and then use the clamped value for the scale, the min, and
+ // the max. If we don't have a positive scale, assert that we are setting
+ // the auto scale flag.
+ if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
+ scaleFloat > CSSToScreenScale(0.0f)) {
+ scaleFloat = scaleMinFloat = scaleMaxFloat =
+ clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
+ }
+ MOZ_ASSERT(
+ scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
+ "If we don't have a positive scale, we should be using auto scale.");
+
+ // We need to perform a conversion, but only if the initial or maximum
+ // scale were set explicitly by the user.
+ if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
+ scaleFloat <= scaleMaxFloat) {
+ CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
+ size.width = std::max(size.width, displaySize.width);
+ size.height = std::max(size.height, displaySize.height);
+ } else if (effectiveValidMaxScale) {
+ CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
+ size.width = std::max(size.width, displaySize.width);
+ size.height = std::max(size.height, displaySize.height);
+ }
+
+ return nsViewportInfo(
+ scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
+ mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
+ : nsViewportInfo::AutoScaleFlag::AutoScale,
+ effectiveZoomFlag, mViewportFit);
+ }
+}
+
+ViewportMetaData Document::GetViewportMetaData() const {
+ return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
+ : ViewportMetaData();
+}
+
+void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
+ mLastModifiedViewportMetaData = std::move(aData);
+ // Trigger recomputation of the nsViewportInfo the next time it's queried.
+ mViewportType = Unknown;
+
+ AsyncEventDispatcher::RunDOMEventWhenSafe(
+ *this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes,
+ ChromeOnlyDispatch::eYes);
+}
+
+EventListenerManager* Document::GetOrCreateListenerManager() {
+ if (!mListenerManager) {
+ mListenerManager =
+ new EventListenerManager(static_cast<EventTarget*>(this));
+ SetFlags(NODE_HAS_LISTENERMANAGER);
+ }
+
+ return mListenerManager;
+}
+
+EventListenerManager* Document::GetExistingListenerManager() const {
+ return mListenerManager;
+}
+
+void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
+ aVisitor.mCanHandle = true;
+ // FIXME! This is a hack to make middle mouse paste working also in Editor.
+ // Bug 329119
+ aVisitor.mForceContentDispatch = true;
+
+ // Load events must not propagate to |window| object, see bug 335251.
+ if (aVisitor.mEvent->mMessage != eLoad) {
+ nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
+ aVisitor.SetParentTarget(
+ window ? window->GetTargetForEventTargetChain() : nullptr, false);
+ }
+}
+
+already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
+ CallerType aCallerType,
+ ErrorResult& rv) const {
+ nsPresContext* presContext = GetPresContext();
+
+ // Create event even without presContext.
+ RefPtr<Event> ev =
+ EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
+ nullptr, aEventType, aCallerType);
+ if (!ev) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+ WidgetEvent* e = ev->WidgetEventPtr();
+ e->mFlags.mBubbles = false;
+ e->mFlags.mCancelable = false;
+ return ev.forget();
+}
+
+void Document::FlushPendingNotifications(FlushType aType) {
+ mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
+ FlushPendingNotifications(flush);
+}
+
+void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
+ FlushType flushType = aFlush.mFlushType;
+
+ RefPtr<Document> documentOnStack = this;
+
+ // We need to flush the sink for non-HTML documents (because the XML
+ // parser still does insertion with deferred notifications). We
+ // also need to flush the sink if this is a layout-related flush, to
+ // make sure that layout is started as needed. But we can skip that
+ // part if we have no presshell or if it's already done an initial
+ // reflow.
+ if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
+ mPresShell && !mPresShell->DidInitialize())) &&
+ (mParser || mWeakSink)) {
+ nsCOMPtr<nsIContentSink> sink;
+ if (mParser) {
+ sink = mParser->GetContentSink();
+ } else {
+ sink = do_QueryReferent(mWeakSink);
+ if (!sink) {
+ mWeakSink = nullptr;
+ }
+ }
+ // Determine if it is safe to flush the sink notifications
+ // by determining if it safe to flush all the presshells.
+ if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
+ sink->FlushPendingNotifications(flushType);
+ }
+ }
+
+ // Should we be flushing pending binding constructors in here?
+
+ if (flushType <= FlushType::ContentAndNotify) {
+ // Nothing to do here
+ return;
+ }
+
+ // If we have a parent we must flush the parent too to ensure that our
+ // container is reflowed if its size was changed.
+ //
+ // We do it only if the subdocument and the parent can observe each other
+ // synchronously (that is, if we're not cross-origin), to avoid work that is
+ // not observable, and if the parent document has finished loading all its
+ // render-blocking stylesheets and may start laying out the document, to avoid
+ // unnecessary flashes of unstyled content on the parent document. Note that
+ // this last bit means that size-dependent media queries in this document may
+ // produce incorrect results temporarily.
+ //
+ // But if it's not safe to flush ourselves, then don't flush the parent, since
+ // that can cause things like resizes of our frame's widget, which we can't
+ // handle while flushing is unsafe.
+ if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
+ mParentDocument->MayStartLayout() && IsSafeToFlush()) {
+ ChangesToFlush parentFlush = aFlush;
+ if (flushType >= FlushType::Style) {
+ // Since media queries mean that a size change of our container can affect
+ // style, we need to promote a style flush on ourself to a layout flush on
+ // our parent, since we need our container to be the correct size to
+ // determine the correct style.
+ parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
+ }
+ mParentDocument->FlushPendingNotifications(parentFlush);
+ }
+
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ presShell->FlushPendingNotifications(aFlush);
+ }
+}
+
+void Document::FlushExternalResources(FlushType aType) {
+ NS_ASSERTION(
+ aType >= FlushType::Style,
+ "should only need to flush for style or higher in external resources");
+ if (GetDisplayDocument()) {
+ return;
+ }
+
+ auto flush = [aType](Document& aDoc) {
+ aDoc.FlushPendingNotifications(aType);
+ return CallState::Continue;
+ };
+
+ EnumerateExternalResources(flush);
+}
+
+void Document::SetXMLDeclaration(const char16_t* aVersion,
+ const char16_t* aEncoding,
+ const int32_t aStandalone) {
+ if (!aVersion || *aVersion == '\0') {
+ mXMLDeclarationBits = 0;
+ return;
+ }
+
+ mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
+
+ if (aEncoding && *aEncoding != '\0') {
+ mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
+ }
+
+ if (aStandalone == 1) {
+ mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
+ XML_DECLARATION_BITS_STANDALONE_YES;
+ } else if (aStandalone == 0) {
+ mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
+ }
+}
+
+void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
+ nsAString& aStandalone) {
+ aVersion.Truncate();
+ aEncoding.Truncate();
+ aStandalone.Truncate();
+
+ if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
+ return;
+ }
+
+ // always until we start supporting 1.1 etc.
+ aVersion.AssignLiteral("1.0");
+
+ if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
+ // This is what we have stored, not necessarily what was written
+ // in the original
+ GetCharacterSet(aEncoding);
+ }
+
+ if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
+ if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
+ aStandalone.AssignLiteral("yes");
+ } else {
+ aStandalone.AssignLiteral("no");
+ }
+ }
+}
+
+void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
+ mColorSchemeMetaTags.Insert(aMeta);
+ RecomputeColorScheme();
+}
+
+void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
+ mColorSchemeMetaTags.RemoveElement(aMeta);
+ RecomputeColorScheme();
+}
+
+void Document::RecomputeColorScheme() {
+ if (!StaticPrefs::layout_css_color_scheme_enabled()) {
+ return;
+ }
+ auto oldColorScheme = mColorSchemeBits;
+ mColorSchemeBits = 0;
+ const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
+ for (const HTMLMetaElement* el : elements) {
+ nsAutoString content;
+ if (!el->GetAttr(nsGkAtoms::content, content)) {
+ continue;
+ }
+
+ NS_ConvertUTF16toUTF8 contentU8(content);
+ if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
+ break;
+ }
+ }
+
+ if (mColorSchemeBits == oldColorScheme) {
+ return;
+ }
+
+ if (nsPresContext* pc = GetPresContext()) {
+ // This affects system colors, which are inherited, so we need to recascade.
+ pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
+ }
+}
+
+bool Document::IsScriptEnabled() const {
+ // If this document is sandboxed without 'allow-scripts'
+ // script is not enabled
+ if (HasScriptsBlockedBySandbox()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject =
+ do_QueryInterface(GetInnerWindow());
+ if (!globalObject || !globalObject->HasJSGlobal()) {
+ return false;
+ }
+
+ return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
+ .Allowed();
+}
+
+void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
+ PRTime modDate = 0;
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (httpChannel) {
+ nsAutoCString tmp;
+ rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
+
+ if (NS_SUCCEEDED(rv)) {
+ PRTime time;
+ PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
+ if (st == PR_SUCCESS) {
+ modDate = time;
+ }
+ }
+
+ static const char* const headers[] = {
+ "default-style", "content-style-type", "content-language",
+ "content-disposition", "refresh", "x-dns-prefetch-control",
+ "x-frame-options", "origin-trial",
+ // add more http headers if you need
+ // XXXbz don't add content-location support without reading bug
+ // 238654 and its dependencies/dups first.
+ 0};
+
+ nsAutoCString headerVal;
+ const char* const* name = headers;
+ while (*name) {
+ rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
+ if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
+ RefPtr<nsAtom> key = NS_Atomize(*name);
+ SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
+ }
+ ++name;
+ }
+ } else {
+ nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
+ if (fileChannel) {
+ nsCOMPtr<nsIFile> file;
+ fileChannel->GetFile(getter_AddRefs(file));
+ if (file) {
+ PRTime msecs;
+ rv = file->GetLastModifiedTime(&msecs);
+
+ if (NS_SUCCEEDED(rv)) {
+ modDate = msecs * int64_t(PR_USEC_PER_MSEC);
+ }
+ }
+ } else {
+ nsAutoCString contentDisp;
+ rv = aChannel->GetContentDispositionHeader(contentDisp);
+ if (NS_SUCCEEDED(rv)) {
+ SetHeaderData(nsGkAtoms::headerContentDisposition,
+ NS_ConvertASCIItoUTF16(contentDisp));
+ }
+ }
+ }
+
+ mLastModified.Truncate();
+ if (modDate != 0) {
+ GetFormattedTimeString(modDate, mLastModified);
+ }
+}
+
+void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
+ // set any HTTP-EQUIV data into document's header data as well as url
+ nsAutoString header;
+ aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
+ if (!header.IsEmpty()) {
+ // Ignore META REFRESH when document is sandboxed from automatic features.
+ nsContentUtils::ASCIIToLower(header);
+ if (nsGkAtoms::refresh->Equals(header) &&
+ (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
+ return;
+ }
+
+ nsAutoString result;
+ aMetaElement->GetAttr(nsGkAtoms::content, result);
+ if (!result.IsEmpty()) {
+ RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
+ SetHeaderData(fieldAtom, result);
+ }
+ }
+
+ if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ nsGkAtoms::handheldFriendly, eIgnoreCase)) {
+ nsAutoString result;
+ aMetaElement->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result);
+ if (!result.IsEmpty()) {
+ nsContentUtils::ASCIIToLower(result);
+ SetHeaderData(nsGkAtoms::handheldFriendly, result);
+ }
+ }
+}
+
+already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
+ nsAtom* aPrefix,
+ int32_t aNamespaceID,
+ const nsAString* aIs) {
+#ifdef DEBUG
+ nsAutoString qName;
+ if (aPrefix) {
+ aPrefix->ToString(qName);
+ qName.Append(':');
+ }
+ qName.Append(aName);
+
+ // Note: "a:b:c" is a valid name in non-namespaces XML, and
+ // Document::CreateElement can call us with such a name and no prefix,
+ // which would cause an error if we just used true here.
+ bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
+ NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
+ "Don't pass invalid prefixes to Document::CreateElem, "
+ "check caller.");
+#endif
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
+ getter_AddRefs(nodeInfo));
+ NS_ENSURE_TRUE(nodeInfo, nullptr);
+
+ nsCOMPtr<Element> element;
+ nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
+ NOT_FROM_PARSER, aIs);
+ return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
+}
+
+bool Document::IsSafeToFlush() const {
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return true;
+ }
+ return presShell->IsSafeToFlush();
+}
+
+void Document::Sanitize() {
+ // Sanitize the document by resetting all (current and former) password fields
+ // and any form fields with autocomplete=off to their default values. We do
+ // this now, instead of when the presentation is restored, to offer some
+ // protection in case there is ever an exploit that allows a cached document
+ // to be accessed from a different document.
+
+ // First locate all input elements, regardless of whether they are
+ // in a form, and reset the password and autocomplete=off elements.
+
+ RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
+
+ nsAutoString value;
+
+ uint32_t length = nodes->Length(true);
+ for (uint32_t i = 0; i < length; ++i) {
+ NS_ASSERTION(nodes->Item(i), "null item in node list!");
+
+ RefPtr<HTMLInputElement> input =
+ HTMLInputElement::FromNodeOrNull(nodes->Item(i));
+ if (!input) continue;
+
+ input->GetAttr(nsGkAtoms::autocomplete, value);
+ if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
+ input->Reset();
+ }
+ }
+
+ // Now locate all _form_ elements that have autocomplete=off and reset them
+ nodes = GetElementsByTagName(u"form"_ns);
+
+ length = nodes->Length(true);
+ for (uint32_t i = 0; i < length; ++i) {
+ // Reset() may change the list dynamically.
+ RefPtr<HTMLFormElement> form =
+ HTMLFormElement::FromNodeOrNull(nodes->Item(i));
+ if (!form) continue;
+
+ form->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, value);
+ if (value.LowerCaseEqualsLiteral("off")) form->Reset();
+ }
+}
+
+void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
+ if (!mSubDocuments) {
+ return;
+ }
+
+ // PLDHashTable::Iterator can't handle modifications while iterating so we
+ // copy all entries to an array first before calling any callbacks.
+ AutoTArray<RefPtr<Document>, 8> subdocs;
+ for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<SubDocMapEntry*>(iter.Get());
+ if (Document* subdoc = entry->mSubDocument) {
+ subdocs.AppendElement(subdoc);
+ }
+ }
+ for (auto& subdoc : subdocs) {
+ if (aCallback(*subdoc) == CallState::Stop) {
+ break;
+ }
+ }
+}
+
+void Document::CollectDescendantDocuments(
+ nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
+ if (!mSubDocuments) {
+ return;
+ }
+
+ for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<SubDocMapEntry*>(iter.Get());
+ const Document* subdoc = entry->mSubDocument;
+ if (subdoc) {
+ if (aCallback(subdoc)) {
+ aDescendants.AppendElement(entry->mSubDocument);
+ }
+ subdoc->CollectDescendantDocuments(aDescendants, aCallback);
+ }
+ }
+}
+
+bool Document::CanSavePresentation(nsIRequest* aNewRequest,
+ uint32_t& aBFCacheCombo,
+ bool aIncludeSubdocuments,
+ bool aAllowUnloadListeners) {
+ bool ret = true;
+
+ if (!IsBFCachingAllowed()) {
+ aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
+ ret = false;
+ }
+
+ nsAutoCString uri;
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
+ if (mDocumentURI) {
+ mDocumentURI->GetSpec(uri);
+ }
+ }
+
+ if (EventHandlingSuppressed()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked on event handling suppression", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
+ ret = false;
+ }
+
+ // Do not allow suspended windows to be placed in the
+ // bfcache. This method is also used to verify a document
+ // coming out of the bfcache is ok to restore, though. So
+ // we only want to block suspend windows that aren't also
+ // frozen.
+ nsPIDOMWindowInner* win = GetInnerWindow();
+ if (win && win->IsSuspended() && !win->IsFrozen()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked on suspended Window", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::SUSPENDED;
+ ret = false;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
+ bool thirdParty = false;
+ // Currently some other mobile browsers seem to bfcache only cross-domain
+ // pages, but bfcache those also when there are unload event listeners, so
+ // this is trying to match that behavior as much as possible.
+ bool allowUnloadListeners =
+ aAllowUnloadListeners &&
+ StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
+ (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
+ channel, &thirdParty)) &&
+ thirdParty));
+
+ // Check our event listener manager for unload/beforeunload listeners.
+ nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
+ if (!allowUnloadListeners && piTarget) {
+ EventListenerManager* manager = piTarget->GetExistingListenerManager();
+ if (manager) {
+ if (manager->HasUnloadListeners()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to unload handlers", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
+ ret = false;
+ }
+ if (manager->HasBeforeUnloadListeners()) {
+ if (!mozilla::SessionHistoryInParent() ||
+ !StaticPrefs::
+ docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
+ MOZ_LOG(
+ gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to beforeUnload handlers", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
+ ret = false;
+ }
+ }
+ }
+ }
+
+ // Check if we have pending network requests
+ nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
+ if (loadGroup) {
+ nsCOMPtr<nsISimpleEnumerator> requests;
+ loadGroup->GetRequests(getter_AddRefs(requests));
+
+ bool hasMore = false;
+
+ // We want to bail out if we have any requests other than aNewRequest (or
+ // in the case when aNewRequest is a part of a multipart response the base
+ // channel the multipart response is coming in on).
+ nsCOMPtr<nsIChannel> baseChannel;
+ nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
+ if (part) {
+ part->GetBaseChannel(getter_AddRefs(baseChannel));
+ }
+
+ while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> elem;
+ requests->GetNext(getter_AddRefs(elem));
+
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
+ if (request && request != aNewRequest && request != baseChannel) {
+ // Favicon loads don't need to block caching.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
+ if (li->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
+ continue;
+ }
+ }
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
+ nsAutoCString requestName;
+ request->GetName(requestName);
+ MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
+ ("Save of %s blocked because document has request %s",
+ uri.get(), requestName.get()));
+ }
+ aBFCacheCombo |= BFCacheStatus::REQUEST;
+ ret = false;
+ }
+ }
+ }
+
+ // Check if we have active GetUserMedia use
+ if (MediaManager::Exists() && win &&
+ MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to GetUserMedia", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
+ ret = false;
+ }
+
+#ifdef MOZ_WEBRTC
+ // Check if we have active PeerConnections
+ if (win && win->HasActivePeerConnections()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to PeerConnection", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
+ ret = false;
+ }
+#endif // MOZ_WEBRTC
+
+ // Don't save presentations for documents containing EME content, so that
+ // CDMs reliably shutdown upon user navigation.
+ if (ContainsEMEContent()) {
+ aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
+ ret = false;
+ }
+
+ // Don't save presentations for documents containing MSE content, to
+ // reduce memory usage.
+ if (ContainsMSEContent()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to MSE use", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
+ ret = false;
+ }
+
+ if (aIncludeSubdocuments && mSubDocuments) {
+ for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<SubDocMapEntry*>(iter.Get());
+ Document* subdoc = entry->mSubDocument;
+
+ uint32_t subDocBFCacheCombo = 0;
+ // The aIgnoreRequest we were passed is only for us, so don't pass it on.
+ bool canCache =
+ subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
+ true, allowUnloadListeners)
+ : false;
+ if (!canCache) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to subdocument blocked", uri.get()));
+ aBFCacheCombo |= subDocBFCacheCombo;
+ ret = false;
+ }
+ }
+ }
+
+ if (!mozilla::BFCacheInParent()) {
+ // BFCache is currently not compatible with remote subframes (bug 1609324)
+ if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
+ for (auto& child : browsingContext->Children()) {
+ if (!child->IsInProcess()) {
+ aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
+ ret = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (win) {
+ auto* globalWindow = nsGlobalWindowInner::Cast(win);
+#ifdef MOZ_WEBSPEECH
+ if (globalWindow->HasActiveSpeechSynthesis()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to Speech use", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
+ ret = false;
+ }
+#endif
+ if (globalWindow->HasUsedVR()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to having used VR", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
+ ret = false;
+ }
+
+ if (win->HasActiveLocks()) {
+ MOZ_LOG(
+ gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to having active lock requests", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
+ ret = false;
+ }
+
+ if (win->HasActiveWebTransports()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Save of %s blocked due to WebTransport", uri.get()));
+ aBFCacheCombo |= BFCacheStatus::ACTIVE_WEBTRANSPORT;
+ ret = false;
+ }
+ }
+
+ return ret;
+}
+
+void Document::Destroy() {
+ // The ContentViewer wants to release the document now. So, tell our content
+ // to drop any references to the document so that it can be destroyed.
+ if (mIsGoingAway) {
+ return;
+ }
+
+ ReportDocumentUseCounters();
+ SetDevToolsWatchingDOMMutations(false);
+
+ mIsGoingAway = true;
+
+ ScriptLoader()->Destroy();
+ SetScriptGlobalObject(nullptr);
+ RemovedFromDocShell();
+
+ bool oldVal = mInUnlinkOrDeletion;
+ mInUnlinkOrDeletion = true;
+
+#ifdef DEBUG
+ uint32_t oldChildCount = GetChildCount();
+#endif
+
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ child->DestroyContent();
+ MOZ_ASSERT(child->GetParentNode() == this);
+ }
+ MOZ_ASSERT(oldChildCount == GetChildCount());
+ MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
+
+ mInUnlinkOrDeletion = oldVal;
+
+ mLayoutHistoryState = nullptr;
+
+ if (mOriginalDocument) {
+ mOriginalDocument->mLatestStaticClone = nullptr;
+ }
+
+ if (IsStaticDocument()) {
+ RemoveProperty(nsGkAtoms::printisfocuseddoc);
+ RemoveProperty(nsGkAtoms::printselectionranges);
+ }
+
+ // Shut down our external resource map. We might not need this for
+ // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
+ // tearing down all those frame trees right now is the right thing to do.
+ mExternalResourceMap.Shutdown();
+
+ // Manually break cycles via promise's global object pointer.
+ mReadyForIdle = nullptr;
+ mOrientationPendingPromise = nullptr;
+
+ // To break cycles.
+ mPreloadService.ClearAllPreloads();
+
+ if (mDocumentL10n) {
+ mDocumentL10n->Destroy();
+ }
+}
+
+void Document::RemovedFromDocShell() {
+ mEditingState = EditingState::eOff;
+
+ if (mRemovedFromDocShell) return;
+
+ mRemovedFromDocShell = true;
+ NotifyActivityChanged();
+
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ child->SaveSubtreeState();
+ }
+
+ nsIDocShell* docShell = GetDocShell();
+ if (docShell) {
+ docShell->SynchronizeLayoutHistoryState();
+ }
+}
+
+already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
+ const {
+ nsCOMPtr<nsILayoutHistoryState> state;
+ if (!mScriptGlobalObject) {
+ state = mLayoutHistoryState;
+ } else {
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (docShell) {
+ docShell->GetLayoutHistoryState(getter_AddRefs(state));
+ }
+ }
+
+ return state.forget();
+}
+
+void Document::EnsureOnloadBlocker() {
+ // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
+ // -- it's not ours.
+ if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
+ nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
+ if (loadGroup) {
+ // Check first to see if mOnloadBlocker is in the loadgroup.
+ nsCOMPtr<nsISimpleEnumerator> requests;
+ loadGroup->GetRequests(getter_AddRefs(requests));
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> elem;
+ requests->GetNext(getter_AddRefs(elem));
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
+ if (request && request == mOnloadBlocker) {
+ return;
+ }
+ }
+
+ // Not in the loadgroup, so add it.
+ loadGroup->AddRequest(mOnloadBlocker, nullptr);
+ }
+ }
+}
+
+void Document::BlockOnload() {
+ if (mDisplayDocument) {
+ mDisplayDocument->BlockOnload();
+ return;
+ }
+
+ // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
+ // -- it's not ours.
+ if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
+ if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
+ loadGroup->AddRequest(mOnloadBlocker, nullptr);
+ }
+ }
+ ++mOnloadBlockCount;
+}
+
+void Document::UnblockOnload(bool aFireSync) {
+ if (mDisplayDocument) {
+ mDisplayDocument->UnblockOnload(aFireSync);
+ return;
+ }
+
+ --mOnloadBlockCount;
+
+ if (mOnloadBlockCount == 0) {
+ if (mScriptGlobalObject) {
+ // Only manipulate the loadgroup in this case, because if
+ // mScriptGlobalObject is null, it's not ours.
+ if (aFireSync) {
+ // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
+ ++mOnloadBlockCount;
+ DoUnblockOnload();
+ } else {
+ PostUnblockOnloadEvent();
+ }
+ } else if (mIsBeingUsedAsImage) {
+ // To correctly unblock onload for a document that contains an SVG
+ // image, we need to know when all of the SVG document's resources are
+ // done loading, in a way comparable to |window.onload|. We fire this
+ // event to indicate that the SVG should be considered fully loaded.
+ // Because scripting is disabled on SVG-as-image documents, this event
+ // is not accessible to content authors. (See bug 837315.)
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
+ CanBubble::eNo, ChromeOnlyDispatch::eNo);
+ asyncDispatcher->PostDOMEvent();
+ }
+ }
+}
+
+class nsUnblockOnloadEvent : public Runnable {
+ public:
+ explicit nsUnblockOnloadEvent(Document* aDoc)
+ : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
+ NS_IMETHOD Run() override {
+ mDoc->DoUnblockOnload();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<Document> mDoc;
+};
+
+void Document::PostUnblockOnloadEvent() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
+ nsresult rv = Dispatch(TaskCategory::Other, evt.forget());
+ if (NS_SUCCEEDED(rv)) {
+ // Stabilize block count so we don't post more events while this one is up
+ ++mOnloadBlockCount;
+ } else {
+ NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
+ }
+}
+
+void Document::DoUnblockOnload() {
+ MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
+ MOZ_ASSERT(mOnloadBlockCount != 0,
+ "Shouldn't have a count of zero here, since we stabilized in "
+ "PostUnblockOnloadEvent");
+
+ --mOnloadBlockCount;
+
+ if (mOnloadBlockCount != 0) {
+ // We blocked again after the last unblock. Nothing to do here. We'll
+ // post a new event when we unblock again.
+ return;
+ }
+
+ // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
+ // -- it's not ours.
+ if (mScriptGlobalObject) {
+ if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
+ loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
+ }
+ }
+}
+
+nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
+ nsIContent* content = f->GetContent();
+ if (!content) {
+ continue;
+ }
+
+ if (content->OwnerDoc() == this) {
+ return content;
+ }
+ // We must be in a subdocument so jump directly to the root frame.
+ // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
+ // the containing document.
+ f = f->PresContext()->GetPresShell()->GetRootFrame();
+ }
+
+ return nullptr;
+}
+
+void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
+ const nsAString& aType, bool aInFrameSwap,
+ bool aPersisted, bool aOnlySystemGroup) {
+ if (!aDispatchTarget) {
+ return;
+ }
+
+ PageTransitionEventInit init;
+ init.mBubbles = true;
+ init.mCancelable = true;
+ init.mPersisted = aPersisted;
+ init.mInFrameSwap = aInFrameSwap;
+
+ RefPtr<PageTransitionEvent> event =
+ PageTransitionEvent::Constructor(this, aType, init);
+
+ event->SetTrusted(true);
+ event->SetTarget(this);
+ if (aOnlySystemGroup) {
+ event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
+ }
+ EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
+ nullptr);
+}
+
+void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
+ bool aOnlySystemGroup) {
+ if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
+ nsCString uri;
+ if (GetDocumentURI()) {
+ uri = GetDocumentURI()->GetSpecOrDefault();
+ }
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
+ }
+
+ const bool inFrameLoaderSwap = !!aDispatchStartTarget;
+ MOZ_DIAGNOSTIC_ASSERT(
+ inFrameLoaderSwap ==
+ (mDocumentContainer && mDocumentContainer->InFrameSwap()));
+
+ Element* root = GetRootElement();
+ if (aPersisted && root) {
+ // Send out notifications that our <link> elements are attached.
+ RefPtr<nsContentList> links =
+ NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
+
+ uint32_t linkCount = links->Length(true);
+ for (uint32_t i = 0; i < linkCount; ++i) {
+ static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
+ }
+ }
+
+ // See Document
+ if (!inFrameLoaderSwap) {
+ if (aPersisted) {
+ ImageTracker()->SetAnimatingState(true);
+ }
+
+ // Set mIsShowing before firing events, in case those event handlers
+ // move us around.
+ mIsShowing = true;
+ mVisible = true;
+
+ UpdateVisibilityState();
+ }
+
+ NotifyActivityChanged();
+
+ auto notifyExternal = [aPersisted](Document& aExternalResource) {
+ aExternalResource.OnPageShow(aPersisted, nullptr);
+ return CallState::Continue;
+ };
+ EnumerateExternalResources(notifyExternal);
+
+ if (mAnimationController) {
+ mAnimationController->OnPageShow();
+ }
+
+ if (!mIsBeingUsedAsImage) {
+ // Dispatch observer notification to notify observers page is shown.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsIPrincipal* principal = NodePrincipal();
+ os->NotifyObservers(ToSupports(this),
+ principal->IsSystemPrincipal() ? "chrome-page-shown"
+ : "content-page-shown",
+ nullptr);
+ }
+
+ nsCOMPtr<EventTarget> target = aDispatchStartTarget;
+ if (!target) {
+ target = do_QueryInterface(GetWindow());
+ }
+ DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
+ aPersisted, aOnlySystemGroup);
+ }
+}
+
+static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
+ if (nsPresContext* presContext = aDocument.GetPresContext()) {
+ auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
+ FullscreenEventType::Change, &aDocument, aTarget);
+ presContext->RefreshDriver()->ScheduleFullscreenEvent(
+ std::move(pendingEvent));
+ }
+}
+
+void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
+ bool aOnlySystemGroup) {
+ if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
+ nsCString uri;
+ if (GetDocumentURI()) {
+ uri = GetDocumentURI()->GetSpecOrDefault();
+ }
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
+ }
+
+ const bool inFrameLoaderSwap = !!aDispatchStartTarget;
+ MOZ_DIAGNOSTIC_ASSERT(
+ inFrameLoaderSwap ==
+ (mDocumentContainer && mDocumentContainer->InFrameSwap()));
+
+ // Send out notifications that our <link> elements are detached,
+ // but only if this is not a full unload.
+ Element* root = GetRootElement();
+ if (aPersisted && root) {
+ RefPtr<nsContentList> links =
+ NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
+
+ uint32_t linkCount = links->Length(true);
+ for (uint32_t i = 0; i < linkCount; ++i) {
+ static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved();
+ }
+ }
+
+ if (mAnimationController) {
+ mAnimationController->OnPageHide();
+ }
+
+ if (!inFrameLoaderSwap) {
+ if (aPersisted) {
+ // We do not stop the animations (bug 1024343) when the page is refreshing
+ // while being dragged out.
+ ImageTracker()->SetAnimatingState(false);
+ }
+
+ // Set mIsShowing before firing events, in case those event handlers
+ // move us around.
+ mIsShowing = false;
+ mVisible = false;
+ }
+
+ ExitPointerLock();
+
+ if (!mIsBeingUsedAsImage) {
+ // Dispatch observer notification to notify observers page is hidden.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsIPrincipal* principal = NodePrincipal();
+ os->NotifyObservers(ToSupports(this),
+ principal->IsSystemPrincipal()
+ ? "chrome-page-hidden"
+ : "content-page-hidden",
+ nullptr);
+ }
+
+ // Now send out a PageHide event.
+ nsCOMPtr<EventTarget> target = aDispatchStartTarget;
+ if (!target) {
+ target = do_QueryInterface(GetWindow());
+ }
+ {
+ PageUnloadingEventTimeStamp timeStamp(this);
+ DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
+ aPersisted, aOnlySystemGroup);
+ }
+ }
+
+ if (!inFrameLoaderSwap) {
+ UpdateVisibilityState();
+ }
+
+ auto notifyExternal = [aPersisted](Document& aExternalResource) {
+ aExternalResource.OnPageHide(aPersisted, nullptr);
+ return CallState::Continue;
+ };
+ EnumerateExternalResources(notifyExternal);
+ NotifyActivityChanged();
+
+ ClearPendingFullscreenRequests(this);
+ if (Fullscreen()) {
+ // If this document was fullscreen, we should exit fullscreen in this
+ // doctree branch. This ensures that if the user navigates while in
+ // fullscreen mode we don't leave its still visible ancestor documents
+ // in fullscreen mode. So exit fullscreen in the document's fullscreen
+ // root document, as this will exit fullscreen in all the root's
+ // descendant documents. Note that documents are removed from the
+ // doctree by the time OnPageHide() is called, so we must store a
+ // reference to the root (in Document::mFullscreenRoot) since we can't
+ // just traverse the doctree to get the root.
+ Document::ExitFullscreenInDocTree(this);
+
+ // Since the document is removed from the doctree before OnPageHide() is
+ // called, ExitFullscreen() can't traverse from the root down to *this*
+ // document, so we must manually call CleanupFullscreenState() below too.
+ // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
+ // so we *must* call it after ExitFullscreen(), not before.
+ // OnPageHide() is called in every hidden (i.e. descendant) document,
+ // so calling CleanupFullscreenState() here will ensure all hidden
+ // documents have their fullscreen state reset.
+ CleanupFullscreenState();
+
+ // The fullscreenchange event is to be queued in the refresh driver,
+ // however a hidden page wouldn't trigger that again, so it makes no
+ // sense to dispatch such event here.
+ }
+}
+
+void Document::WillDispatchMutationEvent(nsINode* aTarget) {
+ NS_ASSERTION(
+ mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
+ "mSubtreeModifiedTargets not cleared after dispatching?");
+ ++mSubtreeModifiedDepth;
+ if (aTarget) {
+ // MayDispatchMutationEvent is often called just before this method,
+ // so it has already appended the node to mSubtreeModifiedTargets.
+ int32_t count = mSubtreeModifiedTargets.Count();
+ if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
+ mSubtreeModifiedTargets.AppendObject(aTarget);
+ }
+ }
+}
+
+void Document::MutationEventDispatched(nsINode* aTarget) {
+ if (--mSubtreeModifiedDepth) {
+ return;
+ }
+
+ int32_t count = mSubtreeModifiedTargets.Count();
+ if (!count) {
+ return;
+ }
+
+ nsPIDOMWindowInner* window = GetInnerWindow();
+ if (window &&
+ !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
+ mSubtreeModifiedTargets.Clear();
+ return;
+ }
+
+ nsCOMArray<nsINode> realTargets;
+ for (nsINode* possibleTarget : mSubtreeModifiedTargets) {
+ if (possibleTarget->ChromeOnlyAccess()) {
+ continue;
+ }
+
+ nsINode* commonAncestor = nullptr;
+ int32_t realTargetCount = realTargets.Count();
+ for (int32_t j = 0; j < realTargetCount; ++j) {
+ commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
+ possibleTarget, realTargets[j]);
+ if (commonAncestor) {
+ realTargets.ReplaceObjectAt(commonAncestor, j);
+ break;
+ }
+ }
+ if (!commonAncestor) {
+ realTargets.AppendObject(possibleTarget);
+ }
+ }
+
+ mSubtreeModifiedTargets.Clear();
+
+ for (const nsCOMPtr<nsINode>& target : realTargets) {
+ InternalMutationEvent mutation(true, eLegacySubtreeModified);
+ // MOZ_KnownLive due to bug 1620312
+ AsyncEventDispatcher::RunDOMEventWhenSafe(MOZ_KnownLive(*target), mutation);
+ }
+}
+
+void Document::DestroyElementMaps() {
+#ifdef DEBUG
+ mStyledLinksCleared = true;
+#endif
+ mStyledLinks.Clear();
+ // Notify ID change listeners before clearing the identifier map.
+ for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
+ iter.Get()->ClearAndNotify();
+ }
+ mIdentifierMap.Clear();
+ mComposedShadowRoots.Clear();
+ mResponsiveContent.Clear();
+ IncrementExpandoGeneration(*this);
+}
+
+void Document::RefreshLinkHrefs() {
+ // Get a list of all links we know about. We will reset them, which will
+ // remove them from the document, so we need a copy of what is in the
+ // hashtable.
+ const LinkArray linksToNotify = ToArray(mStyledLinks);
+
+ // Reset all of our styled links.
+ nsAutoScriptBlocker scriptBlocker;
+ for (LinkArray::size_type i = 0; i < linksToNotify.Length(); i++) {
+ linksToNotify[i]->ResetLinkState(true, linksToNotify[i]->ElementHasHref());
+ }
+}
+
+nsresult Document::CloneDocHelper(Document* clone) const {
+ clone->mIsStaticDocument = mCreatingStaticClone;
+
+ // Init document
+ nsresult rv = clone->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mCreatingStaticClone) {
+ if (mOriginalDocument) {
+ clone->mOriginalDocument = mOriginalDocument;
+ } else {
+ clone->mOriginalDocument = const_cast<Document*>(this);
+ }
+ clone->mOriginalDocument->mLatestStaticClone = clone;
+ clone->mOriginalDocument->mStaticCloneCount++;
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+
+ // |mDocumentContainer| is the container of the document that is being
+ // created and not the original container. See CreateStaticClone function().
+ nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
+ if (docLoader) {
+ docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
+ }
+ nsCOMPtr<nsIChannel> channel = GetChannel();
+ nsCOMPtr<nsIURI> uri;
+ if (channel) {
+ NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
+ } else {
+ uri = Document::GetDocumentURI();
+ }
+ clone->mChannel = channel;
+ clone->mShouldResistFingerprinting = mShouldResistFingerprinting;
+ if (uri) {
+ clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
+ }
+
+ clone->mIsSrcdocDocument = mIsSrcdocDocument;
+ clone->SetContainer(mDocumentContainer);
+
+ // Setup the navigation time. This will be needed by any animations in the
+ // document, even if they are only paused.
+ MOZ_ASSERT(!clone->GetNavigationTiming(),
+ "Navigation time was already set?");
+ if (mTiming) {
+ RefPtr<nsDOMNavigationTiming> timing =
+ mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
+ clone->SetNavigationTiming(timing);
+ }
+ clone->SetCsp(mCSP);
+ }
+
+ // Now ensure that our clone has the same URI, base URI, and principal as us.
+ // We do this after the mCreatingStaticClone block above, because that block
+ // can set the base URI to an incorrect value in cases when base URI
+ // information came from the channel. So we override explicitly, and do it
+ // for all these properties, in case ResetToURI messes with any of the rest of
+ // them.
+ clone->SetDocumentURI(Document::GetDocumentURI());
+ clone->SetChromeXHRDocURI(mChromeXHRDocURI);
+ clone->SetPrincipals(NodePrincipal(), mPartitionedPrincipal);
+ clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
+ clone->mActiveCookiePrincipal = mActiveCookiePrincipal;
+ // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
+ // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
+ // when printed standalone via window.print() (where there won't be a parent
+ // document to grab the URI from).
+ clone->mDocumentBaseURI = GetDocBaseURI();
+ clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
+ clone->mReferrerInfo =
+ static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
+ clone->mPreloadReferrerInfo = clone->mReferrerInfo;
+
+ bool hasHadScriptObject = true;
+ nsIScriptGlobalObject* scriptObject =
+ GetScriptHandlingObject(hasHadScriptObject);
+ NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
+ if (mCreatingStaticClone) {
+ // If we're doing a static clone (print, print preview), then we're going to
+ // be setting a scope object after the clone. It's better to set it only
+ // once, so we don't do that here. However, we do want to act as if there is
+ // a script handling object. So we set mHasHadScriptHandlingObject.
+ clone->mHasHadScriptHandlingObject = true;
+ } else if (scriptObject) {
+ clone->SetScriptHandlingObject(scriptObject);
+ } else {
+ clone->SetScopeObject(GetScopeObject());
+ }
+ // Make the clone a data document
+ clone->SetLoadedAsData(
+ true,
+ /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
+
+ // Misc state
+
+ // State from Document
+ clone->mCharacterSet = mCharacterSet;
+ clone->mCharacterSetSource = mCharacterSetSource;
+ clone->SetCompatibilityMode(mCompatMode);
+ clone->mBidiOptions = mBidiOptions;
+ clone->mContentLanguage = mContentLanguage;
+ clone->SetContentType(GetContentTypeInternal());
+ clone->mSecurityInfo = mSecurityInfo;
+
+ // State from Document
+ clone->mType = mType;
+ clone->mXMLDeclarationBits = mXMLDeclarationBits;
+ clone->mBaseTarget = mBaseTarget;
+
+ return NS_OK;
+}
+
+void Document::NotifyLoading(bool aNewParentIsLoading,
+ const ReadyState& aCurrentState,
+ ReadyState aNewState) {
+ // Mirror the top-level loading state down to all subdocuments
+ bool was_loading = mAncestorIsLoading ||
+ aCurrentState == READYSTATE_LOADING ||
+ aCurrentState == READYSTATE_INTERACTIVE;
+ bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
+ aNewState == READYSTATE_INTERACTIVE; // new value for state
+ bool set_load_state = was_loading != is_loading;
+
+ MOZ_LOG(
+ gTimeoutDeferralLog, mozilla::LogLevel::Debug,
+ ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
+ "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
+ "set_load_state: %d",
+ (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
+ (int)aNewState, was_loading, is_loading, set_load_state));
+
+ mAncestorIsLoading = aNewParentIsLoading;
+ if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
+ // Tell our innerwindow (and thus TimeoutManager)
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (inner) {
+ inner->SetActiveLoadingState(is_loading);
+ }
+ BrowsingContext* context = GetBrowsingContext();
+ if (context) {
+ // Don't use PreOrderWalk to mirror this down; go down one level as a
+ // time so we can set mAncestorIsLoading and take into account the
+ // readystates of the subdocument. In the child process it will call
+ // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
+ // iterate it's children
+ for (auto& child : context->Children()) {
+ MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
+ ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
+ // Setting ancestor loading on a discarded browsing context has no
+ // effect.
+ Unused << child->SetAncestorLoading(is_loading);
+ }
+ }
+ }
+}
+
+void Document::SetReadyStateInternal(ReadyState aReadyState,
+ bool aUpdateTimingInformation) {
+ if (aReadyState == READYSTATE_UNINITIALIZED) {
+ // Transition back to uninitialized happens only to keep assertions happy
+ // right before readyState transitions to something else. Make this
+ // transition undetectable by Web content.
+ mReadyState = aReadyState;
+ return;
+ }
+
+ if (IsTopLevelContentDocument()) {
+ if (aReadyState == READYSTATE_LOADING) {
+ AddToplevelLoadingDocument(this);
+ } else if (aReadyState == READYSTATE_COMPLETE) {
+ RemoveToplevelLoadingDocument(this);
+ }
+ }
+
+ if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
+ SetLoadingOrRestoredFromBFCacheTimeStampToNow();
+ }
+ NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
+ mReadyState = aReadyState;
+ if (aUpdateTimingInformation && mTiming) {
+ switch (aReadyState) {
+ case READYSTATE_LOADING:
+ mTiming->NotifyDOMLoading(GetDocumentURI());
+ break;
+ case READYSTATE_INTERACTIVE:
+ mTiming->NotifyDOMInteractive(GetDocumentURI());
+ break;
+ case READYSTATE_COMPLETE:
+ mTiming->NotifyDOMComplete(GetDocumentURI());
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
+ break;
+ }
+ }
+ // At the time of loading start, we don't have timing object, record time.
+
+ if (READYSTATE_INTERACTIVE == aReadyState &&
+ NodePrincipal()->IsSystemPrincipal()) {
+ if (!mXULPersist) {
+ mXULPersist = new XULPersist(this);
+ mXULPersist->Init();
+ }
+ if (!mChromeObserver) {
+ mChromeObserver = new ChromeObserver(this);
+ mChromeObserver->Init();
+ }
+ }
+
+ if (aUpdateTimingInformation) {
+ RecordNavigationTiming(aReadyState);
+ }
+
+ AsyncEventDispatcher::RunDOMEventWhenSafe(
+ *this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
+}
+
+void Document::GetReadyState(nsAString& aReadyState) const {
+ switch (mReadyState) {
+ case READYSTATE_LOADING:
+ aReadyState.AssignLiteral(u"loading");
+ break;
+ case READYSTATE_INTERACTIVE:
+ aReadyState.AssignLiteral(u"interactive");
+ break;
+ case READYSTATE_COMPLETE:
+ aReadyState.AssignLiteral(u"complete");
+ break;
+ default:
+ aReadyState.AssignLiteral(u"uninitialized");
+ }
+}
+
+void Document::SuppressEventHandling(uint32_t aIncrease) {
+ mEventsSuppressed += aIncrease;
+ if (mEventsSuppressed == aIncrease) {
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
+ }
+ }
+ UpdateFrameRequestCallbackSchedulingState();
+ for (uint32_t i = 0; i < aIncrease; ++i) {
+ ScriptLoader()->AddExecuteBlocker();
+ }
+
+ auto suppressInSubDoc = [aIncrease](Document& aSubDoc) {
+ aSubDoc.SuppressEventHandling(aIncrease);
+ return CallState::Continue;
+ };
+
+ EnumerateSubDocuments(suppressInSubDoc);
+}
+
+void Document::NotifyAbortedLoad() {
+ // If we still have outstanding work blocking DOMContentLoaded,
+ // then don't try to change the readystate now, but wait until
+ // they finish and then do so.
+ if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
+ mSetCompleteAfterDOMContentLoaded = true;
+ return;
+ }
+
+ // Otherwise we're fully done at this point, so set the
+ // readystate to complete.
+ if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
+ SetReadyStateInternal(Document::READYSTATE_COMPLETE);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents(
+ nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) {
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ if (MOZ_UNLIKELY(!fm)) {
+ return;
+ }
+
+ nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments);
+ for (uint32_t i = 0; i < documents.Length(); ++i) {
+ nsCOMPtr<Document> document = std::move(documents[i]);
+ // NB: Don't bother trying to fire delayed events on documents that were
+ // closed before this event ran.
+ if (!document->EventHandlingSuppressed()) {
+ fm->FireDelayedEvents(document);
+ RefPtr<PresShell> presShell = document->GetPresShell();
+ if (presShell) {
+ // Only fire events for active documents.
+ bool fire = aFireEvents && document->GetInnerWindow() &&
+ document->GetInnerWindow()->IsCurrentInnerWindow();
+ presShell->FireOrClearDelayedEvents(fire);
+ }
+ document->FireOrClearPostMessageEvents(aFireEvents);
+ }
+ }
+}
+
+void Document::PreloadPictureClosed() {
+ MOZ_ASSERT(mPreloadPictureDepth > 0);
+ mPreloadPictureDepth--;
+ if (mPreloadPictureDepth == 0) {
+ mPreloadPictureFoundSource.SetIsVoid(true);
+ }
+}
+
+void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr,
+ const nsAString& aTypeAttr,
+ const nsAString& aMediaAttr) {
+ // Nested pictures are not valid syntax, so while we'll eventually load them,
+ // it's not worth tracking sources mixed between nesting levels to preload
+ // them effectively.
+ if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
+ // <picture> selects the first matching source, so if this returns a URI we
+ // needn't consider new sources until a new <picture> is encountered.
+ bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
+ this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
+ aMediaAttr, mPreloadPictureFoundSource);
+ if (found && mPreloadPictureFoundSource.IsVoid()) {
+ // Found an empty source, which counts
+ mPreloadPictureFoundSource.SetIsVoid(false);
+ }
+ }
+}
+
+already_AddRefed<nsIURI> Document::ResolvePreloadImage(
+ nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr, bool* aIsImgSet) {
+ nsString sourceURL;
+ bool isImgSet;
+ if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
+ // We're in a <picture> element and found a URI from a source previous to
+ // this image, use it.
+ sourceURL = mPreloadPictureFoundSource;
+ isImgSet = true;
+ } else {
+ // Otherwise try to use this <img> as a source
+ HTMLImageElement::SelectSourceForTagWithAttrs(
+ this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
+ VoidString(), sourceURL);
+ isImgSet = !aSrcsetAttr.IsEmpty();
+ }
+
+ // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
+ if (sourceURL.IsEmpty()) {
+ return nullptr;
+ }
+
+ // Construct into URI using passed baseURI (the parser may know of base URI
+ // changes that have not reached us)
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
+ this, aBaseURI);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ *aIsImgSet = isImgSet;
+
+ // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
+ // this this <picture> share the same <sources> (though this is not valid per
+ // spec)
+ return uri.forget();
+}
+
+void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
+ ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
+ bool aLinkPreload, uint64_t aEarlyHintPreloaderId) {
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
+ nsIRequest::LOAD_RECORD_START_REQUEST_DELAY |
+ nsContentUtils::CORSModeToLoadImageFlags(
+ Element::StringToCORSMode(aCrossOriginAttr));
+
+ nsContentPolicyType policyType =
+ aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
+ : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
+
+ RefPtr<imgRequestProxy> request;
+
+ nsLiteralString initiator = aEarlyHintPreloaderId
+ ? u"early-hints"_ns
+ : (aLinkPreload ? u"link"_ns : u"img"_ns);
+
+ nsresult rv = nsContentUtils::LoadImage(
+ aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
+ nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request),
+ policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId);
+
+ // Pin image-reference to avoid evicting it from the img-cache before
+ // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
+ // unlink
+ if (!aLinkPreload && NS_SUCCEEDED(rv)) {
+ mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
+ }
+}
+
+void Document::MaybePreLoadImage(nsIURI* aUri,
+ const nsAString& aCrossOriginAttr,
+ ReferrerPolicyEnum aReferrerPolicy,
+ bool aIsImgSet, bool aLinkPreload,
+ const TimeStamp& aInitTimestamp) {
+ const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr);
+ if (aLinkPreload) {
+ // Check if the image was already preloaded in this document to avoid
+ // duplicate preloading.
+ PreloadHashKey key =
+ PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode);
+ if (!mPreloadService.PreloadExists(key)) {
+ PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
+ aLinkPreload, 0);
+ }
+ return;
+ }
+
+ // Early exit if the img is already present in the img-cache
+ // which indicates that the "real" load has already started and
+ // that we shouldn't preload it.
+ if (nsContentUtils::IsImageAvailable(aUri, NodePrincipal(), corsMode, this)) {
+ return;
+ }
+
+#ifdef NIGHTLY_BUILD
+ Telemetry::Accumulate(
+ Telemetry::DOCUMENT_PRELOAD_IMAGE_ASYNCOPEN_DELAY,
+ static_cast<uint32_t>(
+ (TimeStamp::Now() - aInitTimestamp).ToMilliseconds()));
+#endif
+
+ // Image not in cache - trigger preload
+ PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload,
+ 0);
+}
+
+void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
+ NS_MutateURI mutator(aOrigURI);
+ if (NS_FAILED(mutator.GetStatus())) {
+ return;
+ }
+
+ // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
+ // which ignores the path and uses only the origin. The other is for the
+ // document mPreloadedPreconnects de-duplication hash. Anonymous vs
+ // non-Anonymous preconnects create different connections on the wire and
+ // therefore should not be considred duplicates of each other and we
+ // normalize the path before putting it in the hash to accomplish that.
+
+ if (aCORSMode == CORS_ANONYMOUS) {
+ mutator.SetPathQueryRef("/anonymous"_ns);
+ } else {
+ mutator.SetPathQueryRef("/"_ns);
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = mutator.Finalize(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ const bool existingEntryFound =
+ mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
+ if (entry) {
+ return true;
+ }
+ entry.Insert(true);
+ return false;
+ });
+ if (existingEntryFound) {
+ return;
+ }
+
+ nsCOMPtr<nsISpeculativeConnect> speculator =
+ mozilla::components::IO::Service();
+ if (!speculator) {
+ return;
+ }
+
+ OriginAttributes oa;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(this, oa);
+ speculator->SpeculativeConnectWithOriginAttributesNative(
+ uri, std::move(oa), nullptr, aCORSMode == CORS_ANONYMOUS);
+}
+
+void Document::ForgetImagePreload(nsIURI* aURI) {
+ // Checking count is faster than hashing the URI in the common
+ // case of empty table.
+ if (mPreloadingImages.Count() != 0) {
+ nsCOMPtr<imgIRequest> req;
+ mPreloadingImages.Remove(aURI, getter_AddRefs(req));
+ if (req) {
+ // Make sure to cancel the request so imagelib knows it's gone.
+ req->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ }
+ }
+}
+
+void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates,
+ bool aNotify) {
+ const DocumentState oldStates = mDocumentState;
+ if (aMaybeChangedStates.HasAtLeastOneOfStates(
+ DocumentState::ALL_LOCALEDIR_BITS)) {
+ mDocumentState &= ~DocumentState::ALL_LOCALEDIR_BITS;
+ if (IsDocumentRightToLeft()) {
+ mDocumentState |= DocumentState::RTL_LOCALE;
+ } else {
+ mDocumentState |= DocumentState::LTR_LOCALE;
+ }
+ }
+
+ if (aMaybeChangedStates.HasAtLeastOneOfStates(DocumentState::LWTHEME)) {
+ if (ComputeDocumentLWTheme()) {
+ mDocumentState |= DocumentState::LWTHEME;
+ } else {
+ mDocumentState &= ~DocumentState::LWTHEME;
+ }
+ }
+
+ if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) {
+ if (IsTopLevelWindowInactive()) {
+ mDocumentState |= DocumentState::WINDOW_INACTIVE;
+ } else {
+ mDocumentState &= ~DocumentState::WINDOW_INACTIVE;
+ }
+ }
+
+ const DocumentState changedStates = oldStates ^ mDocumentState;
+ if (aNotify && !changedStates.IsEmpty()) {
+ if (PresShell* ps = GetObservingPresShell()) {
+ ps->DocumentStatesChanged(changedStates);
+ }
+ }
+}
+
+namespace {
+
+/**
+ * Stub for LoadSheet(), since all we want is to get the sheet into
+ * the CSSLoader's style cache
+ */
+class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
+ ~StubCSSLoaderObserver() = default;
+
+ public:
+ NS_IMETHOD
+ StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
+ NS_DECL_ISUPPORTS
+};
+NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
+
+} // namespace
+
+SheetPreloadStatus Document::PreloadStyle(
+ nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
+ const enum ReferrerPolicy aReferrerPolicy, const nsAString& aIntegrity,
+ css::StylePreloadKind aKind, uint64_t aEarlyHintPreloaderId) {
+ MOZ_ASSERT(aKind != css::StylePreloadKind::None);
+
+ // The CSSLoader will retain this object after we return.
+ nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
+
+ // Charset names are always ASCII.
+ auto result = CSSLoader()->LoadSheet(
+ uri, aKind, aEncoding, referrerInfo, obs, aEarlyHintPreloaderId,
+ Element::StringToCORSMode(aCrossOriginAttr), aIntegrity);
+ if (result.isErr()) {
+ return SheetPreloadStatus::Errored;
+ }
+ RefPtr<StyleSheet> sheet = result.unwrap();
+ if (sheet->IsComplete()) {
+ return SheetPreloadStatus::AlreadyComplete;
+ }
+ return SheetPreloadStatus::InProgress;
+}
+
+RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
+ return CSSLoader()
+ ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
+ .unwrapOr(nullptr);
+}
+
+void Document::ResetDocumentDirection() {
+ if (!nsContentUtils::IsChromeDoc(this)) {
+ return;
+ }
+ UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true);
+}
+
+bool Document::IsDocumentRightToLeft() {
+ if (!nsContentUtils::IsChromeDoc(this)) {
+ return false;
+ }
+ // setting the localedir attribute on the root element forces a
+ // specific direction for the document.
+ Element* element = GetRootElement();
+ if (element) {
+ static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
+ nullptr};
+ switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
+ strings, eCaseMatters)) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ break; // otherwise, not a valid value, so fall through
+ }
+ }
+
+ if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
+ !mDocumentURI->SchemeIs("resource")) {
+ return false;
+ }
+
+ return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
+}
+
+class nsDelayedEventDispatcher : public Runnable {
+ public:
+ explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
+ : mozilla::Runnable("nsDelayedEventDispatcher"),
+ mDocuments(std::move(aDocuments)) {}
+ virtual ~nsDelayedEventDispatcher() = default;
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
+ // bug 1535398.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ FireOrClearDelayedEvents(std::move(mDocuments), true);
+ return NS_OK;
+ }
+
+ private:
+ nsTArray<nsCOMPtr<Document>> mDocuments;
+};
+
+static void GetAndUnsuppressSubDocuments(
+ Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
+ if (aDocument.EventHandlingSuppressed() > 0) {
+ aDocument.DecreaseEventSuppression();
+ aDocument.ScriptLoader()->RemoveExecuteBlocker();
+ }
+ aDocuments.AppendElement(&aDocument);
+ auto recurse = [&aDocuments](Document& aSubDoc) {
+ GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
+ return CallState::Continue;
+ };
+ aDocument.EnumerateSubDocuments(recurse);
+}
+
+void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
+ nsTArray<nsCOMPtr<Document>> documents;
+ GetAndUnsuppressSubDocuments(*this, documents);
+
+ for (nsCOMPtr<Document>& doc : documents) {
+ if (!doc->EventHandlingSuppressed()) {
+ if (WindowGlobalChild* wgc = doc->GetWindowGlobalChild()) {
+ wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ nsTArray<RefPtr<net::ChannelEventQueue>> queues =
+ std::move(doc->mSuspendedQueues);
+ for (net::ChannelEventQueue* queue : queues) {
+ queue->Resume();
+ }
+
+ // If there have been any events driven by the refresh driver which were
+ // delayed due to events being suppressed in this document, make sure
+ // there is a refresh scheduled soon so the events will run.
+ if (doc->mHasDelayedRefreshEvent) {
+ doc->mHasDelayedRefreshEvent = false;
+
+ if (doc->mPresShell) {
+ nsRefreshDriver* rd =
+ doc->mPresShell->GetPresContext()->RefreshDriver();
+ rd->RunDelayedEventsSoon();
+ }
+ }
+ }
+ }
+
+ if (aFireEvents) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> ded =
+ new nsDelayedEventDispatcher(std::move(documents));
+ Dispatch(TaskCategory::Other, ded.forget());
+ } else {
+ FireOrClearDelayedEvents(std::move(documents), false);
+ }
+}
+
+bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
+ return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
+}
+
+void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(EventHandlingSuppressed());
+ mSuspendedQueues.AppendElement(aQueue);
+}
+
+bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
+ mSuspendedPostMessageEvents.AppendElement(aEvent);
+ return true;
+ }
+ return false;
+}
+
+void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
+ nsTArray<RefPtr<PostMessageEvent>> events =
+ std::move(mSuspendedPostMessageEvents);
+
+ if (aFireEvents) {
+ for (PostMessageEvent* event : events) {
+ event->Run();
+ }
+ }
+}
+
+void Document::SetSuppressedEventListener(EventListener* aListener) {
+ mSuppressedEventListener = aListener;
+ auto setOnSubDocs = [&](Document& aDocument) {
+ aDocument.SetSuppressedEventListener(aListener);
+ return CallState::Continue;
+ };
+ EnumerateSubDocuments(setOnSubDocs);
+}
+
+bool Document::IsActive() const {
+ return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
+ !GetBrowsingContext()->IsInBFCache();
+}
+
+nsISupports* Document::GetCurrentContentSink() {
+ return mParser ? mParser->GetContentSink() : nullptr;
+}
+
+Document* Document::GetTemplateContentsOwner() {
+ if (!mTemplateContentsOwner) {
+ bool hasHadScriptObject = true;
+ nsIScriptGlobalObject* scriptObject =
+ GetScriptHandlingObject(hasHadScriptObject);
+
+ nsCOMPtr<Document> document;
+ nsresult rv = NS_NewDOMDocument(
+ getter_AddRefs(document),
+ u""_ns, // aNamespaceURI
+ u""_ns, // aQualifiedName
+ nullptr, // aDoctype
+ Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
+ true, // aLoadedAsData
+ scriptObject, // aEventObject
+ IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ mTemplateContentsOwner = document;
+ NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
+
+ if (!scriptObject) {
+ mTemplateContentsOwner->SetScopeObject(GetScopeObject());
+ }
+
+ mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
+
+ // Set |mTemplateContentsOwner| as the template contents owner of itself so
+ // that it is the template contents owner of nested template elements.
+ mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
+ }
+
+ MOZ_ASSERT(mTemplateContentsOwner->IsTemplateContentsOwner());
+ return mTemplateContentsOwner;
+}
+
+// https://html.spec.whatwg.org/#the-autofocus-attribute
+void Document::ElementWithAutoFocusInserted(Element* aAutoFocusCandidate) {
+ BrowsingContext* bc = GetBrowsingContext();
+ if (!bc) {
+ return;
+ }
+
+ // If target is not fully active, then return.
+ if (!IsCurrentActiveDocument()) {
+ return;
+ }
+
+ // If target's active sandboxing flag set has the sandboxed automatic features
+ // browsing context flag, then return.
+ if (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES) {
+ return;
+ }
+
+ // For each ancestorBC of target's browsing context's ancestor browsing
+ // contexts: if ancestorBC's active document's origin is not same origin with
+ // target's origin, then return.
+ while (bc) {
+ BrowsingContext* parent = bc->GetParent();
+ if (!parent) {
+ break;
+ }
+ // AncestorBC is not the same site
+ if (!parent->IsInProcess()) {
+ return;
+ }
+
+ Document* currentDocument = bc->GetDocument();
+ if (!currentDocument) {
+ return;
+ }
+
+ Document* parentDocument = parent->GetDocument();
+ if (!parentDocument) {
+ return;
+ }
+
+ // Not same origin
+ if (!currentDocument->NodePrincipal()->Equals(
+ parentDocument->NodePrincipal())) {
+ return;
+ }
+
+ bc = parent;
+ }
+ MOZ_ASSERT(bc->IsTop());
+
+ Document* topDocument = bc->GetDocument();
+ MOZ_ASSERT(topDocument);
+ topDocument->AppendAutoFocusCandidateToTopDocument(aAutoFocusCandidate);
+}
+
+void Document::ScheduleFlushAutoFocusCandidates() {
+ MOZ_ASSERT(mPresShell && mPresShell->DidInitialize());
+ MOZ_ASSERT(GetBrowsingContext()->IsTop());
+ if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
+ rd->ScheduleAutoFocusFlush(this);
+ }
+}
+
+void Document::AppendAutoFocusCandidateToTopDocument(
+ Element* aAutoFocusCandidate) {
+ MOZ_ASSERT(GetBrowsingContext()->IsTop());
+ if (mAutoFocusFired) {
+ return;
+ }
+
+ if (!HasAutoFocusCandidates()) {
+ // PresShell may be initialized later
+ if (mPresShell && mPresShell->DidInitialize()) {
+ ScheduleFlushAutoFocusCandidates();
+ }
+ }
+
+ nsWeakPtr element = do_GetWeakReference(aAutoFocusCandidate);
+ mAutoFocusCandidates.RemoveElement(element);
+ mAutoFocusCandidates.AppendElement(element);
+}
+
+void Document::SetAutoFocusFired() {
+ mAutoFocusCandidates.Clear();
+ mAutoFocusFired = true;
+}
+
+// https://html.spec.whatwg.org/#flush-autofocus-candidates
+void Document::FlushAutoFocusCandidates() {
+ MOZ_ASSERT(GetBrowsingContext()->IsTop());
+ if (mAutoFocusFired) {
+ return;
+ }
+
+ if (!mPresShell) {
+ return;
+ }
+
+ MOZ_ASSERT(HasAutoFocusCandidates());
+ MOZ_ASSERT(mPresShell->DidInitialize());
+
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetWindow();
+ // We should be the top document
+ if (!topWindow) {
+ return;
+ }
+
+#ifdef DEBUG
+ {
+ // Trying to find the top window (equivalent to window.top).
+ nsCOMPtr<nsPIDOMWindowOuter> top = topWindow->GetInProcessTop();
+ MOZ_ASSERT(topWindow == top);
+ }
+#endif
+
+ // Don't steal the focus from the user
+ if (topWindow->GetFocusedElement()) {
+ SetAutoFocusFired();
+ return;
+ }
+
+ MOZ_ASSERT(mDocumentURI);
+ nsAutoCString ref;
+ // GetRef never fails
+ nsresult rv = mDocumentURI->GetRef(ref);
+ if (NS_SUCCEEDED(rv) &&
+ nsContentUtils::GetTargetElement(this, NS_ConvertUTF8toUTF16(ref))) {
+ SetAutoFocusFired();
+ return;
+ }
+
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mAutoFocusCandidates);
+ while (iter.HasMore()) {
+ nsCOMPtr<Element> autoFocusElement = do_QueryReferent(iter.GetNext());
+ if (!autoFocusElement) {
+ continue;
+ }
+ RefPtr<Document> autoFocusElementDoc = autoFocusElement->OwnerDoc();
+ // Get the latest info about the frame and allow scripts
+ // to run which might affect the focusability of this element.
+ autoFocusElementDoc->FlushPendingNotifications(FlushType::Frames);
+
+ // Above layout flush may cause the PresShell to disappear.
+ if (!mPresShell) {
+ return;
+ }
+
+ // Re-get the element because the ownerDoc() might have changed
+ autoFocusElementDoc = autoFocusElement->OwnerDoc();
+ BrowsingContext* bc = autoFocusElementDoc->GetBrowsingContext();
+ if (!bc) {
+ continue;
+ }
+
+ // If doc is not fully active, then remove element from candidates, and
+ // continue.
+ if (!autoFocusElementDoc->IsCurrentActiveDocument()) {
+ iter.Remove();
+ continue;
+ }
+
+ nsCOMPtr<nsIContentSink> sink =
+ do_QueryInterface(autoFocusElementDoc->GetCurrentContentSink());
+ if (sink) {
+ nsHtml5TreeOpExecutor* executor =
+ static_cast<nsHtml5TreeOpExecutor*>(sink->AsExecutor());
+ if (executor) {
+ // This is a HTML5 document
+ MOZ_ASSERT(autoFocusElementDoc->IsHTMLDocument());
+ // If doc's script-blocking style sheet counter is greater than 0, th
+ // return.
+ if (executor->WaitForPendingSheets()) {
+ // In this case, element is the currently-best candidate, but doc is
+ // not ready for autofocusing. We'll try again next time flush
+ // autofocus candidates is called.
+ ScheduleFlushAutoFocusCandidates();
+ return;
+ }
+ }
+ }
+
+ // The autofocus element could be moved to a different
+ // top level BC.
+ if (bc->Top()->GetDocument() != this) {
+ continue;
+ }
+
+ iter.Remove();
+
+ // Let inclusiveAncestorDocuments be a list consisting of doc, plus the
+ // active documents of each of doc's browsing context's ancestor browsing
+ // contexts.
+ // If any Document in inclusiveAncestorDocuments has non-null target
+ // element, then continue.
+ bool shouldFocus = true;
+ while (bc) {
+ Document* doc = bc->GetDocument();
+ if (!doc) {
+ shouldFocus = false;
+ break;
+ }
+
+ nsIURI* uri = doc->GetDocumentURI();
+ if (!uri) {
+ shouldFocus = false;
+ break;
+ }
+
+ nsAutoCString ref;
+ nsresult rv = uri->GetRef(ref);
+ // If there is an element in the document tree that has an ID equal to
+ // fragment
+ if (NS_SUCCEEDED(rv) &&
+ nsContentUtils::GetTargetElement(doc, NS_ConvertUTF8toUTF16(ref))) {
+ shouldFocus = false;
+ break;
+ }
+ bc = bc->GetParent();
+ }
+
+ if (!shouldFocus) {
+ continue;
+ }
+
+ MOZ_ASSERT(topWindow);
+ if (TryAutoFocusCandidate(*autoFocusElement)) {
+ // We've successfully autofocused an element, don't
+ // need to try to focus the rest.
+ SetAutoFocusFired();
+ break;
+ }
+ }
+
+ if (HasAutoFocusCandidates()) {
+ ScheduleFlushAutoFocusCandidates();
+ }
+}
+
+bool Document::TryAutoFocusCandidate(Element& aElement) {
+ const FocusOptions options;
+ if (RefPtr<Element> target = nsFocusManager::GetTheFocusableArea(
+ &aElement, nsFocusManager::ProgrammaticFocusFlags(options))) {
+ target->Focus(options, CallerType::NonSystem, IgnoreErrors());
+ return true;
+ }
+
+ return false;
+}
+
+void Document::SetScrollToRef(nsIURI* aDocumentURI) {
+ if (!aDocumentURI) {
+ return;
+ }
+
+ nsAutoCString ref;
+
+ // Since all URI's that pass through here aren't URL's we can't
+ // rely on the nsIURI implementation for providing a way for
+ // finding the 'ref' part of the URI, we'll haveto revert to
+ // string routines for finding the data past '#'
+
+ nsresult rv = aDocumentURI->GetSpec(ref);
+ if (NS_FAILED(rv)) {
+ Unused << aDocumentURI->GetRef(mScrollToRef);
+ return;
+ }
+
+ nsReadingIterator<char> start, end;
+
+ ref.BeginReading(start);
+ ref.EndReading(end);
+
+ if (FindCharInReadable('#', start, end)) {
+ ++start; // Skip over the '#'
+
+ mScrollToRef = Substring(start, end);
+ }
+}
+
+void Document::ScrollToRef() {
+ if (mScrolledToRefAlready) {
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (presShell) {
+ presShell->ScrollToAnchor();
+ }
+ return;
+ }
+
+ if (mScrollToRef.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (presShell) {
+ nsresult rv = NS_ERROR_FAILURE;
+ // We assume that the bytes are in UTF-8, as it says in the spec:
+ // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
+ NS_ConvertUTF8toUTF16 ref(mScrollToRef);
+ // Check an empty string which might be caused by the UTF-8 conversion
+ if (!ref.IsEmpty()) {
+ // Note that GoToAnchor will handle flushing layout as needed.
+ rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(rv)) {
+ nsAutoCString buff;
+ const bool unescaped =
+ NS_UnescapeURL(mScrollToRef.BeginReading(), mScrollToRef.Length(),
+ /*aFlags =*/0, buff);
+
+ // This attempt is only necessary if characters were unescaped.
+ if (unescaped) {
+ NS_ConvertUTF8toUTF16 utf16Str(buff);
+ if (!utf16Str.IsEmpty()) {
+ rv = presShell->GoToAnchor(utf16Str,
+ mChangeScrollPosWhenScrollingToRef);
+ }
+ }
+
+ // If UTF-8 URI failed then try to assume the string as a
+ // document's charset.
+ if (NS_FAILED(rv)) {
+ const Encoding* encoding = GetDocumentCharacterSet();
+ rv = encoding->DecodeWithoutBOMHandling(unescaped ? buff : mScrollToRef,
+ ref);
+ if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
+ rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
+ }
+ }
+ }
+ if (NS_SUCCEEDED(rv)) {
+ mScrolledToRefAlready = true;
+ }
+ }
+}
+
+void Document::RegisterActivityObserver(nsISupports* aSupports) {
+ if (!mActivityObservers) {
+ mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
+ }
+ mActivityObservers->Insert(aSupports);
+}
+
+bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
+ if (!mActivityObservers) {
+ return false;
+ }
+ return mActivityObservers->EnsureRemoved(aSupports);
+}
+
+void Document::EnumerateActivityObservers(
+ ActivityObserverEnumerator aEnumerator) {
+ if (!mActivityObservers) {
+ return;
+ }
+
+ const auto keyArray =
+ ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
+ for (auto& observer : keyArray) {
+ aEnumerator(observer.get());
+ }
+}
+
+void Document::RegisterPendingLinkUpdate(Link* aLink) {
+ if (aLink->HasPendingLinkUpdate()) {
+ return;
+ }
+
+ aLink->SetHasPendingLinkUpdate();
+
+ if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
+ &Document::FlushPendingLinkUpdates);
+ // Do this work in a second in the worst case.
+ nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
+ EventQueuePriority::Idle);
+ if (NS_FAILED(rv)) {
+ // If during shutdown posting a runnable doesn't succeed, we probably
+ // don't need to update link states.
+ return;
+ }
+ mHasLinksToUpdateRunnable = true;
+ }
+
+ mLinksToUpdate.InfallibleAppend(aLink);
+}
+
+void Document::FlushPendingLinkUpdates() {
+ MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
+ MOZ_ASSERT(mHasLinksToUpdateRunnable);
+ mHasLinksToUpdateRunnable = false;
+
+ auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
+ mFlushingPendingLinkUpdates = true;
+
+ while (!mLinksToUpdate.IsEmpty()) {
+ LinksToUpdateList links(std::move(mLinksToUpdate));
+ for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
+ Link* link = iter.Get();
+ Element* element = link->GetElement();
+ if (element->OwnerDoc() == this) {
+ link->ClearHasPendingLinkUpdate();
+ if (element->IsInComposedDoc()) {
+ element->UpdateLinkState(link->LinkState());
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Retrieves the node in a static-clone document that corresponds to aOrigNode,
+ * which is a node in the original document from which aStaticClone was cloned.
+ */
+static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
+ Document& aStaticClone) {
+ MOZ_ASSERT(aOrigNode);
+
+ // Selections in anonymous subtrees aren't supported.
+ if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
+ return nullptr;
+ }
+
+ // If the node is disconnected, this is a bug in the selection code, but it
+ // can happen with shadow DOM so handle it.
+ if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
+ return nullptr;
+ }
+
+ AutoTArray<Maybe<uint32_t>, 32> indexArray;
+ const nsINode* current = aOrigNode;
+ while (const nsINode* parent = current->GetParentNode()) {
+ Maybe<uint32_t> index = parent->ComputeIndexOf(current);
+ NS_ENSURE_TRUE(index.isSome(), nullptr);
+ indexArray.AppendElement(std::move(index));
+ current = parent;
+ }
+ MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
+ nsINode* correspondingNode = [&]() -> nsINode* {
+ if (current->IsDocument()) {
+ return &aStaticClone;
+ }
+ const auto* shadow = ShadowRoot::FromNode(*current);
+ if (!shadow) {
+ return nullptr;
+ }
+ nsINode* correspondingHost =
+ GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
+ if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
+ return nullptr;
+ }
+ return correspondingHost->AsElement()->GetShadowRoot();
+ }();
+
+ if (NS_WARN_IF(!correspondingNode)) {
+ return nullptr;
+ }
+ for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
+ correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
+ NS_ENSURE_TRUE(correspondingNode, nullptr);
+ }
+ return correspondingNode;
+}
+
+/**
+ * Caches the selection ranges from the source document onto the static clone in
+ * case the "Print Selection Only" functionality is invoked.
+ *
+ * Note that we cannot use the selection obtained from GetOriginalDocument()
+ * since that selection may have mutated after the print was invoked.
+ *
+ * Note also that because nsRange objects point into a specific document's
+ * nodes, we cannot reuse an array of nsRange objects across multiple static
+ * clone documents. For that reason we cache a new array of ranges on each
+ * static clone that we create.
+ *
+ * TODO(emilio): This can be simplified once we don't re-clone from static
+ * documents.
+ *
+ * @param aSourceDoc the document from which we are caching selection ranges
+ * @param aStaticClone the document that will hold the cache
+ * @return true if a selection range was cached
+ */
+static void CachePrintSelectionRanges(const Document& aSourceDoc,
+ Document& aStaticClone) {
+ MOZ_ASSERT(aStaticClone.IsStaticDocument());
+ MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc));
+ MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
+
+ bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
+
+ // When the user opts to "Print Selection Only", the print code prefers any
+ // selection in the static clone corresponding to the focused frame. If this
+ // is that static clone, flag it for the printing code:
+ const bool isFocusedDoc = [&] {
+ if (sourceDocIsStatic) {
+ return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc));
+ }
+ nsPIDOMWindowOuter* window = aSourceDoc.GetWindow();
+ if (!window) {
+ return false;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
+ if (!rootWindow) {
+ return false;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsFocusManager::GetFocusedDescendant(rootWindow,
+ nsFocusManager::eIncludeAllDescendants,
+ getter_AddRefs(focusedWindow));
+ return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc;
+ }();
+ if (isFocusedDoc) {
+ aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc,
+ reinterpret_cast<void*>(true));
+ }
+
+ const Selection* origSelection = nullptr;
+ const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
+
+ if (sourceDocIsStatic) {
+ origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
+ aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
+ } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
+ origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
+ }
+
+ if (!origSelection && !origRanges) {
+ return;
+ }
+
+ const uint32_t rangeCount =
+ sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
+ auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
+
+ for (const uint32_t i : IntegerRange(rangeCount)) {
+ MOZ_ASSERT_IF(!sourceDocIsStatic,
+ origSelection->RangeCount() == rangeCount);
+ const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
+ : origSelection->GetRangeAt(i);
+ MOZ_ASSERT(range);
+ nsINode* startContainer = range->GetStartContainer();
+ nsINode* endContainer = range->GetEndContainer();
+
+ if (!startContainer || !endContainer) {
+ continue;
+ }
+
+ nsINode* startNode =
+ GetCorrespondingNodeInDocument(startContainer, aStaticClone);
+ nsINode* endNode =
+ GetCorrespondingNodeInDocument(endContainer, aStaticClone);
+
+ if (NS_WARN_IF(!startNode || !endNode)) {
+ continue;
+ }
+
+ RefPtr<nsRange> clonedRange =
+ nsRange::Create(startNode, range->StartOffset(), endNode,
+ range->EndOffset(), IgnoreErrors());
+ if (clonedRange && !clonedRange->Collapsed()) {
+ printRanges->AppendElement(std::move(clonedRange));
+ }
+ }
+
+ if (printRanges->IsEmpty()) {
+ return;
+ }
+
+ aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
+ printRanges.release(),
+ nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
+}
+
+already_AddRefed<Document> Document::CreateStaticClone(
+ nsIDocShell* aCloneContainer, nsIContentViewer* aViewer,
+ nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
+ MOZ_ASSERT(!mCreatingStaticClone);
+ MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
+ MOZ_DIAGNOSTIC_ASSERT(aViewer);
+
+ mCreatingStaticClone = true;
+ SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
+ nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
+
+ auto raii = MakeScopeExit([&] {
+ RemoveProperty(nsGkAtoms::adoptedsheetclones);
+ mCreatingStaticClone = false;
+ });
+
+ // Make document use different container during cloning.
+ //
+ // FIXME(emilio): Why is this needed?
+ RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
+ SetContainer(nsDocShell::Cast(aCloneContainer));
+ IgnoredErrorResult rv;
+ nsCOMPtr<nsINode> clonedNode = CloneNode(true, rv);
+ SetContainer(originalShell);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
+ if (!clonedDoc) {
+ return nullptr;
+ }
+
+ size_t sheetsCount = SheetCount();
+ for (size_t i = 0; i < sheetsCount; ++i) {
+ RefPtr<StyleSheet> sheet = SheetAt(i);
+ if (sheet) {
+ if (sheet->IsApplicable()) {
+ RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
+ NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
+ if (clonedSheet) {
+ clonedDoc->AddStyleSheet(clonedSheet);
+ }
+ }
+ }
+ }
+ clonedDoc->CloneAdoptedSheetsFrom(*this);
+
+ for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
+ auto& sheets = mAdditionalSheets[additionalSheetType(t)];
+ for (StyleSheet* sheet : sheets) {
+ if (sheet->IsApplicable()) {
+ RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
+ NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
+ if (clonedSheet) {
+ clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
+ clonedSheet);
+ }
+ }
+ }
+ }
+
+ // Font faces created with the JS API will not be reflected in the
+ // stylesheets and need to be copied over to the cloned document.
+ if (const FontFaceSet* set = GetFonts()) {
+ set->CopyNonRuleFacesTo(clonedDoc->Fonts());
+ }
+
+ clonedDoc->mReferrerInfo =
+ static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
+ clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
+ CachePrintSelectionRanges(*this, *clonedDoc);
+
+ // We're done with the clone, embed ourselves into the document viewer and
+ // clone our children. The order here is pretty important, because our
+ // document our document needs to have an owner global before we can create
+ // the frame loaders for subdocuments.
+ aViewer->SetDocument(clonedDoc);
+
+ *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
+
+ auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
+ for (const auto& clone : pendingClones) {
+ RefPtr<Element> element = do_QueryObject(clone.mElement);
+ RefPtr<nsFrameLoader> frameLoader =
+ nsFrameLoader::Create(element, /* aNetworkCreated */ false);
+
+ if (NS_WARN_IF(!frameLoader)) {
+ continue;
+ }
+
+ clone.mElement->SetFrameLoader(frameLoader);
+
+ nsresult rv = frameLoader->FinishStaticClone(
+ clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+
+ return clonedDoc.forget();
+}
+
+void Document::UnlinkOriginalDocumentIfStatic() {
+ if (IsStaticDocument() && mOriginalDocument) {
+ MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
+ mOriginalDocument->mStaticCloneCount--;
+ mOriginalDocument = nullptr;
+ }
+ MOZ_ASSERT(!mOriginalDocument);
+}
+
+nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
+ int32_t* aHandle) {
+ nsresult rv = mFrameRequestManager.Schedule(aCallback, aHandle);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ UpdateFrameRequestCallbackSchedulingState();
+ return NS_OK;
+}
+
+void Document::CancelFrameRequestCallback(int32_t aHandle) {
+ if (mFrameRequestManager.Cancel(aHandle)) {
+ UpdateFrameRequestCallbackSchedulingState();
+ }
+}
+
+bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
+ return mFrameRequestManager.IsCanceled(aHandle);
+}
+
+nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
+ // Get the document's current state object. This is the object backing both
+ // history.state and popStateEvent.state.
+ //
+ // mStateObjectContainer may be null; this just means that there's no
+ // current state object.
+
+ if (!mCachedStateObjectValid) {
+ if (mStateObjectContainer) {
+ AutoJSAPI jsapi;
+ // Init with null is "OK" in the sense that it will just fail.
+ if (!jsapi.Init(GetScopeObject())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JS::Rooted<JS::Value> value(jsapi.cx());
+ nsresult rv =
+ mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCachedStateObject = value;
+ if (!value.isNullOrUndefined()) {
+ mozilla::HoldJSObjects(this);
+ }
+ } else {
+ mCachedStateObject = JS::NullValue();
+ }
+ mCachedStateObjectValid = true;
+ }
+
+ aState.set(mCachedStateObject);
+ return NS_OK;
+}
+
+void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
+ mTiming = aTiming;
+ if (!mLoadingOrRestoredFromBFCacheTimeStamp.IsNull() && mTiming) {
+ mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(),
+ mLoadingOrRestoredFromBFCacheTimeStamp);
+ }
+
+ // If there's already the DocumentTimeline instance, tell it since the
+ // DocumentTimeline is based on both the navigation start time stamp and the
+ // refresh driver timestamp.
+ if (mDocumentTimeline) {
+ mDocumentTimeline->UpdateLastRefreshDriverTime();
+ }
+}
+
+nsContentList* Document::ImageMapList() {
+ if (!mImageMaps) {
+ mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
+ nsGkAtoms::map);
+ }
+
+ return mImageMaps;
+}
+
+#define DEPRECATED_OPERATION(_op) #_op "Warning",
+static const char* kDeprecationWarnings[] = {
+#include "nsDeprecatedOperationList.h"
+ nullptr};
+#undef DEPRECATED_OPERATION
+
+#define DOCUMENT_WARNING(_op) #_op "Warning",
+static const char* kDocumentWarnings[] = {
+#include "nsDocumentWarningList.h"
+ nullptr};
+#undef DOCUMENT_WARNING
+
+static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
+ switch (aOperation) {
+#define DEPRECATED_OPERATION(_op) \
+ case DeprecatedOperations::e##_op: \
+ return eUseCounter_##_op;
+#include "nsDeprecatedOperationList.h"
+#undef DEPRECATED_OPERATION
+ default:
+ MOZ_CRASH();
+ }
+}
+
+bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
+ return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
+}
+
+void Document::WarnOnceAbout(
+ DeprecatedOperations aOperation, bool asError /* = false */,
+ const nsTArray<nsString>& aParams /* = empty array */) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasWarnedAbout(aOperation)) {
+ return;
+ }
+ mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
+ // Don't count deprecated operations for about pages since those pages
+ // are almost in our control, and we always need to remove uses there
+ // before we remove the operation itself anyway.
+ if (!IsAboutPage()) {
+ const_cast<Document*>(this)->SetUseCounter(
+ OperationToUseCounter(aOperation));
+ }
+ uint32_t flags =
+ asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
+ nsContentUtils::ReportToConsole(
+ flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
+ kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
+}
+
+bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
+ return mDocWarningWarnedAbout[aWarning];
+}
+
+void Document::WarnOnceAbout(
+ DocumentWarnings aWarning, bool asError /* = false */,
+ const nsTArray<nsString>& aParams /* = empty array */) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasWarnedAbout(aWarning)) {
+ return;
+ }
+ mDocWarningWarnedAbout[aWarning] = true;
+ uint32_t flags =
+ asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
+ nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
+ nsContentUtils::eDOM_PROPERTIES,
+ kDocumentWarnings[aWarning], aParams);
+}
+
+mozilla::dom::ImageTracker* Document::ImageTracker() {
+ if (!mImageTracker) {
+ mImageTracker = new mozilla::dom::ImageTracker;
+ }
+ return mImageTracker;
+}
+
+void Document::ScheduleSVGUseElementShadowTreeUpdate(
+ SVGUseElement& aUseElement) {
+ MOZ_ASSERT(aUseElement.IsInComposedDoc());
+
+ if (MOZ_UNLIKELY(mIsStaticDocument)) {
+ // Printing doesn't deal well with dynamic DOM mutations.
+ return;
+ }
+
+ mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
+
+ if (PresShell* presShell = GetPresShell()) {
+ presShell->EnsureStyleFlush();
+ }
+}
+
+void Document::DoUpdateSVGUseElementShadowTrees() {
+ MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
+
+ do {
+ const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
+ mSVGUseElementsNeedingShadowTreeUpdate);
+ mSVGUseElementsNeedingShadowTreeUpdate.Clear();
+
+ for (const auto& useElement : useElementsToUpdate) {
+ if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
+ // The element was in another <use> shadow tree which we processed
+ // already and also needed an update, and is removed from the document
+ // now, so nothing to do here.
+ MOZ_ASSERT(useElementsToUpdate.Length() > 1);
+ continue;
+ }
+ useElement->UpdateShadowTree();
+ }
+ } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
+}
+
+void Document::NotifyMediaFeatureValuesChanged() {
+ for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
+ imageElement->MediaFeatureValuesChanged();
+ }
+}
+
+already_AddRefed<Touch> Document::CreateTouch(
+ nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
+ int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
+ int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
+ float aRotationAngle, float aForce) {
+ RefPtr<Touch> touch =
+ new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
+ aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
+ return touch.forget();
+}
+
+already_AddRefed<TouchList> Document::CreateTouchList() {
+ RefPtr<TouchList> retval = new TouchList(ToSupports(this));
+ return retval.forget();
+}
+
+already_AddRefed<TouchList> Document::CreateTouchList(
+ Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
+ RefPtr<TouchList> retval = new TouchList(ToSupports(this));
+ retval->Append(&aTouch);
+ for (uint32_t i = 0; i < aTouches.Length(); ++i) {
+ retval->Append(aTouches[i].get());
+ }
+ return retval.forget();
+}
+
+already_AddRefed<TouchList> Document::CreateTouchList(
+ const Sequence<OwningNonNull<Touch>>& aTouches) {
+ RefPtr<TouchList> retval = new TouchList(ToSupports(this));
+ for (uint32_t i = 0; i < aTouches.Length(); ++i) {
+ retval->Append(aTouches[i].get());
+ }
+ return retval.forget();
+}
+
+already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
+ float aX, float aY) {
+ using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+
+ nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
+ nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
+ nsPoint pt(x, y);
+
+ FlushPendingNotifications(FlushType::Layout);
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+
+ // XUL docs, unlike HTML, have no frame tree until everything's done loading
+ if (!rootFrame) {
+ return nullptr;
+ }
+
+ nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
+ RelativeTo{rootFrame}, pt,
+ {{FrameForPointOption::IgnorePaintSuppression,
+ FrameForPointOption::IgnoreCrossDoc}});
+ if (!ptFrame) {
+ return nullptr;
+ }
+
+ // We require frame-relative coordinates for GetContentOffsetsFromPoint.
+ nsPoint adjustedPoint = pt;
+ if (nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
+ adjustedPoint) !=
+ nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ return nullptr;
+ }
+
+ nsIFrame::ContentOffsets offsets =
+ ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
+
+ nsCOMPtr<nsIContent> node = offsets.content;
+ uint32_t offset = offsets.offset;
+ nsCOMPtr<nsIContent> anonNode = node;
+ bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
+ if (nodeIsAnonymous) {
+ node = ptFrame->GetContent();
+ nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
+ HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
+ nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
+ if (textFrame) {
+ // If the anonymous content node has a child, then we need to make sure
+ // that we get the appropriate child, as otherwise the offset may not be
+ // correct when we construct a range for it.
+ nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
+ if (firstChild) {
+ anonNode = firstChild;
+ }
+
+ if (textArea) {
+ offset =
+ nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
+ }
+
+ node = nonanon;
+ } else {
+ node = nullptr;
+ offset = 0;
+ }
+ }
+
+ RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
+ if (nodeIsAnonymous) {
+ aCaretPos->SetAnonymousContentNode(anonNode);
+ }
+ return aCaretPos.forget();
+}
+
+bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
+ // We rely on correct frame information here, so need to flush frames.
+ FlushPendingNotifications(FlushType::Frames);
+
+ // An element that is the HTML body element is potentially scrollable if all
+ // of the following conditions are true:
+
+ // The element has an associated CSS layout box.
+ nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
+ if (!bodyFrame) {
+ return false;
+ }
+
+ // The element's parent element's computed value of the overflow-x and
+ // overflow-y properties are visible.
+ MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
+ nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
+ if (parentFrame &&
+ parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
+ return false;
+ }
+
+ // The element's computed value of the overflow-x or overflow-y properties is
+ // not visible.
+ return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
+}
+
+Element* Document::GetScrollingElement() {
+ // Keep this in sync with IsScrollingElement.
+ if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
+ RefPtr<HTMLBodyElement> body = GetBodyElement();
+ if (body && !IsPotentiallyScrollable(body)) {
+ return body;
+ }
+
+ return nullptr;
+ }
+
+ return GetRootElement();
+}
+
+bool Document::IsScrollingElement(Element* aElement) {
+ // Keep this in sync with GetScrollingElement.
+ MOZ_ASSERT(aElement);
+
+ if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
+ return aElement == GetRootElement();
+ }
+
+ // In the common case when aElement != body, avoid refcounting.
+ HTMLBodyElement* body = GetBodyElement();
+ if (aElement != body) {
+ return false;
+ }
+
+ // Now we know body is non-null, since aElement is not null. It's the
+ // scrolling element for the document if it itself is not potentially
+ // scrollable.
+ RefPtr<HTMLBodyElement> strongBody(body);
+ return !IsPotentiallyScrollable(strongBody);
+}
+
+class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
+
+ explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
+ const BlockParsingOptions& aOptions)
+ : mPromise(aPromise) {
+ nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
+ if (parser &&
+ (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
+ parser->BlockParser();
+ mParser = do_GetWeakReference(parser);
+ mDocument = aDocument;
+ mDocument->BlockOnload();
+ mDocument->BlockDOMContentLoaded();
+ }
+ }
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+ MaybeUnblockParser();
+
+ mPromise->MaybeResolve(aValue);
+ }
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+ MaybeUnblockParser();
+
+ mPromise->MaybeReject(aValue);
+ }
+
+ protected:
+ virtual ~UnblockParsingPromiseHandler() {
+ // If we're being cleaned up by the cycle collector, our mDocument reference
+ // may have been unlinked while our mParser weak reference is still alive.
+ if (mDocument) {
+ MaybeUnblockParser();
+ }
+ }
+
+ private:
+ void MaybeUnblockParser() {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
+ if (parser) {
+ MOZ_DIAGNOSTIC_ASSERT(mDocument);
+ nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
+ if (parser == docParser) {
+ parser->UnblockParser();
+ parser->ContinueInterruptedParsingAsync();
+ }
+ }
+ if (mDocument) {
+ // We blocked DOMContentLoaded and load events on this document. Unblock
+ // them. Note that we want to do that no matter what's going on with the
+ // parser state for this document. Maybe someone caused it to stop being
+ // parsed, so CreatorParserOrNull() is returning null, but we still want
+ // to unblock these.
+ mDocument->UnblockDOMContentLoaded();
+ mDocument->UnblockOnload(false);
+ }
+ mParser = nullptr;
+ mDocument = nullptr;
+ }
+
+ nsWeakPtr mParser;
+ RefPtr<Promise> mPromise;
+ RefPtr<Document> mDocument;
+};
+
+NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
+
+already_AddRefed<Promise> Document::BlockParsing(
+ Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
+ RefPtr<Promise> resultPromise =
+ Promise::Create(aPromise.GetParentObject(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<PromiseNativeHandler> promiseHandler =
+ new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
+ aPromise.AppendNativeHandler(promiseHandler);
+
+ return resultPromise.forget();
+}
+
+already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
+ if (mFailedChannel) {
+ nsCOMPtr<nsIURI> failedURI;
+ if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
+ return failedURI.forget();
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
+ if (!uri) {
+ return nullptr;
+ }
+
+ return uri.forget();
+}
+
+Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
+ if (mIsGoingAway) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ if (!mReadyForIdle) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ mReadyForIdle = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ return mReadyForIdle;
+}
+
+void Document::MaybeResolveReadyForIdle() {
+ IgnoredErrorResult rv;
+ Promise* readyPromise = GetDocumentReadyForIdle(rv);
+ if (readyPromise) {
+ readyPromise->MaybeResolveWithUndefined();
+ }
+}
+
+mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
+ // The policy is created when the document is initialized. We _must_ have a
+ // policy here even if the featurePolicy pref is off. If this assertion fails,
+ // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
+ MOZ_ASSERT(mFeaturePolicy);
+ return mFeaturePolicy;
+}
+
+nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
+ // Only chrome documents are allowed to use command dispatcher.
+ if (!nsContentUtils::IsChromeDoc(this)) {
+ return nullptr;
+ }
+ if (!mCommandDispatcher) {
+ // Create our command dispatcher and hook it up.
+ mCommandDispatcher = new nsXULCommandDispatcher(this);
+ }
+ return mCommandDispatcher;
+}
+
+void Document::InitializeXULBroadcastManager() {
+ if (mXULBroadcastManager) {
+ return;
+ }
+ mXULBroadcastManager = new XULBroadcastManager(this);
+}
+
+namespace {
+
+class DevToolsMutationObserver final : public nsStubMutationObserver {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+
+ // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
+ // relies on the event firing _before_ the removal happens.
+ // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
+ // data changes right now (maybe intentionally?).
+ // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+
+ DevToolsMutationObserver() = default;
+
+ private:
+ void FireEvent(nsINode* aTarget, const nsAString& aType);
+
+ ~DevToolsMutationObserver() = default;
+};
+
+NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
+
+void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
+ const nsAString& aType) {
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*aTarget, aType, CanBubble::eNo,
+ ChromeOnlyDispatch::eYes,
+ Composed::eYes);
+}
+
+void DevToolsMutationObserver::AttributeChanged(Element* aElement,
+ int32_t aNamespaceID,
+ nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ FireEvent(aElement, u"devtoolsattrmodified"_ns);
+}
+
+void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
+ for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
+ ContentInserted(c);
+ }
+}
+
+void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
+ FireEvent(aChild, u"devtoolschildinserted"_ns);
+}
+
+static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
+
+} // namespace
+
+void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
+ if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
+ return;
+ }
+ mDevToolsWatchingDOMMutations = aValue;
+ if (aValue) {
+ if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
+ sDevToolsMutationObserver = new DevToolsMutationObserver();
+ ClearOnShutdown(&sDevToolsMutationObserver);
+ }
+ AddMutationObserver(sDevToolsMutationObserver);
+ } else if (sDevToolsMutationObserver) {
+ RemoveMutationObserver(sDevToolsMutationObserver);
+ }
+}
+
+void Document::MaybeWarnAboutZoom() {
+ if (mHasWarnedAboutZoom) {
+ return;
+ }
+ const bool usedZoom = Servo_IsPropertyIdRecordedInUseCounter(
+ mStyleUseCounters.get(), eCSSProperty_zoom);
+ if (!usedZoom) {
+ return;
+ }
+
+ mHasWarnedAboutZoom = true;
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
+ this, nsContentUtils::eLAYOUT_PROPERTIES,
+ "ZoomPropertyWarning");
+}
+
+nsIHTMLCollection* Document::Children() {
+ if (!mChildrenCollection) {
+ mChildrenCollection =
+ new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
+ nsGkAtoms::_asterisk, false);
+ }
+
+ return mChildrenCollection;
+}
+
+uint32_t Document::ChildElementCount() { return Children()->Length(); }
+
+// Singleton class to manage the list of fullscreen documents which are the
+// root of a branch which contains fullscreen documents. We maintain this list
+// so that we can easily exit all windows from fullscreen when the user
+// presses the escape key.
+class FullscreenRoots {
+ public:
+ // Adds the root of given document to the manager. Calling this method
+ // with a document whose root is already contained has no effect.
+ static void Add(Document* aDoc);
+
+ // Iterates over every root in the root list, and calls aFunction, passing
+ // each root once to aFunction. It is safe to call Add() and Remove() while
+ // iterating over the list (i.e. in aFunction). Documents that are removed
+ // from the manager during traversal are not traversed, and documents that
+ // are added to the manager during traversal are also not traversed.
+ static void ForEach(void (*aFunction)(Document* aDoc));
+
+ // Removes the root of a specific document from the manager.
+ static void Remove(Document* aDoc);
+
+ // Returns true if all roots added to the list have been removed.
+ static bool IsEmpty();
+
+ private:
+ MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
+ MOZ_COUNTED_DTOR(FullscreenRoots)
+
+ enum : uint32_t { NotFound = uint32_t(-1) };
+ // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
+ static uint32_t Find(Document* aRoot);
+
+ // Returns true if aRoot is in the list of fullscreen roots.
+ static bool Contains(Document* aRoot);
+
+ // Singleton instance of the FullscreenRoots. This is instantiated when a
+ // root is added, and it is deleted when the last root is removed.
+ static FullscreenRoots* sInstance;
+
+ // List of weak pointers to roots.
+ nsTArray<nsWeakPtr> mRoots;
+};
+
+FullscreenRoots* FullscreenRoots::sInstance = nullptr;
+
+/* static */
+void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
+ if (!sInstance) {
+ return;
+ }
+ // Create a copy of the roots array, and iterate over the copy. This is so
+ // that if an element is removed from mRoots we don't mess up our iteration.
+ nsTArray<nsWeakPtr> roots(sInstance->mRoots.Clone());
+ // Call aFunction on all entries.
+ for (uint32_t i = 0; i < roots.Length(); i++) {
+ nsCOMPtr<Document> root = do_QueryReferent(roots[i]);
+ // Check that the root isn't in the manager. This is so that new additions
+ // while we were running don't get traversed.
+ if (root && FullscreenRoots::Contains(root)) {
+ aFunction(root);
+ }
+ }
+}
+
+/* static */
+bool FullscreenRoots::Contains(Document* aRoot) {
+ return FullscreenRoots::Find(aRoot) != NotFound;
+}
+
+/* static */
+void FullscreenRoots::Add(Document* aDoc) {
+ nsCOMPtr<Document> root =
+ nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
+ if (!FullscreenRoots::Contains(root)) {
+ if (!sInstance) {
+ sInstance = new FullscreenRoots();
+ }
+ sInstance->mRoots.AppendElement(do_GetWeakReference(root));
+ }
+}
+
+/* static */
+uint32_t FullscreenRoots::Find(Document* aRoot) {
+ if (!sInstance) {
+ return NotFound;
+ }
+ nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
+ for (uint32_t i = 0; i < roots.Length(); i++) {
+ nsCOMPtr<Document> otherRoot(do_QueryReferent(roots[i]));
+ if (otherRoot == aRoot) {
+ return i;
+ }
+ }
+ return NotFound;
+}
+
+/* static */
+void FullscreenRoots::Remove(Document* aDoc) {
+ nsCOMPtr<Document> root =
+ nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
+ uint32_t index = Find(root);
+ NS_ASSERTION(index != NotFound,
+ "Should only try to remove roots which are still added!");
+ if (index == NotFound || !sInstance) {
+ return;
+ }
+ sInstance->mRoots.RemoveElementAt(index);
+ if (sInstance->mRoots.IsEmpty()) {
+ delete sInstance;
+ sInstance = nullptr;
+ }
+}
+
+/* static */
+bool FullscreenRoots::IsEmpty() { return !sInstance; }
+
+// Any fullscreen change waiting for the widget to finish transition
+// is queued here. This is declared static instead of a member of
+// Document because in the majority of time, there would be at most
+// one document requesting or exiting fullscreen. We shouldn't waste
+// the space to hold for it in every document.
+class PendingFullscreenChangeList {
+ public:
+ PendingFullscreenChangeList() = delete;
+
+ template <typename T>
+ static void Add(UniquePtr<T> aChange) {
+ sList.insertBack(aChange.release());
+ }
+
+ static const FullscreenChange* GetLast() { return sList.getLast(); }
+
+ enum IteratorOption {
+ // When we are committing fullscreen changes or preparing for
+ // that, we generally want to iterate all requests in the same
+ // window with eDocumentsWithSameRoot option.
+ eDocumentsWithSameRoot,
+ // If we are removing a document from the tree, we would only
+ // want to remove the requests from the given document and its
+ // descendants. For that case, use eInclusiveDescendants.
+ eInclusiveDescendants
+ };
+
+ template <typename T>
+ class Iterator {
+ public:
+ explicit Iterator(Document* aDoc, IteratorOption aOption)
+ : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
+ if (mCurrent) {
+ if (aDoc->GetBrowsingContext()) {
+ mRootBCForIteration = aDoc->GetBrowsingContext();
+ if (aOption == eDocumentsWithSameRoot) {
+ RefPtr<BrowsingContext> bc =
+ GetParentIgnoreChromeBoundary(mRootBCForIteration);
+ while (bc) {
+ mRootBCForIteration = bc;
+ bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
+ }
+ }
+ }
+ SkipToNextMatch();
+ }
+ }
+
+ UniquePtr<T> TakeAndNext() {
+ auto thisChange = TakeAndNextInternal();
+ SkipToNextMatch();
+ return thisChange;
+ }
+ bool AtEnd() const { return mCurrent == nullptr; }
+
+ private:
+ already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
+ BrowsingContext* aBC) {
+ // Chrome BrowsingContexts are only available in the parent process, so if
+ // we're in a content process, we only worry about the context tree.
+ if (XRE_IsParentProcess()) {
+ return aBC->Canonical()->GetParentCrossChromeBoundary();
+ }
+ return do_AddRef(aBC->GetParent());
+ }
+
+ UniquePtr<T> TakeAndNextInternal() {
+ FullscreenChange* thisChange = mCurrent;
+ MOZ_ASSERT(thisChange->Type() == T::kType);
+ mCurrent = mCurrent->removeAndGetNext();
+ return WrapUnique(static_cast<T*>(thisChange));
+ }
+ void SkipToNextMatch() {
+ while (mCurrent) {
+ if (mCurrent->Type() == T::kType) {
+ RefPtr<BrowsingContext> bc =
+ mCurrent->Document()->GetBrowsingContext();
+ if (!bc) {
+ // Always automatically drop fullscreen changes which are
+ // from a document detached from the doc shell.
+ UniquePtr<T> change = TakeAndNextInternal();
+ change->MayRejectPromise("Document is not active");
+ continue;
+ }
+ while (bc && bc != mRootBCForIteration) {
+ bc = GetParentIgnoreChromeBoundary(bc);
+ }
+ if (bc) {
+ break;
+ }
+ }
+ // The current one either don't have matched type, or isn't
+ // inside the given subtree, so skip this item.
+ mCurrent = mCurrent->getNext();
+ }
+ }
+
+ FullscreenChange* mCurrent;
+ RefPtr<BrowsingContext> mRootBCForIteration;
+ };
+
+ private:
+ static LinkedList<FullscreenChange> sList;
+};
+
+/* static */
+LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
+
+Document* Document::GetFullscreenRoot() {
+ nsCOMPtr<Document> root = do_QueryReferent(mFullscreenRoot);
+ return root;
+}
+
+size_t Document::CountFullscreenElements() const {
+ size_t count = 0;
+ for (const nsWeakPtr& ptr : mTopLayer) {
+ if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
+ if (elem->State().HasState(ElementState::FULLSCREEN)) {
+ count++;
+ }
+ }
+ }
+ return count;
+}
+
+void Document::SetFullscreenRoot(Document* aRoot) {
+ mFullscreenRoot = do_GetWeakReference(aRoot);
+}
+
+void Document::TryCancelDialog() {
+ // Check if the document is blocked by modal dialog
+ for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
+ nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
+ if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
+ dialog->QueueCancelDialog();
+ break;
+ }
+ }
+}
+
+already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
+ UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
+ RefPtr<Promise> promise = exit->GetPromise();
+ RestorePreviousFullscreenState(std::move(exit));
+ return promise.forget();
+}
+
+static void AskWindowToExitFullscreen(Document* aDoc) {
+ if (XRE_GetProcessType() == GeckoProcessType_Content) {
+ nsContentUtils::DispatchEventOnlyToChrome(
+ aDoc, ToSupports(aDoc), u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
+ Cancelable::eNo, /* DefaultAction */ nullptr);
+ } else {
+ if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
+ win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
+ }
+ }
+}
+
+class nsCallExitFullscreen : public Runnable {
+ public:
+ explicit nsCallExitFullscreen(Document* aDoc)
+ : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
+
+ NS_IMETHOD Run() final {
+ if (!mDoc) {
+ FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
+ } else {
+ AskWindowToExitFullscreen(mDoc);
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<Document> mDoc;
+};
+
+/* static */
+void Document::AsyncExitFullscreen(Document* aDoc) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
+ if (aDoc) {
+ aDoc->Dispatch(TaskCategory::Other, exit.forget());
+ } else {
+ NS_DispatchToCurrentThread(exit.forget());
+ }
+}
+
+static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
+ uint32_t count = 0;
+ // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
+ auto subDoc = [&count](Document& aSubDoc) {
+ if (aSubDoc.Fullscreen()) {
+ count++;
+ }
+ return CallState::Continue;
+ };
+ aDoc.EnumerateSubDocuments(subDoc);
+ return count;
+}
+
+bool Document::IsFullscreenLeaf() {
+ // A fullscreen leaf document is fullscreen, and has no fullscreen
+ // subdocuments.
+ //
+ // FIXME(emilio): This doesn't seem to account for fission iframes, is that
+ // ok?
+ return Fullscreen() && CountFullscreenSubDocuments(*this) == 0;
+}
+
+static Document* GetFullscreenLeaf(Document& aDoc) {
+ if (aDoc.IsFullscreenLeaf()) {
+ return &aDoc;
+ }
+ if (!aDoc.Fullscreen()) {
+ return nullptr;
+ }
+ Document* leaf = nullptr;
+ auto recurse = [&leaf](Document& aSubDoc) {
+ leaf = GetFullscreenLeaf(aSubDoc);
+ return leaf ? CallState::Stop : CallState::Continue;
+ };
+ aDoc.EnumerateSubDocuments(recurse);
+ return leaf;
+}
+
+static Document* GetFullscreenLeaf(Document* aDoc) {
+ if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
+ return leaf;
+ }
+ // Otherwise we could be either in a non-fullscreen doc tree, or we're
+ // below the fullscreen doc. Start the search from the root.
+ Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
+ return GetFullscreenLeaf(*root);
+}
+
+static CallState ResetFullscreen(Document& aDocument) {
+ if (Element* fsElement = aDocument.GetUnretargetedFullscreenElement()) {
+ NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
+ "Should have at most 1 fullscreen subdocument.");
+ aDocument.CleanupFullscreenState();
+ NS_ASSERTION(!aDocument.Fullscreen(), "Should reset fullscreen");
+ DispatchFullscreenChange(aDocument, fsElement);
+ aDocument.EnumerateSubDocuments(ResetFullscreen);
+ }
+ return CallState::Continue;
+}
+
+// Since Document::ExitFullscreenInDocTree() could be called from
+// Element::UnbindFromTree() where it is not safe to synchronously run
+// script. This runnable is the script part of that function.
+class ExitFullscreenScriptRunnable : public Runnable {
+ public:
+ explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
+ : mozilla::Runnable("ExitFullscreenScriptRunnable"),
+ mRoot(aRoot),
+ mLeaf(aLeaf) {}
+
+ NS_IMETHOD Run() override {
+ // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
+ // document since we want this event to follow the same path that
+ // MozDOMFullscreen:Entered was dispatched.
+ nsContentUtils::DispatchEventOnlyToChrome(
+ mLeaf, ToSupports(mLeaf), u"MozDOMFullscreen:Exited"_ns,
+ CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
+ // Ensure the window exits fullscreen, as long as we don't have
+ // pending fullscreen requests.
+ if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
+ if (!mRoot->HasPendingFullscreenRequests()) {
+ win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
+ false);
+ }
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<Document> mRoot;
+ nsCOMPtr<Document> mLeaf;
+};
+
+/* static */
+void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
+ MOZ_ASSERT(aMaybeNotARootDoc);
+
+ // Unlock the pointer
+ PointerLockManager::Unlock();
+
+ // Resolve all promises which waiting for exit fullscreen.
+ PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
+ aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ while (!iter.AtEnd()) {
+ UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
+ exit->MayResolvePromise();
+ }
+
+ nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
+ if (!root || !root->Fullscreen()) {
+ // If a document was detached before exiting from fullscreen, it is
+ // possible that the root had left fullscreen state. In this case,
+ // we would not get anything from the ResetFullscreen() call. Root's
+ // not being a fullscreen doc also means the widget should have
+ // exited fullscreen state. It means even if we do not return here,
+ // we would actually do nothing below except crashing ourselves via
+ // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
+ // document.
+ return;
+ }
+
+ // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
+ // See ExitFullscreenScriptRunnable::Run for details. We have to
+ // record it here because we don't have such information after we
+ // reset the fullscreen state below.
+ Document* fullscreenLeaf = GetFullscreenLeaf(root);
+
+ // Walk the tree of fullscreen documents, and reset their fullscreen state.
+ ResetFullscreen(*root);
+
+ NS_ASSERTION(!root->Fullscreen(),
+ "Fullscreen root should no longer be a fullscreen doc...");
+
+ // Move the top-level window out of fullscreen mode.
+ FullscreenRoots::Remove(root);
+
+ nsContentUtils::AddScriptRunner(
+ new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
+}
+
+static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
+ CanBubble::eYes, ChromeOnlyDispatch::eYes);
+ asyncDispatcher->PostDOMEvent();
+}
+
+void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
+ NS_ASSERTION(!Fullscreen() || !FullscreenRoots::IsEmpty(),
+ "Should have at least 1 fullscreen root when fullscreen!");
+
+ if (!GetWindow()) {
+ aExit->MayRejectPromise("No active window");
+ return;
+ }
+ if (!Fullscreen() || FullscreenRoots::IsEmpty()) {
+ aExit->MayRejectPromise("Not in fullscreen mode");
+ return;
+ }
+
+ nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
+ AutoTArray<Element*, 8> exitElements;
+
+ Document* doc = fullScreenDoc;
+ // Collect all subdocuments.
+ for (; doc != this; doc = doc->GetInProcessParentDocument()) {
+ Element* fsElement = doc->GetUnretargetedFullscreenElement();
+ MOZ_ASSERT(fsElement,
+ "Parent document of "
+ "a fullscreen document without fullscreen element?");
+ exitElements.AppendElement(fsElement);
+ }
+ MOZ_ASSERT(doc == this, "Must have reached this doc");
+ // Collect all ancestor documents which we are going to change.
+ for (; doc; doc = doc->GetInProcessParentDocument()) {
+ Element* fsElement = doc->GetUnretargetedFullscreenElement();
+ MOZ_ASSERT(fsElement,
+ "Ancestor of fullscreen document must also be in fullscreen");
+ if (doc != this) {
+ if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
+ if (iframe->FullscreenFlag()) {
+ // If this is an iframe, and it explicitly requested
+ // fullscreen, don't rollback it automatically.
+ break;
+ }
+ }
+ }
+ exitElements.AppendElement(fsElement);
+ if (doc->CountFullscreenElements() > 1) {
+ break;
+ }
+ }
+
+ Document* lastDoc = exitElements.LastElement()->OwnerDoc();
+ size_t fullscreenCount = lastDoc->CountFullscreenElements();
+ if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
+ // If we are fully exiting fullscreen, don't touch anything here,
+ // just wait for the window to get out from fullscreen first.
+ PendingFullscreenChangeList::Add(std::move(aExit));
+ AskWindowToExitFullscreen(this);
+ return;
+ }
+
+ // If fullscreen mode is updated the pointer should be unlocked
+ PointerLockManager::Unlock();
+ // All documents listed in the array except the last one are going to
+ // completely exit from the fullscreen state.
+ for (auto i : IntegerRange(exitElements.Length() - 1)) {
+ exitElements[i]->OwnerDoc()->CleanupFullscreenState();
+ }
+ // The last document will either rollback one fullscreen element, or
+ // completely exit from the fullscreen state as well.
+ Document* newFullscreenDoc;
+ if (fullscreenCount > 1) {
+ DebugOnly<bool> removedFullscreenElement = lastDoc->PopFullscreenElement();
+ MOZ_ASSERT(removedFullscreenElement);
+ newFullscreenDoc = lastDoc;
+ } else {
+ lastDoc->CleanupFullscreenState();
+ newFullscreenDoc = lastDoc->GetInProcessParentDocument();
+ }
+ // Dispatch the fullscreenchange event to all document listed. Note
+ // that the loop order is reversed so that events are dispatched in
+ // the tree order as indicated in the spec.
+ for (Element* e : Reversed(exitElements)) {
+ DispatchFullscreenChange(*e->OwnerDoc(), e);
+ }
+ aExit->MayResolvePromise();
+
+ MOZ_ASSERT(newFullscreenDoc,
+ "If we were going to exit from fullscreen on "
+ "all documents in this doctree, we should've asked the window to "
+ "exit first instead of reaching here.");
+ if (fullScreenDoc != newFullscreenDoc &&
+ !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
+ // We've popped so enough off the stack that we've rolled back to
+ // a fullscreen element in a parent document. If this document is
+ // cross origin, dispatch an event to chrome so it knows to show
+ // the warning UI.
+ DispatchFullscreenNewOriginEvent(newFullscreenDoc);
+ }
+}
+
+static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
+ if (nsPresContext* presContext = aDoc->GetPresContext()) {
+ presContext->UpdateViewportScrollStylesOverride();
+ }
+}
+
+static void NotifyFullScreenChangedForMediaElement(Element& aElement) {
+ // When a media element enters the fullscreen, we would like to notify that
+ // to the media controller in order to update its status.
+ if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) {
+ mediaElem->NotifyFullScreenChanged();
+ }
+}
+
+void Document::CleanupFullscreenState() {
+ while (PopFullscreenElement(UpdateViewport::No)) {
+ // Remove the next one if appropriate
+ }
+
+ UpdateViewportScrollbarOverrideForFullscreen(this);
+ mFullscreenRoot = nullptr;
+
+ // Restore the zoom level that was in place prior to entering fullscreen.
+ if (PresShell* presShell = GetPresShell()) {
+ if (presShell->GetMobileViewportManager()) {
+ presShell->SetResolutionAndScaleTo(
+ mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
+ }
+ }
+}
+
+bool Document::PopFullscreenElement(UpdateViewport aUpdateViewport) {
+ Element* removedElement = TopLayerPop([](Element* element) -> bool {
+ return element->State().HasState(ElementState::FULLSCREEN);
+ });
+
+ if (!removedElement) {
+ return false;
+ }
+
+ MOZ_ASSERT(removedElement->State().HasState(ElementState::FULLSCREEN));
+ removedElement->RemoveStates(ElementState::FULLSCREEN | ElementState::MODAL);
+ NotifyFullScreenChangedForMediaElement(*removedElement);
+ // Reset iframe fullscreen flag.
+ if (auto* iframe = HTMLIFrameElement::FromNode(removedElement)) {
+ iframe->SetFullscreenFlag(false);
+ }
+ if (aUpdateViewport == UpdateViewport::Yes) {
+ UpdateViewportScrollbarOverrideForFullscreen(this);
+ }
+ return true;
+}
+
+void Document::SetFullscreenElement(Element& aElement) {
+ ElementState statesToAdd = ElementState::FULLSCREEN;
+ if (StaticPrefs::dom_fullscreen_modal() && !IsInChromeDocShell()) {
+ // Don't make the document modal in chrome documents, since we don't want
+ // the browser UI like the context menu / etc to be inert.
+ statesToAdd |= ElementState::MODAL;
+ }
+ aElement.AddStates(statesToAdd);
+ TopLayerPush(aElement);
+ NotifyFullScreenChangedForMediaElement(aElement);
+ UpdateViewportScrollbarOverrideForFullscreen(this);
+}
+
+void Document::TopLayerPush(Element& aElement) {
+ const bool modal = aElement.State().HasState(ElementState::MODAL);
+
+ TopLayerPop(aElement);
+ mTopLayer.AppendElement(do_GetWeakReference(&aElement));
+ NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
+
+ if (modal) {
+ aElement.AddStates(ElementState::TOPMOST_MODAL);
+
+ bool foundExistingModalElement = false;
+ for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
+ nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
+ if (element && element != &aElement &&
+ element->State().HasState(ElementState::TOPMOST_MODAL)) {
+ element->RemoveStates(ElementState::TOPMOST_MODAL);
+ foundExistingModalElement = true;
+ break;
+ }
+ }
+
+ if (!foundExistingModalElement) {
+ Element* root = GetRootElement();
+ MOZ_RELEASE_ASSERT(root, "top layer element outside of document?");
+ if (&aElement != root) {
+ // Add inert to the root element so that the inertness is applied to the
+ // entire document.
+ root->AddStates(ElementState::INERT);
+ }
+ }
+ }
+}
+
+void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
+ aDialogElement.AddStates(ElementState::MODAL);
+ TopLayerPush(aDialogElement);
+}
+
+void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
+ DebugOnly<Element*> removedElement = TopLayerPop(aDialogElement);
+ MOZ_ASSERT(removedElement == &aDialogElement);
+ aDialogElement.RemoveStates(ElementState::MODAL);
+}
+
+Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
+ if (mTopLayer.IsEmpty()) {
+ return nullptr;
+ }
+
+ // Remove the topmost element that qualifies aPredicate; This
+ // is required is because the top layer contains not only
+ // fullscreen elements, but also dialog elements.
+ Element* removedElement = nullptr;
+ for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
+ nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
+ if (element && aPredicate(element)) {
+ removedElement = element;
+ mTopLayer.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ // Pop from the stack null elements (references to elements which have
+ // been GC'd since they were added to the stack) and elements which are
+ // no longer in this document.
+ //
+ // FIXME(emilio): If this loop does something, it'd violate the assertions
+ // from GetTopLayerTop()... What gives?
+ while (!mTopLayer.IsEmpty()) {
+ Element* element = GetTopLayerTop();
+ if (!element || element->GetComposedDoc() != this) {
+ mTopLayer.RemoveLastElement();
+ } else {
+ // The top element of the stack is now an in-doc element. Return here.
+ break;
+ }
+ }
+
+ if (!removedElement) {
+ return nullptr;
+ }
+
+ const bool modal = removedElement->State().HasState(ElementState::MODAL);
+
+ if (modal) {
+ removedElement->RemoveStates(ElementState::TOPMOST_MODAL);
+ bool foundExistingModalElement = false;
+ for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
+ nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
+ if (element && element->State().HasState(ElementState::MODAL)) {
+ element->AddStates(ElementState::TOPMOST_MODAL);
+ foundExistingModalElement = true;
+ break;
+ }
+ }
+ // No more modal elements, make the document not inert anymore.
+ if (!foundExistingModalElement) {
+ Element* root = GetRootElement();
+ if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
+ root->RemoveStates(ElementState::INERT);
+ }
+ }
+ }
+
+ return removedElement;
+}
+
+Element* Document::TopLayerPop(Element& aElement) {
+ auto predictFunc = [&aElement](Element* element) {
+ return element == &aElement;
+ };
+ return TopLayerPop(predictFunc);
+}
+
+void Document::GetWireframe(bool aIncludeNodes,
+ Nullable<Wireframe>& aWireframe) {
+ FlushPendingNotifications(FlushType::Layout);
+ GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
+}
+
+void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
+ Nullable<Wireframe>& aWireframe) {
+ using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
+ using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+
+ PresShell* shell = GetPresShell();
+ if (!shell) {
+ return;
+ }
+
+ nsPresContext* pc = shell->GetPresContext();
+ if (!pc) {
+ return;
+ }
+
+ nsIFrame* rootFrame = shell->GetRootFrame();
+ if (!rootFrame) {
+ return;
+ }
+
+ auto& wireframe = aWireframe.SetValue();
+ wireframe.mCanvasBackground = shell->ComputeCanvasBackground().mColor;
+
+ FrameForPointOptions options;
+ options.mBits += FrameForPointOption::IgnoreCrossDoc;
+ options.mBits += FrameForPointOption::IgnorePaintSuppression;
+ options.mBits += FrameForPointOption::OnlyVisible;
+
+ AutoTArray<nsIFrame*, 32> frames;
+ const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
+ nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
+ options);
+
+ // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
+ // something perhaps, but seems hard / like it'd involve at least some extra
+ // copying around, since they don't outlive GetFramesForArea.
+ auto& rects = wireframe.mRects.Construct();
+ if (!rects.SetCapacity(frames.Length(), fallible)) {
+ return;
+ }
+ for (nsIFrame* frame : Reversed(frames)) {
+ auto [rectColor,
+ rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> {
+ if (frame->IsTextFrame()) {
+ return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
+ WireframeRectType::Text};
+ }
+ if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
+ return {0, WireframeRectType::Image};
+ }
+ if (frame->IsThemed()) {
+ return {0, WireframeRectType::Background};
+ }
+ bool drawImage = false;
+ bool drawColor = false;
+ if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) {
+ const nscolor color = nsCSSRendering::DetermineBackgroundColor(
+ pc, bgStyle, frame, drawImage, drawColor);
+ if (drawImage &&
+ !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
+ return {color, WireframeRectType::Image};
+ }
+ if (drawColor && !frame->IsCanvasFrame()) {
+ // Canvas frame background already accounted for in mCanvasBackground.
+ return {color, WireframeRectType::Background};
+ }
+ }
+ return {0, WireframeRectType::Unknown};
+ }();
+
+ if (rectType == WireframeRectType::Unknown) {
+ continue;
+ }
+
+ const auto r =
+ CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
+ frame, frame->GetRectRelativeToSelf(), relativeTo));
+ if ((uint32_t)r.Area() <
+ StaticPrefs::browser_history_wireframeAreaThreshold()) {
+ continue;
+ }
+
+ // Can't really fail because SetCapacity succeeded.
+ auto& taggedRect = *rects.AppendElement(fallible);
+
+ if (aIncludeNodes) {
+ if (nsIContent* c = frame->GetContent()) {
+ taggedRect.mNode.Construct(c);
+ }
+ }
+ taggedRect.mX = r.x;
+ taggedRect.mY = r.y;
+ taggedRect.mWidth = r.width;
+ taggedRect.mHeight = r.height;
+ taggedRect.mColor = rectColor;
+ taggedRect.mType.Construct(rectType);
+ }
+}
+
+Element* Document::GetTopLayerTop() {
+ if (mTopLayer.IsEmpty()) {
+ return nullptr;
+ }
+ uint32_t last = mTopLayer.Length() - 1;
+ nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
+ NS_ASSERTION(element, "Should have a top layer element!");
+ NS_ASSERTION(element->IsInComposedDoc(),
+ "Top layer element should be in doc");
+ NS_ASSERTION(element->OwnerDoc() == this,
+ "Top layer element should be in this doc");
+ return element;
+}
+
+Element* Document::GetUnretargetedFullscreenElement() const {
+ for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
+ nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
+ // Per spec, the fullscreen element is the topmost element in the document’s
+ // top layer whose fullscreen flag is set, if any, and null otherwise.
+ if (element && element->State().HasState(ElementState::FULLSCREEN)) {
+ return element;
+ }
+ }
+ return nullptr;
+}
+
+nsTArray<Element*> Document::GetTopLayer() const {
+ nsTArray<Element*> elements;
+ for (const nsWeakPtr& ptr : mTopLayer) {
+ if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
+ elements.AppendElement(elem);
+ }
+ }
+ return elements;
+}
+
+bool Document::TopLayerContains(Element& aElement) const {
+ if (mTopLayer.IsEmpty()) {
+ return false;
+ }
+ nsWeakPtr weakElement = do_GetWeakReference(&aElement);
+ return mTopLayer.Contains(weakElement);
+}
+
+void Document::HideAllPopoversUntil(nsINode& aEndpoint,
+ bool aFocusPreviousElement,
+ bool aFireEvents) {
+ auto closeAllOpenPopovers = [&aFocusPreviousElement, &aFireEvents,
+ this]() MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ while (RefPtr<Element> topmost = GetTopmostAutoPopover()) {
+ HidePopover(*topmost, aFocusPreviousElement, aFireEvents, IgnoreErrors());
+ }
+ };
+
+ if (&aEndpoint == this) {
+ closeAllOpenPopovers();
+ return;
+ }
+
+ // https://github.com/whatwg/html/pull/9198
+ auto needRepeatingHide = [&]() {
+ auto autoList = AutoPopoverList();
+ return autoList.Contains(&aEndpoint) &&
+ &aEndpoint != autoList.LastElement();
+ };
+
+ MOZ_ASSERT((&aEndpoint)->IsElement() &&
+ (&aEndpoint)->AsElement()->IsAutoPopover());
+ bool repeatingHide = false;
+ bool fireEvents = aFireEvents;
+ do {
+ RefPtr<const Element> lastToHide = nullptr;
+ bool foundEndpoint = false;
+ for (const Element* popover : AutoPopoverList()) {
+ if (popover == &aEndpoint) {
+ foundEndpoint = true;
+ } else if (foundEndpoint) {
+ lastToHide = popover;
+ break;
+ }
+ }
+
+ if (!foundEndpoint) {
+ closeAllOpenPopovers();
+ return;
+ }
+
+ while (lastToHide && lastToHide->IsPopoverOpen()) {
+ RefPtr<Element> topmost = GetTopmostAutoPopover();
+ if (!topmost) {
+ break;
+ }
+ HidePopover(*topmost, aFocusPreviousElement, fireEvents, IgnoreErrors());
+ }
+
+ repeatingHide = needRepeatingHide();
+ if (repeatingHide) {
+ fireEvents = false;
+ }
+ } while (repeatingHide);
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void
+Document::HideAllPopoversWithoutRunningScript() {
+ return HideAllPopoversUntil(*this, false, false);
+}
+
+void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement,
+ bool aFireEvents, ErrorResult& aRv) {
+ RefPtr<nsGenericHTMLElement> popoverHTMLEl =
+ nsGenericHTMLElement::FromNode(aPopover);
+ NS_ASSERTION(popoverHTMLEl, "Not a HTML element");
+
+ if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
+ nullptr, aRv)) {
+ return;
+ }
+
+ bool wasHiding = popoverHTMLEl->GetPopoverData()->IsHiding();
+ popoverHTMLEl->GetPopoverData()->SetIsHiding(true);
+ auto restoreIsHiding = MakeScopeExit([&]() {
+ if (auto* popoverData = popoverHTMLEl->GetPopoverData()) {
+ popoverData->SetIsHiding(wasHiding);
+ }
+ });
+
+ if (popoverHTMLEl->IsAutoPopover()) {
+ HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, aFireEvents);
+ if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
+ nullptr, aRv)) {
+ return;
+ }
+ // TODO: we can't always guarantee:
+ // The last item in document's auto popover list is popoverHTMLEl.
+ // See, https://github.com/whatwg/html/issues/9197
+ // If popoverHTMLEl is not on top, hide popovers again without firing
+ // events.
+ if (NS_WARN_IF(GetTopmostAutoPopover() != popoverHTMLEl)) {
+ HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
+ if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
+ nullptr, aRv)) {
+ return;
+ }
+ MOZ_ASSERT(GetTopmostAutoPopover() == popoverHTMLEl,
+ "popoverHTMLEl should be on top of auto popover list");
+ }
+ }
+
+ auto* data = popoverHTMLEl->GetPopoverData();
+ MOZ_ASSERT(data, "Should have popover data");
+ data->SetInvoker(nullptr);
+
+ // Fire beforetoggle event and re-check popover validity.
+ if (aFireEvents && !wasHiding) {
+ // Intentionally ignore the return value here as only on open event for
+ // beforetoggle the cancelable attribute is initialized to true.
+ popoverHTMLEl->FireToggleEvent(PopoverVisibilityState::Showing,
+ PopoverVisibilityState::Hidden,
+ u"beforetoggle"_ns);
+ if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
+ nullptr, aRv)) {
+ return;
+ }
+ }
+
+ RemovePopoverFromTopLayer(aPopover);
+
+ popoverHTMLEl->PopoverPseudoStateUpdate(false, true);
+ popoverHTMLEl->GetPopoverData()->SetPopoverVisibilityState(
+ PopoverVisibilityState::Hidden);
+
+ // Queue popover toggle event task.
+ if (aFireEvents) {
+ popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing);
+ }
+
+ if (aFocusPreviousElement) {
+ popoverHTMLEl->FocusPreviousElementAfterHidingPopover();
+ } else {
+ popoverHTMLEl->ForgetPreviouslyFocusedElementAfterHidingPopover();
+ }
+}
+
+nsTArray<Element*> Document::AutoPopoverList() const {
+ nsTArray<Element*> elements;
+ for (const nsWeakPtr& ptr : mTopLayer) {
+ if (nsCOMPtr<Element> element = do_QueryReferent(ptr)) {
+ if (element && element->IsAutoPopover()) {
+ elements.AppendElement(element);
+ }
+ }
+ }
+ return elements;
+}
+
+Element* Document::GetTopmostAutoPopover() const {
+ for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
+ nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
+ if (element && element->IsAutoPopover()) {
+ return element;
+ }
+ }
+ return nullptr;
+}
+
+void Document::AddToAutoPopoverList(Element& aElement) {
+ MOZ_ASSERT(aElement.IsAutoPopover());
+ TopLayerPush(aElement);
+}
+
+void Document::RemoveFromAutoPopoverList(Element& aElement) {
+ MOZ_ASSERT(aElement.IsAutoPopover());
+ TopLayerPop(aElement);
+}
+
+void Document::AddPopoverToTopLayer(Element& aElement) {
+ MOZ_ASSERT(aElement.GetPopoverData());
+ TopLayerPush(aElement);
+}
+
+void Document::RemovePopoverFromTopLayer(Element& aElement) {
+ MOZ_ASSERT(aElement.GetPopoverData());
+ TopLayerPop(aElement);
+}
+
+// Returns true if aDoc browsing context is focused.
+bool IsInFocusedTab(Document* aDoc) {
+ BrowsingContext* bc = aDoc->GetBrowsingContext();
+ if (!bc) {
+ return false;
+ }
+
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return false;
+ }
+
+ if (XRE_IsParentProcess()) {
+ // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
+ // by retaining the old code path for the parent process.
+ nsIDocShell* docshell = aDoc->GetDocShell();
+ if (!docshell) {
+ return false;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
+ if (!rootItem) {
+ return false;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
+ if (!rootWin) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
+ activeWindow = fm->GetActiveWindow();
+ if (!activeWindow) {
+ return false;
+ }
+
+ return activeWindow == rootWin;
+ }
+
+ return fm->GetActiveBrowsingContext() == bc->Top();
+}
+
+// Returns true if aDoc browsing context is focused and is also active.
+bool IsInActiveTab(Document* aDoc) {
+ if (!IsInFocusedTab(aDoc)) {
+ return false;
+ }
+
+ BrowsingContext* bc = aDoc->GetBrowsingContext();
+ MOZ_ASSERT(bc, "With no BrowsingContext, we should have failed earlier.");
+ return bc->IsActive();
+}
+
+void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
+ // Ensure the frame element is the fullscreen element in this document.
+ // If the frame element is already the fullscreen element in this document,
+ // this has no effect.
+ auto request = FullscreenRequest::CreateForRemote(aFrameElement);
+ RequestFullscreen(std::move(request), XRE_IsContentProcess());
+}
+
+void Document::RemoteFrameFullscreenReverted() {
+ UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
+ RestorePreviousFullscreenState(std::move(exit));
+}
+
+static bool HasFullscreenSubDocument(Document& aDoc) {
+ uint32_t count = CountFullscreenSubDocuments(aDoc);
+ NS_ASSERTION(count <= 1,
+ "Fullscreen docs should have at most 1 fullscreen child!");
+ return count >= 1;
+}
+
+// Returns nullptr if a request for Fullscreen API is currently enabled
+// in the given document. Returns a static string indicates the reason
+// why it is not enabled otherwise.
+const char* Document::GetFullscreenError(CallerType aCallerType) {
+ if (!StaticPrefs::full_screen_api_enabled()) {
+ return "FullscreenDeniedDisabled";
+ }
+
+ if (aCallerType == CallerType::System) {
+ // Chrome code can always use the fullscreen API, provided it's not
+ // explicitly disabled.
+ return nullptr;
+ }
+
+ if (!IsVisible()) {
+ return "FullscreenDeniedHidden";
+ }
+
+ if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
+ return "FullscreenDeniedFeaturePolicy";
+ }
+
+ // Ensure that all containing elements are <iframe> and have allowfullscreen
+ // attribute set.
+ BrowsingContext* bc = GetBrowsingContext();
+ if (!bc || !bc->FullscreenAllowed()) {
+ return "FullscreenDeniedContainerNotAllowed";
+ }
+
+ return nullptr;
+}
+
+bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
+ Element* elem = aRequest.Element();
+ // Strictly speaking, this isn't part of the fullscreen element ready
+ // check in the spec, but per steps in the spec, when an element which
+ // is already the fullscreen element requests fullscreen, nothing
+ // should change and no event should be dispatched, but we still need
+ // to resolve the returned promise.
+ Element* fullscreenElement = GetUnretargetedFullscreenElement();
+ if (elem == fullscreenElement) {
+ aRequest.MayResolvePromise();
+ return false;
+ }
+ if (!elem->IsInComposedDoc()) {
+ aRequest.Reject("FullscreenDeniedNotInDocument");
+ return false;
+ }
+ if (elem->IsPopoverOpen()) {
+ aRequest.Reject("FullscreenDeniedPopoverOpen");
+ return false;
+ }
+ if (elem->OwnerDoc() != this) {
+ aRequest.Reject("FullscreenDeniedMovedDocument");
+ return false;
+ }
+ if (!GetWindow()) {
+ aRequest.Reject("FullscreenDeniedLostWindow");
+ return false;
+ }
+ if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
+ aRequest.Reject(msg);
+ return false;
+ }
+ if (HasFullscreenSubDocument(*this)) {
+ aRequest.Reject("FullscreenDeniedSubDocFullScreen");
+ return false;
+ }
+ if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
+ aRequest.Reject("FullscreenDeniedHTMLDialog");
+ return false;
+ }
+ if (!nsContentUtils::IsChromeDoc(this) && !IsInFocusedTab(this)) {
+ aRequest.Reject("FullscreenDeniedNotFocusedTab");
+ return false;
+ }
+ // Deny requests when a windowed plugin is focused.
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ NS_WARNING("Failed to retrieve focus manager in fullscreen request.");
+ aRequest.MayRejectPromise("An unexpected error occurred");
+ return false;
+ }
+ if (nsContentUtils::HasPluginWithUncontrolledEventDispatch(
+ fm->GetFocusedElement())) {
+ aRequest.Reject("FullscreenDeniedFocusedPlugin");
+ return false;
+ }
+ return true;
+}
+
+static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ nsIDocShell* docShell = aDoc->GetDocShell();
+ if (!docShell) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
+ return rootItem ? rootItem->GetWindow() : nullptr;
+}
+
+static bool ShouldApplyFullscreenDirectly(Document* aDoc,
+ nsPIDOMWindowOuter* aRootWin) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // If we are in the chrome process, and the window has not been in
+ // fullscreen, we certainly need to make that fullscreen first.
+ if (!aRootWin->GetFullScreen()) {
+ return false;
+ }
+ // The iterator not being at end indicates there is still some
+ // pending fullscreen request relates to this document. We have to
+ // push the request to the pending queue so requests are handled
+ // in the correct order.
+ PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
+ aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ if (!iter.AtEnd()) {
+ return false;
+ }
+
+ // Same thing for exits. If we have any pending, we have to push
+ // to the pending queue.
+ PendingFullscreenChangeList::Iterator<FullscreenExit> iterExit(
+ aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ if (!iterExit.AtEnd()) {
+ return false;
+ }
+
+ // We have to apply the fullscreen state directly in this case,
+ // because nsGlobalWindow::SetFullscreenInternal() will do nothing
+ // if it is already in fullscreen. If we do not apply the state but
+ // instead add it to the queue and wait for the window as normal,
+ // we would get stuck.
+ return true;
+}
+
+static bool CheckFullscreenAllowedElementType(const Element* elem) {
+ // Per spec only HTML, <svg>, and <math> should be allowed, but
+ // we also need to allow XUL elements right now.
+ return elem->IsHTMLElement() || elem->IsXULElement() ||
+ elem->IsSVGElement(nsGkAtoms::svg) ||
+ elem->IsMathMLElement(nsGkAtoms::math);
+}
+
+void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
+ bool aApplyFullscreenDirectly) {
+ if (XRE_IsContentProcess()) {
+ RequestFullscreenInContentProcess(std::move(aRequest),
+ aApplyFullscreenDirectly);
+ } else {
+ RequestFullscreenInParentProcess(std::move(aRequest),
+ aApplyFullscreenDirectly);
+ }
+}
+
+void Document::RequestFullscreenInContentProcess(
+ UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ // If we are in the content process, we can apply the fullscreen
+ // state directly only if we have been in DOM fullscreen, because
+ // otherwise we always need to notify the chrome.
+ if (aApplyFullscreenDirectly ||
+ nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) {
+ ApplyFullscreen(std::move(aRequest));
+ return;
+ }
+
+ if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
+ aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
+ return;
+ }
+
+ // We don't need to check element ready before this point, because
+ // if we called ApplyFullscreen, it would check that for us.
+ if (!FullscreenElementReadyCheck(*aRequest)) {
+ return;
+ }
+
+ PendingFullscreenChangeList::Add(std::move(aRequest));
+ // If we are not the top level process, dispatch an event to make
+ // our parent process go fullscreen first.
+ Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction(
+ "Document::RequestFullscreenInContentProcess", [self = RefPtr{this}] {
+ if (!self->HasPendingFullscreenRequests()) {
+ return;
+ }
+ nsContentUtils::DispatchEventOnlyToChrome(
+ self, ToSupports(self), u"MozDOMFullscreen:Request"_ns,
+ CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
+ }));
+}
+
+void Document::RequestFullscreenInParentProcess(
+ UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
+ if (!rootWin) {
+ aRequest->MayRejectPromise("No active window");
+ return;
+ }
+
+ if (aApplyFullscreenDirectly ||
+ ShouldApplyFullscreenDirectly(this, rootWin)) {
+ ApplyFullscreen(std::move(aRequest));
+ return;
+ }
+
+ if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
+ aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
+ return;
+ }
+
+ // See if we're waiting on an exit. If so, just make this one pending.
+ PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
+ this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ if (!iter.AtEnd()) {
+ PendingFullscreenChangeList::Add(std::move(aRequest));
+ rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
+ return;
+ }
+
+ // We don't need to check element ready before this point, because
+ // if we called ApplyFullscreen, it would check that for us.
+ if (!FullscreenElementReadyCheck(*aRequest)) {
+ return;
+ }
+
+ PendingFullscreenChangeList::Add(std::move(aRequest));
+ // Make the window fullscreen.
+ rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
+}
+
+/* static */
+bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
+ bool handled = false;
+ PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
+ aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ while (!iter.AtEnd()) {
+ UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
+ Document* doc = request->Document();
+ if (doc->ApplyFullscreen(std::move(request))) {
+ handled = true;
+ }
+ }
+ return handled;
+}
+
+/* static */
+void Document::ClearPendingFullscreenRequests(Document* aDoc) {
+ PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
+ aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
+ while (!iter.AtEnd()) {
+ UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
+ request->MayRejectPromise("Fullscreen request aborted");
+ }
+}
+
+bool Document::HasPendingFullscreenRequests() {
+ PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
+ this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
+ return !iter.AtEnd();
+}
+
+bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
+ if (!FullscreenElementReadyCheck(*aRequest)) {
+ return false;
+ }
+
+ RefPtr<Document> doc = aRequest->Document();
+ doc->HideAllPopoversWithoutRunningScript();
+
+ // Stash a reference to any existing fullscreen doc, we'll use this later
+ // to detect if the origin which is fullscreen has changed.
+ nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
+
+ // Stores a list of documents which we must dispatch "fullscreenchange"
+ // too. We're required by the spec to dispatch the events in root-to-leaf
+ // order, but we traverse the doctree in a leaf-to-root order, so we save
+ // references to the documents we must dispatch to so that we get the order
+ // as specified.
+ AutoTArray<Document*, 8> changed;
+
+ // Remember the root document, so that if a fullscreen document is hidden
+ // we can reset fullscreen state in the remaining visible fullscreen
+ // documents.
+ Document* fullScreenRootDoc =
+ nsContentUtils::GetInProcessSubtreeRootDocument(this);
+
+ // If a document is already in fullscreen, then unlock the mouse pointer
+ // before setting a new document to fullscreen
+ PointerLockManager::Unlock();
+
+ // Set the fullscreen element. This sets the fullscreen style on the
+ // element, and the fullscreen-ancestor styles on ancestors of the element
+ // in this document.
+ Element* elem = aRequest->Element();
+ SetFullscreenElement(*elem);
+ // Set the iframe fullscreen flag.
+ if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
+ iframe->SetFullscreenFlag(true);
+ }
+ changed.AppendElement(this);
+
+ // Propagate up the document hierarchy, setting the fullscreen element as
+ // the element's container in ancestor documents. This also sets the
+ // appropriate css styles as well. Note we don't propagate down the
+ // document hierarchy, the fullscreen element (or its container) is not
+ // visible there. Stop when we reach the root document.
+ Document* child = this;
+ while (true) {
+ child->SetFullscreenRoot(fullScreenRootDoc);
+
+ // When entering fullscreen, reset the RCD's resolution to the intrinsic
+ // resolution, otherwise the fullscreen content could be sized larger than
+ // the screen (since fullscreen is implemented using position:fixed and
+ // fixed elements are sized to the layout viewport).
+ // This also ensures that things like video controls aren't zoomed in
+ // when in fullscreen mode.
+ if (PresShell* presShell = child->GetPresShell()) {
+ if (RefPtr<MobileViewportManager> manager =
+ presShell->GetMobileViewportManager()) {
+ // Save the previous resolution so it can be restored.
+ child->mSavedResolution = presShell->GetResolution();
+ presShell->SetResolutionAndScaleTo(
+ manager->ComputeIntrinsicResolution(),
+ ResolutionChangeOrigin::MainThreadRestore);
+ }
+ }
+
+ NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
+ "Fullscreen root should be set!");
+ if (child == fullScreenRootDoc) {
+ break;
+ }
+
+ Element* element = child->GetEmbedderElement();
+ if (!element) {
+ // We've reached the root.No more changes need to be made
+ // to the top layer stacks of documents further up the tree.
+ break;
+ }
+
+ Document* parent = child->GetInProcessParentDocument();
+ parent->SetFullscreenElement(*element);
+ changed.AppendElement(parent);
+ child = parent;
+ }
+
+ FullscreenRoots::Add(this);
+
+ // If it is the first entry of the fullscreen, trigger an event so
+ // that the UI can response to this change, e.g. hide chrome, or
+ // notifying parent process to enter fullscreen. Note that chrome
+ // code may also want to listen to MozDOMFullscreen:NewOrigin event
+ // to pop up warning UI.
+ if (!previousFullscreenDoc) {
+ nsContentUtils::DispatchEventOnlyToChrome(
+ this, ToSupports(elem), u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
+ Cancelable::eNo, /* DefaultAction */ nullptr);
+ }
+
+ // The origin which is fullscreen gets changed. Trigger an event so
+ // that the chrome knows to pop up a warning UI. Note that
+ // previousFullscreenDoc == nullptr upon first entry, we show the warning UI
+ // directly as soon as chrome document goes into fullscreen state. Also note
+ // that, in a multi-process browser, the code in content process is
+ // responsible for sending message with the origin to its parent, and the
+ // parent shouldn't rely on this event itself.
+ if (aRequest->mShouldNotifyNewOrigin && previousFullscreenDoc &&
+ !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
+ DispatchFullscreenNewOriginEvent(this);
+ }
+
+ // Dispatch "fullscreenchange" events. Note that the loop order is
+ // reversed so that events are dispatched in the tree order as
+ // indicated in the spec.
+ for (Document* d : Reversed(changed)) {
+ DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement());
+ }
+ aRequest->MayResolvePromise();
+ return true;
+}
+
+void Document::ClearOrientationPendingPromise() {
+ mOrientationPendingPromise = nullptr;
+}
+
+bool Document::SetOrientationPendingPromise(Promise* aPromise) {
+ if (mIsGoingAway) {
+ return false;
+ }
+
+ mOrientationPendingPromise = aPromise;
+ return true;
+}
+
+void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
+ dom::VisibilityState oldState = mVisibilityState;
+ mVisibilityState = ComputeVisibilityState();
+ if (oldState != mVisibilityState) {
+ if (aDispatchEvent == DispatchVisibilityChange::Yes) {
+ nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
+ u"visibilitychange"_ns,
+ CanBubble::eYes, Cancelable::eNo);
+ }
+ NotifyActivityChanged();
+ if (mVisibilityState == dom::VisibilityState::Visible) {
+ MaybeActiveMediaComponents();
+ }
+
+ bool visible = !Hidden();
+ for (auto* listener : mWorkerListeners) {
+ listener->OnVisible(visible);
+ }
+ }
+}
+
+void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
+ mWorkerListeners.Insert(aListener);
+ aListener->OnVisible(!Hidden());
+}
+
+void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
+ mWorkerListeners.Remove(aListener);
+}
+
+VisibilityState Document::ComputeVisibilityState() const {
+ // We have to check a few pieces of information here:
+ // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
+ // 2) Do we have an outer window? If not, we're hidden. Note that we don't
+ // want to use GetWindow here because it does weird groveling for windows
+ // in some cases.
+ // 3) Is our outer window background? If so, we're hidden.
+ // Otherwise, we're visible.
+ if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
+ mWindow->GetOuterWindow()->IsBackground()) {
+ return dom::VisibilityState::Hidden;
+ }
+
+ return dom::VisibilityState::Visible;
+}
+
+void Document::PostVisibilityUpdateEvent() {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
+ "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
+ DispatchVisibilityChange::Yes);
+ Dispatch(TaskCategory::Other, event.forget());
+}
+
+void Document::MaybeActiveMediaComponents() {
+ auto* window = GetWindow();
+ if (!window || !window->ShouldDelayMediaFromStart()) {
+ return;
+ }
+ window->ActivateMediaComponents();
+}
+
+void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
+ nsINode::AddSizeOfExcludingThis(aWindowSizes,
+ &aWindowSizes.mDOMSizes.mDOMOtherSize);
+
+ for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
+ AddSizeOfNodeTree(*kid, aWindowSizes);
+ }
+
+ // IMPORTANT: for our ComputedValues measurements, we want to measure
+ // ComputedValues accessible from DOM elements before ComputedValues not
+ // accessible from DOM elements (i.e. accessible only from the frame tree).
+ //
+ // Therefore, the measurement of the Document superclass must happen after
+ // the measurement of DOM nodes (above), because Document contains the
+ // PresShell, which contains the frame tree.
+ if (mPresShell) {
+ mPresShell->AddSizeOfIncludingThis(aWindowSizes);
+ }
+
+ mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
+
+ aWindowSizes.mPropertyTablesSize +=
+ mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
+
+ if (EventListenerManager* elm = GetExistingListenerManager()) {
+ aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
+ }
+
+ if (mNodeInfoManager) {
+ mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
+ }
+
+ aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
+ mDOMMediaQueryLists.sizeOfExcludingThis(
+ aWindowSizes.mState.mMallocSizeOf);
+
+ for (const MediaQueryList* mql : mDOMMediaQueryLists) {
+ aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
+ mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
+ }
+
+ DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
+
+ for (auto& sheetArray : mAdditionalSheets) {
+ AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
+ }
+ // Lumping in the loader with the style-sheets size is not ideal,
+ // but most of the things in there are in fact stylesheets, so it
+ // doesn't seem worthwhile to separate it out.
+ // This can be null if we've already been unlinked.
+ if (mCSSLoader) {
+ aWindowSizes.mLayoutStyleSheetsSize +=
+ mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
+ }
+
+ if (mResizeObserverController) {
+ mResizeObserverController->AddSizeOfIncludingThis(aWindowSizes);
+ }
+
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ mAttrStyleSheet ? mAttrStyleSheet->DOMSizeOfIncludingThis(
+ aWindowSizes.mState.mMallocSizeOf)
+ : 0;
+
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ mStyledLinks.ShallowSizeOfExcludingThis(
+ aWindowSizes.mState.mMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - mMidasCommandManager
+ // - many!
+}
+
+void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ aWindowSizes.mState.mMallocSizeOf(this);
+ DocAddSizeOfExcludingThis(aWindowSizes);
+}
+
+void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
+ size_t* aNodeSize) const {
+ // This AddSizeOfExcludingThis() overrides the one from nsINode. But
+ // nsDocuments can only appear at the top of the DOM tree, and we use the
+ // specialized DocAddSizeOfExcludingThis() in that case. So this should never
+ // be called.
+ MOZ_CRASH();
+}
+
+/* static */
+void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
+ size_t nodeSize = 0;
+ aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
+
+ // This is where we transfer the nodeSize obtained from
+ // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
+ switch (aNode.NodeType()) {
+ case nsINode::ELEMENT_NODE:
+ aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
+ break;
+ case nsINode::TEXT_NODE:
+ aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
+ break;
+ case nsINode::CDATA_SECTION_NODE:
+ aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
+ break;
+ case nsINode::COMMENT_NODE:
+ aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
+ break;
+ default:
+ aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
+ break;
+ }
+
+ if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
+ aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
+ }
+
+ if (aNode.IsContent()) {
+ nsTArray<nsIContent*> anonKids;
+ nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
+ nsIContent::eAllChildren);
+ for (nsIContent* anonKid : anonKids) {
+ AddSizeOfNodeTree(*anonKid, aWindowSizes);
+ }
+
+ if (auto* element = Element::FromNode(aNode)) {
+ if (ShadowRoot* shadow = element->GetShadowRoot()) {
+ AddSizeOfNodeTree(*shadow, aWindowSizes);
+ }
+ }
+ }
+
+ // NOTE(emilio): If you feel smart and want to change this function to use
+ // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
+ // sane way, and kids of <content> won't point to the parent, so we'd never
+ // find the root node where we should stop at.
+ for (nsIContent* kid = aNode.GetFirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+ AddSizeOfNodeTree(*kid, aWindowSizes);
+ }
+}
+
+already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
+ ErrorResult& rv) {
+ nsCOMPtr<nsIScriptGlobalObject> global =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIScriptObjectPrincipal> prin =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!prin) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), "about:blank");
+ if (!uri) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsCOMPtr<Document> doc;
+ nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
+ nullptr, uri, uri, prin->GetPrincipal(),
+ true, global, DocumentFlavorPlain);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return nullptr;
+ }
+
+ doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
+
+ return doc.forget();
+}
+
+XPathExpression* Document::CreateExpression(const nsAString& aExpression,
+ XPathNSResolver* aResolver,
+ ErrorResult& rv) {
+ return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
+}
+
+nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
+ return XPathEvaluator()->CreateNSResolver(aNodeResolver);
+}
+
+already_AddRefed<XPathResult> Document::Evaluate(
+ JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
+ XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
+ ErrorResult& rv) {
+ return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
+ aType, aResult, rv);
+}
+
+already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
+ nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
+ if (!item) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIDocShellTreeOwner> owner;
+ item->GetTreeOwner(getter_AddRefs(owner));
+ nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
+ if (!appWin) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIDocShell> appWinShell;
+ appWin->GetDocShell(getter_AddRefs(appWinShell));
+ if (!SameCOMIdentity(appWinShell, item)) {
+ return nullptr;
+ }
+ return appWin.forget();
+}
+
+WindowContext* Document::GetTopLevelWindowContext() const {
+ WindowContext* windowContext = GetWindowContext();
+ return windowContext ? windowContext->TopWindowContext() : nullptr;
+}
+
+Document* Document::GetTopLevelContentDocumentIfSameProcess() {
+ Document* parent;
+
+ if (!mLoadedAsData) {
+ parent = this;
+ } else {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
+ if (!window) {
+ return nullptr;
+ }
+
+ parent = window->GetExtantDoc();
+ if (!parent) {
+ return nullptr;
+ }
+ }
+
+ do {
+ if (parent->IsTopLevelContentDocument()) {
+ break;
+ }
+
+ // If we ever have a non-content parent before we hit a toplevel content
+ // parent, then we're never going to find one. Just bail.
+ if (!parent->IsContentDocument()) {
+ return nullptr;
+ }
+
+ parent = parent->GetInProcessParentDocument();
+ } while (parent);
+
+ return parent;
+}
+
+const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
+ return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
+}
+
+void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
+ MOZ_ASSERT(IsBeingUsedAsImage());
+ MOZ_ASSERT(aReferencingDocument);
+
+ if (!aReferencingDocument->mShouldReportUseCounters) {
+ // No need to propagate use counters to a document that itself won't report
+ // use counters.
+ return;
+ }
+
+ MOZ_LOG(gUseCountersLog, LogLevel::Debug,
+ ("PropagateImageUseCounters from %s to %s",
+ nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
+ nsContentUtils::TruncatedURLForDisplay(
+ aReferencingDocument->mDocumentURI)
+ .get()));
+
+ if (aReferencingDocument->IsBeingUsedAsImage()) {
+ NS_WARNING(
+ "Page use counters from nested image documents may not "
+ "propagate to the top-level document (bug 1657805)");
+ }
+
+ SetCssUseCounterBits();
+ aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
+ aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
+}
+
+bool Document::HasScriptsBlockedBySandbox() const {
+ return mSandboxFlags & SANDBOXED_SCRIPTS;
+}
+
+// Some use-counter sanity-checking.
+static_assert(size_t(eUseCounter_EndCSSProperties) -
+ size_t(eUseCounter_FirstCSSProperty) ==
+ size_t(eCSSProperty_COUNT_with_aliases),
+ "We should have the right amount of CSS property use counters");
+static_assert(size_t(eUseCounter_Count) -
+ size_t(eUseCounter_FirstCountedUnknownProperty) ==
+ size_t(CountedUnknownProperty::Count),
+ "We should have the right amount of counted unknown properties"
+ " use counters");
+static_assert(size_t(eUseCounter_Count) * 2 ==
+ size_t(Telemetry::HistogramUseCounterCount),
+ "There should be two histograms (document and page)"
+ " for each use counter");
+
+#define ASSERT_CSS_COUNTER(id_, method_) \
+ static_assert(size_t(eUseCounter_property_##method_) - \
+ size_t(eUseCounter_FirstCSSProperty) == \
+ size_t(id_), \
+ "Order for CSS counters and CSS property id should match");
+#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
+#define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
+ ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
+#define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
+ ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
+ ASSERT_CSS_COUNTER(eCSSPropertyAlias_##aliasid_, method_)
+#include "mozilla/ServoCSSPropList.h"
+#undef CSS_PROP_ALIAS
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_LONGHAND
+#undef CSS_PROP_PUBLIC_OR_PRIVATE
+#undef ASSERT_CSS_COUNTER
+
+void Document::SetCssUseCounterBits() {
+ if (StaticPrefs::layout_css_use_counters_enabled()) {
+ for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
+ auto id = nsCSSPropertyID(i);
+ if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
+ SetUseCounter(nsCSSProps::UseCounterFor(id));
+ }
+ }
+ }
+
+ if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
+ for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
+ auto id = CountedUnknownProperty(i);
+ if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
+ id)) {
+ SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
+ }
+ }
+ }
+}
+
+void Document::InitUseCounters() {
+ // We can be called more than once, e.g. when session history navigation shows
+ // us a second time.
+ if (mUseCountersInitialized) {
+ return;
+ }
+ mUseCountersInitialized = true;
+
+ static_assert(Telemetry::HistogramUseCounterCount > 0);
+
+ if (!ShouldIncludeInTelemetry(/* aAllowExtensionURIs = */ true)) {
+ return;
+ }
+
+ // Now we know for sure that we should report use counters from this document.
+ mShouldReportUseCounters = true;
+
+ WindowContext* top = GetWindowContextForPageUseCounters();
+ if (!top) {
+ // This is the case for SVG image documents. They are not displayed in a
+ // window, but we still do want to record document use counters for them.
+ //
+ // Page use counter propagation is handled in PropagateImageUseCounters,
+ // so there is no need to use the cross-process machinery to send them.
+ MOZ_LOG(gUseCountersLog, LogLevel::Debug,
+ ("InitUseCounters for a non-displayed document [%s]",
+ nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
+ return;
+ }
+
+ RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
+ if (!wgc) {
+ return;
+ }
+
+ MOZ_LOG(gUseCountersLog, LogLevel::Debug,
+ ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
+ " [from %s]",
+ wgc->InnerWindowId(), top->Id(),
+ nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
+
+ // Inform the parent process that we will send it page use counters later on.
+ wgc->SendExpectPageUseCounters(top);
+ mShouldSendPageUseCounters = true;
+}
+
+// We keep separate counts for individual documents and top-level
+// pages to more accurately track how many web pages might break if
+// certain features were removed. Consider the case of a single
+// HTML document with several SVG images and/or iframes with
+// sub-documents of their own. If we maintained a single set of use
+// counters and all the sub-documents use a particular feature, then
+// telemetry would indicate that we would be breaking N documents if
+// that feature were removed. Whereas with a document/top-level
+// page split, we can see that N documents would be affected, but
+// only a single web page would be affected.
+//
+// The difference between the values of these two histograms and the
+// related use counters below tell us how many pages did *not* use
+// the feature in question. For instance, if we see that a given
+// session has destroyed 30 content documents, but a particular use
+// counter shows only a count of 5, we can infer that the use
+// counter was *not* used in 25 of those 30 documents.
+//
+// We do things this way, rather than accumulating a boolean flag
+// for each use counter, to avoid sending histograms for features
+// that don't get widely used. Doing things in this fashion means
+// smaller telemetry payloads and faster processing on the server
+// side.
+void Document::ReportDocumentUseCounters() {
+ if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
+ return;
+ }
+
+ mReportedDocumentUseCounters = true;
+
+ // Note that a document is being destroyed. See the comment above for how
+ // use counter histograms are interpreted relative to this measurement.
+ // TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED is recorded in
+ // WindowGlobalParent::FinishAccumulatingPageUseCounters.
+ Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
+
+ // Ask all of our resource documents to report their own document use
+ // counters.
+ EnumerateExternalResources([](Document& aDoc) {
+ aDoc.ReportDocumentUseCounters();
+ return CallState::Continue;
+ });
+
+ // Copy StyleUseCounters into our document use counters.
+ SetCssUseCounterBits();
+
+ Maybe<nsCString> urlForLogging;
+ const bool dumpCounters = StaticPrefs::dom_use_counters_dump_document();
+ if (dumpCounters) {
+ urlForLogging.emplace(
+ nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()));
+ }
+
+ // Report our per-document use counters.
+ for (int32_t c = 0; c < eUseCounter_Count; ++c) {
+ auto uc = static_cast<UseCounter>(c);
+ if (!mUseCounters[uc]) {
+ continue;
+ }
+
+ auto id = static_cast<Telemetry::HistogramID>(
+ Telemetry::HistogramFirstUseCounter + uc * 2);
+ if (dumpCounters) {
+ printf_stderr("USE_COUNTER_DOCUMENT: %s - %s\n",
+ Telemetry::GetHistogramName(id), urlForLogging->get());
+ }
+ Telemetry::Accumulate(id, 1);
+ }
+}
+
+void Document::SendPageUseCounters() {
+ if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
+ return;
+ }
+
+ // Ask all of our resource documents to send their own document use
+ // counters to the parent process to be counted as page use counters.
+ EnumerateExternalResources([](Document& aDoc) {
+ aDoc.SendPageUseCounters();
+ return CallState::Continue;
+ });
+
+ // Send our use counters to the parent process to accumulate them towards the
+ // page use counters for the top-level document.
+ //
+ // We take our own document use counters (those in mUseCounters) and any child
+ // document use counters (those in mChildDocumentUseCounters) that have been
+ // explicitly propagated up to us, which includes resource documents, static
+ // clones, and SVG images.
+ RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
+ if (!wgc) {
+ MOZ_ASSERT_UNREACHABLE(
+ "SendPageUseCounters should be called while we still have access "
+ "to our WindowContext");
+ MOZ_LOG(gUseCountersLog, LogLevel::Debug,
+ (" > too late to send page use counters"));
+ return;
+ }
+
+ MOZ_LOG(gUseCountersLog, LogLevel::Debug,
+ ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
+ wgc->WindowContext()->Id(),
+ nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
+
+ // Copy StyleUseCounters into our document use counters.
+ SetCssUseCounterBits();
+
+ UseCounters counters = mUseCounters | mChildDocumentUseCounters;
+ wgc->SendAccumulatePageUseCounters(counters);
+}
+
+bool Document::RecomputeResistFingerprinting() {
+ const bool previous = mShouldResistFingerprinting;
+
+ RefPtr<BrowsingContext> opener =
+ GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
+ // If we have a parent or opener document, defer to it only when we have a
+ // null principal (e.g. a sandboxed iframe or a data: uri) or when the
+ // document's principal matches. This means we will defer about:blank,
+ // about:srcdoc, blob and same-origin iframes/popups to the parent/opener,
+ // but not cross-origin ones. Cross-origin iframes/popups may inherit a
+ // CookieJarSettings.mShouldRFP = false bit however, which will be respected.
+ auto shouldInheritFrom = [this](Document* aDoc) {
+ return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
+ this->NodePrincipal()->GetIsNullPrincipal());
+ };
+
+ if (shouldInheritFrom(mParentDocument)) {
+ MOZ_LOG(
+ nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside RecomputeResistFingerprinting with URI %s and deferring "
+ "to parent document %s",
+ GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
+ mParentDocument->GetDocumentURI()->GetSpecOrDefault().get()));
+ mShouldResistFingerprinting = mParentDocument->ShouldResistFingerprinting(
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ } else if (opener && shouldInheritFrom(opener->GetDocument())) {
+ MOZ_LOG(
+ nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside RecomputeResistFingerprinting with URI %s and deferring to "
+ "opener document %s",
+ GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
+ opener->GetDocument()->GetDocumentURI()->GetSpecOrDefault().get()));
+ mShouldResistFingerprinting =
+ opener->GetDocument()->ShouldResistFingerprinting(
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ } else {
+ bool chromeDoc = nsContentUtils::IsChromeDoc(this);
+ MOZ_LOG(
+ nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Inside RecomputeResistFingerprinting with URI %s ChromeDoc:%x",
+ GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
+ chromeDoc));
+ mShouldResistFingerprinting =
+ !chromeDoc && nsContentUtils::ShouldResistFingerprinting(
+ mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
+ }
+
+ MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
+ ("Finished RecomputeResistFingerprinting with result %x",
+ mShouldResistFingerprinting));
+
+ return previous != mShouldResistFingerprinting;
+}
+
+bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const {
+ return mShouldResistFingerprinting && nsRFPService::IsRFPEnabledFor(aTarget);
+}
+
+WindowContext* Document::GetWindowContextForPageUseCounters() const {
+ if (mDisplayDocument) {
+ // If we are a resource document, then go through it to find the
+ // top-level document.
+ return mDisplayDocument->GetWindowContextForPageUseCounters();
+ }
+
+ if (mOriginalDocument) {
+ // For static clones (print preview documents), contribute page use counters
+ // towards the original document.
+ return mOriginalDocument->GetWindowContextForPageUseCounters();
+ }
+
+ WindowContext* wc = GetTopLevelWindowContext();
+ if (!wc || !wc->GetBrowsingContext()->IsContent()) {
+ return nullptr;
+ }
+
+ return wc;
+}
+
+void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
+ if (mIntersectionObservers.IsEmpty()) {
+ return;
+ }
+
+ DOMHighResTimeStamp time = 0;
+ if (nsPIDOMWindowInner* win = GetInnerWindow()) {
+ if (Performance* perf = win->GetPerformance()) {
+ time = perf->TimeStampToDOMHighResForRendering(aNowTime);
+ }
+ }
+
+ const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
+ mIntersectionObservers);
+ for (const auto& observer : observers) {
+ if (observer) {
+ observer->Update(*this, time);
+ }
+ }
+}
+
+void Document::ScheduleIntersectionObserverNotification() {
+ if (mIntersectionObservers.IsEmpty()) {
+ return;
+ }
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> notification =
+ NewRunnableMethod("Document::NotifyIntersectionObservers", this,
+ &Document::NotifyIntersectionObservers);
+ Dispatch(TaskCategory::Other, notification.forget());
+}
+
+void Document::NotifyIntersectionObservers() {
+ const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
+ mIntersectionObservers);
+ for (const auto& observer : observers) {
+ if (observer) {
+ // MOZ_KnownLive because the 'observers' array guarantees to keep it
+ // alive.
+ MOZ_KnownLive(observer)->Notify();
+ }
+ }
+}
+
+DOMIntersectionObserver& Document::EnsureLazyLoadImageObserver() {
+ if (!mLazyLoadImageObserver) {
+ mLazyLoadImageObserver =
+ DOMIntersectionObserver::CreateLazyLoadObserver(*this);
+ }
+ return *mLazyLoadImageObserver;
+}
+
+DOMIntersectionObserver& Document::EnsureContentVisibilityObserver() {
+ if (!mContentVisibilityObserver) {
+ mContentVisibilityObserver =
+ DOMIntersectionObserver::CreateContentVisibilityObserver(*this);
+ }
+ return *mContentVisibilityObserver;
+}
+
+void Document::ObserveForContentVisibility(Element& aElement) {
+ EnsureContentVisibilityObserver().Observe(aElement);
+}
+
+void Document::UnobserveForContentVisibility(Element& aElement) {
+ if (mContentVisibilityObserver) {
+ mContentVisibilityObserver->Unobserve(aElement);
+ }
+}
+
+ResizeObserver& Document::EnsureLastRememberedSizeObserver() {
+ if (!mLastRememberedSizeObserver) {
+ mLastRememberedSizeObserver =
+ ResizeObserver::CreateLastRememberedSizeObserver(*this);
+ }
+ return *mLastRememberedSizeObserver;
+}
+
+void Document::ObserveForLastRememberedSize(Element& aElement) {
+ if (NS_WARN_IF(!IsActive())) {
+ return;
+ }
+ // Options are initialized with ResizeObserverBoxOptions::Content_box by
+ // default, which is what we want.
+ static ResizeObserverOptions options;
+ EnsureLastRememberedSizeObserver().Observe(aElement, options);
+}
+
+void Document::UnobserveForLastRememberedSize(Element& aElement) {
+ if (mLastRememberedSizeObserver) {
+ mLastRememberedSizeObserver->Unobserve(aElement);
+ }
+}
+
+void Document::NotifyLayerManagerRecreated() {
+ NotifyActivityChanged();
+ EnumerateSubDocuments([](Document& aSubDoc) {
+ aSubDoc.NotifyLayerManagerRecreated();
+ return CallState::Continue;
+ });
+}
+
+XPathEvaluator* Document::XPathEvaluator() {
+ if (!mXPathEvaluator) {
+ mXPathEvaluator.reset(new dom::XPathEvaluator(this));
+ }
+ return mXPathEvaluator.get();
+}
+
+already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
+ return mCachedEncoder.forget();
+}
+
+void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
+ mCachedEncoder = aEncoder;
+}
+
+nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
+
+nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
+
+void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
+ mStateObjectContainer = scContainer;
+ mCachedStateObject = JS::UndefinedValue();
+ mCachedStateObjectValid = false;
+}
+
+bool Document::ComputeDocumentLWTheme() const {
+ if (!NodePrincipal()->IsSystemPrincipal()) {
+ return false;
+ }
+
+ Element* element = GetRootElement();
+ return element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
+ ELEMENT_NODE);
+ MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
+
+ nsCOMPtr<Element> element;
+ DebugOnly<nsresult> rv =
+ NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
+ mozilla::dom::NOT_FROM_PARSER);
+
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
+ return element.forget();
+}
+
+void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
+ BrowsingContext* aContext) {
+ aContext->PreOrderWalk([&](BrowsingContext* aBC) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
+ if (RefPtr<Document> doc = win->GetExtantDoc()) {
+ SuppressDocument(doc);
+ mDocuments.AppendElement(doc);
+ }
+ }
+ });
+}
+
+void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
+ BrowsingContextGroup* aGroup) {
+ for (const auto& bc : aGroup->Toplevels()) {
+ SuppressBrowsingContext(bc);
+ }
+}
+
+nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
+ SyncOperationBehavior aSyncBehavior)
+ : mSyncBehavior(aSyncBehavior) {
+ mMicroTaskLevel = 0;
+ if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
+ mMicroTaskLevel = ccjs->MicroTaskLevel();
+ ccjs->SetMicroTaskLevel(0);
+ }
+ if (aDoc) {
+ mBrowsingContext = aDoc->GetBrowsingContext();
+ if (InputTaskManager::CanSuspendInputEvent()) {
+ if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
+ SuppressBrowsingContextGroup(bcg);
+ }
+ } else if (mBrowsingContext) {
+ SuppressBrowsingContext(mBrowsingContext->Top());
+ }
+ if (mBrowsingContext &&
+ mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
+ InputTaskManager::CanSuspendInputEvent()) {
+ mBrowsingContext->Group()->IncInputEventSuspensionLevel();
+ }
+ }
+}
+
+void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
+ if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
+ win->TimeoutManager().BeginSyncOperation();
+ }
+ aDoc->SetIsInSyncOperation(true);
+}
+
+void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
+ if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
+ win->TimeoutManager().EndSyncOperation();
+ }
+ aDoc->SetIsInSyncOperation(false);
+}
+
+nsAutoSyncOperation::~nsAutoSyncOperation() {
+ UnsuppressDocuments();
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ if (ccjs) {
+ ccjs->SetMicroTaskLevel(mMicroTaskLevel);
+ }
+ if (mBrowsingContext &&
+ mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
+ InputTaskManager::CanSuspendInputEvent()) {
+ mBrowsingContext->Group()->DecInputEventSuspensionLevel();
+ }
+}
+
+void Document::SetIsInSyncOperation(bool aSync) {
+ if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
+ ccjs->UpdateMicroTaskSuppressionGeneration();
+ }
+
+ if (aSync) {
+ ++mInSyncOperationCount;
+ } else {
+ --mInSyncOperationCount;
+ }
+}
+
+gfxUserFontSet* Document::GetUserFontSet() {
+ if (!mFontFaceSet) {
+ return nullptr;
+ }
+
+ return mFontFaceSet->GetImpl();
+}
+
+void Document::FlushUserFontSet() {
+ if (!mFontFaceSetDirty) {
+ return;
+ }
+
+ mFontFaceSetDirty = false;
+
+ if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
+ nsTArray<nsFontFaceRuleContainer> rules;
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (presShell) {
+ MOZ_ASSERT(mStyleSetFilled);
+ mStyleSet->AppendFontFaceRules(rules);
+ }
+
+ if (!mFontFaceSet && !rules.IsEmpty()) {
+ mFontFaceSet = FontFaceSet::CreateForDocument(this);
+ }
+
+ bool changed = false;
+ if (mFontFaceSet) {
+ changed = mFontFaceSet->UpdateRules(rules);
+ }
+
+ // We need to enqueue a style change reflow (for later) to
+ // reflect that we're modifying @font-face rules. (However,
+ // without a reflow, nothing will happen to start any downloads
+ // that are needed.)
+ if (changed && presShell) {
+ if (nsPresContext* presContext = presShell->GetPresContext()) {
+ presContext->UserFontSetUpdated();
+ }
+ }
+ }
+}
+
+void Document::MarkUserFontSetDirty() {
+ if (mFontFaceSetDirty) {
+ return;
+ }
+ mFontFaceSetDirty = true;
+ if (PresShell* presShell = GetPresShell()) {
+ presShell->EnsureStyleFlush();
+ }
+}
+
+FontFaceSet* Document::Fonts() {
+ if (!mFontFaceSet) {
+ mFontFaceSet = FontFaceSet::CreateForDocument(this);
+ FlushUserFontSet();
+ }
+ return mFontFaceSet;
+}
+
+void Document::ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp) {
+ MOZ_ASSERT(!aTimeStamp.IsNull());
+
+ if (!mLastScrollLinkedEffectDetectionTime.IsNull() &&
+ mLastScrollLinkedEffectDetectionTime >= aTimeStamp) {
+ return;
+ }
+
+ if (mLastScrollLinkedEffectDetectionTime.IsNull()) {
+ // Report to console just once.
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
+ nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
+ }
+
+ mLastScrollLinkedEffectDetectionTime = aTimeStamp;
+}
+
+bool Document::HasScrollLinkedEffect() const {
+ if (nsPresContext* pc = GetPresContext()) {
+ return mLastScrollLinkedEffectDetectionTime ==
+ pc->RefreshDriver()->MostRecentRefresh();
+ }
+
+ return false;
+}
+
+void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
+ if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
+ // Setting has user interction on a discarded browsing context has
+ // no effect.
+ Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
+ }
+}
+
+bool Document::GetSHEntryHasUserInteraction() {
+ if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
+ return topWc->GetSHEntryHasUserInteraction();
+ }
+ return false;
+}
+
+void Document::SetUserHasInteracted() {
+ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
+ ("Document %p has been interacted by user.", this));
+
+ // We maybe need to update the user-interaction permission.
+ MaybeStoreUserInteractionAsPermission();
+
+ // For purposes of reducing irrelevant session history entries on
+ // the back button, we annotate entries with whether they had user
+ // interaction. This is gated on its own flag on the WindowContext
+ // (instead of mUserHasInteracted) to account for the fact that multiple
+ // top-level SH entries can be associated with the same document.
+ // Thus, whenever we create a new SH entry for this document,
+ // this flag is reset.
+ if (!GetSHEntryHasUserInteraction()) {
+ nsIDocShell* docShell = GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsISHEntry> currentEntry;
+ bool oshe;
+ nsresult rv =
+ docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
+ if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
+ currentEntry->SetHasUserInteraction(true);
+ }
+ }
+ SetSHEntryHasUserInteraction(true);
+ }
+
+ if (mUserHasInteracted) {
+ return;
+ }
+
+ mUserHasInteracted = true;
+
+ if (mChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ loadInfo->SetDocumentHasUserInteracted(true);
+ }
+ // Tell the parent process about user interaction
+ if (auto* wgc = GetWindowGlobalChild()) {
+ wgc->SendUpdateDocumentHasUserInteracted(true);
+ }
+
+ MaybeAllowStorageForOpenerAfterUserInteraction();
+}
+
+BrowsingContext* Document::GetBrowsingContext() const {
+ nsCOMPtr<nsIDocShell> docshell(mDocumentContainer);
+ return docshell ? docshell->GetBrowsingContext() : nullptr;
+}
+
+void Document::NotifyUserGestureActivation() {
+ if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
+ bc->PreOrderWalk([&](BrowsingContext* aBC) {
+ WindowContext* windowContext = aBC->GetCurrentWindowContext();
+ if (!windowContext) {
+ return;
+ }
+
+ nsIDocShell* docShell = aBC->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ Document* document = docShell->GetDocument();
+ if (!document) {
+ return;
+ }
+
+ // XXXedgar we probably could just check `IsInProcess()` after fission
+ // enable.
+ if (NodePrincipal()->Equals(document->NodePrincipal())) {
+ windowContext->NotifyUserGestureActivation();
+ }
+ });
+
+ for (bc = bc->GetParent(); bc; bc = bc->GetParent()) {
+ if (WindowContext* windowContext = bc->GetCurrentWindowContext()) {
+ windowContext->NotifyUserGestureActivation();
+ }
+ }
+ }
+}
+
+bool Document::HasBeenUserGestureActivated() {
+ RefPtr<WindowContext> wc = GetWindowContext();
+ return wc && wc->HasBeenUserGestureActivated();
+}
+
+DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
+ if (RefPtr<WindowContext> wc = GetWindowContext()) {
+ if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) {
+ if (Performance* perf = innerWindow->GetPerformance()) {
+ return perf->GetDOMTiming()->TimeStampToDOMHighRes(
+ wc->GetUserGestureStart());
+ }
+ }
+ }
+
+ NS_WARNING(
+ "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
+ return 0;
+}
+
+void Document::ClearUserGestureActivation() {
+ if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
+ bc = bc->Top();
+ bc->PreOrderWalk([&](BrowsingContext* aBC) {
+ if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
+ windowContext->NotifyResetUserGestureActivation();
+ }
+ });
+ }
+}
+
+bool Document::HasValidTransientUserGestureActivation() const {
+ RefPtr<WindowContext> wc = GetWindowContext();
+ return wc && wc->HasValidTransientUserGestureActivation();
+}
+
+bool Document::ConsumeTransientUserGestureActivation() {
+ RefPtr<WindowContext> wc = GetWindowContext();
+ return wc && wc->ConsumeTransientUserGestureActivation();
+}
+
+void Document::SetDocTreeHadMedia() {
+ RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
+ if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
+ }
+}
+
+void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
+ if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
+ return;
+ }
+
+ // This will probably change for project fission, but currently this document
+ // and the opener are on the same process. In the future, we should make this
+ // part async.
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (NS_WARN_IF(!inner)) {
+ return;
+ }
+
+ uint32_t cookieBehavior = CookieJarSettings()->GetCookieBehavior();
+ if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
+ cookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
+ // We care about first-party tracking resources only.
+ if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
+ return;
+ }
+ } else {
+ MOZ_ASSERT(net::CookieJarSettings::IsRejectThirdPartyWithExceptions(
+ cookieBehavior));
+ }
+
+ auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
+ if (NS_WARN_IF(!outer)) {
+ return;
+ }
+
+ RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
+ if (!openerBC) {
+ // No opener.
+ return;
+ }
+
+ // We want to ensure the following check works for both fission mode and
+ // non-fission mode:
+ // "If the opener is not a 3rd party and if this window is not a 3rd party
+ // with respect to the opener, we should not continue."
+ //
+ // In non-fission mode, the opener and the opened window are in the same
+ // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
+ // In fission mode, if this window is not a 3rd party with respect to the
+ // opener, they must be in the same process, so we can still use
+ // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
+ // party.
+ if (openerBC->IsInProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
+ if (NS_WARN_IF(!outerOpener)) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> openerInner =
+ outerOpener->GetCurrentInnerWindow();
+ if (NS_WARN_IF(!openerInner)) {
+ return;
+ }
+
+ RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
+ if (NS_WARN_IF(!openerDocument)) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
+ if (NS_WARN_IF(!openerURI)) {
+ return;
+ }
+
+ // If the opener is not a 3rd party and if this window is not
+ // a 3rd party with respect to the opener, we should not continue.
+ if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
+ !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
+ return;
+ }
+ }
+
+ // We don't care when the asynchronous work finishes here.
+ Unused << StorageAccessAPIHelper::AllowAccessFor(
+ NodePrincipal(), openerBC,
+ ContentBlockingNotifier::eOpenerAfterUserInteraction);
+}
+
+namespace {
+
+// Documents can stay alive for days. We don't want to update the permission
+// value at any user-interaction, and, using a timer triggered any X seconds
+// should be good enough. 'X' is taken from
+// privacy.userInteraction.document.interval pref.
+// We also want to store the user-interaction before shutting down, and, for
+// this reason, this class implements nsIAsyncShutdownBlocker interface.
+class UserInteractionTimer final : public Runnable,
+ public nsITimerCallback,
+ public nsIAsyncShutdownBlocker {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit UserInteractionTimer(Document* aDocument)
+ : Runnable("UserInteractionTimer"),
+ mPrincipal(aDocument->NodePrincipal()),
+ mDocument(do_GetWeakReference(aDocument)) {
+ static int32_t userInteractionTimerId = 0;
+ // Blocker names must be unique. Let's create it now because when needed,
+ // the document could be already gone.
+ mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
+ ++userInteractionTimerId, aDocument);
+ }
+
+ // Runnable interface
+
+ NS_IMETHOD
+ Run() override {
+ uint32_t interval =
+ StaticPrefs::privacy_userInteraction_document_interval();
+ if (!interval) {
+ return NS_OK;
+ }
+
+ RefPtr<UserInteractionTimer> self = this;
+ auto raii =
+ MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
+
+ nsresult rv = NS_NewTimerWithCallback(
+ getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
+ NS_ENSURE_TRUE(!!phase, NS_OK);
+
+ rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
+ __LINE__, u"UserInteractionTimer shutdown"_ns);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ raii.release();
+ return NS_OK;
+ }
+
+ // nsITimerCallback interface
+
+ NS_IMETHOD
+ Notify(nsITimer* aTimer) override {
+ StoreUserInteraction();
+ return NS_OK;
+ }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ using nsINamed::GetName;
+#endif
+
+ // nsIAsyncShutdownBlocker interface
+
+ NS_IMETHOD
+ GetName(nsAString& aName) override {
+ aName = mBlockerName;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ BlockShutdown(nsIAsyncShutdownClient* aClient) override {
+ CancelTimerAndStoreUserInteraction();
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetState(nsIPropertyBag**) override { return NS_OK; }
+
+ private:
+ ~UserInteractionTimer() = default;
+
+ void StoreUserInteraction() {
+ // Remove the shutting down blocker
+ nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
+ if (phase) {
+ phase->RemoveBlocker(this);
+ }
+
+ // If the document is not gone, let's reset its timer flag.
+ nsCOMPtr<Document> document = do_QueryReferent(mDocument);
+ if (document) {
+ ContentBlockingUserInteraction::Observe(mPrincipal);
+ document->ResetUserInteractionTimer();
+ }
+ }
+
+ void CancelTimerAndStoreUserInteraction() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ StoreUserInteraction();
+ }
+
+ static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
+ nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
+ NS_ENSURE_TRUE(!!svc, nullptr);
+
+ nsCOMPtr<nsIAsyncShutdownClient> phase;
+ nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return phase.forget();
+ }
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsWeakPtr mDocument;
+
+ nsCOMPtr<nsITimer> mTimer;
+
+ nsString mBlockerName;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
+ nsIAsyncShutdownBlocker)
+
+} // namespace
+
+void Document::MaybeStoreUserInteractionAsPermission() {
+ // We care about user-interaction stored only for top-level documents
+ // and documents with access to the Storage Access API
+ if (!IsTopLevelContentDocument()) {
+ bool hasSA;
+ nsresult rv = HasStorageAccessSync(hasSA);
+ if (NS_FAILED(rv) || !hasSA) {
+ return;
+ }
+ }
+
+ if (!mUserHasInteracted) {
+ // First interaction, let's store this info now.
+ ContentBlockingUserInteraction::Observe(NodePrincipal());
+ return;
+ }
+
+ if (mHasUserInteractionTimerScheduled) {
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
+ nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
+ EventQueuePriority::Idle);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // This value will be reset by the timer.
+ mHasUserInteractionTimerScheduled = true;
+}
+
+void Document::ResetUserInteractionTimer() {
+ mHasUserInteractionTimerScheduled = false;
+}
+
+bool Document::IsExtensionPage() const {
+ return BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
+}
+
+void Document::AddResizeObserver(ResizeObserver& aObserver) {
+ if (!mResizeObserverController) {
+ mResizeObserverController = MakeUnique<ResizeObserverController>(this);
+ }
+ mResizeObserverController->AddResizeObserver(aObserver);
+}
+
+void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
+ MOZ_DIAGNOSTIC_ASSERT(mResizeObserverController, "No controller?");
+ if (MOZ_UNLIKELY(!mResizeObserverController)) {
+ return;
+ }
+ mResizeObserverController->RemoveResizeObserver(aObserver);
+}
+
+PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
+ if (!mPermissionDelegateHandler) {
+ mPermissionDelegateHandler =
+ mozilla::MakeAndAddRef<PermissionDelegateHandler>(this);
+ }
+
+ if (!mPermissionDelegateHandler->Initialize()) {
+ mPermissionDelegateHandler = nullptr;
+ }
+
+ return mPermissionDelegateHandler;
+}
+
+void Document::ScheduleResizeObserversNotification() const {
+ if (!mResizeObserverController) {
+ return;
+ }
+
+ mResizeObserverController->ScheduleNotification();
+}
+
+void Document::ClearStaleServoData() {
+ DocumentStyleRootIterator iter(this);
+ while (Element* root = iter.GetNextStyleRoot()) {
+ RestyleManager::ClearServoDataFromSubtree(root);
+ }
+}
+
+Selection* Document::GetSelection(ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
+ if (!window) {
+ return nullptr;
+ }
+
+ if (!window->IsCurrentInnerWindow()) {
+ return nullptr;
+ }
+
+ return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
+}
+
+void Document::MakeBrowsingContextNonSynthetic() {
+ if (nsContentUtils::ShouldHideObjectOrEmbedImageDocument()) {
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ if (bc->GetSyntheticDocumentContainer()) {
+ Unused << bc->SetSyntheticDocumentContainer(false);
+ }
+ }
+ }
+}
+
+nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
+ // Step 1: check if cookie permissions are available or denied to this
+ // document's principal
+ nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
+ if (!inner) {
+ aHasStorageAccess = false;
+ return NS_OK;
+ }
+ Maybe<bool> resultBecauseCookiesApproved =
+ StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
+ CookieJarSettings(), NodePrincipal());
+ if (resultBecauseCookiesApproved.isSome()) {
+ if (resultBecauseCookiesApproved.value()) {
+ aHasStorageAccess = true;
+ return NS_OK;
+ } else {
+ aHasStorageAccess = false;
+ return NS_OK;
+ }
+ }
+
+ // Step 2: Check if the browser settings determine whether or not this
+ // document has access to its unpartitioned cookies.
+ bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
+ bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
+ bool isOnThirdPartySkipList = false;
+ if (mChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
+ nsILoadInfo::StoragePermissionAllowListed;
+ }
+ bool isThirdPartyTracker =
+ nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
+ Maybe<bool> resultBecauseBrowserSettings =
+ StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
+ CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
+ isOnThirdPartySkipList, isThirdPartyTracker);
+ if (resultBecauseBrowserSettings.isSome()) {
+ if (resultBecauseBrowserSettings.value()) {
+ aHasStorageAccess = true;
+ return NS_OK;
+ } else {
+ aHasStorageAccess = false;
+ return NS_OK;
+ }
+ }
+
+ // Step 3: Check if the location of this call (embedded, top level, same-site)
+ // determines if cookies are permitted or not.
+ Maybe<bool> resultBecauseCallContext =
+ StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
+ false);
+ if (resultBecauseCallContext.isSome()) {
+ if (resultBecauseCallContext.value()) {
+ aHasStorageAccess = true;
+ return NS_OK;
+ } else {
+ aHasStorageAccess = false;
+ return NS_OK;
+ }
+ }
+
+ // Step 4: Check if the permissions for this document determine if if has
+ // access or is denied cookies.
+ Maybe<bool> resultBecausePreviousPermission =
+ StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
+ this, false);
+ if (resultBecausePreviousPermission.isSome()) {
+ if (resultBecausePreviousPermission.value()) {
+ aHasStorageAccess = true;
+ return NS_OK;
+ } else {
+ aHasStorageAccess = false;
+ return NS_OK;
+ }
+ }
+ // If you get here, we default to not giving you permission.
+ aHasStorageAccess = false;
+ return NS_OK;
+}
+
+already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
+ mozilla::ErrorResult& aRv) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise =
+ Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ bool hasStorageAccess;
+ nsresult rv = HasStorageAccessSync(hasStorageAccess);
+ if (NS_FAILED(rv)) {
+ promise->MaybeRejectWithUndefined();
+ } else {
+ promise->MaybeResolve(hasStorageAccess);
+ }
+
+ return promise.forget();
+}
+
+RefPtr<Document::GetContentBlockingEventsPromise>
+Document::GetContentBlockingEvents() {
+ RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
+ if (!wgc) {
+ return nullptr;
+ }
+
+ return wgc->SendGetContentBlockingEvents()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [](const WindowGlobalChild::GetContentBlockingEventsPromise::
+ ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ return Document::GetContentBlockingEventsPromise::CreateAndResolve(
+ aValue.ResolveValue(), __func__);
+ }
+
+ return Document::GetContentBlockingEventsPromise::CreateAndReject(
+ false, __func__);
+ });
+}
+
+StorageAccessAPIHelper::PerformPermissionGrant
+Document::CreatePermissionGrantPromise(
+ nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal,
+ bool aHasUserInteraction, const Maybe<nsCString>& aTopLevelBaseDomain) {
+ MOZ_ASSERT(aInnerWindow);
+ MOZ_ASSERT(aPrincipal);
+ RefPtr<Document> self(this);
+ RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
+ RefPtr<nsIPrincipal> principal(aPrincipal);
+
+ return [inner, self, principal, aHasUserInteraction, aTopLevelBaseDomain]() {
+ // Create the user prompt
+ RefPtr<StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::Private>
+ p = new StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
+ Private(__func__);
+ RefPtr<StorageAccessPermissionRequest> sapr =
+ StorageAccessPermissionRequest::Create(
+ inner, principal, aTopLevelBaseDomain,
+ // Allow
+ [p] {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
+ p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
+ },
+ // Block
+ [p] {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
+ p->Reject(false, __func__);
+ });
+
+ using PromptResult = ContentPermissionRequestBase::PromptResult;
+ PromptResult pr = sapr->CheckPromptPrefs();
+
+ if (pr == PromptResult::Pending) {
+ // We're about to show a prompt, record the request attempt
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
+ }
+
+ // Try to auto-grant the storage access so the user doesn't see the prompt.
+ self->AutomaticStorageAccessPermissionCanBeGranted(aHasUserInteraction)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // If the autogrant check didn't fail, call this function
+ [p, pr, sapr, inner](
+ const Document::AutomaticStorageAccessPermissionGrantPromise::
+ ResolveOrRejectValue& aValue) -> void {
+ // Make a copy because we can't modified copy-captured lambda
+ // variables.
+ PromptResult pr2 = pr;
+
+ // If the user didn't already click "allow" and we can autogrant,
+ // do that!
+ bool storageAccessCanBeGrantedAutomatically =
+ aValue.IsResolve() && aValue.ResolveValue();
+ bool autoGrant = false;
+ if (pr2 == PromptResult::Pending &&
+ storageAccessCanBeGrantedAutomatically) {
+ pr2 = PromptResult::Granted;
+ autoGrant = true;
+
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_API_UI::
+ AllowAutomatically);
+ }
+
+ // If we can complete the permission request, do so.
+ if (pr2 != PromptResult::Pending) {
+ MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
+ pr2 == PromptResult::Denied);
+ if (pr2 == PromptResult::Granted) {
+ StorageAccessAPIHelper::StorageAccessPromptChoices choice =
+ StorageAccessAPIHelper::eAllow;
+ if (autoGrant) {
+ choice = StorageAccessAPIHelper::eAllowAutoGrant;
+ }
+ if (!autoGrant) {
+ p->Resolve(choice, __func__);
+ } else {
+ sapr->MaybeDelayAutomaticGrants()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [p, choice] { p->Resolve(choice, __func__); },
+ [p] { p->Reject(false, __func__); });
+ }
+ return;
+ }
+ p->Reject(false, __func__);
+ return;
+ }
+
+ // If we get here, the auto-decision failed and we need to
+ // wait for the user prompt to complete.
+ sapr->RequestDelayedTask(
+ inner->EventTargetFor(TaskCategory::Other),
+ ContentPermissionRequestBase::DelayedTaskType::Request);
+ });
+
+ return p;
+ };
+}
+
+already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
+ mozilla::ErrorResult& aRv) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 0: Check that we have user activation before proceeding to prevent
+ // rapid calls to the API to leak information.
+ if (!HasValidTransientUserGestureActivation()) {
+ // Report an error to the console for this case
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ nsLiteralCString("requestStorageAccess"),
+ this, nsContentUtils::eDOM_PROPERTIES,
+ "RequestStorageAccessUserGesture");
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ // Get a pointer to the inner window- We need this for convenience sake
+ RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
+ if (!inner) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ // Step 1: Check if the principal calling this has a permission that lets
+ // them use cookies or forbids them from using cookies.
+ // This is outside of the spec of the StorageAccess API, but makes the return
+ // values to have proper semantics.
+ Maybe<bool> resultBecauseCookiesApproved =
+ StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
+ CookieJarSettings(), NodePrincipal());
+ if (resultBecauseCookiesApproved.isSome()) {
+ if (resultBecauseCookiesApproved.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ } else {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ }
+
+ // Step 2: Check if the browser settings always allow or deny cookies.
+ // We should always return a resolved promise if the cookieBehavior is ACCEPT.
+ // This is outside of the spec of the StorageAccess API, but makes the return
+ // values to have proper semantics.
+ bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
+ bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
+ bool isOnThirdPartySkipList = false;
+ if (mChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
+ nsILoadInfo::StoragePermissionAllowListed;
+ }
+ bool isThirdPartyTracker =
+ nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
+ Maybe<bool> resultBecauseBrowserSettings =
+ StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
+ CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
+ isOnThirdPartySkipList, isThirdPartyTracker);
+ if (resultBecauseBrowserSettings.isSome()) {
+ if (resultBecauseBrowserSettings.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ } else {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ }
+
+ // Step 3: Check if the Document calling requestStorageAccess has anything to
+ // gain from storage access. It should be embedded, non-null, etc.
+ Maybe<bool> resultBecauseCallContext =
+ StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
+ true);
+ if (resultBecauseCallContext.isSome()) {
+ if (resultBecauseCallContext.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ } else {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ }
+
+ // Step 4: Check if we already allowed or denied storage access for this
+ // document's storage key.
+ Maybe<bool> resultBecausePreviousPermission =
+ StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
+ this, true);
+ if (resultBecausePreviousPermission.isSome()) {
+ if (resultBecausePreviousPermission.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ } else {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ }
+
+ // Get pointers to some objects that will be used in the async portion
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ RefPtr<nsGlobalWindowOuter> outer =
+ nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
+ if (!outer) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ RefPtr<Document> self(this);
+
+ // Consume user activation before entering the async part of this method.
+ // This prevents usage of other transient activation-gated APIs.
+ ConsumeTransientUserGestureActivation();
+
+ // Step 5. Start an async call to request storage access. This will either
+ // perform an automatic decision or notify the user, then perform some follow
+ // on work changing state to reflect the result of the API. If it resolves,
+ // the request was granted. If it rejects it was denied.
+ StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
+ this, inner, bc, NodePrincipal(), true,
+ ContentBlockingNotifier::eStorageAccessAPI, true)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self, inner, promise] {
+ inner->SaveStorageAccessPermissionGranted();
+ self->NotifyUserGestureActivation();
+ promise->MaybeResolveWithUndefined();
+ },
+ [promise] {
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ });
+
+ return promise.forget();
+}
+
+already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
+ const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
+ mozilla::ErrorResult& aRv) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 0: Check that we have user activation before proceeding to prevent
+ // rapid calls to the API to leak information.
+ if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) {
+ // Report an error to the console for this case
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ nsLiteralCString("requestStorageAccess"),
+ this, nsContentUtils::eDOM_PROPERTIES,
+ "RequestStorageAccessUserGesture");
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ // Step 1: Check if the provided URI is different-site to this Document
+ nsCOMPtr<nsIURI> thirdPartyURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ bool isThirdPartyDocument;
+ rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ bool isOnRejectForeignAllowList =
+ RejectForeignAllowList::Check(thirdPartyURI);
+ Maybe<bool> resultBecauseBrowserSettings =
+ StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
+ CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
+ false, true);
+ if (resultBecauseBrowserSettings.isSome()) {
+ if (resultBecauseBrowserSettings.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ // Step 2: Check that this Document is same-site to the top, and check that
+ // we have user activation if we require it.
+ Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
+ CheckSameSiteCallingContextDecidesStorageAccessAPI(
+ this, aRequireUserActivation);
+ if (resultBecauseCallContext.isSome()) {
+ if (resultBecauseCallContext.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ // Step 3: Get some useful variables that can be captured by the lambda for
+ // the asynchronous portion
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
+ if (!inner) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ RefPtr<nsGlobalWindowOuter> outer =
+ nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
+ if (!outer) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+ nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
+ thirdPartyURI, NodePrincipal()->OriginAttributesRef());
+ if (!principal) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ return promise.forget();
+ }
+
+ RefPtr<Document> self(this);
+ bool hasUserActivation = HasValidTransientUserGestureActivation();
+
+ // Consume user activation before entering the async part of this method.
+ // This prevents usage of other transient activation-gated APIs.
+ ConsumeTransientUserGestureActivation();
+
+ // Step 4a: Start the async part of this function. Check the cookie
+ // permission, but this can't be done in this process. We needs the cookie
+ // permission of the URL as if it were embedded on this page, so we need to
+ // make this check in the ContentParent.
+ StorageAccessAPIHelper::AsyncCheckCookiesPermittedDecidesStorageAccessAPI(
+ GetBrowsingContext(), principal)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [inner, thirdPartyURI, bc, principal, hasUserActivation, self,
+ promise](Maybe<bool> cookieResult) {
+ // Handle the result of the cookie permission check that took place
+ // in the ContentParent.
+ if (cookieResult.isSome()) {
+ if (cookieResult.value()) {
+ return MozPromise<int, bool, true>::CreateAndResolve(true,
+ __func__);
+ }
+ return MozPromise<int, bool, true>::CreateAndReject(false,
+ __func__);
+ }
+
+ // Step 4b: Check for the existing storage access permission
+ nsAutoCString type;
+ bool ok =
+ AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
+ if (!ok) {
+ return MozPromise<int, bool, true>::CreateAndReject(false,
+ __func__);
+ }
+ if (AntiTrackingUtils::CheckStoragePermission(
+ self->NodePrincipal(), type,
+ nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
+ return MozPromise<int, bool, true>::CreateAndResolve(true,
+ __func__);
+ }
+
+ // Step 4c: Try to request storage access, either automatically or
+ // with a user-prompt. This is the part that is async in the
+ // typical requestStorageAccess function.
+ return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
+ self, inner, bc, principal, hasUserActivation,
+ ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI,
+ true);
+ },
+ // If the IPC rejects, we should reject our promise here which will
+ // cause a rejection of the promise we already returned
+ [promise]() {
+ return MozPromise<int, bool, true>::CreateAndReject(false,
+ __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // If the previous handlers resolved, we should reinstate user
+ // activation and resolve the promise we returned in Step 5.
+ [self, inner, promise] {
+ inner->SaveStorageAccessPermissionGranted();
+ self->NotifyUserGestureActivation();
+ promise->MaybeResolveWithUndefined();
+ },
+ // If the previous handler rejected, we should reject the promise
+ // returned by this function.
+ [promise] {
+ promise->MaybeRejectWithNotAllowedError(
+ "requestStorageAccess not allowed"_ns);
+ });
+
+ // Step 5: While the async stuff is happening, we should return the promise so
+ // our caller can continue executing.
+ return promise.forget();
+}
+
+already_AddRefed<Promise> Document::RequestStorageAccessUnderSite(
+ const nsAString& aSerializedSite, ErrorResult& aRv) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Check that we have user activation before proceeding to prevent
+ // rapid calls to the API to leak information.
+ if (!ConsumeTransientUserGestureActivation()) {
+ // Report an error to the console for this case
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::errorFlag, "requestStorageAccess"_ns, this,
+ nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture");
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check if the provided URI is different-site to this Document
+ nsCOMPtr<nsIURI> siteURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+ bool isCrossSiteArgument;
+ rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ if (!isCrossSiteArgument) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check if this party has broad cookie permissions.
+ Maybe<bool> resultBecauseCookiesApproved =
+ StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
+ CookieJarSettings(), NodePrincipal());
+ if (resultBecauseCookiesApproved.isSome()) {
+ if (resultBecauseCookiesApproved.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check if browser settings preclude this document getting storage
+ // access under the provided site
+ bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
+ Maybe<bool> resultBecauseBrowserSettings =
+ StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
+ CookieJarSettings(), true, isOnRejectForeignAllowList, false, true);
+ if (resultBecauseBrowserSettings.isSome()) {
+ if (resultBecauseBrowserSettings.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check that this Document is same-site to the top
+ Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
+ CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
+ if (resultBecauseCallContext.isSome()) {
+ if (resultBecauseCallContext.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ nsCOMPtr<nsIPrincipal> principal(NodePrincipal());
+
+ // Test if the permission this is requesting is already set
+ nsCOMPtr<nsIPrincipal> argumentPrincipal =
+ BasePrincipal::CreateContentPrincipal(
+ siteURI, NodePrincipal()->OriginAttributesRef());
+ if (!argumentPrincipal) {
+ ConsumeTransientUserGestureActivation();
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+ nsCString originNoSuffix;
+ rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ RefPtr<Document> self(this);
+ cc->SendTestStorageAccessPermission(argumentPrincipal, originNoSuffix)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise, siteURI,
+ self](const ContentChild::TestStorageAccessPermissionPromise::
+ ResolveValueType& aResult) {
+ if (aResult) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndResolve(
+ StorageAccessAPIHelper::eAllow, __func__);
+ }
+ // Get a grant for the storage access permission that will be set
+ // when this is completed in the embedding context
+ nsCString serializedSite;
+ RefPtr<nsEffectiveTLDService> etld =
+ nsEffectiveTLDService::GetInstance();
+ if (!etld) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+ }
+ nsresult rv = etld->GetSite(siteURI, serializedSite);
+ if (NS_FAILED(rv)) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+ }
+ return self->CreatePermissionGrantPromise(
+ self->GetInnerWindow(), self->NodePrincipal(), true,
+ Some(serializedSite))();
+ },
+ [](const ContentChild::TestStorageAccessPermissionPromise::
+ RejectValueType& aResult) {
+ return StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
+ CreateAndReject(false, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise, principal, siteURI](int result) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ if (!cc) {
+ // TODO(bug 1778561): Make this work in non-content processes.
+ promise->MaybeRejectWithUndefined();
+ return;
+ }
+ // Set a permission in the parent process that this document wants
+ // storage access under the argument's site, resolving our returned
+ // promise on success
+ cc->SendSetAllowStorageAccessRequestFlag(principal, siteURI)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](bool success) {
+ if (success) {
+ promise->MaybeResolveWithUndefined();
+ } else {
+ promise->MaybeRejectWithUndefined();
+ }
+ },
+ [promise](mozilla::ipc::ResponseRejectReason reason) {
+ promise->MaybeRejectWithUndefined();
+ });
+ },
+ [promise](bool result) { promise->MaybeRejectWithUndefined(); });
+
+ // Return the promise that is resolved in the async handler above
+ return promise.forget();
+}
+
+already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite(
+ const nsAString& aSerializedOrigin, ErrorResult& aRv) {
+ nsIGlobalObject* global = GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Check that the provided URI is different-site to this Document
+ nsCOMPtr<nsIURI> argumentURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+ bool isCrossSiteArgument;
+ rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ if (!isCrossSiteArgument) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check if browser settings preclude this document getting storage
+ // access under the provided site
+ bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(argumentURI);
+ Maybe<bool> resultBecauseBrowserSettings =
+ StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
+ CookieJarSettings(), true, isOnRejectForeignAllowList, false, true);
+ if (resultBecauseBrowserSettings.isSome()) {
+ if (resultBecauseBrowserSettings.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Check that this Document is same-site to the top
+ Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
+ CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
+ if (resultBecauseCallContext.isSome()) {
+ if (resultBecauseCallContext.value()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Create principal of the embedded site requesting storage access
+ nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
+ argumentURI, NodePrincipal()->OriginAttributesRef());
+ if (!principal) {
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+
+ // Get versions of these objects that we can use in lambdas for callbacks
+ RefPtr<Document> self(this);
+ RefPtr<BrowsingContext> bc = GetBrowsingContext();
+ nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
+
+ // Test that the permission was set by a call to RequestStorageAccessUnderSite
+ // from a top level document that is same-site with the argument
+ ContentChild* cc = ContentChild::GetSingleton();
+ if (!cc) {
+ // TODO(bug 1778561): Make this work in non-content processes.
+ promise->MaybeRejectWithUndefined();
+ return promise.forget();
+ }
+ cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [inner, bc, self, principal](bool success) {
+ if (success) {
+ // If that resolved with true, check that we don't already have a
+ // permission that gives cookie access.
+ return StorageAccessAPIHelper::
+ AsyncCheckCookiesPermittedDecidesStorageAccessAPI(bc,
+ principal);
+ }
+ return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ },
+ [](mozilla::ipc::ResponseRejectReason reason) {
+ return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [inner, bc, principal, self, promise](Maybe<bool> cookieResult) {
+ // Handle the result of the cookie permission check that took place
+ // in the ContentParent.
+ if (cookieResult.isSome()) {
+ if (cookieResult.value()) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndResolve(
+ StorageAccessAPIHelper::eAllowAutoGrant, __func__);
+ }
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+ }
+
+ // Check for the existing storage access permission
+ nsAutoCString type;
+ bool ok =
+ AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
+ if (!ok) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+ }
+ if (AntiTrackingUtils::CheckStoragePermission(
+ self->NodePrincipal(), type,
+ nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
+ return StorageAccessAPIHelper::
+ StorageAccessPermissionGrantPromise::CreateAndResolve(
+ StorageAccessAPIHelper::eAllowAutoGrant, __func__);
+ }
+
+ // Try to request storage access, ignoring the final checks.
+ // We ignore the final checks because this is where the "grant"
+ // either by prompt doorhanger or autogrant takes place. We already
+ // gathered an equivalent grant in requestStorageAccessUnderSite.
+ return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
+ self, inner, bc, principal, true,
+ ContentBlockingNotifier::eStorageAccessAPI, false);
+ },
+ // If the IPC rejects, we should reject our promise here which will
+ // cause a rejection of the promise we already returned
+ [promise]() {
+ return MozPromise<int, bool, true>::CreateAndReject(false,
+ __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // If the previous handlers resolved, we should reinstate user
+ // activation and resolve the promise we returned in Step 5.
+ [self, inner, promise] {
+ inner->SaveStorageAccessPermissionGranted();
+ promise->MaybeResolveWithUndefined();
+ },
+ // If the previous handler rejected, we should reject the promise
+ // returned by this function.
+ [promise] { promise->MaybeRejectWithUndefined(); });
+
+ return promise.forget();
+}
+
+RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
+Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
+ // requestStorageAccessForOrigin may not require user activation. If we don't
+ // have user activation at this point we should always show the prompt.
+ if (!hasUserActivation ||
+ !StaticPrefs::privacy_antitracking_enableWebcompat()) {
+ return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
+ false, __func__);
+ }
+ if (XRE_IsContentProcess()) {
+ // In the content process, we need to ask the parent process to compute
+ // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
+ // isn't accessible in the content process.
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+
+ return cc->SendAutomaticStorageAccessPermissionCanBeGranted(NodePrincipal())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](const ContentChild::
+ AutomaticStorageAccessPermissionCanBeGrantedPromise::
+ ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ return AutomaticStorageAccessPermissionGrantPromise::
+ CreateAndResolve(aValue.ResolveValue(), __func__);
+ }
+
+ return AutomaticStorageAccessPermissionGrantPromise::
+ CreateAndReject(false, __func__);
+ });
+ }
+
+ if (XRE_IsParentProcess()) {
+ // In the parent process, we can directly compute this.
+ return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
+ AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
+ __func__);
+ }
+
+ return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+}
+
+bool Document::AutomaticStorageAccessPermissionCanBeGranted(
+ nsIPrincipal* aPrincipal) {
+ if (!StaticPrefs::dom_storage_access_auto_grants()) {
+ return false;
+ }
+
+ if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIBrowserUsage> bu = do_ImportModule(
+ "resource:///modules/BrowserUsageTelemetry.jsm", fallible);
+ if (NS_WARN_IF(!bu)) {
+ return false;
+ }
+
+ uint32_t uniqueDomainsVisitedInPast24Hours = 0;
+ nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours(
+ &uniqueDomainsVisitedInPast24Hours);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ Maybe<size_t> maybeOriginsThirdPartyHasAccessTo =
+ AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal);
+ if (maybeOriginsThirdPartyHasAccessTo.isNothing()) {
+ return false;
+ }
+ size_t originsThirdPartyHasAccessTo =
+ maybeOriginsThirdPartyHasAccessTo.value();
+
+ // one percent of the number of top-levels origins visited in the current
+ // session (but not to exceed 24 hours), or the value of the
+ // dom.storage_access.max_concurrent_auto_grants preference, whichever is
+ // higher.
+ size_t maxConcurrentAutomaticGrants = std::max(
+ std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
+ StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
+ 0);
+
+ return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
+}
+
+void Document::RecordNavigationTiming(ReadyState aReadyState) {
+ if (!XRE_IsContentProcess()) {
+ return;
+ }
+ if (!IsTopLevelContentDocument()) {
+ return;
+ }
+ // If we dont have the timing yet (mostly because the doc is still loading),
+ // get it from docshell.
+ RefPtr<nsDOMNavigationTiming> timing = mTiming;
+ if (!timing) {
+ if (!mDocumentContainer) {
+ return;
+ }
+ timing = mDocumentContainer->GetNavigationTiming();
+ if (!timing) {
+ return;
+ }
+ }
+ TimeStamp startTime = timing->GetNavigationStartTimeStamp();
+ switch (aReadyState) {
+ case READYSTATE_LOADING:
+ if (!mDOMLoadingSet) {
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
+ startTime);
+ mDOMLoadingSet = true;
+ }
+ break;
+ case READYSTATE_INTERACTIVE:
+ if (!mDOMInteractiveSet) {
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
+ startTime);
+ mDOMInteractiveSet = true;
+ }
+ break;
+ case READYSTATE_COMPLETE:
+ if (!mDOMCompleteSet) {
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
+ startTime);
+ mDOMCompleteSet = true;
+ }
+ break;
+ default:
+ NS_WARNING("Unexpected ReadyState value");
+ break;
+ }
+}
+
+bool Document::ModuleScriptsEnabled() {
+ return nsContentUtils::IsChromeDoc(this) ||
+ StaticPrefs::dom_moduleScripts_enabled();
+}
+
+bool Document::ImportMapsEnabled() {
+ return nsContentUtils::IsChromeDoc(this) ||
+ StaticPrefs::dom_importMaps_enabled();
+}
+
+void Document::ReportShadowDOMUsage() {
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (NS_WARN_IF(!inner)) {
+ return;
+ }
+
+ WindowContext* wc = inner->GetWindowContext();
+ if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
+ return;
+ }
+
+ WindowContext* topWc = wc->TopWindowContext();
+ if (topWc->GetHasReportedShadowDOMUsage()) {
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
+}
+
+// static
+bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
+ return StaticPrefs::dom_storage_access_enabled() &&
+ (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
+}
+
+bool Document::StorageAccessSandboxed() const {
+ return Document::StorageAccessSandboxed(GetSandboxFlags());
+}
+
+bool Document::GetCachedSizes(nsTabSizes* aSizes) {
+ if (mCachedTabSizeGeneration == 0 ||
+ GetGeneration() != mCachedTabSizeGeneration) {
+ return false;
+ }
+ aSizes->mDom += mCachedTabSizes.mDom;
+ aSizes->mStyle += mCachedTabSizes.mStyle;
+ aSizes->mOther += mCachedTabSizes.mOther;
+ return true;
+}
+
+void Document::SetCachedSizes(nsTabSizes* aSizes) {
+ mCachedTabSizes.mDom = aSizes->mDom;
+ mCachedTabSizes.mStyle = aSizes->mStyle;
+ mCachedTabSizes.mOther = aSizes->mOther;
+ mCachedTabSizeGeneration = GetGeneration();
+}
+
+already_AddRefed<nsAtom> Document::GetContentLanguageAsAtomForStyle() const {
+ nsAutoString contentLang;
+ GetContentLanguage(contentLang);
+ contentLang.StripWhitespace();
+
+ // Content-Language may be a comma-separated list of language codes,
+ // in which case the HTML5 spec says to treat it as unknown
+ if (!contentLang.IsEmpty() && !contentLang.Contains(char16_t(','))) {
+ return NS_Atomize(contentLang);
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsAtom> Document::GetLanguageForStyle() const {
+ RefPtr<nsAtom> lang = GetContentLanguageAsAtomForStyle();
+ if (!lang) {
+ lang = mLanguageFromCharset;
+ }
+ return lang.forget();
+}
+
+const LangGroupFontPrefs* Document::GetFontPrefsForLang(
+ nsAtom* aLanguage, bool* aNeedsToCache) const {
+ nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
+ return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
+}
+
+void Document::DoCacheAllKnownLangPrefs() {
+ MOZ_ASSERT(mMayNeedFontPrefsUpdate);
+ RefPtr<nsAtom> lang = GetLanguageForStyle();
+ StaticPresData* data = StaticPresData::Get();
+ data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
+ data->GetFontPrefsForLang(nsGkAtoms::x_math);
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
+ data->GetFontPrefsForLang(nsGkAtoms::Unicode);
+ for (const auto& key : mLanguagesUsed) {
+ data->GetFontPrefsForLang(key);
+ }
+ mMayNeedFontPrefsUpdate = false;
+}
+
+void Document::RecomputeLanguageFromCharset() {
+ nsLanguageAtomService* service = nsLanguageAtomService::GetService();
+ RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
+ if (language == nsGkAtoms::Unicode) {
+ language = service->GetLocaleLanguage();
+ }
+
+ if (language == mLanguageFromCharset) {
+ return;
+ }
+
+ mMayNeedFontPrefsUpdate = true;
+ mLanguageFromCharset = std::move(language);
+}
+
+nsICookieJarSettings* Document::CookieJarSettings() {
+ // If we are here, this is probably a javascript: URL document. In any case,
+ // we must have a nsCookieJarSettings. Let's create it.
+ if (!mCookieJarSettings) {
+ Document* inProcessParent = GetInProcessParentDocument();
+
+ if (inProcessParent) {
+ mCookieJarSettings = net::CookieJarSettings::Create(
+ inProcessParent->CookieJarSettings()->GetCookieBehavior(),
+ mozilla::net::CookieJarSettings::Cast(
+ inProcessParent->CookieJarSettings())
+ ->GetPartitionKey(),
+ inProcessParent->CookieJarSettings()->GetIsFirstPartyIsolated(),
+ inProcessParent->CookieJarSettings()
+ ->GetIsOnContentBlockingAllowList(),
+ inProcessParent->CookieJarSettings()
+ ->GetShouldResistFingerprinting());
+
+ // Inherit the fingerprinting random key from the parent.
+ nsTArray<uint8_t> randomKey;
+ nsresult rv = inProcessParent->CookieJarSettings()
+ ->GetFingerprintingRandomizationKey(randomKey);
+
+ if (NS_SUCCEEDED(rv)) {
+ net::CookieJarSettings::Cast(mCookieJarSettings)
+ ->SetFingerprintingRandomizationKey(randomKey);
+ }
+ } else {
+ mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
+ }
+
+ if (auto* wgc = GetWindowGlobalChild()) {
+ net::CookieJarSettingsArgs csArgs;
+ net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
+ // Update cookie settings in the parent process
+ if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
+ NS_WARNING(
+ "Failed to update document's cookie jar settings on the "
+ "WindowGlobalParent");
+ }
+ }
+ }
+
+ return mCookieJarSettings;
+}
+
+bool Document::HasStorageAccessPermissionGranted() {
+ // The HasStoragePermission flag in LoadInfo remains fixed when
+ // it is set in the parent process, so we need to check the cache
+ // to see if the permission is granted afterwards.
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (inner && inner->HasStorageAccessPermissionGranted()) {
+ return true;
+ }
+
+ if (!mChannel) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
+}
+
+bool Document::HasStorageAccessPermissionGrantedByAllowList() {
+ // We only care about if the document gets the storage permission via the
+ // allow list here. So we don't check the storage access cache in the inner
+ // window.
+
+ if (!mChannel) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ return loadInfo->GetStoragePermission() ==
+ nsILoadInfo::StoragePermissionAllowListed;
+}
+
+nsIPrincipal* Document::EffectiveStoragePrincipal() const {
+ if (!StaticPrefs::
+ privacy_partition_always_partition_third_party_non_cookie_storage()) {
+ return EffectiveCookiePrincipal();
+ }
+
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (!inner) {
+ return NodePrincipal();
+ }
+
+ // Return our cached storage principal if one exists.
+ if (mActiveStoragePrincipal) {
+ return mActiveStoragePrincipal;
+ }
+
+ // Calling StorageAllowedForDocument will notify the ContentBlockLog. This
+ // loads TrackingDBService.jsm, which in turn pulls in osfile.jsm, making us
+ // fail // browser/base/content/test/performance/browser_startup.js. To avoid
+ // that, we short-circuit the check here by allowing storage access to system
+ // and addon principles, avoiding the test-failure.
+ nsIPrincipal* principal = NodePrincipal();
+ if (principal && (principal->IsSystemPrincipal() ||
+ principal->GetIsAddonOrExpandedAddonPrincipal())) {
+ return mActiveStoragePrincipal = NodePrincipal();
+ }
+
+ auto cookieJarSettings = const_cast<Document*>(this)->CookieJarSettings();
+ if (cookieJarSettings->GetIsOnContentBlockingAllowList()) {
+ return mActiveStoragePrincipal = NodePrincipal();
+ }
+
+ StorageAccess storageAccess = StorageAllowedForDocument(this);
+ if (!ShouldPartitionStorage(storageAccess) ||
+ !StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
+ return mActiveStoragePrincipal = NodePrincipal();
+ }
+
+ Unused << NS_WARN_IF(NS_FAILED(StoragePrincipalHelper::GetPrincipal(
+ nsGlobalWindowInner::Cast(inner),
+ StoragePrincipalHelper::eForeignPartitionedPrincipal,
+ getter_AddRefs(mActiveStoragePrincipal))));
+ return mActiveStoragePrincipal;
+}
+
+nsIPrincipal* Document::EffectiveCookiePrincipal() const {
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (!inner) {
+ return NodePrincipal();
+ }
+
+ // Return our cached storage principal if one exists.
+ if (mActiveCookiePrincipal) {
+ return mActiveCookiePrincipal;
+ }
+
+ // We use the lower-level ContentBlocking API here to ensure this
+ // check doesn't send notifications.
+ uint32_t rejectedReason = 0;
+ if (ShouldAllowAccessFor(inner, GetDocumentURI(), &rejectedReason)) {
+ return mActiveCookiePrincipal = NodePrincipal();
+ }
+
+ // Let's use the storage principal only if we need to partition the cookie
+ // jar. When the permission is granted, access will be different and the
+ // normal principal will be used.
+ if (ShouldPartitionStorage(rejectedReason) &&
+ !StoragePartitioningEnabled(
+ rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
+ return mActiveCookiePrincipal = NodePrincipal();
+ }
+
+ return mActiveCookiePrincipal = mPartitionedPrincipal;
+}
+
+nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
+ // If the document is sandboxed document or data: document, we should
+ // get URI of the parent document.
+ for (const Document* document = this;
+ document && document->IsContentDocument();
+ document = document->GetInProcessParentDocument()) {
+ // The document URI may be about:blank even if it comes from actual web
+ // site. Therefore, we need to check the URI of its principal.
+ nsIPrincipal* principal = document->NodePrincipal();
+ if (principal->GetIsNullPrincipal()) {
+ continue;
+ }
+ return principal;
+ }
+ return nullptr;
+}
+
+void Document::SetIsInitialDocument(bool aIsInitialDocument) {
+ mIsInitialDocumentInWindow = aIsInitialDocument;
+
+ if (aIsInitialDocument && !mIsEverInitialDocumentInWindow) {
+ mIsEverInitialDocumentInWindow = aIsInitialDocument;
+ }
+
+ // Asynchronously tell the parent process that we are, or are no longer, the
+ // initial document. This happens async.
+ if (auto* wgc = GetWindowGlobalChild()) {
+ wgc->SendSetIsInitialDocument(aIsInitialDocument);
+ }
+}
+
+// static
+void Document::AddToplevelLoadingDocument(Document* aDoc) {
+ MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
+ // Currently we're interested in foreground documents only, so bail out early.
+ if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
+ return;
+ }
+
+ if (!sLoadingForegroundTopLevelContentDocument) {
+ sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
+ mozilla::ipc::IdleSchedulerChild* idleScheduler =
+ mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
+ if (idleScheduler) {
+ idleScheduler->SendRunningPrioritizedOperation();
+ }
+ }
+ if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
+ sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
+ }
+}
+
+// static
+void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
+ MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
+ if (sLoadingForegroundTopLevelContentDocument) {
+ sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
+ if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
+ delete sLoadingForegroundTopLevelContentDocument;
+ sLoadingForegroundTopLevelContentDocument = nullptr;
+
+ mozilla::ipc::IdleSchedulerChild* idleScheduler =
+ mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
+ if (idleScheduler) {
+ idleScheduler->SendPrioritizedOperationDone();
+ }
+ }
+ }
+}
+
+ColorScheme Document::DefaultColorScheme() const {
+ return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
+}
+
+ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
+ if (ShouldResistFingerprinting(RFPTarget::CSSPrefersColorScheme) &&
+ aIgnoreRFP == IgnoreRFP::No) {
+ return ColorScheme::Light;
+ }
+
+ if (nsPresContext* pc = GetPresContext()) {
+ if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
+ return *scheme;
+ }
+ }
+
+ // NOTE(emilio): We use IsInChromeDocShell rather than IsChromeDoc
+ // intentionally, to make chrome documents in content docshells (like about
+ // pages) use the content color scheme.
+ if (IsInChromeDocShell()) {
+ return LookAndFeel::ColorSchemeForChrome();
+ }
+ return LookAndFeel::PreferredColorSchemeForContent();
+}
+
+bool Document::HasRecentlyStartedForegroundLoads() {
+ if (!sLoadingForegroundTopLevelContentDocument) {
+ return false;
+ }
+
+ for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
+ ++i) {
+ Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
+ // A page loaded in foreground could be in background now.
+ if (!doc->IsInBackgroundWindow()) {
+ nsPIDOMWindowInner* win = doc->GetInnerWindow();
+ if (win) {
+ Performance* perf = win->GetPerformance();
+ if (perf &&
+ perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
+ return true;
+ }
+ }
+ }
+ }
+
+ // Didn't find any loading foreground documents, just clear the array.
+ delete sLoadingForegroundTopLevelContentDocument;
+ sLoadingForegroundTopLevelContentDocument = nullptr;
+
+ mozilla::ipc::IdleSchedulerChild* idleScheduler =
+ mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
+ if (idleScheduler) {
+ idleScheduler->SendPrioritizedOperationDone();
+ }
+ return false;
+}
+
+void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
+ nsFrameLoader* aStaticCloneOf) {
+ PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
+ clone->mElement = aElement;
+ clone->mStaticCloneOf = aStaticCloneOf;
+}
+
+bool Document::ShouldAvoidNativeTheme() const {
+ return StaticPrefs::widget_non_native_theme_enabled() &&
+ (!IsInChromeDocShell() || XRE_IsContentProcess());
+}
+
+bool Document::UseRegularPrincipal() const {
+ return EffectiveStoragePrincipal() == NodePrincipal();
+}
+
+bool Document::HasThirdPartyChannel() {
+ nsCOMPtr<nsIChannel> channel = GetChannel();
+ if (channel) {
+ // We assume that the channel is a third-party by default.
+ bool thirdParty = true;
+
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ components::ThirdPartyUtil::Service();
+ if (!thirdPartyUtil) {
+ return thirdParty;
+ }
+
+ // Check that if the channel is a third-party to its parent.
+ nsresult rv =
+ thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
+ if (NS_FAILED(rv)) {
+ // Assume third-party in case of failure
+ thirdParty = true;
+ }
+
+ return thirdParty;
+ }
+
+ if (mParentDocument) {
+ return mParentDocument->HasThirdPartyChannel();
+ }
+
+ return false;
+}
+
+bool Document::ShouldIncludeInTelemetry(bool aAllowExtensionURIs) {
+ if (!(IsContentDocument() || IsResourceDoc())) {
+ return false;
+ }
+
+ if (!aAllowExtensionURIs &&
+ NodePrincipal()->GetIsAddonOrExpandedAddonPrincipal()) {
+ return false;
+ }
+
+ return !NodePrincipal()->SchemeIs("about") &&
+ !NodePrincipal()->SchemeIs("chrome") &&
+ !NodePrincipal()->SchemeIs("resource");
+}
+
+void Document::GetConnectedShadowRoots(
+ nsTArray<RefPtr<ShadowRoot>>& aOut) const {
+ AppendToArray(aOut, mComposedShadowRoots);
+}
+
+bool Document::HasPictureInPictureChildElement() const {
+ return mPictureInPictureChildElementCount > 0;
+}
+
+void Document::EnableChildElementInPictureInPictureMode() {
+ mPictureInPictureChildElementCount++;
+ MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
+}
+
+void Document::DisableChildElementInPictureInPictureMode() {
+ mPictureInPictureChildElementCount--;
+ MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
+}
+
+void Document::AddMediaElementWithMSE() {
+ if (mMediaElementWithMSECount++ == 0) {
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
+ }
+ }
+}
+
+void Document::RemoveMediaElementWithMSE() {
+ MOZ_ASSERT(mMediaElementWithMSECount > 0);
+ if (--mMediaElementWithMSECount == 0) {
+ if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
+ wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
+ }
+ }
+}
+
+void Document::UnregisterFromMemoryReportingForDataDocument() {
+ if (!mAddedToMemoryReportingAsDataDocument) {
+ return;
+ }
+ mAddedToMemoryReportingAsDataDocument = false;
+ nsIGlobalObject* global = GetScopeObject();
+ if (global) {
+ if (nsPIDOMWindowInner* win = global->AsInnerWindow()) {
+ nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
+ this);
+ }
+ }
+}
+void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
+ MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
+ mOOPChildrenLoading.AppendElement(aChild);
+ if (mOOPChildrenLoading.Length() == 1) {
+ // Let's block unload so that we're blocked from going into the BFCache
+ // until the child has actually notified us that it has done loading.
+ BlockOnload();
+ }
+}
+
+void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
+ // aChild will not be in the list if nsDocLoader::Stop() was called, since
+ // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
+ // so we don't need to call DocLoaderIsEmpty in that case.
+ if (mOOPChildrenLoading.RemoveElement(aChild)) {
+ if (mOOPChildrenLoading.IsEmpty()) {
+ UnblockOnload(false);
+ }
+ RefPtr<nsDocLoader> docLoader(mDocumentContainer);
+ if (docLoader) {
+ docLoader->OOPChildrenLoadingIsEmpty();
+ }
+ }
+}
+
+void Document::ClearOOPChildrenLoading() {
+ nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
+ mOOPChildrenLoading.SwapElements(oopChildrenLoading);
+ if (!oopChildrenLoading.IsEmpty()) {
+ UnblockOnload(false);
+ }
+}
+
+HighlightRegistry& Document::HighlightRegistry() {
+ if (!mHighlightRegistry) {
+ mHighlightRegistry = MakeRefPtr<class HighlightRegistry>(this);
+ }
+ return *mHighlightRegistry;
+}
+
+} // namespace mozilla::dom