Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "NSSErrorsService.h"
#include "NodeUbiReporting.h"
#include "PLDHashTable.h"
#include "StorageAccessPermissionRequest.h"
#include "ThirdPartyUtil.h"
#include "domstubs.h"
#include "gfxPlatform.h"
#include "imgIContainer.h"
#include "imgLoader.h"
#include "imgRequestProxy.h"
#include "js/Value.h"
#include "jsapi.h"
#include "mozAutoDocUpdate.h"
#include "mozIDOMWindow.h"
#include "mozIThirdPartyUtil.h"
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/ArrayIterator.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/Base64.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/BounceTrackingProtection.h"
#include "mozilla/CSSEnabledState.h"
#include "mozilla/ContentBlockingAllowList.h"
#include "mozilla/ContentBlockingNotifier.h"
#include "mozilla/ContentBlockingUserInteraction.h"
#include "mozilla/ContentPrincipal.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/AttributeStyles.h"
#include "mozilla/DocumentStyleRootIterator.h"
#include "mozilla/EditorBase.h"
#include "mozilla/EditorCommands.h"
#include "mozilla/Encoding.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventQueue.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/ExtensionPolicyService.h"
#include "mozilla/FullscreenChange.h"
#include "mozilla/GlobalStyleSheetCache.h"
#include "mozilla/MappedDeclarationsBuilder.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/IdentifierMapEntry.h"
#include "mozilla/InputTaskManager.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/InternalMutationEvent.h"
#include "mozilla/Likely.h"
#include "mozilla/Logging.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/MacroForEach.h"
#include "mozilla/Maybe.h"
#include "mozilla/MediaFeatureChange.h"
#include "mozilla/MediaManager.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/NeverDestroyed.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/OriginAttributes.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/PendingFullscreenEvent.h"
#include "mozilla/PermissionDelegateHandler.h"
#include "mozilla/PermissionManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/PreloadHashKey.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellForwards.h"
#include "mozilla/PresShellInlines.h"
#include "mozilla/PseudoStyleType.h"
#include "mozilla/RefCountType.h"
#include "mozilla/RelativeTo.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ReverseIterator.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/ScrollTimelineAnimationTracker.h"
#include "mozilla/SMILAnimationController.h"
#include "mozilla/SMILTimeContainer.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ScrollContainerFrame.h"
#include "mozilla/Components.h"
#include "mozilla/SVGUtils.h"
#include "mozilla/ServoStyleConsts.h"
#include "mozilla/ServoTypes.h"
#include "mozilla/SizeOfState.h"
#include "mozilla/Span.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticAnalysisFunctions.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_docshell.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/StaticPrefs_full_screen_api.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StaticPrefs_page_load.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/StaticPrefs_widget.h"
#include "mozilla/StaticPresData.h"
#include "mozilla/StorageAccess.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/TelemetryScalarEnums.h"
#include "mozilla/TextControlElement.h"
#include "mozilla/TextEditor.h"
#include "mozilla/TypedEnumBits.h"
#include "mozilla/URLDecorationStripper.h"
#include "mozilla/URLExtraData.h"
#include "mozilla/Unused.h"
#include "mozilla/css/ImageLoader.h"
#include "mozilla/css/Loader.h"
#include "mozilla/css/Rule.h"
#include "mozilla/css/SheetParsingMode.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/AnonymousContent.h"
#include "mozilla/dom/BlobURLProtocolHandler.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/CanvasRenderingContextHelper.h"
#include "mozilla/dom/CDATASection.h"
#include "mozilla/dom/CSPDictionariesBinding.h"
#include "mozilla/dom/ChromeObserver.h"
#include "mozilla/dom/ClientInfo.h"
#include "mozilla/dom/ClientState.h"
#include "mozilla/dom/CloseWatcherManager.h"
#include "mozilla/dom/Comment.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/CSSBinding.h"
#include "mozilla/dom/CSSCustomPropertyRegisteredEvent.h"
#include "mozilla/dom/DOMImplementation.h"
#include "mozilla/dom/DOMIntersectionObserver.h"
#include "mozilla/dom/DOMStringList.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/DocumentBinding.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DocumentL10n.h"
#include "mozilla/dom/DocumentTimeline.h"
#include "mozilla/dom/DocumentType.h"
#include "mozilla/dom/Sanitizer.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/ErrorEvent.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/EventListenerBinding.h"
#include "mozilla/dom/FailedCertSecurityInfoBinding.h"
#include "mozilla/dom/FeaturePolicy.h"
#include "mozilla/dom/FeaturePolicyUtils.h"
#include "mozilla/dom/FontFaceSet.h"
#include "mozilla/dom/FragmentDirective.h"
#include "mozilla/dom/NavigationBinding.h"
#include "mozilla/dom/fragmentdirectives_ffi_generated.h"
#include "mozilla/dom/FromParser.h"
#include "mozilla/dom/HighlightRegistry.h"
#include "mozilla/dom/HTMLAllCollection.h"
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/HTMLCollectionBinding.h"
#include "mozilla/dom/HTMLDialogElement.h"
#include "mozilla/dom/HTMLEmbedElement.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/HTMLObjectElement.h"
#include "mozilla/dom/HTMLSharedElement.h"
#include "mozilla/dom/HTMLTextAreaElement.h"
#include "mozilla/dom/ImageTracker.h"
#include "mozilla/dom/InspectorUtils.h"
#include "mozilla/dom/InteractiveWidget.h"
#include "mozilla/dom/Link.h"
#include "mozilla/dom/MediaQueryList.h"
#include "mozilla/dom/MediaSource.h"
#include "mozilla/dom/MutationObservers.h"
#include "mozilla/dom/NameSpaceConstants.h"
#include "mozilla/dom/Navigator.h"
#include "mozilla/dom/NetErrorInfoBinding.h"
#include "mozilla/dom/NodeInfo.h"
#include "mozilla/dom/NodeIterator.h"
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
#include "mozilla/dom/PContentChild.h"
#include "mozilla/dom/PWindowGlobalChild.h"
#include "mozilla/dom/PageLoadEventUtils.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/RemoteBrowser.h"
#include "mozilla/dom/ResizeObserver.h"
#include "mozilla/dom/RustTypes.h"
#include "mozilla/dom/SVGElement.h"
#include "mozilla/dom/SVGDocument.h"
#include "mozilla/dom/SVGSVGElement.h"
#include "mozilla/dom/SVGUseElement.h"
#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/ServiceWorkerContainer.h"
#include "mozilla/dom/ServiceWorkerDescriptor.h"
#include "mozilla/dom/ServiceWorkerManager.h"
#include "mozilla/dom/ShadowIncludingTreeIterator.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
#include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
#include "mozilla/dom/StyleSheetList.h"
#include "mozilla/dom/StyleSheetRemovedEvent.h"
#include "mozilla/dom/StyleSheetRemovedEventBinding.h"
#include "mozilla/dom/TimeoutManager.h"
#include "mozilla/dom/ToggleEvent.h"
#include "mozilla/dom/Touch.h"
#include "mozilla/dom/TouchEvent.h"
#include "mozilla/dom/TreeOrderedArrayInlines.h"
#include "mozilla/dom/TreeWalker.h"
#include "mozilla/dom/TrustedHTML.h"
#include "mozilla/dom/TrustedTypeUtils.h"
#include "mozilla/dom/TrustedTypesConstants.h"
#include "mozilla/dom/URL.h"
#include "mozilla/dom/UseCounterMetrics.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/ViewTransition.h"
#include "mozilla/dom/WakeLockJS.h"
#include "mozilla/dom/WakeLockSentinel.h"
#include "mozilla/dom/WindowBinding.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WindowProxyHolder.h"
#include "mozilla/dom/WorkerDocumentListener.h"
#include "mozilla/dom/XPathEvaluator.h"
#include "mozilla/dom/XPathExpression.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/dom/nsCSPUtils.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozilla/fallible.h"
#include "mozilla/gfx/BaseCoord.h"
#include "mozilla/gfx/BaseSize.h"
#include "mozilla/gfx/Coord.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/gfx/ScaleFactor.h"
#include "mozilla/glean/DomMetrics.h"
#include "mozilla/glean/DomUseCounterMetrics.h"
#include "mozilla/intl/LocaleService.h"
#include "mozilla/ipc/IdleSchedulerChild.h"
#include "mozilla/ipc/MessageChannel.h"
#include "mozilla/net/ChannelEventQueue.h"
#include "mozilla/net/Cookie.h"
#include "mozilla/net/CookieCommons.h"
#include "mozilla/net/CookieJarSettings.h"
#include "mozilla/net/CookieParser.h"
#include "mozilla/net/NeckoChannelParams.h"
#include "mozilla/net/RequestContextService.h"
#include "nsAboutProtocolUtils.h"
#include "nsAttrValue.h"
#include "nsMenuPopupFrame.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 "nsError.h"
#include "nsEscape.h"
#include "nsFocusManager.h"
#include "nsFrameLoader.h"
#include "nsFrameLoaderOwner.h"
#include "nsGenericHTMLElement.h"
#include "nsGlobalWindowInner.h"
#include "nsGlobalWindowOuter.h"
#include "nsHTMLDocument.h"
#include "nsHtml5Module.h"
#include "nsHtml5Parser.h"
#include "nsHtml5TreeOpExecutor.h"
#include "nsIAsyncShutdown.h"
#include "nsIAuthPrompt.h"
#include "nsIAuthPrompt2.h"
#include "nsIBFCacheEntry.h"
#include "nsIBaseWindow.h"
#include "nsIBrowserChild.h"
#include "nsIBrowserUsage.h"
#include "nsICSSLoaderObserver.h"
#include "nsICategoryManager.h"
#include "nsICertOverrideService.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsIContentPolicy.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIContentSink.h"
#include "nsICookieJarSettings.h"
#include "nsICookieService.h"
#include "nsIDOMXULCommandDispatcher.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocumentActivity.h"
#include "nsIDocumentEncoder.h"
#include "nsIDocumentLoader.h"
#include "nsIDocumentLoaderFactory.h"
#include "nsIDocumentObserver.h"
#include "nsIDNSService.h"
#include "nsIEditingSession.h"
#include "nsIEditor.h"
#include "nsIEffectiveTLDService.h"
#include "nsIFile.h"
#include "nsIFileChannel.h"
#include "nsIFrame.h"
#include "nsIGlobalObject.h"
#include "nsIHTMLCollection.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIIOService.h"
#include "nsIImageLoadingContent.h"
#include "nsIInlineSpellChecker.h"
#include "nsIInputStreamChannel.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsILayoutHistoryState.h"
#include "nsIMultiPartChannel.h"
#include "nsIMutationObserver.h"
#include "nsINSSErrorsService.h"
#include "nsINamed.h"
#include "nsINodeList.h"
#include "nsIObjectLoadingContent.h"
#include "nsIObserverService.h"
#include "nsIPermission.h"
#include "nsIPrompt.h"
#include "nsIPropertyBag2.h"
#include "nsIPublicKeyPinningService.h"
#include "nsIReferrerInfo.h"
#include "nsIRefreshURI.h"
#include "nsIRequest.h"
#include "nsIRequestContext.h"
#include "nsIRunnable.h"
#include "nsISHEntry.h"
#include "nsIScriptElement.h"
#include "nsIScriptError.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptSecurityManager.h"
#include "nsISecurityConsoleMessage.h"
#include "nsISelectionController.h"
#include "nsISerialEventTarget.h"
#include "nsISimpleEnumerator.h"
#include "nsISiteSecurityService.h"
#include "nsISocketProvider.h"
#include "nsISpeculativeConnect.h"
#include "nsIStructuredCloneContainer.h"
#include "nsIThread.h"
#include "nsITimedChannel.h"
#include "nsITimer.h"
#include "nsITransportSecurityInfo.h"
#include "nsIURIMutator.h"
#include "nsIVariant.h"
#include "nsIWeakReference.h"
#include "nsIWebNavigation.h"
#include "nsIWidget.h"
#include "nsIX509Cert.h"
#include "nsIX509CertValidity.h"
#include "nsIXMLContentSink.h"
#include "nsIHTMLContentSink.h"
#include "nsIXULRuntime.h"
#include "nsImageLoadingContent.h"
#include "nsImportModule.h"
#include "nsLanguageAtomService.h"
#include "nsLayoutUtils.h"
#include "nsMimeTypes.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsNodeInfoManager.h"
#include "nsObjectLoadingContent.h"
#include "nsPIDOMWindowInlines.h"
#include "nsPIWindowRoot.h"
#include "nsPoint.h"
#include "nsPointerHashKeys.h"
#include "nsPresContext.h"
#include "nsQueryFrame.h"
#include "nsQueryObject.h"
#include "nsRange.h"
#include "nsRect.h"
#include "nsRefreshDriver.h"
#include "nsSandboxFlags.h"
#include "nsSerializationHelper.h"
#include "nsServiceManagerUtils.h"
#include "nsStringFlags.h"
#include "nsStyleUtil.h"
#include "nsStringIterator.h"
#include "nsStyleSheetService.h"
#include "nsStyleStruct.h"
#include "nsTextControlFrame.h"
#include "nsSubDocumentFrame.h"
#include "nsTextNode.h"
#include "nsURLHelper.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 {
using namespace net;
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;
};
AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
nullptr;
static LinkedList<Document>& AllDocumentsList() {
static NeverDestroyed<LinkedList<Document>> sAllDocuments;
return *sAllDocuments;
}
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));
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
"mIdentifierMap mDocumentNameContentList");
aCallback->NoteXPCOMChild(
static_cast<nsINodeList*>(mDocumentNameContentList));
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 &&
!mDocumentNameContentList && !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;
mDocumentNameContentList = 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);
}
}
void IdentifierMapEntry::AddDocumentNameElement(
Document* aDocument, nsGenericHTMLElement* aElement) {
if (!mDocumentNameContentList) {
mDocumentNameContentList = new dom::SimpleHTMLCollection(aDocument);
}
mDocumentNameContentList->AppendElement(aElement);
}
void IdentifierMapEntry::RemoveDocumentNameElement(
nsGenericHTMLElement* aElement) {
if (mDocumentNameContentList) {
mDocumentNameContentList->RemoveElement(aElement);
}
}
bool IdentifierMapEntry::HasDocumentNameElement() const {
return mDocumentNameContentList && mDocumentNameContentList->Length() != 0;
}
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) const {
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::CollectDescendantDocuments(
nsTArray<RefPtr<Document>>& aDocs, SubDocTestFunc aCallback) const {
for (const auto& entry : mMap.Values()) {
if (Document* doc = entry->mDocument) {
if (aCallback(doc)) {
aDocs.AppendElement(doc);
}
doc->CollectDescendantDocuments(aDocs, Document::IncludeSubResources::Yes,
aCallback);
}
}
}
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<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
if (viewer) {
viewer->Hide();
}
}
}
void ExternalResourceMap::ShowViewers() {
for (const auto& entry : mMap) {
nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
if (viewer) {
viewer->Show();
}
}
}
void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
if (aFromDoc->IsShowing()) {
aToDoc->OnPageShow(true, nullptr);
}
}
nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
nsIDocumentViewer* aViewer,
nsILoadGroup* aLoadGroup,
Document* aDisplayDocument) {
MOZ_ASSERT(aURI, "Unexpected call");
MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
"Must have both or neither");
RefPtr<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, LayoutDeviceIntRect(), 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<nsIDocumentViewer> 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, nsIDocumentViewer** 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);
nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
nsContentUtils::FindInternalDocumentViewer(type);
NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsIDocumentViewer> viewer;
nsCOMPtr<nsIStreamListener> listener;
nsresult 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_INTERNAL_EXTERNAL_RESOURCE,
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::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),
mRenderingSuppressedForViewTransitions(false),
mBidiEnabled(false),
mMayNeedFontPrefsUpdate(true),
mIsInitialDocumentInWindow(false),
mIsEverInitialDocumentInWindow(false),
mIgnoreDocGroupMismatches(false),
mLoadedAsData(false),
mAddedToMemoryReportingAsDataDocument(false),
mMayStartLayout(true),
mHaveFiredTitleChange(false),
mIsShowing(false),
mVisible(true),
mRemovedFromDocShell(false),
// mAllowDNSPrefetch starts true, so that we can always reliably && it
// with various values that might disable it. Since we never prefetch
// unless we get a window, and in that case the docshell value will get
// &&-ed in, this is safe.
mAllowDNSPrefetch(true),
mIsStaticDocument(false),
mCreatingStaticClone(false),
mHasPrintCallbacks(false),
mInUnlinkOrDeletion(false),
mHasHadScriptHandlingObject(false),
mIsBeingUsedAsImage(false),
mChromeRulesEnabled(false),
mInChromeDocShell(false),
mIsSyntheticDocument(false),
mHasLinksToUpdateRunnable(false),
mFlushingPendingLinkUpdates(false),
mMayHaveDOMMutationObservers(false),
mMayHaveAnimationObservers(false),
mHasCSPDeliveredThroughHeader(false),
mBFCacheDisallowed(false),
mHasHadDefaultView(false),
mStyleSheetChangeEventsEnabled(false),
mDevToolsAnonymousAndShadowEventsEnabled(false),
mIsSrcdocDocument(false),
mHasDisplayDocument(false),
mFontFaceSetDirty(true),
mDidFireDOMContentLoaded(true),
mIsTopLevelContentDocument(false),
mIsContentDocument(false),
mDidCallBeginLoad(false),
mEncodingMenuDisabled(false),
mLinksEnabled(true),
mIsSVGGlyphsDocument(false),
mInDestructor(false),
mIsGoingAway(false),
mStyleSetFilled(false),
mQuirkSheetAdded(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),
mLoadEventFiring(false),
mSkipLoadEventAfterClose(false),
mDisableCookieAccess(false),
mDisableDocWrite(false),
mTooDeepWriteRecursion(false),
mPendingMaybeEditingStateChanged(false),
mHasBeenEditable(false),
mIsRunningExecCommandByContent(false),
mIsRunningExecCommandByChromeOrAddon(false),
mSetCompleteAfterDOMContentLoaded(false),
mDidHitCompleteSheetCache(false),
mUseCountersInitialized(false),
mShouldReportUseCounters(false),
mShouldSendPageUseCounters(false),
mUserHasInteracted(false),
mHasUserInteractionTimerScheduled(false),
mShouldResistFingerprinting(false),
mIsInPrivateBrowsing(false),
mCloningForSVGUse(false),
mAllowDeclarativeShadowRoots(false),
mSuspendDOMNotifications(false),
mForceLoadAtTop(false),
mFireMutationEvents(true),
mHasPolicyWithRequireTrustedTypesForDirective(false),
mClipboardCopyTriggered(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),
mInteractiveWidgetMode(
InteractiveWidgetUtils::DefaultInteractiveWidgetMode()),
mHeaderData(nullptr),
mServoRestyleRootDirtyBits(0),
mThrowOnDynamicMarkupInsertionCounter(0),
mIgnoreOpensDuringUnloadCounter(0),
mSavedResolution(1.0f),
mGeneration(0),
mCachedTabSizeGeneration(0),
mNextFormNumber(0),
mNextControlNumber(0),
mPreloadService(this),
mShouldNotifyFetchSuccess(false),
mShouldNotifyFormOrPasswordRemoved(false) {
MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
SetIsInDocument();
SetIsConnected(true);
// Create these unconditionally, they will be used to warn about the `zoom`
// property, even if use counters are disabled.
mStyleUseCounters.reset(Servo_UseCounters_Create());
SetContentType(nsDependentCString(aContentType));
// Start out mLastStyleSheetSet as null, per spec
SetDOMStringToNull(mLastStyleSheetSet);
// void state used to differentiate an empty source from an unselected source
mPreloadPictureFoundSource.SetIsVoid(true);
RecomputeLanguageFromCharset();
mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
mReferrerInfo = new dom::ReferrerInfo(nullptr);
}
#ifndef ANDROID
// unused by GeckoView
static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
if (NS_WARN_IF(!aWin)) {
return false;
}
nsIURI* uri = aWin->GetDocumentURI();
if (NS_WARN_IF(!uri)) {
return false;
}
// getSpec is an expensive operation, hence we first check the scheme
// to see if the caller is actually an about: page.
if (!uri->SchemeIs("about")) {
return false;
}
nsAutoCString aboutSpec;
nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
NS_ENSURE_SUCCESS(rv, false);
return aboutSpec.EqualsASCII(aSpec);
}
#endif
bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
#ifdef ANDROID
// GeckoView uses data URLs for error pages, so for now just check for any
// error page
return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
#else
return win && IsAboutErrorPage(win, "neterror");
#endif
}
bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
JSObject* aObject) {
nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
#ifdef ANDROID
// GeckoView uses data URLs for error pages, so for now just check for any
// error page
return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
#else
return win && IsAboutErrorPage(win, "httpsonlyerror");
#endif
}
already_AddRefed<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();
}
}
// Given an nsresult that is assumed to be synthesized by PSM and describes a
// certificate or TLS error, attempts to convert it into a string
// representation of the underlying NSS error.
// `aErrorCodeString` will be an empty string if `aResult` is not an error from
// PSM or it does not represent a valid NSS error.
void GetErrorCodeStringFromNSResult(nsresult aResult,
nsAString& aErrorCodeString) {
aErrorCodeString.Truncate();
if (NS_ERROR_GET_MODULE(aResult) != NS_ERROR_MODULE_SECURITY ||
NS_ERROR_GET_SEVERITY(aResult) != NS_ERROR_SEVERITY_ERROR) {
return;
}
PRErrorCode errorCode = -1 * NS_ERROR_GET_CODE(aResult);
if (!mozilla::psm::IsNSSErrorCode(errorCode)) {
return;
}
const char* errorCodeString = PR_ErrorToName(errorCode);
if (!errorCodeString) {
return;
}
aErrorCodeString.AssignASCII(errorCodeString);
}
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<nsIHttpChannel> httpChannel(do_QueryInterface(mFailedChannel));
// We don't throw even if httpChannel is null, we just keep responseStatus and
// responseStatusText empty
if (httpChannel) {
uint32_t responseStatus;
nsAutoCString responseStatusText;
rv = httpChannel->GetResponseStatus(&responseStatus);
if (NS_SUCCEEDED(rv)) {
aInfo.mResponseStatus = responseStatus;
}
rv = httpChannel->GetResponseStatusText(responseStatusText);
if (NS_FAILED(rv) || responseStatusText.IsEmpty()) {
net_GetDefaultStatusTextForCode(responseStatus, responseStatusText);
}
aInfo.mResponseStatusText.AssignASCII(responseStatusText);
}
nsCOMPtr<nsITransportSecurityInfo> tsi;
rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
nsresult channelStatus;
rv = mFailedChannel->GetStatus(&channelStatus);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
aInfo.mChannelStatus = static_cast<uint32_t>(channelStatus);
// If nsITransportSecurityInfo is not set, simply keep the remaining fields
// empty (to make responseStatus and responseStatusText accessible).
if (!tsi) {
return;
}
// TransportSecurityInfo::GetErrorCodeString always returns NS_OK
(void)tsi->GetErrorCodeString(aInfo.mErrorCodeString);
if (aInfo.mErrorCodeString.IsEmpty()) {
GetErrorCodeStringFromNSResult(channelStatus, aInfo.mErrorCodeString);
}
}
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::CallerIsSystemPrincipalOrWebCompatAddon(JSContext* aCx,
JSObject* aObject) {
RefPtr<BasePrincipal> principal =
BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx));
if (!principal) {
return false;
}
// We allow the privileged APIs to be called from system principal.
if (principal->IsSystemPrincipal()) {
return true;
}
// We only allow calling privileged APIs from 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;
}
nsresult channelStatus;
rv = mFailedChannel->GetStatus(&channelStatus);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
aInfo.mChannelStatus = static_cast<uint32_t>(channelStatus);
// TransportSecurityInfo::GetErrorCodeString always returns NS_OK
(void)tsi->GetErrorCodeString(aInfo.mErrorCodeString);
if (aInfo.mErrorCodeString.IsEmpty()) {
GetErrorCodeStringFromNSResult(channelStatus, aInfo.mErrorCodeString);
}
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;
}
aInfo.mErrorIsOverridable = mozilla::psm::ErrorIsOverridable(errorCode);
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();
}
}
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 =
mozilla::components::EffectiveTLD::Service();
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);
}
}
#ifndef ANDROID
static void AccumulateHttp3FcpGleanPref(const nsCString& http3Key,
const TimeDuration& duration) {
if (http3Key == "http3"_ns) {
glean::performance_pageload::http3_fcp_http3.AccumulateRawDuration(
duration);
} else if (http3Key == "supports_http3"_ns) {
glean::performance_pageload::http3_fcp_supports_http3.AccumulateRawDuration(
duration);
} else {
MOZ_ASSERT_UNREACHABLE("Unknown value for http3Key");
}
}
static void AccumulatePriorityFcpGleanPref(
const nsCString& http3WithPriorityKey, const TimeDuration& duration) {
if (http3WithPriorityKey == "with_priority"_ns) {
glean::performance_pageload::h3p_fcp_with_priority.AccumulateRawDuration(
duration);
} else if (http3WithPriorityKey == "without_priority"_ns) {
glean::performance_pageload::http3_fcp_without_priority
.AccumulateRawDuration(duration);
} else {
MOZ_ASSERT_UNREACHABLE("Unknown value for http3WithPriorityKey");
}
}
#endif
void Document::AccumulatePageLoadTelemetry(
glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
// Interested only in top level documents for real websites that are in the
// foreground.
if (!ShouldIncludeInTelemetry() || !IsTopLevelContentDocument() ||
!GetNavigationTiming() ||
!GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
return;
}
if (!GetChannel()) {
return;
}
nsCOMPtr<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) {
glean::perf::dns_first_byte.Get(dnsKey).AccumulateRawDuration(
responseStart - asyncOpen);
}
// First Contentful Composite
if (TimeStamp firstContentfulComposite =
GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) {
glean::performance_pageload::fcp.AccumulateRawDuration(
firstContentfulComposite - navigationStart);
if (!http3Key.IsEmpty()) {
glean::perf::http3_first_contentful_paint.Get(http3Key)
.AccumulateRawDuration(firstContentfulComposite - navigationStart);
#ifndef ANDROID
AccumulateHttp3FcpGleanPref(http3Key,
firstContentfulComposite - navigationStart);
#endif
}
if (!http3WithPriorityKey.IsEmpty()) {
glean::perf::h3p_first_contentful_paint.Get(http3WithPriorityKey)
.AccumulateRawDuration(firstContentfulComposite - navigationStart);
#ifndef ANDROID
AccumulatePriorityFcpGleanPref(
http3WithPriorityKey, firstContentfulComposite - navigationStart);
#endif
}
glean::perf::dns_first_contentful_paint.Get(dnsKey).AccumulateRawDuration(
firstContentfulComposite - navigationStart);
glean::performance_pageload::fcp_responsestart.AccumulateRawDuration(
firstContentfulComposite - responseStart);
TimeDuration fcpTime = firstContentfulComposite - navigationStart;
if (fcpTime > zeroDuration) {
aEventTelemetryDataOut.fcpTime =
mozilla::Some(static_cast<uint32_t>(fcpTime.ToMilliseconds()));
}
}
// Report the most up to date LCP time. For our histogram we actually report
// this on page unload.
if (TimeStamp lcpTime =
GetNavigationTiming()->GetLargestContentfulRenderTimeStamp()) {
aEventTelemetryDataOut.lcpTime = mozilla::Some(
static_cast<uint32_t>((lcpTime - navigationStart).ToMilliseconds()));
}
// Load event
if (TimeStamp loadEventStart =
GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
glean::performance_pageload::load_time.AccumulateRawDuration(
loadEventStart - navigationStart);
if (!http3Key.IsEmpty()) {
glean::perf::http3_page_load_time.Get(http3Key).AccumulateRawDuration(
loadEventStart - navigationStart);
}
if (!http3WithPriorityKey.IsEmpty()) {
glean::perf::h3p_page_load_time.Get(http3WithPriorityKey)
.AccumulateRawDuration(loadEventStart - navigationStart);
}
glean::performance_pageload::load_time_responsestart.AccumulateRawDuration(
loadEventStart - responseStart);
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()));
}
TimeStamp requestStart;
timedChannel->GetRequestStart(&requestStart);
if (requestStart) {
TimeDuration timeToRequestStart = requestStart - navigationStart;
if (timeToRequestStart > zeroDuration) {
aEventTelemetryDataOut.timeToRequestStart = mozilla::Some(
static_cast<uint32_t>(timeToRequestStart.ToMilliseconds()));
}
}
TimeStamp secureConnectStart;
TimeStamp connectEnd;
timedChannel->GetSecureConnectionStart(&secureConnectStart);
timedChannel->GetConnectEnd(&connectEnd);
if (secureConnectStart && connectEnd) {
TimeDuration tlsHandshakeTime = connectEnd - secureConnectStart;
if (tlsHandshakeTime > zeroDuration) {
aEventTelemetryDataOut.tlsHandshakeTime = mozilla::Some(
static_cast<uint32_t>(tlsHandshakeTime.ToMilliseconds()));
}
}
}
#ifdef ACCESSIBILITY
if (GetAccService() != nullptr) {
SetPageloadEventFeature(pageload_event::FeatureBits::USING_A11Y);
}
#endif
aEventTelemetryDataOut.features = mozilla::Some(mPageloadEventFeatures);
}
void Document::AccumulateJSTelemetry(
glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry()) {
return;
}
if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
return;
}
AutoJSContext cx;
JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
JSAutoRealm ar(cx, globalObject);
JS::JSTimers timers = JS::GetJSTimers(cx);
if (!timers.executionTime.IsZero()) {
glean::javascript_pageload::execution_time.AccumulateRawDuration(
timers.executionTime);
aEventTelemetryDataOut.jsExecTime = mozilla::Some(
static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
}
if (!timers.delazificationTime.IsZero()) {
glean::javascript_pageload::delazification_time.AccumulateRawDuration(
timers.delazificationTime);
}
if (!timers.xdrEncodingTime.IsZero()) {
glean::javascript_pageload::xdr_encode_time.AccumulateRawDuration(
timers.xdrEncodingTime);
}
if (!timers.baselineCompileTime.IsZero()) {
glean::javascript_pageload::baseline_compile_time.AccumulateRawDuration(
timers.baselineCompileTime);
}
if (!timers.gcTime.IsZero()) {
glean::javascript_pageload::gc_time.AccumulateRawDuration(timers.gcTime);
}
if (!timers.protectTime.IsZero()) {
glean::javascript_pageload::protect_time.AccumulateRawDuration(
timers.protectTime);
// GLAM EXPERIMENT
// This metric is temporary, disabled by default, and will be enabled only
// for the purpose of experimenting with client-side sampling of data for
// GLAM use. See Bug 1947604 for more information.
glean::glam_experiment::protect_time.AccumulateRawDuration(
timers.protectTime);
// END GLAM EXPERIMENT
}
}
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);
}
mInDestructor = true;
mInUnlinkOrDeletion = true;
mozilla::DropJSObjects(this);
// Clear mObservers to keep it in sync with the mutationobserver list
mObservers.Clear();
mIntersectionObservers.Clear();
if (mStyleSheetSetList) {
mStyleSheetSetList->Disconnect();
}
if (mAnimationController) {
mAnimationController->Disconnect();
}
MOZ_ASSERT(mTimelines.isEmpty());
mParentDocument = nullptr;
// Kill the subdocument map, doing this will release its strong
// references, if any.
mSubDocuments = nullptr;
nsAutoScriptBlocker scriptBlocker;
// Destroy link map now so we don't waste time removing
// links one by one
DestroyElementMaps();
// Invalidate cached array of child nodes
InvalidateChildNodes();
// We should not have child nodes when destructor is called,
// since child nodes keep their owner document alive.
MOZ_ASSERT(!HasChildren());
mCachedRootElement = nullptr;
for (auto& sheets : mAdditionalSheets) {
UnlinkStyleSheets(sheets);
}
if (mAttributeStyles) {
mAttributeStyles->SetOwningDocument(nullptr);
}
if (mListenerManager) {
mListenerManager->Disconnect();
UnsetFlags(NODE_HAS_LISTENERMANAGER);
}
if (mScriptLoader) {
mScriptLoader->DropDocumentReference();
}
if (mCSSLoader) {
// Could be null here if Init() failed or if we have been unlinked.
mCSSLoader->DropDocumentReference();
}
if (mStyleImageLoader) {
mStyleImageLoader->DropDocumentReference();
}
if (mXULBroadcastManager) {
mXULBroadcastManager->DropDocumentReference();
}
if (mXULPersist) {
mXULPersist->DropDocumentReference();
}
if (mPermissionDelegateHandler) {
mPermissionDelegateHandler->DropDocumentReference();
}
mHeaderData = nullptr;
mPendingTitleChangeEvent.Revoke();
MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
"must not have media query lists left");
if (mNodeInfoManager) {
mNodeInfoManager->DropDocumentReference();
}
if (mDocGroup) {
MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
}
UnlinkOriginalDocumentIfStatic();
UnregisterFromMemoryReportingForDataDocument();
if (isInList()) {
MOZ_ASSERT(AllDocumentsList().contains(this));
remove();
}
}
void Document::DropStyleSet() { mStyleSet = nullptr; }
NS_INTERFACE_TABLE_HEAD(Document)
NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
NS_INTERFACE_TABLE_BEGIN
NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
NS_INTERFACE_TABLE_ENTRY(Document, Document)
NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
NS_INTERFACE_TABLE_END
NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Document, LastRelease())
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
if (Element::CanSkip(tmp, aRemovingAllowed)) {
EventListenerManager* elm = tmp->GetExistingListenerManager();
if (elm) {
elm->MarkForCC();
}
return true;
}
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
return Element::CanSkipInCC(tmp);
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
return Element::CanSkipThis(tmp);
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
char name[512];
nsAutoCString loadedAsData;
if (tmp->IsLoadedAsData()) {
loadedAsData.AssignLiteral("data");
} else {
loadedAsData.AssignLiteral("normal");
}
uint32_t nsid = tmp->GetDefaultNamespaceID();
nsAutoCString uri;
if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
"(xhtml)", "(XLink)", "(XSLT)",
"(MathML)", "(RDF)", "(XUL)"};
if (nsid < std::size(kNSURIs)) {
SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
kNSURIs[nsid], uri.get());
} else {
SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
}
cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
} else {
NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
}
if (!nsINode::Traverse(tmp, cb)) {
return NS_SUCCESS_INTERRUPTED_TRAVERSE;
}
tmp->mExternalResourceMap.Traverse(&cb);
// Traverse all Document pointer members.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFragmentDirective)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFullscreenEvents)
// Traverse all Document nsCOMPtrs.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
DocumentOrShadowRoot::Traverse(tmp, cb);
if (tmp->mRadioGroupContainer) {
RadioGroupContainer::Traverse(tmp->mRadioGroupContainer.get(), cb);
}
for (auto& sheets : tmp->mAdditionalSheets) {
tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementsObservedForLastRememberedSize)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPermissionDelegateHandler)
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(mActiveViewTransition)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mViewTransitionUpdateCallbacks)
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);
}
for (auto& tableEntry : tmp->mActiveLocks) {
ImplCycleCollectionTraverse(cb, *tableEntry.GetModifiableData(),
"mActiveLocks entry", 0);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
tmp->mInUnlinkOrDeletion = true;
tmp->SetStateObject(nullptr);
// Clear out our external resources
tmp->mExternalResourceMap.Shutdown();
nsAutoScriptBlocker scriptBlocker;
nsINode::Unlink(tmp);
while (tmp->HasChildren()) {
// Hold a strong ref to the node when we remove it, because we may be
// the last reference to it.
// If this code changes, change the corresponding code in Document's
// unlink impl and ContentUnbinder::UnbindSubtree.
nsCOMPtr<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(mLazyLoadObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementsObservedForLastRememberedSize);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFragmentDirective)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingFullscreenEvents)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPermissionDelegateHandler)
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(mActiveViewTransition)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mViewTransitionUpdateCallbacks)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
tmp->mDocGroup);
}
tmp->mDocGroup = nullptr;
if (tmp->IsTopLevelContentDocument()) {
RemoveToplevelLoadingDocument(tmp);
}
tmp->mParentDocument = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
if (tmp->mListenerManager) {
tmp->mListenerManager->Disconnect();
tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
tmp->mListenerManager = nullptr;
}
if (tmp->mStyleSheetSetList) {
tmp->mStyleSheetSetList->Disconnect();
tmp->mStyleSheetSetList = nullptr;
}
tmp->mSubDocuments = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
DocumentOrShadowRoot::Unlink(tmp);
tmp->mRadioGroupContainer = nullptr;
// Document has a pretty complex destructor, so we're going to
// assume that *most* cycles you actually want to break somewhere
// else, and not unlink an awful lot here.
tmp->mExpandoAndGeneration.OwnerUnlinked();
if (tmp->mAnimationController) {
tmp->mAnimationController->Unlink();
}
tmp->mPendingTitleChangeEvent.Revoke();
if (tmp->mCSSLoader) {
tmp->mCSSLoader->DropDocumentReference();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
}
if (tmp->mScriptLoader) {
tmp->mScriptLoader->DropDocumentReference();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptLoader)
}
// 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->mActiveLocks.Clear();
if (tmp->isInList()) {
MOZ_ASSERT(AllDocumentsList().contains(tmp));
tmp->remove();
}
tmp->mInUnlinkOrDeletion = false;
tmp->UnregisterFromMemoryReportingForDataDocument();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
nsresult Document::Init(nsIPrincipal* aPrincipal,
nsIPrincipal* aPartitionedPrincipal) {
if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
return NS_ERROR_ALREADY_INITIALIZED;
}
// Force initialization.
mOnloadBlocker = new OnloadBlocker();
mStyleImageLoader = new css::ImageLoader(this);
mNodeInfoManager = new nsNodeInfoManager(this, aPrincipal);
// mNodeInfo keeps NodeInfoManager alive!
mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
"Bad NodeType in aNodeInfo");
NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
mCSSLoader = new css::Loader(this);
// Assume we're not quirky, until we know otherwise
mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
// If after creation the owner js global is not set for a document
// we use the default compartment for this document, instead of creating
// wrapper in some random compartment when the document is exposed to js
// via some events.
nsCOMPtr<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());
if (aPrincipal) {
SetPrincipals(aPrincipal, aPartitionedPrincipal);
} else {
RecomputeResistFingerprinting();
}
AllDocumentsList().insertBack(this);
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) {
mIsInPrivateBrowsing = NS_UsePrivateBrowsing(aChannel);
// Note: this code is duplicated in PrototypeDocumentContentSink::Init and
// nsScriptSecurityManager::GetChannelResultPrincipals.
// Note: this should match the uri used for the OnNewURI call in
// nsDocShell::CreateDocumentViewer.
NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
nsIScriptSecurityManager* securityManager =
nsContentUtils::GetSecurityManager();
if (securityManager) {
securityManager->GetChannelResultPrincipals(
aChannel, getter_AddRefs(principal),
getter_AddRefs(partitionedPrincipal));
}
}
bool equal = principal->Equals(partitionedPrincipal);
principal = MaybeDowngradePrincipal(principal);
if (equal) {
partitionedPrincipal = principal;
} else {
partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
}
ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
// Note that, since mTiming does not change during a reset, the
// navigationStart time remains unchanged and therefore any future new
// timeline will have the same global clock time as the old one.
mDocumentTimeline = nullptr;
if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
mDocumentBaseURI = baseURI.forget();
mChromeXHRDocBaseURI = nullptr;
}
}
mChannel = aChannel;
RecomputeResistFingerprinting();
MaybeRecomputePartitionKey();
}
void Document::DisconnectNodeTree() {
// Delete references to sub-documents and kill the subdocument map,
// if any. This is not strictly needed, but makes the node tree
// teardown a bit faster.
mSubDocuments = nullptr;
bool oldVal = mInUnlinkOrDeletion;
mInUnlinkOrDeletion = true;
{ // Scope for update
MOZ_AUTO_DOC_UPDATE(this, true);
// Destroy link map now so we don't waste time removing
// links one by one
DestroyElementMaps();
// Invalidate cached array of child nodes
InvalidateChildNodes();
while (nsCOMPtr<nsIContent> content = GetLastChild()) {
nsMutationGuard::DidMutate();
MutationObservers::NotifyContentWillBeRemoved(this, content, nullptr);
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;
}
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;
if (aLoadGroup) {
nsCOMPtr<nsIInterfaceRequestor> callbacks;
aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
if (callbacks) {
nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
if (loadContext) {
// This is asserting that if we previously set mIsInPrivateBrowsing
// to true from the channel in Document::Reset, that the loadContext
// also believes it to be true.
MOZ_ASSERT(!mIsInPrivateBrowsing ||
mIsInPrivateBrowsing == loadContext->UsePrivateBrowsing());
mIsInPrivateBrowsing = loadContext->UsePrivateBrowsing();
}
}
mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
// there was an assertion here that aLoadGroup was not null. This
// is no longer valid: nsDocShell::SetDocument does not create a
// load group, and it works just fine
// XXXbz what does "just fine" mean exactly? And given that there
// is no nsDocShell::SetDocument, what is this talking about?
if (IsContentDocument()) {
// Inform the associated request context about this load start so
// any of its internal load progress flags gets reset.
nsCOMPtr<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 = nullptr;
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_CRASH(
"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) {
ServoStyleSet& styleSet = EnsureStyleSet();
// lowest index first
const size_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
MOZ_ASSERT(newDocIndex != mStyleSheets.NoIndex);
size_t index = styleSet.SheetCount(StyleOrigin::Author);
while (index--) {
auto* sheet = styleSet.SheetAt(StyleOrigin::Author, index);
MOZ_ASSERT(sheet);
if (!sheet->GetAssociatedDocumentOrShadowRoot()) {
// If the sheet is not owned by the document it should be an author sheet
// registered at nsStyleSheetService, or an additional sheet. In that case
// the doc sheet should end up before it.
// FIXME(emilio): Additional stylesheets inconsistently end up with
// associated document, depending on which code-path adds them. Fix this.
MOZ_ASSERT(
nsStyleSheetService::GetInstance()->AuthorStyleSheets()->Contains(
sheet) ||
mAdditionalSheets[eAuthorSheet].Contains(sheet));
continue;
}
size_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
if (MOZ_UNLIKELY(sheetDocIndex == mStyleSheets.NoIndex)) {
MOZ_ASSERT_UNREACHABLE("Which stylesheet can hit this?");
continue;
}
MOZ_ASSERT(sheetDocIndex != newDocIndex);
if (sheetDocIndex < newDocIndex) {
// We found a document-owned sheet. All of them go together, so if the
// current sheet goes before ours, we're at the right index already.
return index + 1;
}
// Otherwise keep looking. Unfortunately we can't do something clever like:
//
// return index - sheetDocIndex + newDocIndex;
//
// Or so, because we need to deal with disabled / non-applicable sheets
// which are not in the styleset, even though they're in the document.
}
// We found no sheet that goes before us, so we're index 0.
return 0;
}
void Document::ResetStylesheetsToURI(nsIURI* aURI) {
MOZ_ASSERT(aURI);
ClearAdoptedStyleSheets();
ServoStyleSet& styleSet = EnsureStyleSet();
auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
for (auto& sheet : Reversed(aSheetList)) {
sheet->ClearAssociatedDocumentOrShadowRoot();
if (mStyleSetFilled) {
styleSet.RemoveStyleSheet(*sheet);
}
}
aSheetList.Clear();
};
ClearSheetList(mStyleSheets);
for (auto& sheets : mAdditionalSheets) {
ClearSheetList(sheets);
}
if (mStyleSetFilled) {
if (auto* ss = nsStyleSheetService::GetInstance()) {
for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
if (sheet->IsApplicable()) {
styleSet.RemoveStyleSheet(*sheet);
}
}
}
}
// Now reset our inline style and attribute sheets.
if (mAttributeStyles) {
mAttributeStyles->Reset();
mAttributeStyles->SetOwningDocument(this);
} else {
mAttributeStyles = new AttributeStyles(this);
}
if (mStyleSetFilled) {
FillStyleSetDocumentSheets();
if (styleSet.StyleSheetsHaveChanged()) {
ApplicableStylesChanged();
}
}
}
void Document::FillStyleSetUserAndUASheets() {
// Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
// ordering.
// The document will fill in the document sheets when we create the presshell
auto* cache = GlobalStyleSheetCache::Singleton();
nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
MOZ_ASSERT(sheetService,
"should never be creating a StyleSet after the style sheet "
"service has gone");
ServoStyleSet& styleSet = EnsureStyleSet();
for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
styleSet.AppendStyleSheet(*sheet);
}
StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
: cache->GetUserContentSheet();
if (sheet) {
styleSet.AppendStyleSheet(*sheet);
}
styleSet.AppendStyleSheet(*cache->UASheet());
if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
styleSet.AppendStyleSheet(*cache->MathMLSheet());
}
if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
styleSet.AppendStyleSheet(*cache->SVGSheet());
}
styleSet.AppendStyleSheet(*cache->HTMLSheet());
if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
styleSet.AppendStyleSheet(*cache->NoFramesSheet());
}
styleSet.AppendStyleSheet(*cache->CounterStylesSheet());
// Only load the full XUL sheet if we'll need it.
if (LoadsFullXULStyleSheetUpFront()) {
styleSet.AppendStyleSheet(*cache->XULSheet());
}
styleSet.AppendStyleSheet(*cache->FormsSheet());
styleSet.AppendStyleSheet(*cache->ScrollbarsSheet());
for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
styleSet.AppendStyleSheet(*sheet);
}
MOZ_ASSERT(!mQuirkSheetAdded);
if (NeedsQuirksSheet()) {
styleSet.AppendStyleSheet(*cache->QuirkSheet());
mQuirkSheetAdded = true;
}
}
void Document::FillStyleSet() {
MOZ_ASSERT(!mStyleSetFilled);
FillStyleSetUserAndUASheets();
FillStyleSetDocumentSheets();
mStyleSetFilled = true;
}
void Document::FillStyleSetDocumentSheets() {
ServoStyleSet& styleSet = EnsureStyleSet();
MOZ_ASSERT(styleSet.SheetCount(StyleOrigin::Author) == 0,
"Style set already has document sheets?");
// Sheets are added in reverse order to avoid worst-case time complexity when
// looking up the index of a sheet.
//
// Note that usually appending is faster (rebuilds less stuff in the
// styleset), but in this case it doesn't matter since we're filling the
// styleset from scratch anyway.
for (StyleSheet* sheet : Reversed(mStyleSheets)) {
if (sheet->IsApplicable()) {
styleSet.AddDocStyleSheet(*sheet);
}
}
EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
if (aSheet.IsApplicable()) {
styleSet.AddDocStyleSheet(aSheet);
}
});
nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
styleSet.AppendStyleSheet(*sheet);
}
for (auto& sheets : mAdditionalSheets) {
for (StyleSheet* sheet : sheets) {
styleSet.AppendStyleSheet(*sheet);
}
}
}
void Document::CompatibilityModeChanged() {
MOZ_ASSERT(IsHTMLOrXHTML());
CSSLoader()->SetCompatibilityMode(mCompatMode);
if (mStyleSet) {
mStyleSet->CompatibilityModeChanged();
}
if (!mStyleSetFilled) {
MOZ_ASSERT(!mQuirkSheetAdded);
return;
}
MOZ_ASSERT(mStyleSet);
if (PresShell* presShell = GetPresShell()) {
// Selectors may have become case-sensitive / case-insensitive, the stylist
// has already performed the relevant invalidation.
presShell->EnsureStyleFlush();
}
if (mQuirkSheetAdded == NeedsQuirksSheet()) {
return;
}
auto* cache = GlobalStyleSheetCache::Singleton();
StyleSheet* sheet = cache->QuirkSheet();
if (mQuirkSheetAdded) {
mStyleSet->RemoveStyleSheet(*sheet);
} else {
mStyleSet->AppendStyleSheet(*sheet);
}
mQuirkSheetAdded = !mQuirkSheetAdded;
ApplicableStylesChanged();
}
void Document::SetCompatibilityMode(nsCompatibility aMode) {
NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
"Bad compat mode for XHTML document!");
if (mCompatMode == aMode) {
return;
}
mCompatMode = aMode;
CompatibilityModeChanged();
// Trigger recomputation of the nsViewportInfo the next time it's queried.
mViewportType = Unknown;
}
static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
uint32_t aSandboxFlags,
nsIChannel* aChannel) {
// If the document permits allow-top-navigation and
// allow-top-navigation-by-user-activation this will permit all top
// navigation.
if (aSandboxFlags != SANDBOXED_NONE &&
!(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
!(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
nsContentUtils::ReportToConsole(
nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
"BothAllowTopNavigationAndUserActivationPresent");
}
// If the document is sandboxed (via the HTML5 iframe sandbox
// attribute) and both the allow-scripts and allow-same-origin
// keywords are supplied, the sandboxed document can call into its
// parent document and remove its sandboxing entirely - we print a
// warning to the web console in this case.
if (aSandboxFlags & SANDBOXED_NAVIGATION &&
!(aSandboxFlags & SANDBOXED_SCRIPTS) &&
!(aSandboxFlags & SANDBOXED_ORIGIN)) {
RefPtr<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>(),
SourceLocation(iframeUri.get()));
}
}
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());
}
static void CheckIsBadPolicy(nsILoadInfo::CrossOriginOpenerPolicy aPolicy,
BrowsingContext* aContext, nsIChannel* aChannel) {
#if defined(EARLY_BETA_OR_EARLIER)
auto requireCORP =
nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
if (aContext->GetOpenerPolicy() == aPolicy ||
(aContext->GetOpenerPolicy() != requireCORP && aPolicy != requireCORP)) {
return;
}
nsCOMPtr<nsIURI> uri;
bool hasURI = NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(uri)));
bool isViewSource = hasURI && uri->SchemeIs("view-source");
nsCString contentType;
nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
bool isPDFJS = bag &&
NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
contentType)) &&
contentType.EqualsLiteral(APPLICATION_PDF);
MOZ_DIAGNOSTIC_ASSERT(!isViewSource,
"Bug 1834864: Assert due to view-source.");
MOZ_DIAGNOSTIC_ASSERT(!isPDFJS, "Bug 1834864: Assert due to pdfjs.");
MOZ_DIAGNOSTIC_ASSERT(aPolicy == requireCORP,
"Assert due to clearing REQUIRE_CORP.");
MOZ_DIAGNOSTIC_ASSERT(aContext->GetOpenerPolicy() == requireCORP,
"Assert due to setting REQUIRE_CORP.");
#endif // defined(EARLY_BETA_OR_EARLIER)
}
nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
nsILoadGroup* aLoadGroup,
nsISupports* aContainer,
nsIStreamListener** aDocListener,
bool aReset) {
if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
nsCOMPtr<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()) {
CheckIsBadPolicy(policy, docShell->GetBrowsingContext(), aChannel);
// Setting the opener policy on a discarded context has no effect.
Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
}
// The CSP directives upgrade-insecure-requests as well as
// block-all-mixed-content not only apply to the toplevel document,
// but also to nested documents. The loadInfo of a subdocument
// load already holds the correct flag, so let's just set it here
// on the document. Please note that we set the appropriate preload
// bits just for the sake of completeness here, because the preloader
// does not reach into subdocuments.
mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
mBlockAllMixedContentPreloads = mBlockAllMixedContent;
// HTTPS-Only Mode flags
// The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
// sub-resources and sub-documents.
mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
nsresult rv = InitReferrerInfo(aChannel);
NS_ENSURE_SUCCESS(rv, rv);
rv = InitCOEP(aChannel);
NS_ENSURE_SUCCESS(rv, rv);
// HACK: Calling EnsureIPCPoliciesRead() here will parse the CSP using the
// context's current mSelfURI (which is still the previous mSelfURI),
// bypassing some internal bugs with 'self' and iframe inheritance.
// Not calling it here results in the mSelfURI being the current mSelfURI and
// not the previous which breaks said inheritance.
nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
if (cspToInherit) {
cspToInherit->EnsureIPCPoliciesRead();
}
rv = InitCSP(aChannel);
NS_ENSURE_SUCCESS(rv, rv);
rv = InitDocPolicy(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);
MaybeRecomputePartitionKey();
// 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->GetAsInnerWindow()) {
nsGlobalWindowInner::Cast(window)
->RegisterDataDocumentForMemoryReporting(this);
}
}
}
}
nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
void Document::SetCsp(nsIContentSecurityPolicy* aCSP) {
mCSP = aCSP;
mHasPolicyWithRequireTrustedTypesForDirective =
aCSP && aCSP->GetRequireTrustedTypesForDirectiveState() !=
RequireTrustedTypesForDirectiveState::NONE;
}
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:
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();
mHasPolicyWithRequireTrustedTypesForDirective = false;
} else {
mHasPolicyWithRequireTrustedTypesForDirective =
mCSP->GetRequireTrustedTypesForDirectiveState() !=
RequireTrustedTypesForDirectiveState::NONE;
}
// 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();
MOZ_ASSERT(!BasePrincipal::Cast(principal)->Is<ExpandedPrincipal>());
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);
}
// ----- 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 FeaturePolicy* GetFeaturePolicyFromElement(Element* aElement) {
if (auto* iframe = HTMLIFrameElement::FromNodeOrNull(aElement)) {
return iframe->FeaturePolicy();
}
if (!HTMLObjectElement::FromNodeOrNull(aElement) &&
!HTMLEmbedElement::FromNodeOrNull(aElement)) {
return nullptr;
}
return aElement->OwnerDoc()->FeaturePolicy();
}
nsresult Document::InitDocPolicy(nsIChannel* aChannel) {
// We only use document policy to implement the text fragments spec, so leave
// everything at the default value if it isn't enabled. This includes the
// behavior for element fragments.
if (!StaticPrefs::dom_text_fragments_enabled()) {
return NS_OK;
}
nsCOMPtr<nsIHttpChannel> httpChannel;
nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsAutoCString docPolicyString;
if (httpChannel) {
Unused << httpChannel->GetResponseHeader("Document-Policy"_ns,
docPolicyString);
}
if (docPolicyString.IsEmpty()) {
return NS_OK;
}
mForceLoadAtTop = NS_GetForceLoadAtTopFromHeader(docPolicyString);
return NS_OK;
}
void Document::InitFeaturePolicy(
const Variant<Nothing, FeaturePolicyInfo, Element*>&
aContainerFeaturePolicy) {
MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created");
mFeaturePolicy->ResetDeclaredPolicy();
mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
RefPtr<dom::FeaturePolicy> featurePolicy = mFeaturePolicy;
aContainerFeaturePolicy.match(
[](const Nothing&) {},
[featurePolicy](const FeaturePolicyInfo& aContainerFeaturePolicy) {
// Let's inherit the policy from the possibly cross-origin container.
featurePolicy->InheritPolicy(aContainerFeaturePolicy);
featurePolicy->SetSrcOrigin(aContainerFeaturePolicy.mSrcOrigin);
},
[featurePolicy](Element* aContainer) {
// Let's inherit the policy from the parent container element if it
// exists.
if (RefPtr<dom::FeaturePolicy> containerFeaturePolicy =
GetFeaturePolicyFromElement(aContainer)) {
featurePolicy->InheritPolicy(containerFeaturePolicy);
featurePolicy->SetSrcOrigin(containerFeaturePolicy->GetSrcOrigin());
}
});
}
Element* GetEmbedderElementFrom(BrowsingContext* aBrowsingContext) {
if (!aBrowsingContext) {
return nullptr;
}
if (!aBrowsingContext->IsContentSubframe()) {
return nullptr;
}
return aBrowsingContext->GetEmbedderElement();
}
nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
if (Element* embedderElement = GetEmbedderElementFrom(GetBrowsingContext())) {
InitFeaturePolicy(AsVariant(embedderElement));
} else if (Maybe<FeaturePolicyInfo> featurePolicyContainer =
loadInfo->GetContainerFeaturePolicyInfo()) {
InitFeaturePolicy(AsVariant(*featurePolicyContainer));
} else {
InitFeaturePolicy(AsVariant(Nothing{}));
}
// 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.
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;
// This loosely implements §3.4.1 of Text Fragments
// Unlike specified in the spec, the fragment directive is not stripped from
// the URL in the session history entry. Instead it is removed when the URL is
// set in the `Document`. Also, instead of storing the `uninvokedDirective` in
// `Document` as mentioned in the spec, the extracted directives are moved to
// the `FragmentDirective` object which deals with finding the ranges to
// highlight in `ScrollToRef()`.
// XXX(:jjaschke): This is only a temporary solution.
// https://bugzil.la/1881429 is filed for revisiting this.
nsTArray<TextDirective> textDirectives;
FragmentDirective::ParseAndRemoveFragmentDirectiveFromFragment(
mDocumentURI, &textDirectives);
if (!textDirectives.IsEmpty()) {
SetUseCounter(eUseCounter_custom_TextDirectivePages);
}
FragmentDirective()->SetTextDirectives(std::move(textDirectives));
nsIURI* newBase = GetDocBaseURI();
mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI);
bool equalBases = false;
// Changing just the ref of a URI does not change how relative URIs would
// resolve wrt to it, so we can treat the bases as equal as long as they're
// equal ignoring the ref.
if (oldBase && newBase) {
oldBase->EqualsExceptRef(newBase, &equalBases);
} else {
equalBases = !oldBase && !newBase;
}
// If this is the first time we're setting the document's URI, set the
// document's original URI.
if (!mOriginalURI) mOriginalURI = mDocumentURI;
// If changing the document's URI changed the base URI of the document, we
// need to refresh the hrefs of all the links on the page.
if (!equalBases) {
mCachedURLData = nullptr;
RefreshLinkHrefs();
}
// Recalculate our base domain
mBaseDomain.Truncate();
ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
if (thirdPartyUtil) {
Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
}
// Tell our WindowGlobalParent that the document's URI has been changed.
if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
wgc->SetDocumentURI(mDocumentURI);
}
}
static void GetFormattedTimeString(PRTime aTime, bool aUniversal,
nsAString& aFormattedTimeString) {
PRExplodedTime prtime;
PR_ExplodeTime(aTime, aUniversal ? PR_GMTParameters : PR_LocalTimeParameters,
&prtime);
// "MM/DD/YYYY hh:mm:ss"
char formatedTime[24];
if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
} else {
// If we for whatever reason failed to find the last modified time
// (or even the current time), fall back to what NS4.x returned.
aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
}
}
void Document::GetLastModified(nsAString& aLastModified) const {
if (!mLastModified.IsEmpty()) {
aLastModified.Assign(mLastModified);
} else {
GetFormattedTimeString(PR_Now(),
ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
aLastModified);
}
}
static void IncrementExpandoGeneration(Document& aDoc) {
++aDoc.mExpandoAndGeneration.generation;
}
void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
MOZ_ASSERT(nsGenericHTMLElement::ShouldExposeNameAsWindowProperty(aElement),
"Only put elements that need to be exposed as window['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::AddToDocumentNameTable(nsGenericHTMLElement* aElement,
nsAtom* aName) {
MOZ_ASSERT(
nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) ||
nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(
aElement),
"Only put elements that need to be exposed as document['name'] in "
"the document named table.");
if (IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName)) {
entry->AddDocumentNameElement(this, aElement);
}
}
void Document::RemoveFromDocumentNameTable(nsGenericHTMLElement* aElement,
nsAtom* aName) {
if (mIdentifierMap.Count() == 0) {
return;
}
if (IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName)) {
entry->RemoveDocumentNameElement(aElement);
nsBaseContentList* list = entry->GetDocumentNameContentList();
if (!list || list->Length() == 0) {
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;
}
}
mScriptLoader->DeregisterFromCache();
mCSSLoader->DeregisterFromSheetCache();
mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
mPartitionedPrincipal = aNewPartitionedPrincipal;
mCachedURLData = nullptr;
mCSSLoader->RegisterInSheetCache();
mScriptLoader->RegisterToCache();
RecomputeResistFingerprinting();
#ifdef DEBUG
// Validate that the docgroup is set correctly.
//
// 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) {
AssertDocGroupMatchesKey();
}
#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, as
// documents without a BrowsingContext do not need to have a matching
// principal to their DocGroup.
// 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());
mDocGroup->AssertMatches(this);
}
}
#endif
nsresult Document::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) const {
return SchedulerGroup::Dispatch(std::move(aRunnable));
}
void Document::NoteScriptTrackingStatus(const nsACString& aURL,
bool aIsTracking) {
if (aIsTracking) {
mTrackingScripts.Insert(aURL);
}
// Ideally, whether a given script is tracking or not should be consistent,
// but there is a race so that it is not, when loading real sites in debug
// builds. See bug 1925286.
// MOZ_ASSERT_IF(!aIsTracking, !mTrackingScripts.Contains(aURL));
}
bool Document::IsScriptTracking(JSContext* aCx) const {
JS::AutoFilename filename;
if (!JS::DescribeScriptedCaller(&filename, aCx)) {
return false;
}
return mTrackingScripts.Contains(nsDependentCString(filename.get()));
}
void Document::GetContentType(nsAString& aContentType) {
CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
}
void Document::SetContentType(const nsACString& aContentType) {
if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
aContentType.EqualsLiteral("application/xhtml+xml")) {
mDefaultElementType = kNameSpaceID_XHTML;
}
mCachedEncoder = nullptr;
mContentType = aContentType;
}
bool Document::HasPendingInitialTranslation() {
return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
}
bool Document::HasPendingL10nMutations() const {
return mDocumentL10n && mDocumentL10n->HasPendingMutations();
}
bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
JS::Rooted<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(nsGkAtoms::href, href);
if (!mDocumentL10n) {
Element* elem = GetDocumentElement();
MOZ_DIAGNOSTIC_ASSERT(elem);
bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
mDocumentL10n = DocumentL10n::Create(this, isSync);
if (NS_WARN_IF(!mDocumentL10n)) {
return;
}
}
mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
if (mReadyState >= READYSTATE_INTERACTIVE) {
nsContentUtils::AddScriptRunner(NewRunnableMethod(
"DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
&DocumentL10n::TriggerInitialTranslation));
} else {
if (!mDocumentL10n->mBlockingLayout) {
// Our initial translation is going to block layout start. Make sure
// we don't fire the load event until after that stops happening and
// layout has a chance to start.
BlockOnload();
mDocumentL10n->mBlockingLayout = true;
}
}
}
void Document::LocalizationLinkRemoved(Element* aLinkElement) {
if (!AllowsL10n()) {
return;
}
if (mDocumentL10n) {
nsAutoString href;
aLinkElement->GetAttr(nsGkAtoms::href, href);
uint32_t remaining =
mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
if (remaining == 0) {
if (mDocumentL10n->mBlockingLayout) {
mDocumentL10n->mBlockingLayout = false;
UnblockOnload(/* aFireSync = */ false);
}
mDocumentL10n = nullptr;
}
}
}
/**
* This method should be called once the end of the l10n
* resource container has been parsed.
*
* In XUL this is the end of the first </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;
}
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->UpdateEditableState(true);
}
}
MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
}
void Document::SetDocumentEditableFlag(bool aEditable) {
if (HasFlag(NODE_IS_EDITABLE) == aEditable) {
return;
}
SetEditableFlag(aEditable);
// Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
// state of all descendant elements of it. Update that now.
NotifyEditableStateChange(*this);
}
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")) {
SetDocumentEditableFlag(!editableMode);
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",
Com