Bug 1648344 Part 1: Apply default viewport width in more circumstances, matching...
[gecko.git] / dom / base / Document.cpp
blobd22e3ccba936fe513ec27ca4d8f9c13d3b59f8b6
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * Base class for all our document implementations.
9 */
11 #include "AudioChannelService.h"
12 #include "mozilla/dom/Document.h"
13 #include "DocumentInlines.h"
14 #include "mozilla/AntiTrackingUtils.h"
15 #include "mozilla/ArrayUtils.h"
16 #include "mozilla/AutoRestore.h"
17 #include "mozilla/BinarySearch.h"
18 #include "mozilla/ContentBlocking.h"
19 #include "mozilla/ContentBlockingAllowList.h"
20 #include "mozilla/ContentBlockingUserInteraction.h"
21 #include "mozilla/CSSEnabledState.h"
22 #include "mozilla/DebugOnly.h"
23 #include "mozilla/EditorCommands.h"
24 #include "mozilla/EffectSet.h"
25 #include "mozilla/EnumSet.h"
26 #include "mozilla/HTMLEditor.h"
27 #include "mozilla/IdentifierMapEntry.h"
28 #include "mozilla/IntegerRange.h"
29 #include "mozilla/MemoryReporting.h"
30 #include "mozilla/Likely.h"
31 #include "mozilla/LoadInfo.h"
32 #include "mozilla/MediaFeatureChange.h"
33 #include "mozilla/PresShell.h"
34 #include "mozilla/PresShellInlines.h"
35 #include "mozilla/RestyleManager.h"
36 #include "mozilla/StaticPrefs_apz.h"
37 #include "mozilla/StaticPrefs_browser.h"
38 #include "mozilla/StaticPrefs_dom.h"
39 #include "mozilla/StaticPrefs_full_screen_api.h"
40 #include "mozilla/StaticPrefs_layout.h"
41 #include "mozilla/StaticPrefs_network.h"
42 #include "mozilla/StaticPrefs_page_load.h"
43 #include "mozilla/StaticPrefs_plugins.h"
44 #include "mozilla/StaticPrefs_privacy.h"
45 #include "mozilla/StaticPrefs_security.h"
46 #include "mozilla/StaticPrefs_widget.h"
47 #include "mozilla/StaticPresData.h"
48 #include "mozilla/StorageAccess.h"
49 #include "mozilla/StoragePrincipalHelper.h"
50 #include "mozilla/TextControlElement.h"
51 #include "mozilla/TextEditor.h"
52 #include "mozilla/URLDecorationStripper.h"
53 #include "mozilla/URLExtraData.h"
54 #include "mozilla/Base64.h"
55 #include "mozilla/BasePrincipal.h"
56 #include <algorithm>
58 #include "mozilla/Logging.h"
59 #include "plstr.h"
60 #include "mozilla/Sprintf.h"
62 #include "mozilla/Telemetry.h"
63 #include "nsIApplicationCache.h"
64 #include "nsIInlineSpellChecker.h"
65 #include "nsIInterfaceRequestor.h"
66 #include "nsIInterfaceRequestorUtils.h"
67 #include "nsILoadContext.h"
68 #include "nsITextControlFrame.h"
69 #include "nsCommandManager.h"
70 #include "nsCommandParams.h"
71 #include "nsUnicharUtils.h"
72 #include "nsContentList.h"
73 #include "nsCSSPseudoElements.h"
74 #include "nsIObserver.h"
75 #include "nsIBaseWindow.h"
76 #include "nsILayoutHistoryState.h"
77 #include "nsIXULRuntime.h"
78 #include "mozilla/GlobalStyleSheetCache.h"
79 #include "mozilla/css/Loader.h"
80 #include "mozilla/css/ImageLoader.h"
81 #include "nsDocShell.h"
82 #include "nsDocShellLoadTypes.h"
83 #include "nsIDocShellTreeItem.h"
84 #include "nsCOMArray.h"
85 #include "nsQueryObject.h"
86 #include "mozilla/Services.h"
87 #include "nsScreen.h"
88 #include "ChildIterator.h"
89 #include "nsSerializationHelper.h"
90 #include "nsICertOverrideService.h"
91 #include "nsXULElement.h"
92 #include "nsIX509Cert.h"
93 #include "nsIX509CertValidity.h"
94 #include "nsITransportSecurityInfo.h"
95 #include "nsINSSErrorsService.h"
96 #include "nsISocketProvider.h"
97 #include "nsISiteSecurityService.h"
98 #include "nsISHEntry.h"
99 #include "nsSHistory.h"
100 #include "mozilla/PermissionDelegateHandler.h"
102 #include "mozilla/AsyncEventDispatcher.h"
103 #include "mozilla/BasicEvents.h"
104 #include "mozilla/EventListenerManager.h"
105 #include "mozilla/EventStateManager.h"
106 #include "mozilla/FullscreenChange.h"
107 #include "mozilla/PendingAnimationTracker.h"
108 #include "mozilla/intl/LocaleService.h"
110 #include "mozilla/dom/Attr.h"
111 #include "mozilla/dom/BindingDeclarations.h"
112 #include "mozilla/dom/BrowsingContext.h"
113 #include "mozilla/dom/ContentChild.h"
114 #include "mozilla/dom/ContentMediaController.h"
115 #include "mozilla/dom/CSSImportRule.h"
116 #include "mozilla/dom/CSPDictionariesBinding.h"
117 #include "mozilla/dom/DOMIntersectionObserver.h"
118 #include "mozilla/dom/Element.h"
119 #include "mozilla/dom/ElementBinding.h"
120 #include "mozilla/dom/Event.h"
121 #include "mozilla/dom/FailedCertSecurityInfoBinding.h"
122 #include "mozilla/dom/FeaturePolicy.h"
123 #include "mozilla/dom/FeaturePolicyUtils.h"
124 #include "mozilla/dom/HTMLAllCollection.h"
125 #include "mozilla/dom/HTMLMetaElement.h"
126 #include "mozilla/dom/HTMLSharedElement.h"
127 #include "mozilla/dom/HTMLDialogElement.h"
128 #include "mozilla/dom/MediaStatusManager.h"
129 #include "mozilla/dom/MutationObservers.h"
130 #include "mozilla/dom/Navigator.h"
131 #include "mozilla/dom/NetErrorInfoBinding.h"
132 #include "mozilla/dom/Performance.h"
133 #include "mozilla/dom/TreeOrderedArrayInlines.h"
134 #include "mozilla/dom/ResizeObserver.h"
135 #include "mozilla/dom/ResizeObserverController.h"
136 #include "mozilla/dom/ServiceWorkerContainer.h"
137 #include "mozilla/dom/ScriptLoader.h"
138 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
139 #include "mozilla/dom/StyleSheetList.h"
140 #include "mozilla/dom/SVGUseElement.h"
141 #include "mozilla/dom/UserActivation.h"
142 #include "mozilla/net/CookieJarSettings.h"
143 #include "nsGenericHTMLElement.h"
144 #include "mozilla/dom/CDATASection.h"
145 #include "mozilla/dom/ProcessingInstruction.h"
146 #include "mozilla/dom/PostMessageEvent.h"
147 #include "mozilla/ipc/IdleSchedulerChild.h"
148 #include "nsDOMString.h"
149 #include "nsLayoutUtils.h" // for GetFrameForPoint
150 #include "nsIFrame.h"
151 #include "nsIBrowserChild.h"
152 #include "nsImportModule.h"
154 #include "nsRange.h"
155 #include "mozilla/dom/DocumentType.h"
156 #include "mozilla/dom/NodeIterator.h"
157 #include "mozilla/dom/Promise.h"
158 #include "mozilla/dom/PromiseNativeHandler.h"
159 #include "mozilla/dom/TreeWalker.h"
161 #include "mozilla/dom/ServiceWorkerManager.h"
162 #include "imgLoader.h"
164 #include "nsAboutProtocolUtils.h"
165 #include "nsCanvasFrame.h"
166 #include "nsContentCID.h"
167 #include "nsContentSecurityUtils.h"
168 #include "nsError.h"
169 #include "nsPresContext.h"
170 #include "nsThreadUtils.h"
171 #include "nsNodeInfoManager.h"
172 #include "nsIBrowserUsage.h"
173 #include "nsIEditingSession.h"
174 #include "nsIFileChannel.h"
175 #include "nsIMultiPartChannel.h"
176 #include "nsIRefreshURI.h"
177 #include "nsIWebNavigation.h"
178 #include "nsIScriptError.h"
179 #include "nsISimpleEnumerator.h"
180 #include "nsIRequestContext.h"
181 #include "nsStyleSheetService.h"
183 #include "nsNetUtil.h" // for NS_NewURI
184 #include "nsIInputStreamChannel.h"
185 #include "nsIAuthPrompt.h"
186 #include "nsIAuthPrompt2.h"
188 #include "mozilla/PermissionManager.h"
189 #include "nsIScriptSecurityManager.h"
190 #include "nsIPermission.h"
191 #include "nsIPrincipal.h"
192 #include "nsIPrivateBrowsingChannel.h"
193 #include "ExpandedPrincipal.h"
194 #include "mozilla/NullPrincipal.h"
196 #include "nsPIDOMWindow.h"
197 #include "nsFocusManager.h"
198 #include "nsICookieService.h"
200 #include "nsBidiUtils.h"
202 #include "nsContentCreatorFunctions.h"
204 #include "nsIScriptContext.h"
205 #include "nsHTMLDocument.h"
206 #include "nsIRequest.h"
207 #include "mozilla/dom/BlobURLProtocolHandler.h"
209 #include "nsCharsetSource.h"
210 #include "nsIParser.h"
211 #include "nsIContentSink.h"
213 #include "mozilla/EventDispatcher.h"
214 #include "mozilla/EventStates.h"
215 #include "mozilla/InternalMutationEvent.h"
216 #include "nsDOMCID.h"
218 #include "jsapi.h"
219 #include "xpcpublic.h"
220 #include "nsCCUncollectableMarker.h"
221 #include "nsIContentPolicy.h"
222 #include "nsContentPolicyUtils.h"
223 #include "nsICategoryManager.h"
224 #include "nsIDocumentLoaderFactory.h"
225 #include "nsIDocumentLoader.h"
226 #include "nsIContentViewer.h"
227 #include "nsIXMLContentSink.h"
228 #include "nsIPrompt.h"
229 #include "nsIPropertyBag2.h"
230 #include "mozilla/dom/PageTransitionEvent.h"
231 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
232 #include "nsJSUtils.h"
233 #include "nsFrameLoader.h"
234 #include "nsEscape.h"
235 #include "nsObjectLoadingContent.h"
236 #include "nsHtml5TreeOpExecutor.h"
237 #include "mozilla/dom/HTMLFormElement.h"
238 #include "mozilla/dom/HTMLLinkElement.h"
239 #include "mozilla/dom/HTMLMediaElement.h"
240 #include "mozilla/dom/HTMLIFrameElement.h"
241 #include "mozilla/dom/HTMLImageElement.h"
242 #include "mozilla/dom/HTMLTextAreaElement.h"
243 #include "mozilla/dom/MediaSource.h"
245 #include "mozAutoDocUpdate.h"
246 #include "nsGlobalWindow.h"
247 #include "mozilla/Encoding.h"
248 #include "nsDOMNavigationTiming.h"
250 #include "mozilla/SMILAnimationController.h"
251 #include "imgIContainer.h"
253 #include "nsRefreshDriver.h"
255 // FOR CSP (autogenerated by xpidl)
256 #include "nsIContentSecurityPolicy.h"
257 #include "mozilla/dom/nsCSPContext.h"
258 #include "mozilla/dom/nsCSPService.h"
259 #include "mozilla/dom/nsCSPUtils.h"
260 #include "nsHTMLStyleSheet.h"
261 #include "nsHTMLCSSStyleSheet.h"
262 #include "mozilla/dom/DOMImplementation.h"
263 #include "mozilla/dom/ShadowRoot.h"
264 #include "mozilla/dom/Comment.h"
265 #include "nsTextNode.h"
266 #include "mozilla/dom/Link.h"
267 #include "mozilla/dom/HTMLCollectionBinding.h"
268 #include "mozilla/dom/HTMLElementBinding.h"
269 #include "nsXULAppAPI.h"
270 #include "mozilla/dom/Touch.h"
271 #include "mozilla/dom/TouchEvent.h"
273 #include "mozilla/Preferences.h"
275 #include "imgRequestProxy.h"
276 #include "nsWrapperCacheInlines.h"
277 #include "nsSandboxFlags.h"
278 #include "mozilla/dom/AnimatableBinding.h"
279 #include "mozilla/dom/AnonymousContent.h"
280 #include "mozilla/dom/BindingUtils.h"
281 #include "mozilla/dom/ClientInfo.h"
282 #include "mozilla/dom/ClientState.h"
283 #include "mozilla/dom/DocumentFragment.h"
284 #include "mozilla/dom/DocumentTimeline.h"
285 #include "mozilla/dom/Event.h"
286 #include "mozilla/dom/HTMLBodyElement.h"
287 #include "mozilla/dom/HTMLInputElement.h"
288 #include "mozilla/dom/ImageTracker.h"
289 #include "mozilla/dom/MediaQueryList.h"
290 #include "mozilla/dom/NodeFilterBinding.h"
291 #include "mozilla/OwningNonNull.h"
292 #include "mozilla/dom/BrowserChild.h"
293 #include "mozilla/dom/WebComponentsBinding.h"
294 #include "mozilla/dom/CustomElementRegistryBinding.h"
295 #include "mozilla/dom/CustomElementRegistry.h"
296 #include "mozilla/dom/ServiceWorkerDescriptor.h"
297 #include "mozilla/dom/TimeoutManager.h"
298 #include "mozilla/dom/DocumentL10n.h"
299 #include "mozilla/ExtensionPolicyService.h"
300 #include "nsIFrame.h"
301 #include "nsDOMCaretPosition.h"
302 #include "nsViewportInfo.h"
303 #include "mozilla/StaticPtr.h"
304 #include "nsIHttpChannelInternal.h"
305 #include "nsISecurityConsoleMessage.h"
306 #include "nsCharSeparatedTokenizer.h"
307 #include "mozilla/dom/XPathEvaluator.h"
308 #include "mozilla/dom/XPathNSResolverBinding.h"
309 #include "mozilla/dom/XPathResult.h"
310 #include "nsIDocumentEncoder.h"
311 #include "nsIDocumentActivity.h"
312 #include "nsIStructuredCloneContainer.h"
313 #include "mozilla/dom/DOMStringList.h"
314 #include "nsWindowSizes.h"
315 #include "mozilla/dom/Location.h"
316 #include "mozilla/dom/FontFaceSet.h"
317 #include "mozilla/ServoStyleSet.h"
318 #include "mozilla/StyleSheet.h"
319 #include "mozilla/StyleSheetInlines.h"
320 #include "mozilla/dom/SVGDocument.h"
321 #include "mozilla/dom/SVGSVGElement.h"
322 #include "mozilla/dom/DocGroup.h"
323 #include "mozilla/dom/ChromeObserver.h"
324 #ifdef MOZ_XUL
325 # include "mozilla/dom/XULBroadcastManager.h"
326 # include "mozilla/dom/XULPersist.h"
327 # include "nsIAppWindow.h"
328 # include "nsXULPrototypeDocument.h"
329 # include "nsXULCommandDispatcher.h"
330 # include "nsXULPopupManager.h"
331 # include "nsIDocShellTreeOwner.h"
332 #endif
334 #include "mozilla/DocLoadingTimelineMarker.h"
336 #include "mozilla/dom/WindowGlobalChild.h"
338 #include "nsISpeculativeConnect.h"
340 #include "mozilla/MediaManager.h"
342 #include "AutoplayPolicy.h"
343 #include "nsIURIMutator.h"
344 #include "mozilla/DocumentStyleRootIterator.h"
345 #include "mozilla/PendingFullscreenEvent.h"
346 #include "mozilla/RestyleManager.h"
347 #include "mozilla/ClearOnShutdown.h"
348 #include "mozilla/ResultExtensions.h"
349 #include "nsHTMLTags.h"
350 #include "MobileViewportManager.h"
351 #include "NodeUbiReporting.h"
352 #include "nsICookieService.h"
353 #include "mozilla/net/ChannelEventQueue.h"
354 #include "mozilla/net/RequestContextService.h"
355 #include "StorageAccessPermissionRequest.h"
356 #include "mozilla/dom/WindowProxyHolder.h"
357 #include "ThirdPartyUtil.h"
358 #include "nsHtml5Module.h"
359 #include "nsHtml5Parser.h"
360 #include "nsTableWrapperFrame.h"
362 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
363 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
364 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
365 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
367 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
369 mozilla::LazyLogModule gPageCacheLog("PageCache");
370 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
372 namespace mozilla {
373 namespace dom {
375 typedef nsTArray<Link*> LinkArray;
377 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
378 nullptr;
380 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
381 static LazyLogModule gCspPRLog("CSP");
382 LazyLogModule gUserInteractionPRLog("UserInteraction");
384 static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
385 nsIHttpChannel** aHttpChannel) {
386 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
387 if (httpChannel) {
388 httpChannel.forget(aHttpChannel);
389 return NS_OK;
392 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
393 if (!multipart) {
394 *aHttpChannel = nullptr;
395 return NS_OK;
398 nsCOMPtr<nsIChannel> baseChannel;
399 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
400 if (NS_WARN_IF(NS_FAILED(rv))) {
401 return rv;
404 httpChannel = do_QueryInterface(baseChannel);
405 httpChannel.forget(aHttpChannel);
407 return NS_OK;
410 } // namespace dom
412 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
414 IdentifierMapEntry::IdentifierMapEntry(
415 const IdentifierMapEntry::DependentAtomOrString* aKey)
416 : mKey(aKey ? *aKey : nullptr) {}
418 void IdentifierMapEntry::Traverse(
419 nsCycleCollectionTraversalCallback* aCallback) {
420 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
421 "mIdentifierMap mNameContentList");
422 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
424 if (mImageElement) {
425 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
426 "mIdentifierMap mImageElement element");
427 nsIContent* imageElement = mImageElement;
428 aCallback->NoteXPCOMChild(imageElement);
432 bool IdentifierMapEntry::IsEmpty() {
433 return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
434 !mImageElement;
437 bool IdentifierMapEntry::HasNameElement() const {
438 return mNameContentList && mNameContentList->Length() != 0;
441 void IdentifierMapEntry::AddContentChangeCallback(
442 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
443 if (!mChangeCallbacks) {
444 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
447 ChangeCallback cc = {aCallback, aData, aForImage};
448 mChangeCallbacks->PutEntry(cc);
451 void IdentifierMapEntry::RemoveContentChangeCallback(
452 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
453 if (!mChangeCallbacks) return;
454 ChangeCallback cc = {aCallback, aData, aForImage};
455 mChangeCallbacks->RemoveEntry(cc);
456 if (mChangeCallbacks->Count() == 0) {
457 mChangeCallbacks = nullptr;
461 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
462 Element* aNewElement,
463 bool aImageOnly) {
464 if (!mChangeCallbacks) return;
466 for (auto iter = mChangeCallbacks->ConstIter(); !iter.Done(); iter.Next()) {
467 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
468 // Don't fire image changes for non-image observers, and don't fire element
469 // changes for image observers when an image override is active.
470 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
471 continue;
474 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
475 iter.Remove();
480 void IdentifierMapEntry::AddIdElement(Element* aElement) {
481 MOZ_ASSERT(aElement, "Must have element");
482 MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
484 size_t index = mIdContentList.Insert(*aElement);
485 if (index == 0) {
486 Element* oldElement = mIdContentList->SafeElementAt(1);
487 FireChangeCallbacks(oldElement, aElement);
491 void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
492 MOZ_ASSERT(aElement, "Missing element");
494 // This should only be called while the document is in an update.
495 // Assertions near the call to this method guarantee this.
497 // This could fire in OOM situations
498 // Only assert this in HTML documents for now as XUL does all sorts of weird
499 // crap.
500 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
501 mIdContentList->Contains(aElement),
502 "Removing id entry that doesn't exist");
504 // XXXbz should this ever Compact() I guess when all the content is gone
505 // we'll just get cleaned up in the natural order of things...
506 Element* currentElement = mIdContentList->SafeElementAt(0);
507 mIdContentList.RemoveElement(*aElement);
508 if (currentElement == aElement) {
509 FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
513 void IdentifierMapEntry::SetImageElement(Element* aElement) {
514 Element* oldElement = GetImageIdElement();
515 mImageElement = aElement;
516 Element* newElement = GetImageIdElement();
517 if (oldElement != newElement) {
518 FireChangeCallbacks(oldElement, newElement, true);
522 void IdentifierMapEntry::ClearAndNotify() {
523 Element* currentElement = mIdContentList->SafeElementAt(0);
524 mIdContentList.Clear();
525 if (currentElement) {
526 FireChangeCallbacks(currentElement, nullptr);
528 mNameContentList = nullptr;
529 if (mImageElement) {
530 SetImageElement(nullptr);
532 mChangeCallbacks = nullptr;
535 namespace dom {
537 class SimpleHTMLCollection final : public nsSimpleContentList,
538 public nsIHTMLCollection {
539 public:
540 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
542 NS_DECL_ISUPPORTS_INHERITED
544 virtual nsINode* GetParentObject() override {
545 return nsSimpleContentList::GetParentObject();
547 virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
548 virtual Element* GetElementAt(uint32_t aIndex) override {
549 return mElements.SafeElementAt(aIndex)->AsElement();
552 virtual Element* GetFirstNamedElement(const nsAString& aName,
553 bool& aFound) override {
554 aFound = false;
555 RefPtr<nsAtom> name = NS_Atomize(aName);
556 for (uint32_t i = 0; i < mElements.Length(); i++) {
557 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
558 Element* element = mElements[i]->AsElement();
559 if (element->GetID() == name ||
560 (element->HasName() &&
561 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
562 aFound = true;
563 return element;
566 return nullptr;
569 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
570 AutoTArray<nsAtom*, 8> atoms;
571 for (uint32_t i = 0; i < mElements.Length(); i++) {
572 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
573 Element* element = mElements[i]->AsElement();
575 nsAtom* id = element->GetID();
576 MOZ_ASSERT(id != nsGkAtoms::_empty);
577 if (id && !atoms.Contains(id)) {
578 atoms.AppendElement(id);
581 if (element->HasName()) {
582 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
583 MOZ_ASSERT(name && name != nsGkAtoms::_empty);
584 if (name && !atoms.Contains(name)) {
585 atoms.AppendElement(name);
590 nsString* names = aNames.AppendElements(atoms.Length());
591 for (uint32_t i = 0; i < atoms.Length(); i++) {
592 atoms[i]->ToString(names[i]);
596 virtual JSObject* GetWrapperPreserveColorInternal() override {
597 return nsWrapperCache::GetWrapperPreserveColor();
599 virtual void PreserveWrapperInternal(
600 nsISupports* aScriptObjectHolder) override {
601 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
603 virtual JSObject* WrapObject(JSContext* aCx,
604 JS::Handle<JSObject*> aGivenProto) override {
605 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
608 using nsBaseContentList::Item;
610 private:
611 virtual ~SimpleHTMLCollection() = default;
614 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
615 nsIHTMLCollection)
617 } // namespace dom
619 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
620 if (!mNameContentList) {
621 mNameContentList = new dom::SimpleHTMLCollection(aNode);
624 mNameContentList->AppendElement(aElement);
627 void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
628 if (mNameContentList) {
629 mNameContentList->RemoveElement(aElement);
633 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() {
634 Element* idElement = GetIdElement();
635 return idElement &&
636 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
639 size_t IdentifierMapEntry::SizeOfExcludingThis(
640 MallocSizeOf aMallocSizeOf) const {
641 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
644 // Helper structs for the content->subdoc map
646 class SubDocMapEntry : public PLDHashEntryHdr {
647 public:
648 // Both of these are strong references
649 Element* mKey; // must be first, to look like PLDHashEntryStub
650 dom::Document* mSubDocument;
653 class OnloadBlocker final : public nsIRequest {
654 public:
655 OnloadBlocker() = default;
657 NS_DECL_ISUPPORTS
658 NS_DECL_NSIREQUEST
660 private:
661 ~OnloadBlocker() = default;
664 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
666 NS_IMETHODIMP
667 OnloadBlocker::GetName(nsACString& aResult) {
668 aResult.AssignLiteral("about:document-onload-blocker");
669 return NS_OK;
672 NS_IMETHODIMP
673 OnloadBlocker::IsPending(bool* _retval) {
674 *_retval = true;
675 return NS_OK;
678 NS_IMETHODIMP
679 OnloadBlocker::GetStatus(nsresult* status) {
680 *status = NS_OK;
681 return NS_OK;
684 NS_IMETHODIMP
685 OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
686 NS_IMETHODIMP
687 OnloadBlocker::Suspend(void) { return NS_OK; }
688 NS_IMETHODIMP
689 OnloadBlocker::Resume(void) { return NS_OK; }
691 NS_IMETHODIMP
692 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
693 *aLoadGroup = nullptr;
694 return NS_OK;
697 NS_IMETHODIMP
698 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
700 NS_IMETHODIMP
701 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
702 *aLoadFlags = nsIRequest::LOAD_NORMAL;
703 return NS_OK;
706 NS_IMETHODIMP
707 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
708 return GetTRRModeImpl(aTRRMode);
711 NS_IMETHODIMP
712 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
713 return SetTRRModeImpl(aTRRMode);
716 NS_IMETHODIMP
717 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
719 // ==================================================================
721 namespace dom {
723 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
725 Document* ExternalResourceMap::RequestResource(
726 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
727 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
728 // If we ever start allowing non-same-origin loads here, we might need to do
729 // something interesting with aRequestingPrincipal even for the hashtable
730 // gets.
731 MOZ_ASSERT(aURI, "Must have a URI");
732 MOZ_ASSERT(aRequestingNode, "Must have a node");
733 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
734 *aPendingLoad = nullptr;
735 if (mHaveShutDown) {
736 return nullptr;
739 // First, make sure we strip the ref from aURI.
740 nsCOMPtr<nsIURI> clone;
741 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
742 if (NS_FAILED(rv) || !clone) {
743 return nullptr;
746 ExternalResource* resource;
747 mMap.Get(clone, &resource);
748 if (resource) {
749 return resource->mDocument;
752 RefPtr<PendingLoad>& loadEntry = mPendingLoads.GetOrInsert(clone);
753 if (loadEntry) {
754 RefPtr<PendingLoad> load(loadEntry);
755 load.forget(aPendingLoad);
756 return nullptr;
759 RefPtr<PendingLoad> load(new PendingLoad(aDisplayDocument));
760 loadEntry = load;
762 if (NS_FAILED(load->StartLoad(clone, aReferrerInfo, aRequestingNode))) {
763 // Make sure we don't thrash things by trying this load again, since
764 // chances are it failed for good reasons (security check, etc).
765 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
766 } else {
767 load.forget(aPendingLoad);
770 return nullptr;
773 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
774 nsTArray<RefPtr<Document>> docs(mMap.Count());
775 for (const auto& entry : mMap) {
776 if (Document* doc = entry.GetData()->mDocument) {
777 docs.AppendElement(doc);
781 for (auto& doc : docs) {
782 if (aCallback(*doc) == CallState::Stop) {
783 return;
788 void ExternalResourceMap::Traverse(
789 nsCycleCollectionTraversalCallback* aCallback) const {
790 // mPendingLoads will get cleared out as the requests complete, so
791 // no need to worry about those here.
792 for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
793 ExternalResourceMap::ExternalResource* resource = iter.UserData();
795 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
796 "mExternalResourceMap.mMap entry"
797 "->mDocument");
798 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
800 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
801 "mExternalResourceMap.mMap entry"
802 "->mViewer");
803 aCallback->NoteXPCOMChild(resource->mViewer);
805 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
806 "mExternalResourceMap.mMap entry"
807 "->mLoadGroup");
808 aCallback->NoteXPCOMChild(resource->mLoadGroup);
812 void ExternalResourceMap::HideViewers() {
813 for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) {
814 nsCOMPtr<nsIContentViewer> viewer = iter.UserData()->mViewer;
815 if (viewer) {
816 viewer->Hide();
821 void ExternalResourceMap::ShowViewers() {
822 for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) {
823 nsCOMPtr<nsIContentViewer> viewer = iter.UserData()->mViewer;
824 if (viewer) {
825 viewer->Show();
830 void TransferOverrideDPPX(Document* aFromDoc, Document* aToDoc) {
831 MOZ_ASSERT(aFromDoc && aToDoc, "transferring zoom levels from/to null doc");
833 nsPresContext* fromCtxt = aFromDoc->GetPresContext();
834 if (!fromCtxt) return;
836 nsPresContext* toCtxt = aToDoc->GetPresContext();
837 if (!toCtxt) return;
839 toCtxt->SetOverrideDPPX(fromCtxt->GetOverrideDPPX());
842 void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
843 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
845 if (aFromDoc->IsShowing()) {
846 aToDoc->OnPageShow(true, nullptr);
850 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
851 nsIContentViewer* aViewer,
852 nsILoadGroup* aLoadGroup,
853 Document* aDisplayDocument) {
854 MOZ_ASSERT(aURI, "Unexpected call");
855 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
856 "Must have both or neither");
858 RefPtr<PendingLoad> load;
859 mPendingLoads.Remove(aURI, getter_AddRefs(load));
861 nsresult rv = NS_OK;
863 nsCOMPtr<Document> doc;
864 if (aViewer) {
865 doc = aViewer->GetDocument();
866 NS_ASSERTION(doc, "Must have a document");
868 doc->SetDisplayDocument(aDisplayDocument);
870 // Make sure that hiding our viewer will tear down its presentation.
871 aViewer->SetSticky(false);
873 rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
874 if (NS_SUCCEEDED(rv)) {
875 rv = aViewer->Open(nullptr, nullptr);
878 if (NS_FAILED(rv)) {
879 doc = nullptr;
880 aViewer = nullptr;
881 aLoadGroup = nullptr;
885 ExternalResource* newResource = new ExternalResource();
886 mMap.Put(aURI, newResource);
888 newResource->mDocument = doc;
889 newResource->mViewer = aViewer;
890 newResource->mLoadGroup = aLoadGroup;
891 if (doc) {
892 if (nsPresContext* pc = doc->GetPresContext()) {
893 pc->RecomputeBrowsingContextDependentData();
895 TransferOverrideDPPX(aDisplayDocument, doc);
896 TransferShowingState(aDisplayDocument, doc);
899 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
900 for (uint32_t i = 0; i < obs.Length(); ++i) {
901 obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
902 nullptr);
905 return rv;
908 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
909 nsIRequestObserver)
911 NS_IMETHODIMP
912 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
913 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
914 if (map.HaveShutDown()) {
915 return NS_BINDING_ABORTED;
918 nsCOMPtr<nsIContentViewer> viewer;
919 nsCOMPtr<nsILoadGroup> loadGroup;
920 nsresult rv =
921 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
923 // Make sure to do this no matter what
924 nsresult rv2 =
925 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
926 if (NS_FAILED(rv)) {
927 return rv;
929 if (NS_FAILED(rv2)) {
930 mTargetListener = nullptr;
931 return rv2;
934 return mTargetListener->OnStartRequest(aRequest);
937 nsresult ExternalResourceMap::PendingLoad::SetupViewer(
938 nsIRequest* aRequest, nsIContentViewer** aViewer,
939 nsILoadGroup** aLoadGroup) {
940 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
941 *aViewer = nullptr;
942 *aLoadGroup = nullptr;
944 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
945 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
947 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
948 if (httpChannel) {
949 bool requestSucceeded;
950 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
951 !requestSucceeded) {
952 // Bail out on this load, since it looks like we have an HTTP error page
953 return NS_BINDING_ABORTED;
957 nsAutoCString type;
958 chan->GetContentType(type);
960 nsCOMPtr<nsILoadGroup> loadGroup;
961 chan->GetLoadGroup(getter_AddRefs(loadGroup));
963 // Give this document its own loadgroup
964 nsCOMPtr<nsILoadGroup> newLoadGroup =
965 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
966 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
967 newLoadGroup->SetLoadGroup(loadGroup);
969 nsCOMPtr<nsIInterfaceRequestor> callbacks;
970 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
972 nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
973 new LoadgroupCallbacks(callbacks);
974 newLoadGroup->SetNotificationCallbacks(newCallbacks);
976 // This is some serious hackery cribbed from docshell
977 nsCOMPtr<nsICategoryManager> catMan =
978 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
979 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
980 nsCString contractId;
981 nsresult rv =
982 catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
983 NS_ENSURE_SUCCESS(rv, rv);
984 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
985 do_GetService(contractId.get());
986 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
988 nsCOMPtr<nsIContentViewer> viewer;
989 nsCOMPtr<nsIStreamListener> listener;
990 rv = docLoaderFactory->CreateInstance(
991 "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
992 getter_AddRefs(listener), getter_AddRefs(viewer));
993 NS_ENSURE_SUCCESS(rv, rv);
994 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
996 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
997 if (!parser) {
998 /// We don't want to deal with the various fake documents yet
999 return NS_ERROR_NOT_IMPLEMENTED;
1002 // We can't handle HTML and other weird things here yet.
1003 nsIContentSink* sink = parser->GetContentSink();
1004 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
1005 if (!xmlSink) {
1006 return NS_ERROR_NOT_IMPLEMENTED;
1009 listener.swap(mTargetListener);
1010 viewer.forget(aViewer);
1011 newLoadGroup.forget(aLoadGroup);
1012 return NS_OK;
1015 NS_IMETHODIMP
1016 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
1017 nsIInputStream* aStream,
1018 uint64_t aOffset,
1019 uint32_t aCount) {
1020 // mTargetListener might be null if SetupViewer or AddExternalResource failed.
1021 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
1022 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
1023 return NS_BINDING_ABORTED;
1025 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1028 NS_IMETHODIMP
1029 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
1030 nsresult aStatus) {
1031 // mTargetListener might be null if SetupViewer or AddExternalResource failed
1032 if (mTargetListener) {
1033 nsCOMPtr<nsIStreamListener> listener;
1034 mTargetListener.swap(listener);
1035 return listener->OnStopRequest(aRequest, aStatus);
1038 return NS_OK;
1041 nsresult ExternalResourceMap::PendingLoad::StartLoad(
1042 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
1043 MOZ_ASSERT(aURI, "Must have a URI");
1044 MOZ_ASSERT(aRequestingNode, "Must have a node");
1045 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
1047 nsCOMPtr<nsILoadGroup> loadGroup =
1048 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
1050 nsresult rv = NS_OK;
1051 nsCOMPtr<nsIChannel> channel;
1052 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
1053 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1054 nsIContentPolicy::TYPE_OTHER,
1055 nullptr, // aPerformanceStorage
1056 loadGroup);
1057 NS_ENSURE_SUCCESS(rv, rv);
1059 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1060 if (httpChannel) {
1061 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
1062 Unused << NS_WARN_IF(NS_FAILED(rv));
1065 mURI = aURI;
1067 return channel->AsyncOpen(this);
1070 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
1071 nsIInterfaceRequestor)
1073 #define IMPL_SHIM(_i) \
1074 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
1076 IMPL_SHIM(nsILoadContext)
1077 IMPL_SHIM(nsIProgressEventSink)
1078 IMPL_SHIM(nsIChannelEventSink)
1079 IMPL_SHIM(nsIApplicationCacheContainer)
1081 #undef IMPL_SHIM
1083 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
1085 #define TRY_SHIM(_i) \
1086 PR_BEGIN_MACRO \
1087 if (IID_IS(_i)) { \
1088 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
1089 if (!real) { \
1090 return NS_NOINTERFACE; \
1092 nsCOMPtr<_i> shim = new _i##Shim(this, real); \
1093 shim.forget(aSink); \
1094 return NS_OK; \
1096 PR_END_MACRO
1098 NS_IMETHODIMP
1099 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
1100 void** aSink) {
1101 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
1102 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
1103 return mCallbacks->GetInterface(aIID, aSink);
1106 *aSink = nullptr;
1108 TRY_SHIM(nsILoadContext);
1109 TRY_SHIM(nsIProgressEventSink);
1110 TRY_SHIM(nsIChannelEventSink);
1111 TRY_SHIM(nsIApplicationCacheContainer);
1113 return NS_NOINTERFACE;
1116 #undef TRY_SHIM
1117 #undef IID_IS
1119 ExternalResourceMap::ExternalResource::~ExternalResource() {
1120 if (mViewer) {
1121 mViewer->Close(nullptr);
1122 mViewer->Destroy();
1126 // ==================================================================
1127 // =
1128 // ==================================================================
1130 // If we ever have an nsIDocumentObserver notification for stylesheet title
1131 // changes we should update the list from that instead of overriding
1132 // EnsureFresh.
1133 class DOMStyleSheetSetList final : public DOMStringList {
1134 public:
1135 explicit DOMStyleSheetSetList(Document* aDocument);
1137 void Disconnect() { mDocument = nullptr; }
1139 virtual void EnsureFresh() override;
1141 protected:
1142 Document* mDocument; // Our document; weak ref. It'll let us know if it
1143 // dies.
1146 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
1147 : mDocument(aDocument) {
1148 NS_ASSERTION(mDocument, "Must have document!");
1151 void DOMStyleSheetSetList::EnsureFresh() {
1152 MOZ_ASSERT(NS_IsMainThread());
1154 mNames.Clear();
1156 if (!mDocument) {
1157 return; // Spec says "no exceptions", and we have no style sets if we have
1158 // no document, for sure
1161 size_t count = mDocument->SheetCount();
1162 nsAutoString title;
1163 for (size_t index = 0; index < count; index++) {
1164 StyleSheet* sheet = mDocument->SheetAt(index);
1165 NS_ASSERTION(sheet, "Null sheet in sheet list!");
1166 sheet->GetTitle(title);
1167 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
1168 return;
1173 // ==================================================================
1174 Document::SelectorCache::SelectorCache(nsIEventTarget* aEventTarget)
1175 : nsExpirationTracker<SelectorCacheKey, 4>(1000, "Document::SelectorCache",
1176 aEventTarget) {}
1178 Document::SelectorCache::~SelectorCache() { AgeAllGenerations(); }
1180 void Document::SelectorCache::NotifyExpired(SelectorCacheKey* aSelector) {
1181 MOZ_ASSERT(NS_IsMainThread());
1182 MOZ_ASSERT(aSelector);
1184 // There is no guarantee that this method won't be re-entered when selector
1185 // matching is ongoing because "memory-pressure" could be notified immediately
1186 // when OOM happens according to the design of nsExpirationTracker.
1187 // The perfect solution is to delete the |aSelector| and its
1188 // RawServoSelectorList in mTable asynchronously.
1189 // We remove these objects synchronously for now because NotifyExpired() will
1190 // never be triggered by "memory-pressure" which is not implemented yet in
1191 // the stage 2 of mozalloc_handle_oom().
1192 // Once these objects are removed asynchronously, we should update the warning
1193 // added in mozalloc_handle_oom() as well.
1194 RemoveObject(aSelector);
1195 mTable.Remove(aSelector->mKey);
1196 delete aSelector;
1199 Document::FrameRequest::FrameRequest(FrameRequestCallback& aCallback,
1200 int32_t aHandle)
1201 : mCallback(&aCallback), mHandle(aHandle) {}
1203 Document::FrameRequest::~FrameRequest() = default;
1205 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1207 struct Document::MetaViewportElementAndData {
1208 RefPtr<HTMLMetaElement> mElement;
1209 ViewportMetaData mData;
1211 bool operator==(const MetaViewportElementAndData& aOther) const {
1212 return mElement == aOther.mElement && mData == aOther.mData;
1216 // ==================================================================
1217 // =
1218 // ==================================================================
1220 Document::InternalCommandDataHashtable*
1221 Document::sInternalCommandDataHashtable = nullptr;
1223 // static
1224 void Document::Shutdown() {
1225 if (sInternalCommandDataHashtable) {
1226 sInternalCommandDataHashtable->Clear();
1227 delete sInternalCommandDataHashtable;
1228 sInternalCommandDataHashtable = nullptr;
1232 Document::Document(const char* aContentType)
1233 : nsINode(nullptr),
1234 DocumentOrShadowRoot(this),
1235 mBlockAllMixedContent(false),
1236 mBlockAllMixedContentPreloads(false),
1237 mUpgradeInsecureRequests(false),
1238 mUpgradeInsecurePreloads(false),
1239 mDontWarnAboutMutationEventsAndAllowSlowDOMMutations(false),
1240 mCharacterSet(WINDOWS_1252_ENCODING),
1241 mCharacterSetSource(0),
1242 mParentDocument(nullptr),
1243 mCachedRootElement(nullptr),
1244 mNodeInfoManager(nullptr),
1245 #ifdef DEBUG
1246 mStyledLinksCleared(false),
1247 #endif
1248 mBidiEnabled(false),
1249 mMayNeedFontPrefsUpdate(true),
1250 mMathMLEnabled(false),
1251 mIsInitialDocumentInWindow(false),
1252 mIgnoreDocGroupMismatches(false),
1253 mLoadedAsData(false),
1254 mMayStartLayout(true),
1255 mHaveFiredTitleChange(false),
1256 mIsShowing(false),
1257 mVisible(true),
1258 mRemovedFromDocShell(false),
1259 // mAllowDNSPrefetch starts true, so that we can always reliably && it
1260 // with various values that might disable it. Since we never prefetch
1261 // unless we get a window, and in that case the docshell value will get
1262 // &&-ed in, this is safe.
1263 mAllowDNSPrefetch(true),
1264 mIsStaticDocument(false),
1265 mCreatingStaticClone(false),
1266 mInUnlinkOrDeletion(false),
1267 mHasHadScriptHandlingObject(false),
1268 mIsBeingUsedAsImage(false),
1269 mDocURISchemeIsChrome(false),
1270 mInChromeDocShell(false),
1271 mIsDevToolsDocument(false),
1272 mIsSyntheticDocument(false),
1273 mHasLinksToUpdateRunnable(false),
1274 mFlushingPendingLinkUpdates(false),
1275 mMayHaveDOMMutationObservers(false),
1276 mMayHaveAnimationObservers(false),
1277 mHasCSP(false),
1278 mHasUnsafeEvalCSP(false),
1279 mHasUnsafeInlineCSP(false),
1280 mHasCSPDeliveredThroughHeader(false),
1281 mBFCacheDisallowed(false),
1282 mHasHadDefaultView(false),
1283 mStyleSheetChangeEventsEnabled(false),
1284 mIsSrcdocDocument(false),
1285 mHasDisplayDocument(false),
1286 mFontFaceSetDirty(true),
1287 mDidFireDOMContentLoaded(true),
1288 mHasScrollLinkedEffect(false),
1289 mFrameRequestCallbacksScheduled(false),
1290 mIsTopLevelContentDocument(false),
1291 mIsContentDocument(false),
1292 mDidCallBeginLoad(false),
1293 mAllowPaymentRequest(false),
1294 mEncodingMenuDisabled(false),
1295 mLinksEnabled(true),
1296 mIsSVGGlyphsDocument(false),
1297 mInDestructor(false),
1298 mIsGoingAway(false),
1299 mInXBLUpdate(false),
1300 mNeedsReleaseAfterStackRefCntRelease(false),
1301 mStyleSetFilled(false),
1302 mQuirkSheetAdded(false),
1303 mContentEditableSheetAdded(false),
1304 mDesignModeSheetAdded(false),
1305 mSSApplicableStateNotificationPending(false),
1306 mMayHaveTitleElement(false),
1307 mDOMLoadingSet(false),
1308 mDOMInteractiveSet(false),
1309 mDOMCompleteSet(false),
1310 mAutoFocusFired(false),
1311 mScrolledToRefAlready(false),
1312 mChangeScrollPosWhenScrollingToRef(false),
1313 mDelayFrameLoaderInitialization(false),
1314 mSynchronousDOMContentLoaded(false),
1315 mMaybeServiceWorkerControlled(false),
1316 mAllowZoom(false),
1317 mValidScaleFloat(false),
1318 mValidMinScale(false),
1319 mValidMaxScale(false),
1320 mWidthStrEmpty(false),
1321 mParserAborted(false),
1322 mReportedUseCounters(false),
1323 mHasReportedShadowDOMUsage(false),
1324 mHasDelayedRefreshEvent(false),
1325 mLoadEventFiring(false),
1326 mSkipLoadEventAfterClose(false),
1327 mDisableCookieAccess(false),
1328 mDisableDocWrite(false),
1329 mTooDeepWriteRecursion(false),
1330 mPendingMaybeEditingStateChanged(false),
1331 mHasBeenEditable(false),
1332 mHasWarnedAboutZoom(false),
1333 mIsRunningExecCommand(false),
1334 mSetCompleteAfterDOMContentLoaded(false),
1335 mPendingFullscreenRequests(0),
1336 mXMLDeclarationBits(0),
1337 mOnloadBlockCount(0),
1338 mAsyncOnloadBlockCount(0),
1339 mWriteLevel(0),
1340 mContentEditableCount(0),
1341 mEditingState(EditingState::eOff),
1342 mCompatMode(eCompatibility_FullStandards),
1343 mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
1344 mAncestorIsLoading(false),
1345 mVisibilityState(dom::VisibilityState::Hidden),
1346 mType(eUnknown),
1347 mDefaultElementType(0),
1348 mAllowXULXBL(eTriUnset),
1349 mSkipDTDSecurityChecks(false),
1350 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
1351 mSandboxFlags(0),
1352 mPartID(0),
1353 mMarkedCCGeneration(0),
1354 mPresShell(nullptr),
1355 mSubtreeModifiedDepth(0),
1356 mPreloadPictureDepth(0),
1357 mEventsSuppressed(0),
1358 mIgnoreDestructiveWritesCounter(0),
1359 mFrameRequestCallbackCounter(0),
1360 mStaticCloneCount(0),
1361 mWindow(nullptr),
1362 mBFCacheEntry(nullptr),
1363 mInSyncOperationCount(0),
1364 mBlockDOMContentLoaded(0),
1365 mUseCounters(0),
1366 mChildDocumentUseCounters(0),
1367 mUserHasInteracted(false),
1368 mHasUserInteractionTimerScheduled(false),
1369 mStackRefCnt(0),
1370 mUpdateNestLevel(0),
1371 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
1372 mViewportType(Unknown),
1373 mViewportFit(ViewportFitType::Auto),
1374 mSubDocuments(nullptr),
1375 mHeaderData(nullptr),
1376 mFlashClassification(FlashClassification::Unknown),
1377 mScrollAnchorAdjustmentLength(0),
1378 mScrollAnchorAdjustmentCount(0),
1379 mServoRestyleRootDirtyBits(0),
1380 mThrowOnDynamicMarkupInsertionCounter(0),
1381 mIgnoreOpensDuringUnloadCounter(0),
1382 mDocLWTheme(Doc_Theme_Uninitialized),
1383 mSavedResolution(1.0f),
1384 mSavedResolutionBeforeMVM(1.0f),
1385 mGeneration(0),
1386 mCachedTabSizeGeneration(0),
1387 mNextFormNumber(0),
1388 mNextControlNumber(0),
1389 mPreloadService(this) {
1390 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
1392 SetIsInDocument();
1393 SetIsConnected(true);
1395 // Create these unconditionally, they will be used to warn about the `zoom`
1396 // property, even if use counters are disabled.
1397 mStyleUseCounters = Servo_UseCounters_Create().Consume();
1399 SetContentTypeInternal(nsDependentCString(aContentType));
1401 // Start out mLastStyleSheetSet as null, per spec
1402 SetDOMStringToNull(mLastStyleSheetSet);
1404 // void state used to differentiate an empty source from an unselected source
1405 mPreloadPictureFoundSource.SetIsVoid(true);
1407 RecomputeLanguageFromCharset();
1409 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
1410 mReferrerInfo = new dom::ReferrerInfo(nullptr);
1413 #ifndef ANDROID
1414 // unused by GeckoView
1415 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
1416 if (NS_WARN_IF(!aWin)) {
1417 return false;
1420 nsIURI* uri = aWin->GetDocumentURI();
1421 if (NS_WARN_IF(!uri)) {
1422 return false;
1424 // getSpec is an expensive operation, hence we first check the scheme
1425 // to see if the caller is actually an about: page.
1426 if (!uri->SchemeIs("about")) {
1427 return false;
1430 nsAutoCString aboutSpec;
1431 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
1432 NS_ENSURE_SUCCESS(rv, false);
1434 return aboutSpec.EqualsASCII(aSpec);
1436 #endif
1438 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
1439 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1440 #ifdef ANDROID
1441 // GeckoView uses data URLs for error pages, so for now just check for any
1442 // error page
1443 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1444 #else
1445 return win && IsAboutErrorPage(win, "neterror");
1446 #endif
1449 already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
1450 bool aIsTemporary) {
1451 nsIGlobalObject* global = GetScopeObject();
1452 if (!global) {
1453 return nullptr;
1456 ErrorResult er;
1457 RefPtr<Promise> promise =
1458 Promise::Create(global, er, Promise::ePropagateUserInteraction);
1459 if (er.Failed()) {
1460 return nullptr;
1463 nsCOMPtr<nsISupports> info;
1464 nsCOMPtr<nsITransportSecurityInfo> tsi;
1465 nsresult rv = NS_OK;
1466 if (NS_WARN_IF(!mFailedChannel)) {
1467 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1468 return promise.forget();
1471 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1472 if (NS_WARN_IF(NS_FAILED(rv))) {
1473 promise->MaybeReject(rv);
1474 return promise.forget();
1476 nsCOMPtr<nsIURI> failedChannelURI;
1477 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
1478 if (!failedChannelURI) {
1479 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1480 return promise.forget();
1482 nsAutoCString host;
1483 failedChannelURI->GetAsciiHost(host);
1484 int32_t port;
1485 failedChannelURI->GetPort(&port);
1487 tsi = do_QueryInterface(info);
1488 if (NS_WARN_IF(!tsi)) {
1489 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1490 return promise.forget();
1493 bool isUntrusted = true;
1494 rv = tsi->GetIsUntrusted(&isUntrusted);
1495 if (NS_WARN_IF(NS_FAILED(rv))) {
1496 promise->MaybeReject(rv);
1497 return promise.forget();
1500 bool isDomainMismatch = true;
1501 rv = tsi->GetIsDomainMismatch(&isDomainMismatch);
1502 if (NS_WARN_IF(NS_FAILED(rv))) {
1503 promise->MaybeReject(rv);
1504 return promise.forget();
1507 bool isNotValidAtThisTime = true;
1508 rv = tsi->GetIsNotValidAtThisTime(&isNotValidAtThisTime);
1509 if (NS_WARN_IF(NS_FAILED(rv))) {
1510 promise->MaybeReject(rv);
1511 return promise.forget();
1514 nsCOMPtr<nsIX509Cert> cert;
1515 rv = tsi->GetServerCert(getter_AddRefs(cert));
1516 if (NS_WARN_IF(NS_FAILED(rv))) {
1517 promise->MaybeReject(rv);
1518 return promise.forget();
1520 if (NS_WARN_IF(!cert)) {
1521 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1522 return promise.forget();
1525 uint32_t flags = 0;
1526 if (isUntrusted) {
1527 flags |= nsICertOverrideService::ERROR_UNTRUSTED;
1529 if (isDomainMismatch) {
1530 flags |= nsICertOverrideService::ERROR_MISMATCH;
1532 if (isNotValidAtThisTime) {
1533 flags |= nsICertOverrideService::ERROR_TIME;
1536 if (XRE_IsContentProcess()) {
1537 nsCOMPtr<nsISerializable> certSer = do_QueryInterface(cert);
1538 nsCString certSerialized;
1539 NS_SerializeToString(certSer, certSerialized);
1541 ContentChild* cc = ContentChild::GetSingleton();
1542 MOZ_ASSERT(cc);
1543 cc->SendAddCertException(certSerialized, flags, host, port, aIsTemporary)
1544 ->Then(GetCurrentSerialEventTarget(), __func__,
1545 [promise](const mozilla::MozPromise<
1546 nsresult, mozilla::ipc::ResponseRejectReason,
1547 true>::ResolveOrRejectValue& aValue) {
1548 if (aValue.IsResolve()) {
1549 promise->MaybeResolve(aValue.ResolveValue());
1550 } else {
1551 promise->MaybeRejectWithUndefined();
1554 return promise.forget();
1557 if (XRE_IsParentProcess()) {
1558 nsCOMPtr<nsICertOverrideService> overrideService =
1559 do_GetService(NS_CERTOVERRIDE_CONTRACTID);
1560 if (!overrideService) {
1561 promise->MaybeReject(NS_ERROR_FAILURE);
1562 return promise.forget();
1565 rv = overrideService->RememberValidityOverride(host, port, cert, flags,
1566 aIsTemporary);
1567 if (NS_WARN_IF(NS_FAILED(rv))) {
1568 promise->MaybeReject(rv);
1569 return promise.forget();
1572 promise->MaybeResolveWithUndefined();
1573 return promise.forget();
1576 promise->MaybeReject(NS_ERROR_FAILURE);
1577 return promise.forget();
1580 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
1581 nsCOMPtr<nsISupports> info;
1582 nsCOMPtr<nsITransportSecurityInfo> tsi;
1583 nsresult rv = NS_OK;
1584 if (NS_WARN_IF(!mFailedChannel)) {
1585 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1586 return;
1589 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1590 if (NS_WARN_IF(NS_FAILED(rv))) {
1591 aRv.Throw(rv);
1592 return;
1594 tsi = do_QueryInterface(info);
1595 if (NS_WARN_IF(!tsi)) {
1596 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1597 return;
1600 nsAutoString errorCodeString;
1601 rv = tsi->GetErrorCodeString(errorCodeString);
1602 if (NS_WARN_IF(NS_FAILED(rv))) {
1603 aRv.Throw(rv);
1604 return;
1606 aInfo.mErrorCodeString.Assign(errorCodeString);
1609 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
1610 JSObject* aObject) {
1611 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1612 #ifdef ANDROID
1613 // GeckoView uses data URLs for error pages, so for now just check for any
1614 // error page
1615 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1616 #else
1617 return win && IsAboutErrorPage(win, "certerror");
1618 #endif
1621 bool Document::IsErrorPage() const {
1622 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
1623 return loadInfo && loadInfo->GetLoadErrorPage();
1626 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
1627 ErrorResult& aRv) {
1628 nsCOMPtr<nsISupports> info;
1629 nsCOMPtr<nsITransportSecurityInfo> tsi;
1630 nsresult rv = NS_OK;
1631 if (NS_WARN_IF(!mFailedChannel)) {
1632 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1633 return;
1636 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1637 if (NS_WARN_IF(NS_FAILED(rv))) {
1638 aRv.Throw(rv);
1639 return;
1641 tsi = do_QueryInterface(info);
1642 if (NS_WARN_IF(!tsi)) {
1643 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1644 return;
1647 nsAutoString errorCodeString;
1648 rv = tsi->GetErrorCodeString(errorCodeString);
1649 if (NS_WARN_IF(NS_FAILED(rv))) {
1650 aRv.Throw(rv);
1651 return;
1653 aInfo.mErrorCodeString.Assign(errorCodeString);
1655 rv = tsi->GetIsUntrusted(&aInfo.mIsUntrusted);
1656 if (NS_WARN_IF(NS_FAILED(rv))) {
1657 aRv.Throw(rv);
1658 return;
1661 rv = tsi->GetIsDomainMismatch(&aInfo.mIsDomainMismatch);
1662 if (NS_WARN_IF(NS_FAILED(rv))) {
1663 aRv.Throw(rv);
1664 return;
1667 rv = tsi->GetIsNotValidAtThisTime(&aInfo.mIsNotValidAtThisTime);
1668 if (NS_WARN_IF(NS_FAILED(rv))) {
1669 aRv.Throw(rv);
1670 return;
1673 nsCOMPtr<nsIX509Cert> cert;
1674 nsCOMPtr<nsIX509CertValidity> validity;
1675 rv = tsi->GetServerCert(getter_AddRefs(cert));
1676 if (NS_WARN_IF(NS_FAILED(rv))) {
1677 aRv.Throw(rv);
1678 return;
1680 if (NS_WARN_IF(!cert)) {
1681 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1682 return;
1685 rv = cert->GetValidity(getter_AddRefs(validity));
1686 if (NS_WARN_IF(NS_FAILED(rv))) {
1687 aRv.Throw(rv);
1688 return;
1690 if (NS_WARN_IF(!validity)) {
1691 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1692 return;
1695 PRTime validityResult;
1696 rv = validity->GetNotBefore(&validityResult);
1697 if (NS_WARN_IF(NS_FAILED(rv))) {
1698 aRv.Throw(rv);
1699 return;
1701 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1703 rv = validity->GetNotAfter(&validityResult);
1704 if (NS_WARN_IF(NS_FAILED(rv))) {
1705 aRv.Throw(rv);
1706 return;
1708 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1710 nsAutoString subjectAltNames;
1711 rv = cert->GetSubjectAltNames(subjectAltNames);
1712 if (NS_WARN_IF(NS_FAILED(rv))) {
1713 aRv.Throw(rv);
1714 return;
1716 aInfo.mSubjectAltNames = subjectAltNames;
1718 nsAutoString issuerCommonName;
1719 nsAutoString certChainPEMString;
1720 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
1721 int64_t maxValidity = std::numeric_limits<int64_t>::max();
1722 int64_t minValidity = 0;
1723 PRTime notBefore, notAfter;
1724 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
1725 rv = tsi->GetFailedCertChain(failedCertArray);
1726 if (NS_WARN_IF(NS_FAILED(rv))) {
1727 aRv.Throw(rv);
1728 return;
1731 if (NS_WARN_IF(failedCertArray.IsEmpty())) {
1732 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1733 return;
1736 for (const auto& certificate : failedCertArray) {
1737 rv = certificate->GetIssuerCommonName(issuerCommonName);
1738 if (NS_WARN_IF(NS_FAILED(rv))) {
1739 aRv.Throw(rv);
1740 return;
1743 rv = certificate->GetValidity(getter_AddRefs(validity));
1744 if (NS_WARN_IF(NS_FAILED(rv))) {
1745 aRv.Throw(rv);
1746 return;
1748 if (NS_WARN_IF(!validity)) {
1749 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1750 return;
1753 rv = validity->GetNotBefore(&notBefore);
1754 if (NS_WARN_IF(NS_FAILED(rv))) {
1755 aRv.Throw(rv);
1756 return;
1759 rv = validity->GetNotAfter(&notAfter);
1760 if (NS_WARN_IF(NS_FAILED(rv))) {
1761 aRv.Throw(rv);
1762 return;
1765 notBefore = std::max(minValidity, notBefore);
1766 notAfter = std::min(maxValidity, notAfter);
1767 nsTArray<uint8_t> certArray;
1768 rv = certificate->GetRawDER(certArray);
1769 if (NS_WARN_IF(NS_FAILED(rv))) {
1770 aRv.Throw(rv);
1771 return;
1774 certArray.AppendElement(
1775 0); // Append null terminator, required by nsC*String.
1776 nsDependentCString derString(reinterpret_cast<char*>(certArray.Elements()),
1777 certArray.Length() - 1);
1778 nsAutoCString der64;
1779 rv = Base64Encode(derString, der64);
1780 if (NS_WARN_IF(NS_FAILED(rv))) {
1781 aRv.Throw(rv);
1782 return;
1784 if (!certChainStrings.AppendElement(NS_ConvertUTF8toUTF16(der64),
1785 fallible)) {
1786 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1787 return;
1791 aInfo.mIssuerCommonName.Assign(issuerCommonName);
1792 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
1793 aInfo.mCertValidityRangeNotBefore =
1794 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
1796 int32_t errorCode;
1797 rv = tsi->GetErrorCode(&errorCode);
1798 if (NS_WARN_IF(NS_FAILED(rv))) {
1799 aRv.Throw(rv);
1800 return;
1803 nsCOMPtr<nsINSSErrorsService> nsserr =
1804 do_GetService("@mozilla.org/nss_errors_service;1");
1805 if (NS_WARN_IF(!nsserr)) {
1806 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1807 return;
1809 nsresult res;
1810 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
1811 if (NS_WARN_IF(NS_FAILED(rv))) {
1812 aRv.Throw(rv);
1813 return;
1815 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
1816 if (NS_WARN_IF(NS_FAILED(rv))) {
1817 aRv.Throw(rv);
1818 return;
1821 bool isPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(this);
1822 uint32_t flags =
1823 isPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
1824 OriginAttributes attrs;
1825 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1826 nsCOMPtr<nsIURI> aURI;
1827 mFailedChannel->GetURI(getter_AddRefs(aURI));
1828 if (XRE_IsContentProcess()) {
1829 ContentChild* cc = ContentChild::GetSingleton();
1830 MOZ_ASSERT(cc);
1831 cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags, attrs,
1832 &aInfo.mHasHSTS);
1833 cc->SendIsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI, flags,
1834 attrs, &aInfo.mHasHPKP);
1835 } else {
1836 nsCOMPtr<nsISiteSecurityService> sss =
1837 do_GetService(NS_SSSERVICE_CONTRACTID);
1838 if (NS_WARN_IF(!sss)) {
1839 return;
1841 Unused << NS_WARN_IF(NS_FAILED(
1842 sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags,
1843 attrs, nullptr, nullptr, &aInfo.mHasHSTS)));
1844 Unused << NS_WARN_IF(NS_FAILED(
1845 sss->IsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI, flags,
1846 attrs, nullptr, nullptr, &aInfo.mHasHPKP)));
1850 bool Document::AllowDeprecatedTls() {
1851 return Preferences::GetBool("security.tls.version.enable-deprecated", false);
1854 void Document::SetAllowDeprecatedTls(bool value) {
1855 if (!IsErrorPage()) {
1856 return;
1859 auto docShell = GetDocShell();
1860 if (!docShell) {
1861 return;
1864 auto child = BrowserChild::GetFrom(docShell);
1865 if (!child) {
1866 return;
1869 child->SendSetAllowDeprecatedTls(value);
1872 bool Document::IsAboutPage() const {
1873 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
1874 return principal->SchemeIs("about");
1877 void Document::ConstructUbiNode(void* storage) {
1878 JS::ubi::Concrete<Document>::construct(storage, this);
1881 Document::~Document() {
1882 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
1883 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
1884 "Can't be top-level and a resource doc at the same time");
1886 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
1888 if (IsTopLevelContentDocument()) {
1889 RemoveToplevelLoadingDocument(this);
1891 // don't report for about: pages
1892 if (!IsAboutPage()) {
1893 // record CSP telemetry on this document
1894 if (mHasCSP) {
1895 Accumulate(Telemetry::CSP_DOCUMENTS_COUNT, 1);
1897 if (mHasUnsafeInlineCSP) {
1898 Accumulate(Telemetry::CSP_UNSAFE_INLINE_DOCUMENTS_COUNT, 1);
1900 if (mHasUnsafeEvalCSP) {
1901 Accumulate(Telemetry::CSP_UNSAFE_EVAL_DOCUMENTS_COUNT, 1);
1904 if (MOZ_UNLIKELY(mMathMLEnabled)) {
1905 ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
1908 if (IsHTMLDocument()) {
1909 switch (GetCompatibilityMode()) {
1910 case eCompatibility_FullStandards:
1911 Telemetry::AccumulateCategorical(
1912 Telemetry::LABELS_QUIRKS_MODE::FullStandards);
1913 break;
1914 case eCompatibility_AlmostStandards:
1915 Telemetry::AccumulateCategorical(
1916 Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
1917 break;
1918 case eCompatibility_NavQuirks:
1919 Telemetry::AccumulateCategorical(
1920 Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
1921 break;
1922 default:
1923 MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
1924 break;
1930 mInDestructor = true;
1931 mInUnlinkOrDeletion = true;
1933 mozilla::DropJSObjects(this);
1935 // Clear mObservers to keep it in sync with the mutationobserver list
1936 mObservers.Clear();
1938 mIntersectionObservers.Clear();
1940 if (mStyleSheetSetList) {
1941 mStyleSheetSetList->Disconnect();
1944 if (mAnimationController) {
1945 mAnimationController->Disconnect();
1948 MOZ_ASSERT(mTimelines.isEmpty());
1950 mParentDocument = nullptr;
1952 // Kill the subdocument map, doing this will release its strong
1953 // references, if any.
1954 delete mSubDocuments;
1955 mSubDocuments = nullptr;
1957 nsAutoScriptBlocker scriptBlocker;
1959 // Destroy link map now so we don't waste time removing
1960 // links one by one
1961 DestroyElementMaps();
1963 // Invalidate cached array of child nodes
1964 InvalidateChildNodes();
1966 // We should not have child nodes when destructor is called,
1967 // since child nodes keep their owner document alive.
1968 MOZ_ASSERT(!HasChildren());
1970 mCachedRootElement = nullptr;
1972 for (auto& sheets : mAdditionalSheets) {
1973 UnlinkStyleSheets(sheets);
1976 if (mAttrStyleSheet) {
1977 mAttrStyleSheet->SetOwningDocument(nullptr);
1980 if (mListenerManager) {
1981 mListenerManager->Disconnect();
1982 UnsetFlags(NODE_HAS_LISTENERMANAGER);
1985 if (mScriptLoader) {
1986 mScriptLoader->DropDocumentReference();
1989 if (mCSSLoader) {
1990 // Could be null here if Init() failed or if we have been unlinked.
1991 mCSSLoader->DropDocumentReference();
1994 if (mStyleImageLoader) {
1995 mStyleImageLoader->DropDocumentReference();
1998 if (mXULBroadcastManager) {
1999 mXULBroadcastManager->DropDocumentReference();
2002 if (mXULPersist) {
2003 mXULPersist->DropDocumentReference();
2006 if (mPermissionDelegateHandler) {
2007 mPermissionDelegateHandler->DropDocumentReference();
2010 delete mHeaderData;
2012 mPendingTitleChangeEvent.Revoke();
2014 mPlugins.Clear();
2016 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
2017 "must not have media query lists left");
2019 if (mNodeInfoManager) {
2020 mNodeInfoManager->DropDocumentReference();
2023 if (mDocGroup) {
2024 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
2025 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(mDocGroup->GetKey(),
2026 this);
2029 UnlinkOriginalDocumentIfStatic();
2032 NS_INTERFACE_TABLE_HEAD(Document)
2033 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
2034 NS_INTERFACE_TABLE_BEGIN
2035 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
2036 NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
2037 NS_INTERFACE_TABLE_ENTRY(Document, Document)
2038 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
2039 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
2040 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
2041 NS_INTERFACE_TABLE_ENTRY(Document, nsIRadioGroupContainer)
2042 NS_INTERFACE_TABLE_ENTRY(Document, nsIMutationObserver)
2043 NS_INTERFACE_TABLE_ENTRY(Document, nsIApplicationCacheContainer)
2044 NS_INTERFACE_TABLE_END
2045 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
2046 NS_INTERFACE_MAP_END
2048 NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
2049 NS_IMETHODIMP_(MozExternalRefCountType)
2050 Document::Release() {
2051 MOZ_ASSERT(0 != mRefCnt, "dup release");
2052 NS_ASSERT_OWNINGTHREAD(Document);
2053 nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(Document)::Upcast(this);
2054 bool shouldDelete = false;
2055 nsrefcnt count = mRefCnt.decr(base, &shouldDelete);
2056 NS_LOG_RELEASE(this, count, "Document");
2057 if (count == 0) {
2058 if (mStackRefCnt && !mNeedsReleaseAfterStackRefCntRelease) {
2059 mNeedsReleaseAfterStackRefCntRelease = true;
2060 NS_ADDREF_THIS();
2061 return mRefCnt.get();
2063 mRefCnt.incr(base);
2064 LastRelease();
2065 mRefCnt.decr(base);
2066 if (shouldDelete) {
2067 mRefCnt.stabilizeForDeletion();
2068 DeleteCycleCollectable();
2071 return count;
2074 NS_IMETHODIMP_(void)
2075 Document::DeleteCycleCollectable() { delete this; }
2077 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2078 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2079 EventListenerManager* elm = tmp->GetExistingListenerManager();
2080 if (elm) {
2081 elm->MarkForCC();
2083 return true;
2085 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2087 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2088 return Element::CanSkipInCC(tmp);
2089 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2091 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2092 return Element::CanSkipThis(tmp);
2093 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2095 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2096 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2097 char name[512];
2098 nsAutoCString loadedAsData;
2099 if (tmp->IsLoadedAsData()) {
2100 loadedAsData.AssignLiteral("data");
2101 } else {
2102 loadedAsData.AssignLiteral("normal");
2104 uint32_t nsid = tmp->GetDefaultNamespaceID();
2105 nsAutoCString uri;
2106 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2107 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2108 "(xhtml)", "(XLink)", "(XSLT)",
2109 "(MathML)", "(RDF)", "(XUL)"};
2110 if (nsid < ArrayLength(kNSURIs)) {
2111 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2112 kNSURIs[nsid], uri.get());
2113 } else {
2114 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2116 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2117 } else {
2118 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2121 if (!nsINode::Traverse(tmp, cb)) {
2122 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2125 tmp->mExternalResourceMap.Traverse(&cb);
2127 // Traverse all Document pointer members.
2128 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2129 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2130 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2131 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2132 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2134 // Traverse all Document nsCOMPtrs.
2135 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2136 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2137 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2138 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2139 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2141 DocumentOrShadowRoot::Traverse(tmp, cb);
2143 for (auto& sheets : tmp->mAdditionalSheets) {
2144 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2147 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2148 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserver)
2149 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2150 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2151 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2152 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2153 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2154 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStateObjectCached)
2155 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2156 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
2157 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2158 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2159 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2160 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2161 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2162 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2163 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2164 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2165 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2166 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2167 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2168 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2169 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2170 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2171 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2172 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2174 // Traverse all our nsCOMArrays.
2175 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2177 for (uint32_t i = 0; i < tmp->mFrameRequestCallbacks.Length(); ++i) {
2178 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameRequestCallbacks[i]");
2179 cb.NoteXPCOMChild(tmp->mFrameRequestCallbacks[i].mCallback);
2182 // Traverse animation components
2183 if (tmp->mAnimationController) {
2184 tmp->mAnimationController->Traverse(&cb);
2187 if (tmp->mSubDocuments) {
2188 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2189 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2191 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2192 cb.NoteXPCOMChild(entry->mKey);
2193 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2194 "mSubDocuments entry->mSubDocument");
2195 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2199 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2201 // We own only the items in mDOMMediaQueryLists that have listeners;
2202 // this reference is managed by their AddListener and RemoveListener
2203 // methods.
2204 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2205 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2206 if (mql->HasListeners() &&
2207 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2208 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2209 cb.NoteXPCOMChild(mql);
2213 if (tmp->mResizeObserverController) {
2214 tmp->mResizeObserverController->Traverse(cb);
2216 for (size_t i = 0; i < tmp->mMetaViewports.Length(); i++) {
2217 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMetaViewports[i].mElement);
2220 // XXX: This should be not needed once bug 1569185 lands.
2221 for (auto it = tmp->mL10nProtoElements.ConstIter(); !it.Done(); it.Next()) {
2222 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2223 cb.NoteXPCOMChild(it.Key());
2224 CycleCollectionNoteChild(cb, it.UserData(), "mL10nProtoElements value");
2227 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2228 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2229 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2230 mPendingFrameStaticClones[i].mStaticCloneOf);
2232 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2234 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2236 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Document)
2238 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2239 tmp->mInUnlinkOrDeletion = true;
2241 // Clear out our external resources
2242 tmp->mExternalResourceMap.Shutdown();
2244 nsAutoScriptBlocker scriptBlocker;
2246 nsINode::Unlink(tmp);
2248 while (tmp->HasChildren()) {
2249 // Hold a strong ref to the node when we remove it, because we may be
2250 // the last reference to it.
2251 // If this code changes, change the corresponding code in Document's
2252 // unlink impl and ContentUnbinder::UnbindSubtree.
2253 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2254 tmp->DisconnectChild(child);
2255 child->UnbindFromTree();
2258 tmp->UnlinkOriginalDocumentIfStatic();
2260 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2262 tmp->SetScriptGlobalObject(nullptr);
2264 for (auto& sheets : tmp->mAdditionalSheets) {
2265 tmp->UnlinkStyleSheets(sheets);
2268 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2269 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2270 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserver)
2271 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2272 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2273 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2274 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2275 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2276 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2277 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2278 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2279 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2280 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2281 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStateObjectCached)
2282 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2283 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
2284 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2285 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2286 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2287 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2288 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2289 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2290 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2291 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2292 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2293 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2294 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2295 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2296 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2297 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2298 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2299 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2300 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2301 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2303 if (tmp->IsTopLevelContentDocument()) {
2304 RemoveToplevelLoadingDocument(tmp);
2307 tmp->mParentDocument = nullptr;
2309 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2311 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2313 if (tmp->mListenerManager) {
2314 tmp->mListenerManager->Disconnect();
2315 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2316 tmp->mListenerManager = nullptr;
2319 if (tmp->mStyleSheetSetList) {
2320 tmp->mStyleSheetSetList->Disconnect();
2321 tmp->mStyleSheetSetList = nullptr;
2324 delete tmp->mSubDocuments;
2325 tmp->mSubDocuments = nullptr;
2327 tmp->mFrameRequestCallbacks.Clear();
2328 MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
2329 "How did we get here without our presshell going away "
2330 "first?");
2332 DocumentOrShadowRoot::Unlink(tmp);
2334 // Document has a pretty complex destructor, so we're going to
2335 // assume that *most* cycles you actually want to break somewhere
2336 // else, and not unlink an awful lot here.
2338 tmp->mExpandoAndGeneration.OwnerUnlinked();
2340 if (tmp->mAnimationController) {
2341 tmp->mAnimationController->Unlink();
2344 tmp->mPendingTitleChangeEvent.Revoke();
2346 if (tmp->mCSSLoader) {
2347 tmp->mCSSLoader->DropDocumentReference();
2348 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2351 // We own only the items in mDOMMediaQueryLists that have listeners;
2352 // this reference is managed by their AddListener and RemoveListener
2353 // methods.
2354 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2355 MediaQueryList* next =
2356 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2357 mql->Disconnect();
2358 mql = next;
2361 tmp->mPendingFrameStaticClones.Clear();
2363 tmp->mInUnlinkOrDeletion = false;
2365 if (tmp->mResizeObserverController) {
2366 tmp->mResizeObserverController->Unlink();
2368 tmp->mMetaViewports.Clear();
2369 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2370 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2371 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2372 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2374 nsresult Document::Init() {
2375 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2376 return NS_ERROR_ALREADY_INITIALIZED;
2379 // Force initialization.
2380 nsINode::nsSlots* slots = Slots();
2382 // Prepend self as mutation-observer whether we need it or not (some
2383 // subclasses currently do, other don't). This is because the code in
2384 // MutationObservers always notifies the first observer first, expecting the
2385 // first observer to be the document.
2386 slots->mMutationObservers.PrependElementUnlessExists(
2387 static_cast<nsIMutationObserver*>(this));
2389 mOnloadBlocker = new OnloadBlocker();
2390 mStyleImageLoader = new css::ImageLoader(this);
2392 mNodeInfoManager = new nsNodeInfoManager();
2393 nsresult rv = mNodeInfoManager->Init(this);
2394 NS_ENSURE_SUCCESS(rv, rv);
2396 // mNodeInfo keeps NodeInfoManager alive!
2397 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2398 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2399 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2400 "Bad NodeType in aNodeInfo");
2402 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2404 mCSSLoader = new css::Loader(this);
2405 // Assume we're not quirky, until we know otherwise
2406 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2408 // If after creation the owner js global is not set for a document
2409 // we use the default compartment for this document, instead of creating
2410 // wrapper in some random compartment when the document is exposed to js
2411 // via some events.
2412 nsCOMPtr<nsIGlobalObject> global =
2413 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2414 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2415 mScopeObject = do_GetWeakReference(global);
2416 MOZ_ASSERT(mScopeObject);
2418 mScriptLoader = new dom::ScriptLoader(this);
2420 // we need to create a policy here so getting the policy within
2421 // ::Policy() can *always* return a non null policy
2422 mFeaturePolicy = new dom::FeaturePolicy(this);
2423 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2425 mStyleSet = MakeUnique<ServoStyleSet>(*this);
2427 mozilla::HoldJSObjects(this);
2429 return NS_OK;
2432 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2434 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2435 PropertyTable().RemoveAllPropertiesFor(aNode);
2438 bool Document::IsVisibleConsideringAncestors() const {
2439 const Document* parent = this;
2440 do {
2441 if (!parent->IsVisible()) {
2442 return false;
2444 } while ((parent = parent->GetInProcessParentDocument()));
2446 return true;
2449 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2450 nsCOMPtr<nsIURI> uri;
2451 nsCOMPtr<nsIPrincipal> principal;
2452 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2453 if (aChannel) {
2454 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2455 // nsScriptSecurityManager::GetChannelResultPrincipals.
2456 // Note: this should match the uri used for the OnNewURI call in
2457 // nsDocShell::CreateContentViewer.
2458 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2460 nsIScriptSecurityManager* securityManager =
2461 nsContentUtils::GetSecurityManager();
2462 if (securityManager) {
2463 securityManager->GetChannelResultPrincipals(
2464 aChannel, getter_AddRefs(principal),
2465 getter_AddRefs(partitionedPrincipal));
2469 bool equal = principal->Equals(partitionedPrincipal);
2471 principal = MaybeDowngradePrincipal(principal);
2472 if (equal) {
2473 partitionedPrincipal = principal;
2474 } else {
2475 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2478 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2480 // Note that, since mTiming does not change during a reset, the
2481 // navigationStart time remains unchanged and therefore any future new
2482 // timeline will have the same global clock time as the old one.
2483 mDocumentTimeline = nullptr;
2485 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
2486 if (bag) {
2487 nsCOMPtr<nsIURI> baseURI;
2488 bag->GetPropertyAsInterface(u"baseURI"_ns, NS_GET_IID(nsIURI),
2489 getter_AddRefs(baseURI));
2490 if (baseURI) {
2491 mDocumentBaseURI = baseURI;
2492 mChromeXHRDocBaseURI = nullptr;
2496 mChannel = aChannel;
2499 void Document::DisconnectNodeTree() {
2500 // Delete references to sub-documents and kill the subdocument map,
2501 // if any. This is not strictly needed, but makes the node tree
2502 // teardown a bit faster.
2503 delete mSubDocuments;
2504 mSubDocuments = nullptr;
2506 bool oldVal = mInUnlinkOrDeletion;
2507 mInUnlinkOrDeletion = true;
2508 { // Scope for update
2509 MOZ_AUTO_DOC_UPDATE(this, true);
2511 // Destroy link map now so we don't waste time removing
2512 // links one by one
2513 DestroyElementMaps();
2515 // Invalidate cached array of child nodes
2516 InvalidateChildNodes();
2518 while (HasChildren()) {
2519 nsMutationGuard::DidMutate();
2520 nsCOMPtr<nsIContent> content = GetLastChild();
2521 nsIContent* previousSibling = content->GetPreviousSibling();
2522 DisconnectChild(content);
2523 if (content == mCachedRootElement) {
2524 // Immediately clear mCachedRootElement, now that it's been removed
2525 // from mChildren, so that GetRootElement() will stop returning this
2526 // now-stale value.
2527 mCachedRootElement = nullptr;
2529 MutationObservers::NotifyContentRemoved(this, content, previousSibling);
2530 content->UnbindFromTree();
2532 MOZ_ASSERT(!mCachedRootElement,
2533 "After removing all children, there should be no root elem");
2535 mInUnlinkOrDeletion = oldVal;
2538 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2539 nsIPrincipal* aPrincipal,
2540 nsIPrincipal* aPartitionedPrincipal) {
2541 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2542 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2544 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2545 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2547 mSecurityInfo = nullptr;
2549 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2550 if (!aLoadGroup || group != aLoadGroup) {
2551 mDocumentLoadGroup = nullptr;
2554 DisconnectNodeTree();
2556 // Reset our stylesheets
2557 ResetStylesheetsToURI(aURI);
2559 // Release the listener manager
2560 if (mListenerManager) {
2561 mListenerManager->Disconnect();
2562 mListenerManager = nullptr;
2565 // Release the stylesheets list.
2566 mDOMStyleSheets = nullptr;
2568 // Release our principal after tearing down the document, rather than before.
2569 // This ensures that, during teardown, the document and the dying window
2570 // (which already nulled out its document pointer and cached the principal)
2571 // have matching principals.
2572 SetPrincipals(nullptr, nullptr);
2574 // Clear the original URI so SetDocumentURI sets it.
2575 mOriginalURI = nullptr;
2577 SetDocumentURI(aURI);
2578 mChromeXHRDocURI = nullptr;
2579 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
2580 // mDocumentURI.
2581 mDocumentBaseURI = nullptr;
2582 mChromeXHRDocBaseURI = nullptr;
2584 // Check if the current document is the top-level DevTools document.
2585 // For inner DevTools frames, mIsDevToolsDocument will be set when
2586 // calling SetDocumentParent.
2587 if (aURI && aURI->SchemeIs("about") &&
2588 aURI->GetSpecOrDefault().EqualsLiteral("about:devtools-toolbox")) {
2589 mIsDevToolsDocument = true;
2592 if (aLoadGroup) {
2593 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
2594 // there was an assertion here that aLoadGroup was not null. This
2595 // is no longer valid: nsDocShell::SetDocument does not create a
2596 // load group, and it works just fine
2598 // XXXbz what does "just fine" mean exactly? And given that there
2599 // is no nsDocShell::SetDocument, what is this talking about?
2601 if (IsContentDocument()) {
2602 // Inform the associated request context about this load start so
2603 // any of its internal load progress flags gets reset.
2604 nsCOMPtr<nsIRequestContextService> rcsvc =
2605 net::RequestContextService::GetOrCreate();
2606 if (rcsvc) {
2607 nsCOMPtr<nsIRequestContext> rc;
2608 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
2609 if (rc) {
2610 rc->BeginLoad();
2616 mLastModified.Truncate();
2617 // XXXbz I guess we're assuming that the caller will either pass in
2618 // a channel with a useful type or call SetContentType?
2619 SetContentTypeInternal(EmptyCString());
2620 mContentLanguage.Truncate();
2621 mBaseTarget.Truncate();
2623 mXMLDeclarationBits = 0;
2625 // Now get our new principal
2626 if (aPrincipal) {
2627 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2628 } else {
2629 nsIScriptSecurityManager* securityManager =
2630 nsContentUtils::GetSecurityManager();
2631 if (securityManager) {
2632 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
2634 if (!loadContext && aLoadGroup) {
2635 nsCOMPtr<nsIInterfaceRequestor> cbs;
2636 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
2637 loadContext = do_GetInterface(cbs);
2640 MOZ_ASSERT(loadContext,
2641 "must have a load context or pass in an explicit principal");
2643 nsCOMPtr<nsIPrincipal> principal;
2644 nsresult rv = securityManager->GetLoadContextContentPrincipal(
2645 mDocumentURI, loadContext, getter_AddRefs(principal));
2646 if (NS_SUCCEEDED(rv)) {
2647 SetPrincipals(principal, principal);
2652 if (mFontFaceSet) {
2653 mFontFaceSet->RefreshStandardFontLoadPrincipal();
2656 // Refresh the principal on the realm.
2657 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
2658 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
2662 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
2663 nsIPrincipal* aPrincipal) {
2664 if (!aPrincipal) {
2665 return nullptr;
2668 // We can't load a document with an expanded principal. If we're given one,
2669 // automatically downgrade it to the last principal it subsumes (which is the
2670 // extension principal, in the case of extension content scripts).
2671 auto* basePrin = BasePrincipal::Cast(aPrincipal);
2672 if (basePrin->Is<ExpandedPrincipal>()) {
2673 MOZ_DIAGNOSTIC_ASSERT(false,
2674 "Should never try to create a document with "
2675 "an expanded principal");
2677 auto* expanded = basePrin->As<ExpandedPrincipal>();
2678 return do_AddRef(expanded->AllowList().LastElement());
2681 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
2682 // We basically want the parent document here, but because this is very
2683 // early in the load, GetInProcessParentDocument() returns null, so we use
2684 // the docshell hierarchy to get this information instead.
2685 if (RefPtr<BrowsingContext> parent =
2686 mDocumentContainer->GetBrowsingContext()->GetParent()) {
2687 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
2688 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
2689 nsCOMPtr<nsIPrincipal> nullPrincipal =
2690 do_CreateInstance("@mozilla.org/nullprincipal;1");
2691 return nullPrincipal.forget();
2695 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
2696 return principal.forget();
2699 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
2700 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
2702 // lowest index first
2703 int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
2705 size_t count = mStyleSet->SheetCount(StyleOrigin::Author);
2706 size_t index = 0;
2707 for (; index < count; index++) {
2708 auto* sheet = mStyleSet->SheetAt(StyleOrigin::Author, index);
2709 MOZ_ASSERT(sheet);
2710 int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
2711 if (sheetDocIndex > newDocIndex) {
2712 break;
2715 // If the sheet is not owned by the document it can be an author
2716 // sheet registered at nsStyleSheetService or an additional author
2717 // sheet on the document, which means the new
2718 // doc sheet should end up before it.
2719 if (sheetDocIndex < 0) {
2720 if (sheetService) {
2721 auto& authorSheets = *sheetService->AuthorStyleSheets();
2722 if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
2723 break;
2726 if (sheet == GetFirstAdditionalAuthorSheet()) {
2727 break;
2732 return index;
2735 void Document::ResetStylesheetsToURI(nsIURI* aURI) {
2736 MOZ_ASSERT(aURI);
2738 ClearAdoptedStyleSheets();
2740 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
2741 for (auto& sheet : Reversed(aSheetList)) {
2742 sheet->ClearAssociatedDocumentOrShadowRoot();
2743 if (mStyleSetFilled) {
2744 mStyleSet->RemoveStyleSheet(*sheet);
2747 aSheetList.Clear();
2749 ClearSheetList(mStyleSheets);
2750 for (auto& sheets : mAdditionalSheets) {
2751 ClearSheetList(sheets);
2753 if (mStyleSetFilled) {
2754 if (auto* ss = nsStyleSheetService::GetInstance()) {
2755 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
2756 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
2757 if (sheet->IsApplicable()) {
2758 mStyleSet->RemoveStyleSheet(*sheet);
2764 // Now reset our inline style and attribute sheets.
2765 if (mAttrStyleSheet) {
2766 mAttrStyleSheet->Reset();
2767 mAttrStyleSheet->SetOwningDocument(this);
2768 } else {
2769 mAttrStyleSheet = new nsHTMLStyleSheet(this);
2772 if (!mStyleAttrStyleSheet) {
2773 mStyleAttrStyleSheet = new nsHTMLCSSStyleSheet();
2776 if (mStyleSetFilled) {
2777 FillStyleSetDocumentSheets();
2779 if (mStyleSet->StyleSheetsHaveChanged()) {
2780 ApplicableStylesChanged();
2785 static void AppendSheetsToStyleSet(
2786 ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
2787 for (StyleSheet* sheet : Reversed(aSheets)) {
2788 aStyleSet->AppendStyleSheet(*sheet);
2792 void Document::FillStyleSetUserAndUASheets() {
2793 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
2794 // ordering.
2796 // The document will fill in the document sheets when we create the presshell
2797 auto* cache = GlobalStyleSheetCache::Singleton();
2799 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
2800 MOZ_ASSERT(sheetService,
2801 "should never be creating a StyleSet after the style sheet "
2802 "service has gone");
2804 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
2805 mStyleSet->AppendStyleSheet(*sheet);
2808 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
2809 : cache->GetUserContentSheet();
2810 if (sheet) {
2811 mStyleSet->AppendStyleSheet(*sheet);
2814 mStyleSet->AppendStyleSheet(*cache->UASheet());
2816 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
2817 mStyleSet->AppendStyleSheet(*cache->MathMLSheet());
2820 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
2821 mStyleSet->AppendStyleSheet(*cache->SVGSheet());
2824 mStyleSet->AppendStyleSheet(*cache->HTMLSheet());
2826 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
2827 mStyleSet->AppendStyleSheet(*cache->NoFramesSheet());
2830 if (nsLayoutUtils::ShouldUseNoScriptSheet(this)) {
2831 mStyleSet->AppendStyleSheet(*cache->NoScriptSheet());
2834 mStyleSet->AppendStyleSheet(*cache->CounterStylesSheet());
2836 // Load the minimal XUL rules for scrollbars and a few other XUL things
2837 // that non-XUL (typically HTML) documents commonly use.
2838 mStyleSet->AppendStyleSheet(*cache->MinimalXULSheet());
2840 // Only load the full XUL sheet if we'll need it.
2841 if (LoadsFullXULStyleSheetUpFront()) {
2842 mStyleSet->AppendStyleSheet(*cache->XULSheet());
2845 mStyleSet->AppendStyleSheet(*cache->FormsSheet());
2846 mStyleSet->AppendStyleSheet(*cache->ScrollbarsSheet());
2847 mStyleSet->AppendStyleSheet(*cache->PluginProblemSheet());
2849 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
2850 mStyleSet->AppendStyleSheet(*sheet);
2853 MOZ_ASSERT(!mQuirkSheetAdded);
2854 if (NeedsQuirksSheet()) {
2855 mStyleSet->AppendStyleSheet(*cache->QuirkSheet());
2856 mQuirkSheetAdded = true;
2860 void Document::FillStyleSet() {
2861 MOZ_ASSERT(!mStyleSetFilled);
2862 FillStyleSetUserAndUASheets();
2863 FillStyleSetDocumentSheets();
2864 mStyleSetFilled = true;
2867 void Document::RemoveContentEditableStyleSheets() {
2868 MOZ_ASSERT(IsHTMLOrXHTML());
2870 auto* cache = GlobalStyleSheetCache::Singleton();
2871 bool changed = false;
2872 if (mDesignModeSheetAdded) {
2873 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
2874 mDesignModeSheetAdded = false;
2875 changed = true;
2877 if (mContentEditableSheetAdded) {
2878 mStyleSet->RemoveStyleSheet(*cache->ContentEditableSheet());
2879 mContentEditableSheetAdded = false;
2880 changed = true;
2882 if (changed) {
2883 MOZ_ASSERT(mStyleSetFilled);
2884 ApplicableStylesChanged();
2888 void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
2889 MOZ_ASSERT(IsHTMLOrXHTML());
2890 MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
2891 "Caller should ensure we're being rendered");
2893 auto* cache = GlobalStyleSheetCache::Singleton();
2894 bool changed = false;
2895 if (!mContentEditableSheetAdded) {
2896 mStyleSet->AppendStyleSheet(*cache->ContentEditableSheet());
2897 mContentEditableSheetAdded = true;
2898 changed = true;
2900 if (mDesignModeSheetAdded != aDesignMode) {
2901 if (mDesignModeSheetAdded) {
2902 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
2903 } else {
2904 mStyleSet->AppendStyleSheet(*cache->DesignModeSheet());
2906 mDesignModeSheetAdded = !mDesignModeSheetAdded;
2907 changed = true;
2909 if (changed) {
2910 ApplicableStylesChanged();
2914 void Document::FillStyleSetDocumentSheets() {
2915 MOZ_ASSERT(mStyleSet->SheetCount(StyleOrigin::Author) == 0,
2916 "Style set already has document sheets?");
2918 // Sheets are added in reverse order to avoid worst-case time complexity when
2919 // looking up the index of a sheet.
2921 // Note that usually appending is faster (rebuilds less stuff in the
2922 // styleset), but in this case it doesn't matter since we're filling the
2923 // styleset from scratch anyway.
2924 for (StyleSheet* sheet : Reversed(mStyleSheets)) {
2925 if (sheet->IsApplicable()) {
2926 mStyleSet->AddDocStyleSheet(*sheet);
2930 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
2931 if (aSheet.IsApplicable()) {
2932 mStyleSet->AddDocStyleSheet(aSheet);
2936 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
2937 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
2938 mStyleSet->AppendStyleSheet(*sheet);
2941 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAgentSheet]);
2942 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eUserSheet]);
2943 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAuthorSheet]);
2946 void Document::CompatibilityModeChanged() {
2947 MOZ_ASSERT(IsHTMLOrXHTML());
2948 CSSLoader()->SetCompatibilityMode(mCompatMode);
2949 mStyleSet->CompatibilityModeChanged();
2950 if (PresShell* presShell = GetPresShell()) {
2951 // Selectors may have become case-sensitive / case-insensitive, the stylist
2952 // has already performed the relevant invalidation.
2953 presShell->EnsureStyleFlush();
2955 if (!mStyleSetFilled) {
2956 MOZ_ASSERT(!mQuirkSheetAdded);
2957 return;
2959 if (mQuirkSheetAdded == NeedsQuirksSheet()) {
2960 return;
2962 auto* cache = GlobalStyleSheetCache::Singleton();
2963 StyleSheet* sheet = cache->QuirkSheet();
2964 if (mQuirkSheetAdded) {
2965 mStyleSet->RemoveStyleSheet(*sheet);
2966 } else {
2967 mStyleSet->AppendStyleSheet(*sheet);
2969 mQuirkSheetAdded = !mQuirkSheetAdded;
2970 ApplicableStylesChanged();
2973 void Document::SetCompatibilityMode(nsCompatibility aMode) {
2974 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
2975 "Bad compat mode for XHTML document!");
2977 if (mCompatMode == aMode) {
2978 return;
2980 mCompatMode = aMode;
2981 CompatibilityModeChanged();
2984 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
2985 uint32_t aSandboxFlags,
2986 nsIChannel* aChannel) {
2987 // If the document permits allow-top-navigation and
2988 // allow-top-navigation-by-user-activation this will permit all top
2989 // navigation.
2990 if (aSandboxFlags != SANDBOXED_NONE &&
2991 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
2992 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
2993 nsCOMPtr<nsIURI> iframeUri;
2994 nsContentUtils::ReportToConsole(
2995 nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
2996 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
2997 "BothAllowTopNavigationAndUserActivationPresent");
2999 // If the document is sandboxed (via the HTML5 iframe sandbox
3000 // attribute) and both the allow-scripts and allow-same-origin
3001 // keywords are supplied, the sandboxed document can call into its
3002 // parent document and remove its sandboxing entirely - we print a
3003 // warning to the web console in this case.
3004 if (aSandboxFlags & SANDBOXED_NAVIGATION &&
3005 !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
3006 !(aSandboxFlags & SANDBOXED_ORIGIN)) {
3007 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
3008 MOZ_ASSERT(bc->IsInProcess());
3010 RefPtr<BrowsingContext> parentBC = bc->GetParent();
3011 if (!parentBC || !parentBC->IsInProcess()) {
3012 // If parent document is not in process, then by construction it
3013 // cannot be same origin.
3014 return;
3017 // Don't warn if our parent is not the top-level document.
3018 if (!parentBC->IsTopContent()) {
3019 return;
3022 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
3023 MOZ_ASSERT(parentDocShell);
3025 nsCOMPtr<nsIChannel> parentChannel;
3026 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
3027 if (!parentChannel) {
3028 return;
3030 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
3031 if (NS_FAILED(rv)) {
3032 return;
3035 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
3036 nsCOMPtr<nsIURI> iframeUri;
3037 parentChannel->GetURI(getter_AddRefs(iframeUri));
3038 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3039 "Iframe Sandbox"_ns, parentDocument,
3040 nsContentUtils::eSECURITY_PROPERTIES,
3041 "BothAllowScriptsAndSameOriginPresent",
3042 nsTArray<nsString>(), iframeUri);
3046 bool Document::IsSynthesized() {
3047 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
3048 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
3051 // static
3052 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
3053 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
3054 return principal && (principal->IsSystemPrincipal() ||
3055 principal->GetIsAddonOrExpandedAddonPrincipal());
3058 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
3059 nsILoadGroup* aLoadGroup,
3060 nsISupports* aContainer,
3061 nsIStreamListener** aDocListener,
3062 bool aReset, nsIContentSink* aSink) {
3063 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
3064 nsCOMPtr<nsIURI> uri;
3065 aChannel->GetURI(getter_AddRefs(uri));
3066 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
3067 ("DOCUMENT %p StartDocumentLoad %s", this,
3068 uri ? uri->GetSpecOrDefault().get() : ""));
3071 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
3072 "Bad readyState");
3073 SetReadyStateInternal(READYSTATE_LOADING);
3075 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
3076 mLoadedAsData = true;
3077 // We need to disable script & style loading in this case.
3078 // We leave them disabled even in EndLoad(), and let anyone
3079 // who puts the document on display to worry about enabling.
3081 // Do not load/process scripts when loading as data
3082 ScriptLoader()->SetEnabled(false);
3084 // styles
3085 CSSLoader()->SetEnabled(
3086 false); // Do not load/process styles when loading as data
3087 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
3088 // Allow CSS, but not scripts
3089 ScriptLoader()->SetEnabled(false);
3092 mMayStartLayout = false;
3093 MOZ_ASSERT(!mReadyForIdle,
3094 "We should never hit DOMContentLoaded before this point");
3096 if (aReset) {
3097 Reset(aChannel, aLoadGroup);
3100 nsAutoCString contentType;
3101 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3102 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3103 contentType))) ||
3104 NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
3105 // XXX this is only necessary for viewsource:
3106 nsACString::const_iterator start, end, semicolon;
3107 contentType.BeginReading(start);
3108 contentType.EndReading(end);
3109 semicolon = start;
3110 FindCharInReadable(';', semicolon, end);
3111 SetContentTypeInternal(Substring(start, semicolon));
3114 RetrieveRelevantHeaders(aChannel);
3116 mChannel = aChannel;
3117 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
3118 if (inStrmChan) {
3119 bool isSrcdocChannel;
3120 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
3121 if (isSrcdocChannel) {
3122 mIsSrcdocDocument = true;
3126 if (mChannel) {
3127 nsLoadFlags loadFlags;
3128 mChannel->GetLoadFlags(&loadFlags);
3129 bool isDocument = false;
3130 mChannel->GetIsDocument(&isDocument);
3131 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
3132 IsSynthesized() && XRE_IsContentProcess()) {
3133 ContentChild::UpdateCookieStatus(mChannel);
3136 // Store the security info for future use.
3137 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
3140 // If this document is being loaded by a docshell, copy its sandbox flags
3141 // to the document, and store the fullscreen enabled flag. These are
3142 // immutable after being set here.
3143 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
3145 // If this is an error page, don't inherit sandbox flags
3146 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3147 if (docShell && !loadInfo->GetLoadErrorPage()) {
3148 mSandboxFlags = loadInfo->GetSandboxFlags();
3149 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
3152 // Set the opener policy for the top level content document.
3153 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
3154 nsILoadInfo::CrossOriginOpenerPolicy policy =
3155 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
3156 if (IsTopLevelContentDocument() && httpChan &&
3157 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
3158 docShell->GetBrowsingContext()) {
3159 docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
3162 // The CSP directives upgrade-insecure-requests as well as
3163 // block-all-mixed-content not only apply to the toplevel document,
3164 // but also to nested documents. The loadInfo of a subdocument
3165 // load already holds the correct flag, so let's just set it here
3166 // on the document. Please note that we set the appropriate preload
3167 // bits just for the sake of completeness here, because the preloader
3168 // does not reach into subdocuments.
3169 mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
3170 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3171 mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
3172 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3174 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
3175 // sub-resources and sub-documents.
3176 mHttpsOnlyStatus =
3177 loadInfo->GetHttpsOnlyStatus() & nsILoadInfo::HTTPS_ONLY_EXEMPT;
3179 nsresult rv = InitReferrerInfo(aChannel);
3180 NS_ENSURE_SUCCESS(rv, rv);
3182 rv = InitCOEP(aChannel);
3183 NS_ENSURE_SUCCESS(rv, rv);
3185 // Check CSP navigate-to
3186 // We need to enforce the CSP of the document that initiated the load,
3187 // which is the CSP to inherit.
3188 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3189 if (cspToInherit) {
3190 bool allowsNavigateTo = false;
3191 rv = cspToInherit->GetAllowsNavigateTo(
3192 mDocumentURI, loadInfo->GetIsFormSubmission(),
3193 !loadInfo->RedirectChain().IsEmpty(), /* aWasRedirected */
3194 true, /* aEnforceWhitelist */
3195 &allowsNavigateTo);
3196 NS_ENSURE_SUCCESS(rv, rv);
3198 if (!allowsNavigateTo) {
3199 aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
3200 return NS_OK;
3204 rv = InitCSP(aChannel);
3205 NS_ENSURE_SUCCESS(rv, rv);
3207 // Initialize FeaturePolicy
3208 rv = InitFeaturePolicy(aChannel);
3209 NS_ENSURE_SUCCESS(rv, rv);
3211 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3212 NS_ENSURE_SUCCESS(rv, rv);
3214 // Generally XFO and CSP frame-ancestors is handled within
3215 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3216 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3217 nsContentPolicyType internalContentType =
3218 loadInfo->InternalContentPolicyType();
3219 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3220 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3221 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3223 nsresult status;
3224 aChannel->GetStatus(&status);
3225 if (status == NS_ERROR_XFO_VIOLATION) {
3226 // stop! ERROR page!
3227 // But before we have to reset the principal of the document
3228 // because the onload() event fires before the error page
3229 // is displayed and we do not want the enclosing document
3230 // to access the contentDocument.
3231 RefPtr<NullPrincipal> nullPrincipal =
3232 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3233 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3234 // and also GetInnerWindow() is still null at this point, before
3235 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3236 // within Document.cpp
3237 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3238 SetPrincipals(nullPrincipal, nullPrincipal);
3242 return NS_OK;
3245 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3247 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
3249 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3250 return mPreloadCSP;
3253 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3254 mPreloadCSP = aPreloadCSP;
3257 void Document::GetCspJSON(nsString& aJSON) {
3258 aJSON.Truncate();
3260 if (!mCSP) {
3261 dom::CSPPolicies jsonPolicies;
3262 jsonPolicies.ToJSON(aJSON);
3263 return;
3265 mCSP->ToJSON(aJSON);
3268 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3269 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3270 nsAutoString messageTag;
3271 aMessages[i]->GetTag(messageTag);
3273 nsAutoString category;
3274 aMessages[i]->GetCategory(category);
3276 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3277 NS_ConvertUTF16toUTF8(category), this,
3278 nsContentUtils::eSECURITY_PROPERTIES,
3279 NS_ConvertUTF16toUTF8(messageTag).get());
3283 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3284 nsresult rv = NS_OK;
3285 if (!aSpeculative) {
3286 // 1) apply settings from regular CSP
3287 if (mCSP) {
3288 // Set up 'block-all-mixed-content' if not already inherited
3289 // from the parent context or set by any other CSP.
3290 if (!mBlockAllMixedContent) {
3291 rv = mCSP->GetBlockAllMixedContent(&mBlockAllMixedContent);
3292 NS_ENSURE_SUCCESS_VOID(rv);
3294 if (!mBlockAllMixedContentPreloads) {
3295 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3298 // Set up 'upgrade-insecure-requests' if not already inherited
3299 // from the parent context or set by any other CSP.
3300 if (!mUpgradeInsecureRequests) {
3301 rv = mCSP->GetUpgradeInsecureRequests(&mUpgradeInsecureRequests);
3302 NS_ENSURE_SUCCESS_VOID(rv);
3304 if (!mUpgradeInsecurePreloads) {
3305 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3307 // Update csp settings in the parent process
3308 if (auto* wgc = GetWindowGlobalChild()) {
3309 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3310 mUpgradeInsecureRequests);
3313 return;
3316 // 2) apply settings from speculative csp
3317 if (mPreloadCSP) {
3318 if (!mBlockAllMixedContentPreloads) {
3319 rv = mPreloadCSP->GetBlockAllMixedContent(&mBlockAllMixedContentPreloads);
3320 NS_ENSURE_SUCCESS_VOID(rv);
3322 if (!mUpgradeInsecurePreloads) {
3323 rv = mPreloadCSP->GetUpgradeInsecureRequests(&mUpgradeInsecurePreloads);
3324 NS_ENSURE_SUCCESS_VOID(rv);
3329 nsresult Document::InitCSP(nsIChannel* aChannel) {
3330 MOZ_ASSERT(!mScriptGlobalObject,
3331 "CSP must be initialized before mScriptGlobalObject is set!");
3332 if (!StaticPrefs::security_csp_enable()) {
3333 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3334 ("CSP is disabled, skipping CSP init for document %p", this));
3335 return NS_OK;
3338 // If this is a data document - no need to set CSP.
3339 if (mLoadedAsData) {
3340 return NS_OK;
3343 // If this is an image, no need to set a CSP. Otherwise SVG images
3344 // served with a CSP might block internally applied inline styles.
3345 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3346 if (loadInfo->GetExternalContentPolicyType() ==
3347 nsIContentPolicy::TYPE_IMAGE) {
3348 return NS_OK;
3351 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3353 // If there is a CSP that needs to be inherited from whatever
3354 // global is considered the client of the document fetch then
3355 // we query it here from the loadinfo in case the newly created
3356 // document needs to inherit the CSP. See:
3357 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3358 if (CSP_ShouldResponseInheritCSP(aChannel)) {
3359 mCSP = loadInfo->GetCspToInherit();
3362 // If there is no CSP to inherit, then we create a new CSP here so
3363 // that history entries always have the right reference in case a
3364 // Meta CSP gets dynamically added after the history entry has
3365 // already been created.
3366 if (!mCSP) {
3367 mCSP = new nsCSPContext();
3370 // Always overwrite the requesting context of the CSP so that any new
3371 // 'self' keyword added to an inherited CSP translates correctly.
3372 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3373 if (NS_WARN_IF(NS_FAILED(rv))) {
3374 return rv;
3377 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3379 nsCOMPtr<nsIHttpChannel> httpChannel;
3380 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3381 if (NS_WARN_IF(NS_FAILED(rv))) {
3382 return rv;
3385 if (httpChannel) {
3386 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3387 tCspHeaderValue);
3389 Unused << httpChannel->GetResponseHeader(
3390 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3392 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3393 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3395 // Check if this is a document from a WebExtension.
3396 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3397 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3399 // If there's no CSP to apply, go ahead and return early
3400 if (!addonPolicy && cspHeaderValue.IsEmpty() && cspROHeaderValue.IsEmpty()) {
3401 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3402 nsCOMPtr<nsIURI> chanURI;
3403 aChannel->GetURI(getter_AddRefs(chanURI));
3404 nsAutoCString aspec;
3405 chanURI->GetAsciiSpec(aspec);
3406 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3407 ("no CSP for document, %s", aspec.get()));
3410 return NS_OK;
3413 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3414 ("Document is an add-on or CSP header specified %p", this));
3416 // ----- if the doc is an addon, apply its CSP.
3417 if (addonPolicy) {
3418 nsAutoString extensionPageCSP;
3419 Unused << ExtensionPolicyService::GetSingleton().GetBaseCSP(
3420 extensionPageCSP);
3421 mCSP->AppendPolicy(extensionPageCSP, false, false);
3423 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3424 // Bug 1548468: Move CSP off ExpandedPrincipal
3425 // Currently the LoadInfo holds the source of truth for every resource load
3426 // because LoadInfo::GetCSP() queries the CSP from an ExpandedPrincipal
3427 // (and not from the Client) if the load was triggered by an extension.
3428 auto* basePrin = BasePrincipal::Cast(principal);
3429 if (basePrin->Is<ExpandedPrincipal>()) {
3430 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3434 // ----- if there's a full-strength CSP header, apply it.
3435 if (!cspHeaderValue.IsEmpty()) {
3436 mHasCSPDeliveredThroughHeader = true;
3437 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3438 NS_ENSURE_SUCCESS(rv, rv);
3441 // ----- if there's a report-only CSP header, apply it.
3442 if (!cspROHeaderValue.IsEmpty()) {
3443 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3444 NS_ENSURE_SUCCESS(rv, rv);
3447 // ----- Enforce sandbox policy if supplied in CSP header
3448 // The document may already have some sandbox flags set (e.g. if the document
3449 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3450 // directive, intersect the CSP sandbox flags with the existing flags. This
3451 // corresponds to the _least_ permissive policy.
3452 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3453 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3454 NS_ENSURE_SUCCESS(rv, rv);
3456 // Probably the iframe sandbox attribute already caused the creation of a
3457 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3458 // and no one has been created yet.
3459 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3460 !(mSandboxFlags & SANDBOXED_ORIGIN);
3462 mSandboxFlags |= cspSandboxFlags;
3464 if (needNewNullPrincipal) {
3465 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3466 SetPrincipals(principal, principal);
3469 ApplySettingsFromCSP(false);
3470 return NS_OK;
3473 already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
3474 if (!mDocumentContainer) {
3475 return nullptr;
3478 nsPIDOMWindowOuter* containerWindow = mDocumentContainer->GetWindow();
3479 if (!containerWindow) {
3480 return nullptr;
3483 BrowsingContext* context = containerWindow->GetBrowsingContext();
3484 if (!context) {
3485 return nullptr;
3488 RefPtr<dom::FeaturePolicy> parentPolicy;
3489 if (context->IsContentSubframe() && !context->GetParent()->IsInProcess()) {
3490 // We are in cross process, so try to get feature policy from
3491 // container's BrowsingContext
3492 parentPolicy = context->GetFeaturePolicy();
3493 return parentPolicy.forget();
3496 nsCOMPtr<nsINode> node = containerWindow->GetFrameElementInternal();
3497 HTMLIFrameElement* iframe = HTMLIFrameElement::FromNodeOrNull(node);
3498 if (!iframe) {
3499 return nullptr;
3502 parentPolicy = iframe->FeaturePolicy();
3503 return parentPolicy.forget();
3506 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
3507 MOZ_ASSERT(mFeaturePolicy, "we should only call init once");
3509 mFeaturePolicy->ResetDeclaredPolicy();
3511 if (!StaticPrefs::dom_security_featurePolicy_enabled()) {
3512 return NS_OK;
3515 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
3517 RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
3518 if (parentPolicy) {
3519 // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
3520 mFeaturePolicy->InheritPolicy(parentPolicy);
3521 mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
3524 // We don't want to parse the http Feature-Policy header if this pref is off.
3525 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
3526 return NS_OK;
3529 nsCOMPtr<nsIHttpChannel> httpChannel;
3530 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3531 if (NS_WARN_IF(NS_FAILED(rv))) {
3532 return rv;
3535 if (!httpChannel) {
3536 return NS_OK;
3539 // query the policy from the header
3540 nsAutoCString value;
3541 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
3542 if (NS_SUCCEEDED(rv)) {
3543 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
3544 NodePrincipal(), nullptr);
3547 return NS_OK;
3550 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
3551 MOZ_ASSERT(mReferrerInfo);
3552 MOZ_ASSERT(mPreloadReferrerInfo);
3554 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
3555 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
3556 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
3557 // they have an opaque origin.
3558 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
3559 if (BrowsingContext* bc = GetBrowsingContext()) {
3560 // At this point the document is not fully created and mParentDocument has
3561 // not been set yet,
3562 Document* parentDoc = bc->GetEmbedderElement()
3563 ? bc->GetEmbedderElement()->OwnerDoc()
3564 : nullptr;
3565 if (parentDoc) {
3566 mReferrerInfo = parentDoc->GetReferrerInfo();
3567 mPreloadReferrerInfo = mReferrerInfo;
3568 return NS_OK;
3571 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
3572 "srcdoc without null principal as toplevel!");
3576 nsCOMPtr<nsIHttpChannel> httpChannel;
3577 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3578 if (NS_WARN_IF(NS_FAILED(rv))) {
3579 return rv;
3582 if (!httpChannel) {
3583 return NS_OK;
3586 nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
3587 if (referrerInfo) {
3588 mReferrerInfo = referrerInfo;
3591 // Override policy if we get one from Referrerr-Policy header
3592 mozilla::dom::ReferrerPolicy policy =
3593 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
3594 mReferrerInfo = static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
3595 ->CloneWithNewPolicy(policy);
3597 mPreloadReferrerInfo = mReferrerInfo;
3598 return NS_OK;
3601 nsresult Document::InitCOEP(nsIChannel* aChannel) {
3602 nsCOMPtr<nsIHttpChannel> httpChannel;
3603 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3604 if (NS_FAILED(rv)) {
3605 return NS_OK;
3608 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
3610 if (!intChannel) {
3611 return NS_OK;
3614 nsILoadInfo::CrossOriginEmbedderPolicy policy =
3615 nsILoadInfo::EMBEDDER_POLICY_NULL;
3616 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(&policy))) {
3617 mEmbedderPolicy = Some(policy);
3620 return NS_OK;
3623 void Document::StopDocumentLoad() {
3624 if (mParser) {
3625 mParserAborted = true;
3626 mParser->Terminate();
3630 void Document::SetDocumentURI(nsIURI* aURI) {
3631 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
3632 mDocumentURI = aURI;
3633 nsIURI* newBase = GetDocBaseURI();
3635 mDocURISchemeIsChrome = aURI && IsChromeURI(aURI);
3637 bool equalBases = false;
3638 // Changing just the ref of a URI does not change how relative URIs would
3639 // resolve wrt to it, so we can treat the bases as equal as long as they're
3640 // equal ignoring the ref.
3641 if (oldBase && newBase) {
3642 oldBase->EqualsExceptRef(newBase, &equalBases);
3643 } else {
3644 equalBases = !oldBase && !newBase;
3647 // If this is the first time we're setting the document's URI, set the
3648 // document's original URI.
3649 if (!mOriginalURI) mOriginalURI = mDocumentURI;
3651 // If changing the document's URI changed the base URI of the document, we
3652 // need to refresh the hrefs of all the links on the page.
3653 if (!equalBases) {
3654 RefreshLinkHrefs();
3657 // Recalculate our base domain
3658 mBaseDomain.Truncate();
3659 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
3660 if (thirdPartyUtil) {
3661 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
3664 // Tell our WindowGlobalParent that the document's URI has been changed.
3665 nsPIDOMWindowInner* inner = GetInnerWindow();
3666 if (inner && inner->GetWindowGlobalChild()) {
3667 inner->GetWindowGlobalChild()->SetDocumentURI(mDocumentURI);
3671 static void GetFormattedTimeString(PRTime aTime,
3672 nsAString& aFormattedTimeString) {
3673 PRExplodedTime prtime;
3674 PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
3675 // "MM/DD/YYYY hh:mm:ss"
3676 char formatedTime[24];
3677 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
3678 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
3679 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
3680 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
3681 } else {
3682 // If we for whatever reason failed to find the last modified time
3683 // (or even the current time), fall back to what NS4.x returned.
3684 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
3688 void Document::GetLastModified(nsAString& aLastModified) const {
3689 if (!mLastModified.IsEmpty()) {
3690 aLastModified.Assign(mLastModified);
3691 } else {
3692 GetFormattedTimeString(PR_Now(), aLastModified);
3696 static void IncrementExpandoGeneration(Document& aDoc) {
3697 ++aDoc.mExpandoAndGeneration.generation;
3700 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
3701 MOZ_ASSERT(
3702 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
3703 "Only put elements that need to be exposed as document['name'] in "
3704 "the named table.");
3706 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
3708 // Null for out-of-memory
3709 if (entry) {
3710 if (!entry->HasNameElement() &&
3711 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
3712 IncrementExpandoGeneration(*this);
3714 entry->AddNameElement(this, aElement);
3718 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
3719 // Speed up document teardown
3720 if (mIdentifierMap.Count() == 0) return;
3722 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
3723 if (!entry) // Could be false if the element was anonymous, hence never added
3724 return;
3726 entry->RemoveNameElement(aElement);
3727 if (!entry->HasNameElement() &&
3728 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
3729 IncrementExpandoGeneration(*this);
3733 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
3734 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
3736 if (entry) { /* True except on OOM */
3737 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
3738 !entry->HasNameElement() &&
3739 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
3740 IncrementExpandoGeneration(*this);
3742 entry->AddIdElement(aElement);
3746 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
3747 NS_ASSERTION(aId, "huhwhatnow?");
3749 // Speed up document teardown
3750 if (mIdentifierMap.Count() == 0) {
3751 return;
3754 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
3755 if (!entry) // Can be null for XML elements with changing ids.
3756 return;
3758 entry->RemoveIdElement(aElement);
3759 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
3760 !entry->HasNameElement() &&
3761 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
3762 IncrementExpandoGeneration(*this);
3764 if (entry->IsEmpty()) {
3765 mIdentifierMap.RemoveEntry(entry);
3769 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
3770 nsIPrincipal* aNewPartitionedPrincipal) {
3771 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
3772 if (aNewPrincipal && mAllowDNSPrefetch &&
3773 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
3774 if (aNewPrincipal->SchemeIs("https")) {
3775 mAllowDNSPrefetch = false;
3779 mCSSLoader->DeregisterFromSheetCache();
3781 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
3782 mPartitionedPrincipal = aNewPartitionedPrincipal;
3784 mCSSLoader->RegisterInSheetCache();
3786 ContentBlockingAllowList::ComputePrincipal(
3787 aNewPrincipal, getter_AddRefs(mContentBlockingAllowListPrincipal));
3789 #ifdef DEBUG
3790 // Validate that the docgroup is set correctly by calling its getter and
3791 // triggering its sanity check.
3793 // If we're setting the principal to null, we don't want to perform the check,
3794 // as the document is entering an intermediate state where it does not have a
3795 // principal. It will be given another real principal shortly which we will
3796 // check. It's not unsafe to have a document which has a null principal in the
3797 // same docgroup as another document, so this should not be a problem.
3798 if (aNewPrincipal) {
3799 GetDocGroup();
3801 #endif
3804 #ifdef DEBUG
3805 void Document::AssertDocGroupMatchesKey() const {
3806 // Sanity check that we have an up-to-date and accurate docgroup
3807 // We only check if the principal when we can get the browsing context.
3808 if (!GetBrowsingContext()) {
3809 return;
3812 if (mDocGroup) {
3813 nsAutoCString docGroupKey;
3815 // GetKey() can fail, e.g. after the TLD service has shut down.
3816 nsresult rv = mozilla::dom::DocGroup::GetKey(
3817 NodePrincipal(), GetBrowsingContext()->CrossOriginIsolated(),
3818 docGroupKey);
3819 if (NS_SUCCEEDED(rv)) {
3820 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
3824 #endif
3826 nsresult Document::Dispatch(TaskCategory aCategory,
3827 already_AddRefed<nsIRunnable>&& aRunnable) {
3828 // Note that this method may be called off the main thread.
3829 if (mDocGroup) {
3830 return mDocGroup->Dispatch(aCategory, std::move(aRunnable));
3832 return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
3835 nsISerialEventTarget* Document::EventTargetFor(TaskCategory aCategory) const {
3836 if (mDocGroup) {
3837 return mDocGroup->EventTargetFor(aCategory);
3839 return DispatcherTrait::EventTargetFor(aCategory);
3842 AbstractThread* Document::AbstractMainThreadFor(
3843 mozilla::TaskCategory aCategory) {
3844 MOZ_ASSERT(NS_IsMainThread());
3845 if (mDocGroup) {
3846 return mDocGroup->AbstractMainThreadFor(aCategory);
3848 return DispatcherTrait::AbstractMainThreadFor(aCategory);
3851 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
3852 bool aIsTracking) {
3853 if (aIsTracking) {
3854 mTrackingScripts.PutEntry(aURL);
3855 } else {
3856 MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
3860 bool Document::IsScriptTracking(JSContext* aCx) const {
3861 JS::AutoFilename filename;
3862 uint32_t line = 0;
3863 uint32_t column = 0;
3864 if (!JS::DescribeScriptedCaller(aCx, &filename, &line, &column)) {
3865 return false;
3867 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
3870 NS_IMETHODIMP
3871 Document::GetApplicationCache(nsIApplicationCache** aApplicationCache) {
3872 NS_IF_ADDREF(*aApplicationCache = mApplicationCache);
3873 return NS_OK;
3876 NS_IMETHODIMP
3877 Document::SetApplicationCache(nsIApplicationCache* aApplicationCache) {
3878 mApplicationCache = aApplicationCache;
3879 return NS_OK;
3882 void Document::GetContentType(nsAString& aContentType) {
3883 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
3886 void Document::SetContentType(const nsAString& aContentType) {
3887 SetContentTypeInternal(NS_ConvertUTF16toUTF8(aContentType));
3890 bool Document::GetAllowPlugins() {
3891 // First, we ask our docshell if it allows plugins.
3892 auto* browsingContext = GetBrowsingContext();
3894 if (browsingContext) {
3895 if (!browsingContext->GetAllowPlugins()) {
3896 return false;
3899 // If the docshell allows plugins, we check whether
3900 // we are sandboxed and plugins should not be allowed.
3901 if (mSandboxFlags & SANDBOXED_PLUGINS) {
3902 return false;
3906 FlashClassification classification = DocumentFlashClassification();
3907 if (classification == FlashClassification::Denied) {
3908 return false;
3911 return true;
3914 void Document::EnsureL10n() {
3915 if (!mDocumentL10n) {
3916 Element* elem = GetDocumentElement();
3917 if (NS_WARN_IF(!elem)) {
3918 return;
3920 bool isSync = elem->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nsync);
3921 mDocumentL10n = DocumentL10n::Create(this, isSync);
3922 MOZ_ASSERT(mDocumentL10n);
3926 bool Document::HasPendingInitialTranslation() {
3927 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
3930 DocumentL10n* Document::GetL10n() { return mDocumentL10n; }
3932 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
3933 nsCOMPtr<nsIPrincipal> callerPrincipal =
3934 nsContentUtils::SubjectPrincipal(aCx);
3935 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
3936 bool allowed = false;
3937 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
3938 &allowed);
3939 return allowed;
3942 void Document::LocalizationLinkAdded(Element* aLinkElement) {
3943 if (!AllowsL10n()) {
3944 return;
3947 EnsureL10n();
3949 nsAutoString href;
3950 aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
3952 mDocumentL10n->AddResourceId(href);
3954 if (mReadyState >= READYSTATE_INTERACTIVE) {
3955 mDocumentL10n->Activate(true);
3956 mDocumentL10n->TriggerInitialTranslation();
3957 } else {
3958 if (!mDocumentL10n->mBlockingLayout) {
3959 // Our initial translation is going to block layout start. Make sure
3960 // we don't fire the load event until after that stops happening and
3961 // layout has a chance to start.
3962 BlockOnload();
3963 mDocumentL10n->mBlockingLayout = true;
3968 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
3969 if (!AllowsL10n()) {
3970 return;
3973 if (mDocumentL10n) {
3974 nsAutoString href;
3975 aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
3976 uint32_t remaining = mDocumentL10n->RemoveResourceId(href);
3977 if (remaining == 0) {
3978 if (mDocumentL10n->mBlockingLayout) {
3979 mDocumentL10n->mBlockingLayout = false;
3980 UnblockOnload(/* aFireSync = */ false);
3982 mDocumentL10n = nullptr;
3988 * This method should be called once the end of the l10n
3989 * resource container has been parsed.
3991 * In XUL this is the end of the first </linkset>,
3992 * In XHTML/HTML this is the end of </head>.
3994 * This milestone is used to allow for batch
3995 * localization context I/O and building done
3996 * once when all resources in the document have been
3997 * collected.
3999 void Document::OnL10nResourceContainerParsed() {
4000 if (mDocumentL10n) {
4001 mDocumentL10n->Activate(false);
4005 void Document::OnParsingCompleted() {
4006 // Let's call it again, in case the resource
4007 // container has not been closed, and only
4008 // now we're closing the document.
4009 OnL10nResourceContainerParsed();
4011 if (mDocumentL10n) {
4012 mDocumentL10n->TriggerInitialTranslation();
4016 void Document::InitialTranslationCompleted(bool aL10nCached) {
4017 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4018 // This means we blocked the load event in LocalizationLinkAdded. It's
4019 // important that the load blocker removal here be async, because our caller
4020 // will notify the content sink after us, and we want the content sync's
4021 // work to happen before the load event fires.
4022 mDocumentL10n->mBlockingLayout = false;
4023 UnblockOnload(/* aFireSync = */ false);
4026 mL10nProtoElements.Clear();
4028 nsXULPrototypeDocument* proto = GetPrototype();
4029 if (proto) {
4030 proto->SetIsL10nCached(aL10nCached);
4034 bool Document::AllowsL10n() const {
4035 bool allowed = false;
4036 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4037 return allowed;
4040 bool Document::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) {
4041 MOZ_ASSERT(NS_IsMainThread());
4043 return nsContentUtils::IsSystemCaller(aCx) ||
4044 StaticPrefs::dom_animations_api_core_enabled();
4047 bool Document::IsWebAnimationsEnabled(CallerType aCallerType) {
4048 MOZ_ASSERT(NS_IsMainThread());
4050 return aCallerType == dom::CallerType::System ||
4051 StaticPrefs::dom_animations_api_core_enabled();
4054 bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
4055 JSObject* /*unused*/
4057 MOZ_ASSERT(NS_IsMainThread());
4059 return nsContentUtils::IsSystemCaller(aCx) ||
4060 StaticPrefs::dom_animations_api_getAnimations_enabled();
4063 bool Document::AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
4064 JSObject* /*unused*/
4066 MOZ_ASSERT(NS_IsMainThread());
4068 return nsContentUtils::IsSystemCaller(aCx) ||
4069 StaticPrefs::dom_animations_api_implicit_keyframes_enabled();
4072 bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
4073 JSObject* /*unused*/
4075 MOZ_ASSERT(NS_IsMainThread());
4077 return nsContentUtils::IsSystemCaller(aCx) ||
4078 StaticPrefs::dom_animations_api_timelines_enabled();
4081 DocumentTimeline* Document::Timeline() {
4082 if (!mDocumentTimeline) {
4083 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4086 return mDocumentTimeline;
4089 SVGSVGElement* Document::GetSVGRootElement() const {
4090 Element* root = GetRootElement();
4091 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4092 return nullptr;
4094 return static_cast<SVGSVGElement*>(root);
4097 /* Return true if the document is in the focused top-level window, and is an
4098 * ancestor of the focused DOMWindow. */
4099 bool Document::HasFocus(ErrorResult& rv) const {
4100 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
4101 if (!fm) {
4102 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4103 return false;
4106 // Is there a focused DOMWindow?
4107 nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
4108 fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
4109 if (!focusedWindow) {
4110 return false;
4113 nsPIDOMWindowOuter* piWindow = nsPIDOMWindowOuter::From(focusedWindow);
4115 // Are we an ancestor of the focused DOMWindow?
4116 for (Document* currentDoc = piWindow->GetDoc(); currentDoc;
4117 currentDoc = currentDoc->GetInProcessParentDocument()) {
4118 if (currentDoc == this) {
4119 // Yes, we are an ancestor
4120 return true;
4124 return false;
4127 void Document::GetDesignMode(nsAString& aDesignMode) {
4128 if (HasFlag(NODE_IS_EDITABLE)) {
4129 aDesignMode.AssignLiteral("on");
4130 } else {
4131 aDesignMode.AssignLiteral("off");
4135 void Document::SetDesignMode(const nsAString& aDesignMode,
4136 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4137 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4140 static void NotifyEditableStateChange(Document& aDoc) {
4141 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4142 nsMutationGuard g;
4143 #endif
4144 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4145 node = node->GetNextNode(&aDoc)) {
4146 if (auto* element = Element::FromNode(node)) {
4147 element->UpdateState(true);
4150 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4153 void Document::SetDesignMode(const nsAString& aDesignMode,
4154 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4155 ErrorResult& rv) {
4156 if (aSubjectPrincipal.isSome() &&
4157 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4158 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4159 return;
4161 bool editableMode = HasFlag(NODE_IS_EDITABLE);
4162 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4163 SetEditableFlag(!editableMode);
4164 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4165 // state of all descendant elements of it. Update that now.
4166 NotifyEditableStateChange(*this);
4167 rv = EditingStateChanged();
4171 nsCommandManager* Document::GetMidasCommandManager() {
4172 // check if we have it cached
4173 if (mMidasCommandManager) {
4174 return mMidasCommandManager;
4177 nsPIDOMWindowOuter* window = GetWindow();
4178 if (!window) {
4179 return nullptr;
4182 nsIDocShell* docshell = window->GetDocShell();
4183 if (!docshell) {
4184 return nullptr;
4187 mMidasCommandManager = docshell->GetCommandManager();
4188 return mMidasCommandManager;
4191 // static
4192 void Document::EnsureInitializeInternalCommandDataHashtable() {
4193 if (sInternalCommandDataHashtable) {
4194 return;
4196 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4197 // clang-format off
4198 sInternalCommandDataHashtable->Put(
4199 u"bold"_ns,
4200 InternalCommandData(
4201 "cmd_bold",
4202 Command::FormatBold,
4203 ExecCommandParam::Ignore,
4204 StyleUpdatingCommand::GetInstance));
4205 sInternalCommandDataHashtable->Put(
4206 u"italic"_ns,
4207 InternalCommandData(
4208 "cmd_italic",
4209 Command::FormatItalic,
4210 ExecCommandParam::Ignore,
4211 StyleUpdatingCommand::GetInstance));
4212 sInternalCommandDataHashtable->Put(
4213 u"underline"_ns,
4214 InternalCommandData(
4215 "cmd_underline",
4216 Command::FormatUnderline,
4217 ExecCommandParam::Ignore,
4218 StyleUpdatingCommand::GetInstance));
4219 sInternalCommandDataHashtable->Put(
4220 u"strikethrough"_ns,
4221 InternalCommandData(
4222 "cmd_strikethrough",
4223 Command::FormatStrikeThrough,
4224 ExecCommandParam::Ignore,
4225 StyleUpdatingCommand::GetInstance));
4226 sInternalCommandDataHashtable->Put(
4227 u"subscript"_ns,
4228 InternalCommandData(
4229 "cmd_subscript",
4230 Command::FormatSubscript,
4231 ExecCommandParam::Ignore,
4232 StyleUpdatingCommand::GetInstance));
4233 sInternalCommandDataHashtable->Put(
4234 u"superscript"_ns,
4235 InternalCommandData(
4236 "cmd_superscript",
4237 Command::FormatSuperscript,
4238 ExecCommandParam::Ignore,
4239 StyleUpdatingCommand::GetInstance));
4240 sInternalCommandDataHashtable->Put(
4241 u"cut"_ns,
4242 InternalCommandData(
4243 "cmd_cut",
4244 Command::Cut,
4245 ExecCommandParam::Ignore,
4246 CutCommand::GetInstance));
4247 sInternalCommandDataHashtable->Put(
4248 u"copy"_ns,
4249 InternalCommandData(
4250 "cmd_copy",
4251 Command::Copy,
4252 ExecCommandParam::Ignore,
4253 CopyCommand::GetInstance));
4254 sInternalCommandDataHashtable->Put(
4255 u"paste"_ns,
4256 InternalCommandData(
4257 "cmd_paste",
4258 Command::Paste,
4259 ExecCommandParam::Ignore,
4260 PasteCommand::GetInstance));
4261 sInternalCommandDataHashtable->Put(
4262 u"delete"_ns,
4263 InternalCommandData(
4264 "cmd_deleteCharBackward",
4265 Command::DeleteCharBackward,
4266 ExecCommandParam::Ignore,
4267 DeleteCommand::GetInstance));
4268 sInternalCommandDataHashtable->Put(
4269 u"forwarddelete"_ns,
4270 InternalCommandData(
4271 "cmd_deleteCharForward",
4272 Command::DeleteCharForward,
4273 ExecCommandParam::Ignore,
4274 DeleteCommand::GetInstance));
4275 sInternalCommandDataHashtable->Put(
4276 u"selectall"_ns,
4277 InternalCommandData(
4278 "cmd_selectAll",
4279 Command::SelectAll,
4280 ExecCommandParam::Ignore,
4281 SelectAllCommand::GetInstance));
4282 sInternalCommandDataHashtable->Put(
4283 u"undo"_ns,
4284 InternalCommandData(
4285 "cmd_undo",
4286 Command::HistoryUndo,
4287 ExecCommandParam::Ignore,
4288 UndoCommand::GetInstance));
4289 sInternalCommandDataHashtable->Put(
4290 u"redo"_ns,
4291 InternalCommandData(
4292 "cmd_redo",
4293 Command::HistoryRedo,
4294 ExecCommandParam::Ignore,
4295 RedoCommand::GetInstance));
4296 sInternalCommandDataHashtable->Put(
4297 u"indent"_ns,
4298 InternalCommandData("cmd_indent",
4299 Command::FormatIndent,
4300 ExecCommandParam::Ignore,
4301 IndentCommand::GetInstance));
4302 sInternalCommandDataHashtable->Put(
4303 u"outdent"_ns,
4304 InternalCommandData(
4305 "cmd_outdent",
4306 Command::FormatOutdent,
4307 ExecCommandParam::Ignore,
4308 OutdentCommand::GetInstance));
4309 sInternalCommandDataHashtable->Put(
4310 u"backcolor"_ns,
4311 InternalCommandData(
4312 "cmd_highlight",
4313 Command::FormatBackColor,
4314 ExecCommandParam::String,
4315 HighlightColorStateCommand::GetInstance));
4316 sInternalCommandDataHashtable->Put(
4317 u"hilitecolor"_ns,
4318 InternalCommandData(
4319 "cmd_highlight",
4320 Command::FormatBackColor,
4321 ExecCommandParam::String,
4322 HighlightColorStateCommand::GetInstance));
4323 sInternalCommandDataHashtable->Put(
4324 u"forecolor"_ns,
4325 InternalCommandData(
4326 "cmd_fontColor",
4327 Command::FormatFontColor,
4328 ExecCommandParam::String,
4329 FontColorStateCommand::GetInstance));
4330 sInternalCommandDataHashtable->Put(
4331 u"fontname"_ns,
4332 InternalCommandData(
4333 "cmd_fontFace",
4334 Command::FormatFontName,
4335 ExecCommandParam::String,
4336 FontFaceStateCommand::GetInstance));
4337 sInternalCommandDataHashtable->Put(
4338 u"fontsize"_ns,
4339 InternalCommandData(
4340 "cmd_fontSize",
4341 Command::FormatFontSize,
4342 ExecCommandParam::String,
4343 FontSizeStateCommand::GetInstance));
4344 sInternalCommandDataHashtable->Put(
4345 u"increasefontsize"_ns,
4346 InternalCommandData(
4347 "cmd_increaseFont",
4348 Command::FormatIncreaseFontSize,
4349 ExecCommandParam::Ignore,
4350 IncreaseFontSizeCommand::GetInstance));
4351 sInternalCommandDataHashtable->Put(
4352 u"decreasefontsize"_ns,
4353 InternalCommandData(
4354 "cmd_decreaseFont",
4355 Command::FormatDecreaseFontSize,
4356 ExecCommandParam::Ignore,
4357 DecreaseFontSizeCommand::GetInstance));
4358 sInternalCommandDataHashtable->Put(
4359 u"inserthorizontalrule"_ns,
4360 InternalCommandData(
4361 "cmd_insertHR",
4362 Command::InsertHorizontalRule,
4363 ExecCommandParam::Ignore,
4364 InsertTagCommand::GetInstance));
4365 sInternalCommandDataHashtable->Put(
4366 u"createlink"_ns,
4367 InternalCommandData(
4368 "cmd_insertLinkNoUI",
4369 Command::InsertLink,
4370 ExecCommandParam::String,
4371 InsertTagCommand::GetInstance));
4372 sInternalCommandDataHashtable->Put(
4373 u"insertimage"_ns,
4374 InternalCommandData(
4375 "cmd_insertImageNoUI",
4376 Command::InsertImage,
4377 ExecCommandParam::String,
4378 InsertTagCommand::GetInstance));
4379 sInternalCommandDataHashtable->Put(
4380 u"inserthtml"_ns,
4381 InternalCommandData(
4382 "cmd_insertHTML",
4383 Command::InsertHTML,
4384 ExecCommandParam::String,
4385 InsertHTMLCommand::GetInstance));
4386 sInternalCommandDataHashtable->Put(
4387 u"inserttext"_ns,
4388 InternalCommandData(
4389 "cmd_insertText",
4390 Command::InsertText,
4391 ExecCommandParam::String,
4392 InsertPlaintextCommand::GetInstance));
4393 sInternalCommandDataHashtable->Put(
4394 u"gethtml"_ns,
4395 InternalCommandData(
4396 "cmd_getContents",
4397 Command::GetHTML,
4398 ExecCommandParam::Ignore,
4399 nullptr)); // Not defined in EditorCommands.h
4400 sInternalCommandDataHashtable->Put(
4401 u"justifyleft"_ns,
4402 InternalCommandData(
4403 "cmd_align",
4404 Command::FormatJustifyLeft,
4405 ExecCommandParam::Ignore, // Will be set to "left"
4406 AlignCommand::GetInstance));
4407 sInternalCommandDataHashtable->Put(
4408 u"justifyright"_ns,
4409 InternalCommandData(
4410 "cmd_align",
4411 Command::FormatJustifyRight,
4412 ExecCommandParam::Ignore, // Will be set to "right"
4413 AlignCommand::GetInstance));
4414 sInternalCommandDataHashtable->Put(
4415 u"justifycenter"_ns,
4416 InternalCommandData(
4417 "cmd_align",
4418 Command::FormatJustifyCenter,
4419 ExecCommandParam::Ignore, // Will be set to "center"
4420 AlignCommand::GetInstance));
4421 sInternalCommandDataHashtable->Put(
4422 u"justifyfull"_ns,
4423 InternalCommandData(
4424 "cmd_align",
4425 Command::FormatJustifyFull,
4426 ExecCommandParam::Ignore, // Will be set to "justify"
4427 AlignCommand::GetInstance));
4428 sInternalCommandDataHashtable->Put(
4429 u"removeformat"_ns,
4430 InternalCommandData(
4431 "cmd_removeStyles",
4432 Command::FormatRemove,
4433 ExecCommandParam::Ignore,
4434 RemoveStylesCommand::GetInstance));
4435 sInternalCommandDataHashtable->Put(
4436 u"unlink"_ns,
4437 InternalCommandData(
4438 "cmd_removeLinks",
4439 Command::FormatRemoveLink,
4440 ExecCommandParam::Ignore,
4441 StyleUpdatingCommand::GetInstance));
4442 sInternalCommandDataHashtable->Put(
4443 u"insertorderedlist"_ns,
4444 InternalCommandData(
4445 "cmd_ol",
4446 Command::InsertOrderedList,
4447 ExecCommandParam::Ignore,
4448 ListCommand::GetInstance));
4449 sInternalCommandDataHashtable->Put(
4450 u"insertunorderedlist"_ns,
4451 InternalCommandData(
4452 "cmd_ul",
4453 Command::InsertUnorderedList,
4454 ExecCommandParam::Ignore,
4455 ListCommand::GetInstance));
4456 sInternalCommandDataHashtable->Put(
4457 u"insertparagraph"_ns,
4458 InternalCommandData(
4459 "cmd_insertParagraph",
4460 Command::InsertParagraph,
4461 ExecCommandParam::Ignore,
4462 InsertParagraphCommand::GetInstance));
4463 sInternalCommandDataHashtable->Put(
4464 u"insertlinebreak"_ns,
4465 InternalCommandData(
4466 "cmd_insertLineBreak",
4467 Command::InsertLineBreak,
4468 ExecCommandParam::Ignore,
4469 InsertLineBreakCommand::GetInstance));
4470 sInternalCommandDataHashtable->Put(
4471 u"formatblock"_ns,
4472 InternalCommandData(
4473 "cmd_paragraphState",
4474 Command::FormatBlock,
4475 ExecCommandParam::String,
4476 ParagraphStateCommand::GetInstance));
4477 sInternalCommandDataHashtable->Put(
4478 u"heading"_ns,
4479 InternalCommandData(
4480 "cmd_paragraphState",
4481 Command::FormatBlock,
4482 ExecCommandParam::String,
4483 ParagraphStateCommand::GetInstance));
4484 sInternalCommandDataHashtable->Put(
4485 u"styleWithCSS"_ns,
4486 InternalCommandData(
4487 "cmd_setDocumentUseCSS",
4488 Command::SetDocumentUseCSS,
4489 ExecCommandParam::Boolean,
4490 SetDocumentStateCommand::GetInstance));
4491 sInternalCommandDataHashtable->Put(
4492 u"usecss"_ns, // Legacy command
4493 InternalCommandData(
4494 "cmd_setDocumentUseCSS",
4495 Command::SetDocumentUseCSS,
4496 ExecCommandParam::InvertedBoolean,
4497 SetDocumentStateCommand::GetInstance));
4498 sInternalCommandDataHashtable->Put(
4499 u"contentReadOnly"_ns,
4500 InternalCommandData(
4501 "cmd_setDocumentReadOnly",
4502 Command::SetDocumentReadOnly,
4503 ExecCommandParam::Boolean,
4504 SetDocumentStateCommand::GetInstance));
4505 sInternalCommandDataHashtable->Put(
4506 u"readonly"_ns, // Legacy command
4507 InternalCommandData(
4508 "cmd_setDocumentReadOnly",
4509 Command::SetDocumentReadOnly,
4510 ExecCommandParam::InvertedBoolean,
4511 SetDocumentStateCommand::GetInstance));
4512 sInternalCommandDataHashtable->Put(
4513 u"insertBrOnReturn"_ns,
4514 InternalCommandData(
4515 "cmd_insertBrOnReturn",
4516 Command::SetDocumentInsertBROnEnterKeyPress,
4517 ExecCommandParam::Boolean,
4518 SetDocumentStateCommand::GetInstance));
4519 sInternalCommandDataHashtable->Put(
4520 u"defaultParagraphSeparator"_ns,
4521 InternalCommandData(
4522 "cmd_defaultParagraphSeparator",
4523 Command::SetDocumentDefaultParagraphSeparator,
4524 ExecCommandParam::String,
4525 SetDocumentStateCommand::GetInstance));
4526 sInternalCommandDataHashtable->Put(
4527 u"enableObjectResizing"_ns,
4528 InternalCommandData(
4529 "cmd_enableObjectResizing",
4530 Command::ToggleObjectResizers,
4531 ExecCommandParam::Boolean,
4532 SetDocumentStateCommand::GetInstance));
4533 sInternalCommandDataHashtable->Put(
4534 u"enableInlineTableEditing"_ns,
4535 InternalCommandData(
4536 "cmd_enableInlineTableEditing",
4537 Command::ToggleInlineTableEditor,
4538 ExecCommandParam::Boolean,
4539 SetDocumentStateCommand::GetInstance));
4540 sInternalCommandDataHashtable->Put(
4541 u"enableAbsolutePositionEditing"_ns,
4542 InternalCommandData(
4543 "cmd_enableAbsolutePositionEditing",
4544 Command::ToggleAbsolutePositionEditor,
4545 ExecCommandParam::Boolean,
4546 SetDocumentStateCommand::GetInstance));
4547 #if 0
4548 // with empty string
4549 sInternalCommandDataHashtable->Put(
4550 u"justifynone"_ns,
4551 InternalCommandData(
4552 "cmd_align",
4553 Command::Undefined,
4554 ExecCommandParam::Ignore,
4555 nullptr)); // Not implemented yet.
4556 // REQUIRED SPECIAL REVIEW special review
4557 sInternalCommandDataHashtable->Put(
4558 u"saveas"_ns,
4559 InternalCommandData(
4560 "cmd_saveAs",
4561 Command::Undefined,
4562 ExecCommandParam::Boolean,
4563 nullptr)); // Not implemented yet.
4564 // REQUIRED SPECIAL REVIEW special review
4565 sInternalCommandDataHashtable->Put(
4566 u"print"_ns,
4567 InternalCommandData(
4568 "cmd_print",
4569 Command::Undefined,
4570 ExecCommandParam::Boolean,
4571 nullptr)); // Not implemented yet.
4572 #endif // #if 0
4573 // clang-format on
4576 Document::InternalCommandData Document::ConvertToInternalCommand(
4577 const nsAString& aHTMLCommandName,
4578 const nsAString& aValue /* = EmptyString() */,
4579 nsAString* aAdjustedValue /* = nullptr */) {
4580 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
4581 EnsureInitializeInternalCommandDataHashtable();
4582 InternalCommandData commandData;
4583 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
4584 return InternalCommandData();
4586 if (!aAdjustedValue) {
4587 // No further work to do
4588 return commandData;
4590 switch (commandData.mExecCommandParam) {
4591 case ExecCommandParam::Ignore:
4592 // Just have to copy it, no checking
4593 switch (commandData.mCommand) {
4594 case Command::FormatJustifyLeft:
4595 aAdjustedValue->AssignLiteral("left");
4596 break;
4597 case Command::FormatJustifyRight:
4598 aAdjustedValue->AssignLiteral("right");
4599 break;
4600 case Command::FormatJustifyCenter:
4601 aAdjustedValue->AssignLiteral("center");
4602 break;
4603 case Command::FormatJustifyFull:
4604 aAdjustedValue->AssignLiteral("justify");
4605 break;
4606 default:
4607 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
4608 EditorCommandParamType::None);
4609 break;
4611 return commandData;
4613 case ExecCommandParam::Boolean:
4614 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
4615 EditorCommandParamType::Bool));
4616 // If this is a boolean value and it's not explicitly false (e.g. no
4617 // value). We default to "true" (see bug 301490).
4618 if (!aValue.LowerCaseEqualsLiteral("false")) {
4619 aAdjustedValue->AssignLiteral("true");
4620 } else {
4621 aAdjustedValue->AssignLiteral("false");
4623 return commandData;
4625 case ExecCommandParam::InvertedBoolean:
4626 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
4627 EditorCommandParamType::Bool));
4628 // For old backwards commands we invert the check.
4629 if (aValue.LowerCaseEqualsLiteral("false")) {
4630 aAdjustedValue->AssignLiteral("true");
4631 } else {
4632 aAdjustedValue->AssignLiteral("false");
4634 return commandData;
4636 case ExecCommandParam::String:
4637 MOZ_ASSERT(!!(
4638 EditorCommand::GetParamType(commandData.mCommand) &
4639 (EditorCommandParamType::String | EditorCommandParamType::CString)));
4640 switch (commandData.mCommand) {
4641 case Command::FormatBlock: {
4642 const char16_t* start = aValue.BeginReading();
4643 const char16_t* end = aValue.EndReading();
4644 if (start != end && *start == '<' && *(end - 1) == '>') {
4645 ++start;
4646 --end;
4648 // XXX Should we reorder this array with actual usage?
4649 static const nsStaticAtom* kFormattableBlockTags[] = {
4650 // clang-format off
4651 nsGkAtoms::address,
4652 nsGkAtoms::blockquote,
4653 nsGkAtoms::dd,
4654 nsGkAtoms::div,
4655 nsGkAtoms::dl,
4656 nsGkAtoms::dt,
4657 nsGkAtoms::h1,
4658 nsGkAtoms::h2,
4659 nsGkAtoms::h3,
4660 nsGkAtoms::h4,
4661 nsGkAtoms::h5,
4662 nsGkAtoms::h6,
4663 nsGkAtoms::p,
4664 nsGkAtoms::pre,
4665 // clang-format on
4667 nsAutoString value(nsDependentSubstring(start, end));
4668 ToLowerCase(value);
4669 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
4670 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
4671 if (valueAtom == kTag) {
4672 kTag->ToString(*aAdjustedValue);
4673 return commandData;
4676 return InternalCommandData();
4678 case Command::FormatFontSize: {
4679 // Per editing spec as of April 23, 2012, we need to reject the value
4680 // if it's not a valid floating-point number surrounded by optional
4681 // whitespace. Otherwise, we parse it as a legacy font size. For
4682 // now, we just parse as a legacy font size regardless (matching
4683 // WebKit) -- bug 747879.
4684 int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
4685 if (!size) {
4686 return InternalCommandData();
4688 MOZ_ASSERT(aAdjustedValue->IsEmpty());
4689 aAdjustedValue->AppendInt(size);
4690 return commandData;
4692 case Command::InsertImage:
4693 case Command::InsertLink:
4694 if (aValue.IsEmpty()) {
4695 // Invalid value, return false
4696 return InternalCommandData();
4698 aAdjustedValue->Assign(aValue);
4699 return commandData;
4700 case Command::SetDocumentDefaultParagraphSeparator:
4701 if (!aValue.LowerCaseEqualsLiteral("div") &&
4702 !aValue.LowerCaseEqualsLiteral("p") &&
4703 !aValue.LowerCaseEqualsLiteral("br")) {
4704 // Invalid value
4705 return InternalCommandData();
4707 aAdjustedValue->Assign(aValue);
4708 return commandData;
4709 default:
4710 aAdjustedValue->Assign(aValue);
4711 return commandData;
4714 default:
4715 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
4716 return InternalCommandData();
4720 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
4721 const nsAString& aValue,
4722 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
4723 // Only allow on HTML documents.
4724 if (!IsHTMLOrXHTML()) {
4725 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_DOCUMENT_EXEC_COMMAND);
4726 return false;
4729 // if they are requesting UI from us, let's fail since we have no UI
4730 if (aShowUI) {
4731 return false;
4734 // If we're running an execCommand, we should just return false.
4735 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
4736 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
4737 mIsRunningExecCommand) {
4738 return false;
4741 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
4742 // this might add some ugly JS dependencies?
4744 nsAutoString adjustedValue;
4745 InternalCommandData commandData =
4746 ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
4747 if (commandData.mCommand == Command::DoNothing) {
4748 return false;
4751 // if editing is not on, bail
4752 if (commandData.IsAvailableOnlyWhenEditable() && !IsEditingOnAfterFlush()) {
4753 return false;
4756 if (commandData.mCommand == Command::GetHTML) {
4757 aRv.Throw(NS_ERROR_FAILURE);
4758 return false;
4761 // Do security check first.
4762 if (commandData.IsCutOrCopyCommand()) {
4763 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
4764 // We have rejected the event due to it not being performed in an
4765 // input-driven context therefore, we report the error to the console.
4766 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
4767 this, nsContentUtils::eDOM_PROPERTIES,
4768 "ExecCommandCutCopyDeniedNotInputDriven");
4769 return false;
4771 } else if (commandData.IsPasteCommand()) {
4772 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
4773 nsGkAtoms::clipboardRead)) {
4774 return false;
4778 // Next, consider context of command handling which is automatically resolved
4779 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
4780 // The order is:
4781 // 1. HTMLEditor for the document, if there is.
4782 // 2. TextEditor if there is an active element and it has TextEditor like
4783 // <input type="text"> or <textarea>.
4784 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
4785 // XXX Chromium handles `execCommand()` in <input type="text"> or
4786 // <textarea> when it's in an editing host and has focus. So, our
4787 // traditional behavior is different and does not make sense.
4788 RefPtr<TextEditor> maybeHTMLEditor;
4789 if (nsPresContext* presContext = GetPresContext()) {
4790 if (commandData.IsCutOrCopyCommand()) {
4791 // Note that we used to use DocShell to handle `cut` and `copy` command
4792 // for dispatching corresponding events for making possible web apps to
4793 // implement their own editor without editable elements but supports
4794 // standard shortcut keys, etc. In this case, we prefer to use active
4795 // element's editor to keep same behavior.
4796 maybeHTMLEditor = nsContentUtils::GetActiveEditor(presContext);
4797 } else {
4798 maybeHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
4799 if (!maybeHTMLEditor) {
4800 maybeHTMLEditor = nsContentUtils::GetActiveEditor(presContext);
4805 // Then, retrieve editor command class instance which should handle it
4806 // and can handle it now.
4807 RefPtr<EditorCommand> editorCommand;
4808 if (!maybeHTMLEditor) {
4809 // If the command is available without editor, we should redirect the
4810 // command to focused descendant with DocShell.
4811 if (commandData.IsAvailableOnlyWhenEditable()) {
4812 return false;
4814 } else {
4815 // Otherwise, we should use EditorCommand instance (which is singleton
4816 // instance) when it's enabled.
4817 editorCommand = commandData.mGetEditorCommandFunc();
4818 if (NS_WARN_IF(!editorCommand)) {
4819 aRv.Throw(NS_ERROR_FAILURE);
4820 return false;
4823 if (!editorCommand->IsCommandEnabled(commandData.mCommand,
4824 maybeHTMLEditor)) {
4825 // If the EditorCommand instance is disabled, we should do nothing if
4826 // the command requires an editor.
4827 if (commandData.IsAvailableOnlyWhenEditable()) {
4828 // Return false if editor specific commands is disabled (bug 760052).
4829 return false;
4831 // Otherwise, we should redirect it to focused descendant with DocShell.
4832 editorCommand = nullptr;
4836 AutoRunningExecCommandMarker markRunningExecCommand(*this);
4838 // If we cannot use EditorCommand instance directly, we need to handle the
4839 // command with traditional path (i.e., with DocShell or nsCommandManager).
4840 if (!editorCommand) {
4841 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
4843 // Special case clipboard write commands like Command::Cut and
4844 // Command::Copy. For such commands, we need the behaviour from
4845 // nsWindowRoot::GetControllers() which is to look at the focused element,
4846 // and defer to a focused textbox's controller. The code past taken by
4847 // other commands in ExecCommand() always uses the window directly, rather
4848 // than deferring to the textbox, which is desireable for most editor
4849 // commands, but not these commands (as those should allow copying out of
4850 // embedded editors). This behaviour is invoked if we call DoCommand()
4851 // directly on the docShell.
4852 // XXX This means that we allow web app to pick up selected content in
4853 // descendant document and write it into the clipboard when a
4854 // descendant document has focus. However, Chromium does not allow
4855 // this and this seems that it's not good behavior from point of view
4856 // of security. We should treat this issue in another bug.
4857 if (commandData.IsCutOrCopyCommand()) {
4858 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
4859 if (!docShell) {
4860 return false;
4862 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
4863 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
4864 return false;
4866 return NS_SUCCEEDED(rv);
4869 // Otherwise (currently, only clipboard read commands like Command::Paste),
4870 // we don't need to redirect the command to focused subdocument.
4871 // Therefore, we should handle it with nsCommandManager as used to be.
4872 // It may dispatch only preceding event of editing on non-editable element
4873 // to make web apps possible to handle standard shortcut key, etc in
4874 // their own editor.
4875 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
4876 if (!commandManager) {
4877 aRv.Throw(NS_ERROR_FAILURE);
4878 return false;
4881 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
4882 if (!window) {
4883 aRv.Throw(NS_ERROR_FAILURE);
4884 return false;
4887 // Return false for disabled commands (bug 760052)
4888 if (!commandManager->IsCommandEnabled(
4889 nsDependentCString(commandData.mXULCommandName), window)) {
4890 return false;
4893 MOZ_ASSERT(commandData.IsPasteCommand());
4894 aRv =
4895 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
4896 return !aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION) && !aRv.Failed();
4899 // Now, our target is fixed to the editor. So, we can use EditorCommand
4900 // with the editor directly.
4901 MOZ_ASSERT(maybeHTMLEditor);
4903 EditorCommandParamType paramType =
4904 EditorCommand::GetParamType(commandData.mCommand);
4906 // If we don't have meaningful parameter or the EditorCommand does not
4907 // require additional parameter, we can use `DoCommand()`.
4908 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
4909 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
4910 aRv = editorCommand->DoCommand(commandData.mCommand, *maybeHTMLEditor,
4911 &aSubjectPrincipal);
4912 return !aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION) && !aRv.Failed();
4915 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
4916 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
4917 // a `bool` value.
4918 if (!!(paramType & EditorCommandParamType::Bool)) {
4919 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
4920 adjustedValue.EqualsLiteral("false"));
4921 aRv = editorCommand->DoCommandParam(
4922 commandData.mCommand, Some(adjustedValue.EqualsLiteral("true")),
4923 *maybeHTMLEditor, &aSubjectPrincipal);
4924 return !aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION) && !aRv.Failed();
4927 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
4928 // in this case. However, `paramType` may contain both `String` and
4929 // `CString` but in such case, we should use `DoCommandParam()` which
4930 // takes `nsAString`. So, we should check whether `paramType` contains
4931 // `String` or not first.
4932 if (!!(paramType & EditorCommandParamType::String)) {
4933 MOZ_ASSERT(!adjustedValue.IsVoid());
4934 aRv = editorCommand->DoCommandParam(commandData.mCommand, adjustedValue,
4935 *maybeHTMLEditor, &aSubjectPrincipal);
4936 return !aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION) && !aRv.Failed();
4939 // Finally, `paramType` should have `CString`. We should use
4940 // `DoCommandParam()` which takes `nsACString`.
4941 if (!!(paramType & EditorCommandParamType::CString)) {
4942 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
4943 MOZ_ASSERT(!utf8Value.IsVoid());
4944 aRv = editorCommand->DoCommandParam(commandData.mCommand, utf8Value,
4945 *maybeHTMLEditor, &aSubjectPrincipal);
4946 return !aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION) && !aRv.Failed();
4949 MOZ_ASSERT_UNREACHABLE(
4950 "Not yet implemented to handle new EditorCommandParamType");
4951 return false;
4954 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
4955 nsIPrincipal& aSubjectPrincipal,
4956 ErrorResult& aRv) {
4957 // Only allow on HTML documents.
4958 if (!IsHTMLOrXHTML()) {
4959 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_DOCUMENT_QUERY_COMMAND_ENABLED);
4960 return false;
4963 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
4964 if (commandData.mCommand == Command::DoNothing) {
4965 return false;
4968 // cut & copy are always allowed
4969 if (commandData.IsCutOrCopyCommand()) {
4970 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
4973 // Report false for restricted commands
4974 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
4975 return false;
4978 // if editing is not on, bail
4979 if (!IsEditingOnAfterFlush()) {
4980 return false;
4983 // get command manager and dispatch command to our window if it's acceptable
4984 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
4985 if (!commandManager) {
4986 aRv.Throw(NS_ERROR_FAILURE);
4987 return false;
4990 nsPIDOMWindowOuter* window = GetWindow();
4991 if (!window) {
4992 aRv.Throw(NS_ERROR_FAILURE);
4993 return false;
4996 return commandManager->IsCommandEnabled(
4997 nsDependentCString(commandData.mXULCommandName), window);
5000 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5001 ErrorResult& aRv) {
5002 // Only allow on HTML documents.
5003 if (!IsHTMLOrXHTML()) {
5004 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_DOCUMENT_QUERY_COMMAND_INDETERM);
5005 return false;
5008 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5009 if (commandData.mCommand == Command::DoNothing) {
5010 return false;
5013 // if editing is not on, bail
5014 if (!IsEditingOnAfterFlush()) {
5015 return false;
5018 // get command manager and dispatch command to our window if it's acceptable
5019 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5020 if (!commandManager) {
5021 aRv.Throw(NS_ERROR_FAILURE);
5022 return false;
5025 nsPIDOMWindowOuter* window = GetWindow();
5026 if (!window) {
5027 aRv.Throw(NS_ERROR_FAILURE);
5028 return false;
5031 RefPtr<nsCommandParams> params = new nsCommandParams();
5032 aRv = commandManager->GetCommandState(commandData.mXULCommandName, window,
5033 params);
5034 if (aRv.Failed()) {
5035 return false;
5038 // If command does not have a state_mixed value, this call fails and sets
5039 // retval to false. This is fine -- we want to return false in that case
5040 // anyway (bug 738385), so we just don't throw regardless.
5041 return params->GetBool("state_mixed");
5044 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5045 ErrorResult& aRv) {
5046 // Only allow on HTML documents.
5047 if (!IsHTMLOrXHTML()) {
5048 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_DOCUMENT_QUERY_COMMAND_STATE);
5049 return false;
5052 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5053 if (commandData.mCommand == Command::DoNothing) {
5054 return false;
5057 // if editing is not on, bail
5058 if (!IsEditingOnAfterFlush()) {
5059 return false;
5062 // get command manager and dispatch command to our window if it's acceptable
5063 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5064 if (!commandManager) {
5065 aRv.Throw(NS_ERROR_FAILURE);
5066 return false;
5069 nsPIDOMWindowOuter* window = GetWindow();
5070 if (!window) {
5071 aRv.Throw(NS_ERROR_FAILURE);
5072 return false;
5075 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5076 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5077 // return false always.
5078 return false;
5081 RefPtr<nsCommandParams> params = new nsCommandParams();
5082 aRv = commandManager->GetCommandState(commandData.mXULCommandName, window,
5083 params);
5084 if (aRv.Failed()) {
5085 return false;
5088 // handle alignment as a special case (possibly other commands too?)
5089 // Alignment is special because the external api is individual
5090 // commands but internally we use cmd_align with different
5091 // parameters. When getting the state of this command, we need to
5092 // return the boolean for this particular alignment rather than the
5093 // string of 'which alignment is this?'
5094 switch (commandData.mCommand) {
5095 case Command::FormatJustifyLeft: {
5096 nsAutoCString currentValue;
5097 aRv = params->GetCString("state_attribute", currentValue);
5098 if (aRv.Failed()) {
5099 return false;
5101 return currentValue.EqualsLiteral("left");
5103 case Command::FormatJustifyRight: {
5104 nsAutoCString currentValue;
5105 aRv = params->GetCString("state_attribute", currentValue);
5106 if (aRv.Failed()) {
5107 return false;
5109 return currentValue.EqualsLiteral("right");
5111 case Command::FormatJustifyCenter: {
5112 nsAutoCString currentValue;
5113 aRv = params->GetCString("state_attribute", currentValue);
5114 if (aRv.Failed()) {
5115 return false;
5117 return currentValue.EqualsLiteral("center");
5119 case Command::FormatJustifyFull: {
5120 nsAutoCString currentValue;
5121 aRv = params->GetCString("state_attribute", currentValue);
5122 if (aRv.Failed()) {
5123 return false;
5125 return currentValue.EqualsLiteral("justify");
5127 default:
5128 break;
5131 // If command does not have a state_all value, this call fails and sets
5132 // retval to false. This is fine -- we want to return false in that case
5133 // anyway (bug 738385), so we just succeed and return false regardless.
5134 return params->GetBool("state_all");
5137 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5138 CallerType aCallerType, ErrorResult& aRv) {
5139 // Only allow on HTML documents.
5140 if (!IsHTMLOrXHTML()) {
5141 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_DOCUMENT_QUERY_COMMAND_SUPPORTED);
5142 return false;
5145 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5146 if (commandData.mCommand == Command::DoNothing) {
5147 return false;
5150 // Gecko technically supports all the clipboard commands including
5151 // cut/copy/paste, but non-privileged content will be unable to call
5152 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5153 // may also be disallowed to be called from non-privileged content.
5154 // For that reason, we report the support status of corresponding
5155 // command accordingly.
5156 if (aCallerType != CallerType::System) {
5157 if (commandData.IsPasteCommand()) {
5158 return false;
5160 if (commandData.IsCutOrCopyCommand() &&
5161 !StaticPrefs::dom_allow_cut_copy()) {
5162 // XXXbz should we worry about correctly reporting "true" in the
5163 // "restricted, but we're an addon with clipboardWrite permissions" case?
5164 // See also nsContentUtils::IsCutCopyAllowed.
5165 return false;
5169 // aHTMLCommandName is supported if it can be converted to a Midas command
5170 return true;
5173 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5174 nsAString& aValue, ErrorResult& aRv) {
5175 aValue.Truncate();
5177 // Only allow on HTML documents.
5178 if (!IsHTMLOrXHTML()) {
5179 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_DOCUMENT_QUERY_COMMAND_VALUE);
5180 return;
5183 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5184 if (commandData.mCommand == Command::DoNothing) {
5185 // Return empty string
5186 return;
5189 // if editing is not on, bail
5190 if (!IsEditingOnAfterFlush()) {
5191 return;
5194 // get command manager and dispatch command to our window if it's acceptable
5195 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5196 if (!commandManager) {
5197 aRv.Throw(NS_ERROR_FAILURE);
5198 return;
5201 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5202 if (!window) {
5203 aRv.Throw(NS_ERROR_FAILURE);
5204 return;
5207 // this is a special command since we are calling DoCommand rather than
5208 // GetCommandState like the other commands
5209 RefPtr<nsCommandParams> params = new nsCommandParams();
5210 if (commandData.mCommand == Command::GetHTML) {
5211 aRv = params->SetBool("selection_only", true);
5212 if (aRv.Failed()) {
5213 return;
5215 aRv = params->SetCString("format", "text/html"_ns);
5216 if (aRv.Failed()) {
5217 return;
5219 aRv =
5220 commandManager->DoCommand(commandData.mXULCommandName, params, window);
5221 if (aRv.Failed()) {
5222 return;
5224 params->GetString("result", aValue);
5225 return;
5228 aRv = params->SetCString("state_attribute", EmptyCString());
5229 if (aRv.Failed()) {
5230 return;
5233 aRv = commandManager->GetCommandState(commandData.mXULCommandName, window,
5234 params);
5235 if (aRv.Failed()) {
5236 return;
5239 // If command does not have a state_attribute value, this call fails, and
5240 // aValue will wind up being the empty string. This is fine -- we want to
5241 // return "" in that case anyway (bug 738385), so we just return NS_OK
5242 // regardless.
5243 nsAutoCString result;
5244 params->GetCString("state_attribute", result);
5245 CopyUTF8toUTF16(result, aValue);
5248 bool Document::IsEditingOnAfterFlush() {
5249 RefPtr<Document> doc = GetInProcessParentDocument();
5250 if (doc) {
5251 // Make sure frames are up to date, since that can affect whether
5252 // we're editable.
5253 doc->FlushPendingNotifications(FlushType::Frames);
5256 return IsEditingOn();
5259 void Document::MaybeEditingStateChanged() {
5260 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
5261 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
5262 if (nsContentUtils::IsSafeToRunScript()) {
5263 EditingStateChanged();
5264 } else if (!mInDestructor) {
5265 nsContentUtils::AddScriptRunner(
5266 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
5267 &Document::MaybeEditingStateChanged));
5272 void Document::TearingDownEditor() {
5273 if (IsEditingOn()) {
5274 mEditingState = EditingState::eTearingDown;
5275 if (IsHTMLOrXHTML()) {
5276 RemoveContentEditableStyleSheets();
5281 nsresult Document::TurnEditingOff() {
5282 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
5284 nsPIDOMWindowOuter* window = GetWindow();
5285 if (!window) {
5286 return NS_ERROR_FAILURE;
5289 nsIDocShell* docshell = window->GetDocShell();
5290 if (!docshell) {
5291 return NS_ERROR_FAILURE;
5294 bool isBeingDestroyed = false;
5295 docshell->IsBeingDestroyed(&isBeingDestroyed);
5296 if (isBeingDestroyed) {
5297 return NS_ERROR_FAILURE;
5300 nsCOMPtr<nsIEditingSession> editSession;
5301 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5302 NS_ENSURE_SUCCESS(rv, rv);
5304 // turn editing off
5305 rv = editSession->TearDownEditorOnWindow(window);
5306 NS_ENSURE_SUCCESS(rv, rv);
5308 mEditingState = EditingState::eOff;
5310 // Editor resets selection since it is being destroyed. But if focus is
5311 // still into editable control, we have to initialize selection again.
5312 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
5313 if (RefPtr<TextControlElement> textControlElement =
5314 TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
5315 RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor();
5316 if (textEditor) {
5317 textEditor->ReinitializeSelection(*textControlElement);
5322 return NS_OK;
5325 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
5326 nsIDocShell* docShell = aWindow->GetDocShell();
5327 if (!docShell) {
5328 return false;
5330 return docShell->GetPresShell() != nullptr;
5333 nsresult Document::EditingStateChanged() {
5334 if (mRemovedFromDocShell) {
5335 return NS_OK;
5338 if (mEditingState == EditingState::eSettingUp ||
5339 mEditingState == EditingState::eTearingDown) {
5340 // XXX We shouldn't recurse
5341 return NS_OK;
5344 bool designMode = HasFlag(NODE_IS_EDITABLE);
5345 EditingState newState =
5346 designMode ? EditingState::eDesignMode
5347 : (mContentEditableCount > 0 ? EditingState::eContentEditable
5348 : EditingState::eOff);
5349 if (mEditingState == newState) {
5350 // No changes in editing mode.
5351 return NS_OK;
5354 if (newState == EditingState::eOff) {
5355 // Editing is being turned off.
5356 nsAutoScriptBlocker scriptBlocker;
5357 NotifyEditableStateChange(*this);
5358 return TurnEditingOff();
5361 // Flush out style changes on our _parent_ document, if any, so that
5362 // our check for a presshell won't get stale information.
5363 if (mParentDocument) {
5364 mParentDocument->FlushPendingNotifications(FlushType::Style);
5367 // get editing session, make sure this is a strong reference so the
5368 // window can't get deleted during the rest of this call.
5369 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5370 if (!window) {
5371 return NS_ERROR_FAILURE;
5374 nsIDocShell* docshell = window->GetDocShell();
5375 if (!docshell) {
5376 return NS_ERROR_FAILURE;
5379 // FlushPendingNotifications might destroy our docshell.
5380 bool isBeingDestroyed = false;
5381 docshell->IsBeingDestroyed(&isBeingDestroyed);
5382 if (isBeingDestroyed) {
5383 return NS_ERROR_FAILURE;
5386 nsCOMPtr<nsIEditingSession> editSession;
5387 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5388 NS_ENSURE_SUCCESS(rv, rv);
5390 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
5391 if (htmlEditor) {
5392 // We might already have an editor if it was set up for mail, let's see
5393 // if this is actually the case.
5394 uint32_t flags = 0;
5395 htmlEditor->GetFlags(&flags);
5396 if (flags & nsIEditor::eEditorMailMask) {
5397 // We already have a mail editor, then we should not attempt to create
5398 // another one.
5399 return NS_OK;
5403 if (!HasPresShell(window)) {
5404 // We should not make the window editable or setup its editor.
5405 // It's probably style=display:none.
5406 return NS_OK;
5409 bool makeWindowEditable = mEditingState == EditingState::eOff;
5410 bool spellRecheckAll = false;
5411 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
5412 htmlEditor = nullptr;
5415 EditingState oldState = mEditingState;
5416 nsAutoEditingState push(this, EditingState::eSettingUp);
5418 RefPtr<PresShell> presShell = GetPresShell();
5419 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
5421 // If we're entering the design mode from non-editable state, put the
5422 // selection at the beginning of the document for compatibility reasons.
5423 bool collapseSelectionAtBeginningOfDocument =
5424 designMode && oldState == EditingState::eOff;
5425 // However, mEditingState may be eOff even if there is some
5426 // `contenteditable` area and selection has been initialized for it because
5427 // mEditingState for `contenteditable` may have been scheduled to modify
5428 // when safe. In such case, we should not reinitialize selection.
5429 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
5430 Selection* selection =
5431 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
5432 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
5433 if (selection && selection->RangeCount()) {
5434 // Perhaps, we don't need to check whether the selection is in
5435 // an editing host or not because all contents will be editable
5436 // in designMode. (And we don't want to make this code so complicated
5437 // because of legacy API.)
5438 collapseSelectionAtBeginningOfDocument = false;
5442 MOZ_ASSERT(mStyleSetFilled);
5444 // Before making this window editable, we need to modify UA style sheet
5445 // because new style may change whether focused element will be focusable
5446 // or not.
5447 if (IsHTMLOrXHTML()) {
5448 AddContentEditableStyleSheetsToStyleSet(designMode);
5451 if (designMode) {
5452 // designMode is being turned on (overrides contentEditable).
5453 spellRecheckAll = oldState == EditingState::eContentEditable;
5456 // Adjust focused element with new style but blur event shouldn't be fired
5457 // until mEditingState is modified with newState.
5458 nsAutoScriptBlocker scriptBlocker;
5459 if (designMode) {
5460 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
5461 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
5462 window, nsFocusManager::eOnlyCurrentWindow,
5463 getter_AddRefs(focusedWindow));
5464 if (focusedContent) {
5465 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
5466 bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable()
5467 : !focusedContent->IsFocusable();
5468 if (clearFocus) {
5469 nsFocusManager* fm = nsFocusManager::GetFocusManager();
5470 if (fm) {
5471 fm->ClearFocus(window);
5472 // If we need to dispatch blur event, we should put off after
5473 // modifying mEditingState since blur event handler may change
5474 // designMode state again.
5475 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
5481 if (makeWindowEditable) {
5482 // Editing is being turned on (through designMode or contentEditable)
5483 // Turn on editor.
5484 // XXX This can cause flushing which can change the editing state, so make
5485 // sure to avoid recursing.
5486 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
5487 NS_ENSURE_SUCCESS(rv, rv);
5490 // XXX Need to call TearDownEditorOnWindow for all failures.
5491 htmlEditor = docshell->GetHTMLEditor();
5492 if (!htmlEditor) {
5493 // Return NS_OK even though we've failed to create an editor here. This
5494 // is so that the setter of designMode on non-HTML documents does not
5495 // fail.
5496 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
5497 // would detect that we can't support the mimetype if appropriate and
5498 // would fall onto the eEditorErrorCantEditMimeType path.
5499 return NS_OK;
5502 if (collapseSelectionAtBeginningOfDocument) {
5503 htmlEditor->BeginningOfDocument();
5506 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
5507 nsContentUtils::AddScriptBlocker();
5511 mEditingState = newState;
5512 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
5513 nsContentUtils::RemoveScriptBlocker();
5514 // If mEditingState is overwritten by another call and already disabled
5515 // the editing, we shouldn't keep making window editable.
5516 if (mEditingState == EditingState::eOff) {
5517 return NS_OK;
5521 if (makeWindowEditable) {
5522 // Set the editor to not insert br's on return when in p
5523 // elements by default.
5524 // XXX Do we only want to do this for designMode?
5525 // Note that it doesn't matter what CallerType we pass, because the callee
5526 // doesn't use it for this command. Play it safe and pass the more
5527 // restricted one.
5528 ErrorResult errorResult;
5529 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
5530 Unused << ExecCommand(u"insertBrOnReturn"_ns, false, u"false"_ns,
5531 // Principal doesn't matter here, because the
5532 // insertBrOnReturn command doesn't use it. Still
5533 // it's too bad we can't easily grab a nullprincipal
5534 // from somewhere without allocating one..
5535 *principal, errorResult);
5537 if (errorResult.Failed()) {
5538 // Editor setup failed. Editing is not on after all.
5539 // XXX Should we reset the editable flag on nodes?
5540 editSession->TearDownEditorOnWindow(window);
5541 mEditingState = EditingState::eOff;
5543 return errorResult.StealNSResult();
5547 // Resync the editor's spellcheck state.
5548 if (spellRecheckAll) {
5549 nsCOMPtr<nsISelectionController> selectionController =
5550 htmlEditor->GetSelectionController();
5551 if (NS_WARN_IF(!selectionController)) {
5552 return NS_ERROR_FAILURE;
5555 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
5556 nsISelectionController::SELECTION_SPELLCHECK);
5557 if (spellCheckSelection) {
5558 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
5561 htmlEditor->SyncRealTimeSpell();
5563 MaybeDispatchCheckKeyPressEventModelEvent();
5565 return NS_OK;
5568 // Helper class, used below in ChangeContentEditableCount().
5569 class DeferredContentEditableCountChangeEvent : public Runnable {
5570 public:
5571 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
5572 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
5573 mDoc(aDoc),
5574 mElement(aElement) {}
5576 NS_IMETHOD Run() override {
5577 if (mElement && mElement->OwnerDoc() == mDoc) {
5578 mDoc->DeferredContentEditableCountChange(mElement);
5580 return NS_OK;
5583 private:
5584 RefPtr<Document> mDoc;
5585 RefPtr<Element> mElement;
5588 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
5589 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
5590 "Trying to decrement too much.");
5592 mContentEditableCount += aChange;
5594 nsContentUtils::AddScriptRunner(
5595 new DeferredContentEditableCountChangeEvent(this, aElement));
5598 void Document::DeferredContentEditableCountChange(Element* aElement) {
5599 if (mParser ||
5600 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
5601 return;
5604 EditingState oldState = mEditingState;
5606 nsresult rv = EditingStateChanged();
5607 NS_ENSURE_SUCCESS_VOID(rv);
5609 if (oldState == mEditingState &&
5610 mEditingState == EditingState::eContentEditable) {
5611 // We just changed the contentEditable state of a node, we need to reset
5612 // the spellchecking state of that node.
5613 if (aElement) {
5614 nsPIDOMWindowOuter* window = GetWindow();
5615 if (!window) {
5616 return;
5619 nsIDocShell* docshell = window->GetDocShell();
5620 if (!docshell) {
5621 return;
5624 RefPtr<HTMLEditor> htmlEditor = docshell->GetHTMLEditor();
5625 if (htmlEditor) {
5626 RefPtr<nsRange> range = nsRange::Create(aElement);
5627 IgnoredErrorResult res;
5628 range->SelectNode(*aElement, res);
5629 if (res.Failed()) {
5630 // The node might be detached from the document at this point,
5631 // which would cause this call to fail. In this case, we can
5632 // safely ignore the contenteditable count change.
5633 return;
5636 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
5637 rv = htmlEditor->GetInlineSpellChecker(false,
5638 getter_AddRefs(spellChecker));
5639 NS_ENSURE_SUCCESS_VOID(rv);
5641 if (spellChecker) {
5642 rv = spellChecker->SpellCheckRange(range);
5643 NS_ENSURE_SUCCESS_VOID(rv);
5650 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
5651 // Currently, we need to check only when we're becoming editable for
5652 // contenteditable.
5653 if (mEditingState != EditingState::eContentEditable) {
5654 return;
5657 if (mHasBeenEditable) {
5658 return;
5660 mHasBeenEditable = true;
5662 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
5663 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
5664 // with proper keypress event for the active web app.
5665 WidgetEvent checkEvent(true, eUnidentifiedEvent);
5666 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
5667 checkEvent.mFlags.mCancelable = false;
5668 checkEvent.mFlags.mBubbles = false;
5669 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
5670 // Post the event rather than dispatching it synchronously because we need
5671 // a call of SetKeyPressEventModel() before first key input. Therefore, we
5672 // can avoid paying unnecessary runtime cost for most web apps.
5673 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
5676 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
5677 PresShell* presShell = GetPresShell();
5678 if (!presShell) {
5679 return;
5681 presShell->SetKeyPressEventModel(aKeyPressEventModel);
5684 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
5686 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
5687 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
5688 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
5689 aFocusTime >= mLastFocusTime);
5690 mLastFocusTime = aFocusTime;
5693 void Document::GetReferrer(nsAString& aReferrer) const {
5694 aReferrer.Truncate();
5695 if (!mReferrerInfo) {
5696 return;
5699 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
5700 if (!referrer) {
5701 return;
5704 nsAutoCString uri;
5705 nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
5706 if (NS_WARN_IF(NS_FAILED(rv))) {
5707 return;
5710 CopyUTF8toUTF16(uri, aReferrer);
5713 void Document::GetCookie(nsAString& aCookie, ErrorResult& rv) {
5714 aCookie.Truncate(); // clear current cookie in case service fails;
5715 // no cookie isn't an error condition.
5717 if (mDisableCookieAccess) {
5718 return;
5721 // If the document's sandboxed origin flag is set, access to read cookies
5722 // is prohibited.
5723 if (mSandboxFlags & SANDBOXED_ORIGIN) {
5724 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
5725 return;
5728 StorageAccess storageAccess = StorageAllowedForDocument(this);
5729 if (storageAccess == StorageAccess::eDeny) {
5730 return;
5733 if (ShouldPartitionStorage(storageAccess) &&
5734 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
5735 return;
5738 // If the document is a cookie-averse Document... return the empty string.
5739 if (IsCookieAverse()) {
5740 return;
5743 // not having a cookie service isn't an error
5744 nsCOMPtr<nsICookieService> service =
5745 do_GetService(NS_COOKIESERVICE_CONTRACTID);
5746 if (service) {
5747 nsAutoCString cookie;
5748 service->GetCookieStringFromDocument(this, cookie);
5749 // CopyUTF8toUTF16 doesn't handle error
5750 // because it assumes that the input is valid.
5751 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
5755 void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
5756 if (mDisableCookieAccess) {
5757 return;
5760 // If the document's sandboxed origin flag is set, access to write cookies
5761 // is prohibited.
5762 if (mSandboxFlags & SANDBOXED_ORIGIN) {
5763 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
5764 return;
5767 StorageAccess storageAccess = StorageAllowedForDocument(this);
5768 if (storageAccess == StorageAccess::eDeny) {
5769 return;
5772 if (ShouldPartitionStorage(storageAccess) &&
5773 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
5774 return;
5777 // If the document is a cookie-averse Document... do nothing.
5778 if (IsCookieAverse()) {
5779 return;
5782 if (!mDocumentURI) {
5783 return;
5786 // not having a cookie service isn't an error
5787 nsCOMPtr<nsICookieService> service =
5788 do_GetService(NS_COOKIESERVICE_CONTRACTID);
5789 if (!service) {
5790 return;
5793 NS_ConvertUTF16toUTF8 cookie(aCookie);
5794 nsresult rv = service->SetCookieStringFromDocument(this, cookie);
5796 // No warning messages here.
5797 if (NS_FAILED(rv)) {
5798 return;
5801 nsCOMPtr<nsIObserverService> observerService =
5802 mozilla::services::GetObserverService();
5803 if (observerService) {
5804 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
5805 nsString(aCookie).get());
5809 ReferrerPolicy Document::GetReferrerPolicy() const {
5810 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
5811 : ReferrerPolicy::_empty;
5814 void Document::GetAlinkColor(nsAString& aAlinkColor) {
5815 aAlinkColor.Truncate();
5817 HTMLBodyElement* body = GetBodyElement();
5818 if (body) {
5819 body->GetALink(aAlinkColor);
5823 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
5824 HTMLBodyElement* body = GetBodyElement();
5825 if (body) {
5826 body->SetALink(aAlinkColor);
5830 void Document::GetLinkColor(nsAString& aLinkColor) {
5831 aLinkColor.Truncate();
5833 HTMLBodyElement* body = GetBodyElement();
5834 if (body) {
5835 body->GetLink(aLinkColor);
5839 void Document::SetLinkColor(const nsAString& aLinkColor) {
5840 HTMLBodyElement* body = GetBodyElement();
5841 if (body) {
5842 body->SetLink(aLinkColor);
5846 void Document::GetVlinkColor(nsAString& aVlinkColor) {
5847 aVlinkColor.Truncate();
5849 HTMLBodyElement* body = GetBodyElement();
5850 if (body) {
5851 body->GetVLink(aVlinkColor);
5855 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
5856 HTMLBodyElement* body = GetBodyElement();
5857 if (body) {
5858 body->SetVLink(aVlinkColor);
5862 void Document::GetBgColor(nsAString& aBgColor) {
5863 aBgColor.Truncate();
5865 HTMLBodyElement* body = GetBodyElement();
5866 if (body) {
5867 body->GetBgColor(aBgColor);
5871 void Document::SetBgColor(const nsAString& aBgColor) {
5872 HTMLBodyElement* body = GetBodyElement();
5873 if (body) {
5874 body->SetBgColor(aBgColor);
5878 void Document::GetFgColor(nsAString& aFgColor) {
5879 aFgColor.Truncate();
5881 HTMLBodyElement* body = GetBodyElement();
5882 if (body) {
5883 body->GetText(aFgColor);
5887 void Document::SetFgColor(const nsAString& aFgColor) {
5888 HTMLBodyElement* body = GetBodyElement();
5889 if (body) {
5890 body->SetText(aFgColor);
5894 void Document::CaptureEvents() { WarnOnceAbout(Document::eUseOfCaptureEvents); }
5896 void Document::ReleaseEvents() { WarnOnceAbout(Document::eUseOfReleaseEvents); }
5898 HTMLAllCollection* Document::All() {
5899 if (!mAll) {
5900 mAll = new HTMLAllCollection(this);
5902 return mAll;
5905 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
5906 if (mIsSrcdocDocument) {
5907 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
5908 if (inStrmChan) {
5909 return inStrmChan->GetSrcdocData(aSrcdocData);
5912 aSrcdocData = VoidString();
5913 return NS_OK;
5916 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
5917 nsPIDOMWindowOuter* win = GetWindow();
5918 if (!win) {
5919 return nullptr;
5921 return WindowProxyHolder(win->GetBrowsingContext());
5924 nsIContent* Document::GetUnretargetedFocusedContent() const {
5925 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5926 if (!window) {
5927 return nullptr;
5929 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
5930 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
5931 window, nsFocusManager::eOnlyCurrentWindow,
5932 getter_AddRefs(focusedWindow));
5933 if (!focusedContent) {
5934 return nullptr;
5936 // be safe and make sure the element is from this document
5937 if (focusedContent->OwnerDoc() != this) {
5938 return nullptr;
5941 if (focusedContent->ChromeOnlyAccess()) {
5942 return focusedContent->FindFirstNonChromeOnlyAccessContent();
5944 return focusedContent;
5947 Element* Document::GetActiveElement() {
5948 // Get the focused element.
5949 Element* focusedElement = GetRetargetedFocusedElement();
5950 if (focusedElement) {
5951 return focusedElement;
5954 // No focused element anywhere in this document. Try to get the BODY.
5955 if (IsHTMLOrXHTML()) {
5956 Element* bodyElement = AsHTMLDocument()->GetBody();
5957 if (bodyElement) {
5958 return bodyElement;
5960 // Special case to handle the transition to XHTML from XUL documents
5961 // where there currently isn't a body element, but we need to match the
5962 // XUL behavior. This should be removed when bug 1540278 is resolved.
5963 if (nsContentUtils::IsChromeDoc(this)) {
5964 Element* docElement = GetDocumentElement();
5965 if (docElement && docElement->IsXULElement()) {
5966 return docElement;
5969 // Because of IE compatibility, return null when html document doesn't have
5970 // a body.
5971 return nullptr;
5974 // If we couldn't get a BODY, return the root element.
5975 return GetDocumentElement();
5978 Element* Document::GetCurrentScript() {
5979 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
5980 return el;
5983 void Document::ReleaseCapture() const {
5984 // only release the capture if the caller can access it. This prevents a
5985 // page from stopping a scrollbar grab for example.
5986 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
5987 if (node && nsContentUtils::CanCallerAccess(node)) {
5988 PresShell::ReleaseCapturingContent();
5992 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
5993 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
5994 return mChromeXHRDocBaseURI;
5997 return GetDocBaseURI();
6000 void Document::SetBaseURI(nsIURI* aURI) {
6001 if (!aURI && !mDocumentBaseURI) {
6002 return;
6005 // Don't do anything if the URI wasn't actually changed.
6006 if (aURI && mDocumentBaseURI) {
6007 bool equalBases = false;
6008 mDocumentBaseURI->Equals(aURI, &equalBases);
6009 if (equalBases) {
6010 return;
6014 mDocumentBaseURI = aURI;
6015 RefreshLinkHrefs();
6018 Result<nsCOMPtr<nsIURI>, nsresult> Document::ResolveWithBaseURI(
6019 const nsAString& aURI) {
6020 nsCOMPtr<nsIURI> resolvedURI;
6021 MOZ_TRY(
6022 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
6023 return resolvedURI;
6026 URLExtraData* Document::DefaultStyleAttrURLData() {
6027 MOZ_ASSERT(NS_IsMainThread());
6028 nsIURI* baseURI = GetDocBaseURI();
6029 nsIPrincipal* principal = NodePrincipal();
6030 bool equals;
6031 if (!mCachedURLData || mCachedURLData->BaseURI() != baseURI ||
6032 mCachedURLData->Principal() != principal || !mCachedReferrerInfo ||
6033 NS_FAILED(mCachedURLData->ReferrerInfo()->Equals(mCachedReferrerInfo,
6034 &equals)) ||
6035 !equals) {
6036 mCachedReferrerInfo = ReferrerInfo::CreateForInternalCSSResources(this);
6037 mCachedURLData = new URLExtraData(baseURI, mCachedReferrerInfo, principal);
6039 return mCachedURLData;
6042 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
6043 if (mCharacterSet != aEncoding) {
6044 mCharacterSet = aEncoding;
6045 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
6046 RecomputeLanguageFromCharset();
6048 if (nsPresContext* context = GetPresContext()) {
6049 context->DocumentCharSetChanged(aEncoding);
6054 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
6055 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
6058 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
6059 aData.Truncate();
6060 const DocHeaderData* data = mHeaderData;
6061 while (data) {
6062 if (data->mField == aHeaderField) {
6063 aData = data->mData;
6065 break;
6067 data = data->mNext;
6071 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
6072 if (!aHeaderField) {
6073 NS_ERROR("null headerField");
6074 return;
6077 if (!mHeaderData) {
6078 if (!aData.IsEmpty()) { // don't bother storing empty string
6079 mHeaderData = new DocHeaderData(aHeaderField, aData);
6081 } else {
6082 DocHeaderData* data = mHeaderData;
6083 DocHeaderData** lastPtr = &mHeaderData;
6084 bool found = false;
6085 do { // look for existing and replace
6086 if (data->mField == aHeaderField) {
6087 if (!aData.IsEmpty()) {
6088 data->mData.Assign(aData);
6089 } else { // don't store empty string
6090 *lastPtr = data->mNext;
6091 data->mNext = nullptr;
6092 delete data;
6094 found = true;
6096 break;
6098 lastPtr = &(data->mNext);
6099 data = *lastPtr;
6100 } while (data);
6102 if (!aData.IsEmpty() && !found) {
6103 // didn't find, append
6104 *lastPtr = new DocHeaderData(aHeaderField, aData);
6108 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
6109 CopyUTF16toUTF8(aData, mContentLanguage);
6110 mMayNeedFontPrefsUpdate = true;
6111 if (auto* presContext = GetPresContext()) {
6112 presContext->ContentLanguageChanged();
6116 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
6117 SetPreferredStyleSheetSet(aData);
6120 if (aHeaderField == nsGkAtoms::refresh) {
6121 // We get into this code before we have a script global yet, so get to
6122 // our container via mDocumentContainer.
6123 nsCOMPtr<nsIRefreshURI> refresher(mDocumentContainer);
6124 if (refresher) {
6125 // Note: using mDocumentURI instead of mBaseURI here, for consistency
6126 // (used to just use the current URI of our webnavigation, but that
6127 // should really be the same thing). Note that this code can run
6128 // before the current URI of the webnavigation has been updated, so we
6129 // can't assert equality here.
6130 refresher->SetupRefreshURIFromHeader(mDocumentURI, NodePrincipal(),
6131 InnerWindowID(),
6132 NS_ConvertUTF16toUTF8(aData));
6136 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
6137 mAllowDNSPrefetch) {
6138 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
6139 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
6142 if (aHeaderField == nsGkAtoms::viewport ||
6143 aHeaderField == nsGkAtoms::handheldFriendly) {
6144 mViewportType = Unknown;
6148 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
6149 NotNull<const Encoding*>& aEncoding,
6150 nsHtml5TreeOpExecutor* aExecutor) {
6151 if (aChannel) {
6152 nsAutoCString charsetVal;
6153 nsresult rv = aChannel->GetContentCharset(charsetVal);
6154 if (NS_SUCCEEDED(rv)) {
6155 const Encoding* preferred = Encoding::ForLabel(charsetVal);
6156 if (preferred) {
6157 aEncoding = WrapNotNull(preferred);
6158 aCharsetSource = kCharsetFromChannel;
6159 return;
6160 } else if (aExecutor && !charsetVal.IsEmpty()) {
6161 aExecutor->ComplainAboutBogusProtocolCharset(this);
6167 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
6168 #ifdef DEBUG
6169 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
6170 const Element* element = Element::FromNode(node);
6171 if (!element) {
6172 continue;
6174 MOZ_ASSERT(!element->HasServoData());
6176 #endif
6179 already_AddRefed<PresShell> Document::CreatePresShell(
6180 nsPresContext* aContext, nsViewManager* aViewManager) {
6181 MOZ_ASSERT(!mPresShell, "We have a presshell already!");
6183 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
6185 AssertNoStaleServoDataIn(*this);
6187 RefPtr<PresShell> presShell = new PresShell(this);
6188 // Note: we don't hold a ref to the shell (it holds a ref to us)
6189 mPresShell = presShell;
6191 if (!mStyleSetFilled) {
6192 FillStyleSet();
6195 presShell->Init(aContext, aViewManager);
6197 // Gaining a shell causes changes in how media queries are evaluated, so
6198 // invalidate that.
6199 aContext->MediaFeatureValuesChanged({MediaFeatureChange::kAllChanges});
6201 // Make sure to never paint if we belong to an invisible DocShell.
6202 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
6203 if (docShell && docShell->IsInvisible()) {
6204 presShell->SetNeverPainting(true);
6207 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
6208 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
6209 presShell.get(), docShell.get()));
6211 mExternalResourceMap.ShowViewers();
6213 UpdateFrameRequestCallbackSchedulingState();
6215 if (mDocumentL10n) {
6216 // In case we already accumulated mutations,
6217 // we'll trigger the refresh driver now.
6218 mDocumentL10n->OnCreatePresShell();
6221 // Now that we have a shell, we might have @font-face rules (the presence of a
6222 // shell may change which rules apply to us). We don't need to do anything
6223 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
6224 // is ready to update we'll flush the font set.
6225 MarkUserFontSetDirty();
6227 return presShell.forget();
6230 void Document::UpdateFrameRequestCallbackSchedulingState(
6231 PresShell* aOldPresShell) {
6232 // If the condition for shouldBeScheduled changes to depend on some other
6233 // variable, add UpdateFrameRequestCallbackSchedulingState() calls to the
6234 // places where that variable can change.
6235 bool shouldBeScheduled = mPresShell && IsEventHandlingEnabled() &&
6236 !mFrameRequestCallbacks.IsEmpty();
6237 if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
6238 // nothing to do
6239 return;
6242 PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
6243 MOZ_RELEASE_ASSERT(presShell);
6245 nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
6246 if (shouldBeScheduled) {
6247 rd->ScheduleFrameRequestCallbacks(this);
6248 } else {
6249 rd->RevokeFrameRequestCallbacks(this);
6252 mFrameRequestCallbacksScheduled = shouldBeScheduled;
6255 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
6256 MOZ_ASSERT(aCallbacks.IsEmpty());
6257 aCallbacks.SwapElements(mFrameRequestCallbacks);
6258 mCanceledFrameRequestCallbacks.clear();
6259 // No need to manually remove ourselves from the refresh driver; it will
6260 // handle that part. But we do have to update our state.
6261 mFrameRequestCallbacksScheduled = false;
6264 bool Document::ShouldThrottleFrameRequests() {
6265 if (mStaticCloneCount > 0) {
6266 // Even if we're not visible, a static clone may be, so run at full speed.
6267 return false;
6270 if (Hidden()) {
6271 // We're not visible (probably in a background tab or the bf cache).
6272 return true;
6275 if (!mPresShell) {
6276 return false; // Can't do anything smarter.
6279 nsIFrame* frame = mPresShell->GetRootFrame();
6280 if (!frame) {
6281 return false; // Can't do anything smarter.
6284 nsIFrame* displayRootFrame = nsLayoutUtils::GetDisplayRootFrame(frame);
6285 if (!displayRootFrame) {
6286 return false; // Can't do anything smarter.
6289 if (!displayRootFrame->DidPaintPresShell(mPresShell)) {
6290 // We didn't get painted during the last paint, so we're not visible.
6291 // Throttle. Note that because we have to paint this document at least
6292 // once to unthrottle it, we will drop one requestAnimationFrame frame
6293 // when a document that previously wasn't visible scrolls into view. This
6294 // is acceptable since it would happen outside the viewport on APZ
6295 // platforms and is unlikely to be human-perceivable on non-APZ platforms.
6296 return true;
6299 // We got painted during the last paint, so run at full speed.
6300 return false;
6303 void Document::DeletePresShell() {
6304 mExternalResourceMap.HideViewers();
6305 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
6306 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
6309 // When our shell goes away, request that all our images be immediately
6310 // discarded, so we don't carry around decoded image data for a document we
6311 // no longer intend to paint.
6312 ImageTracker()->RequestDiscardAll();
6314 // Now that we no longer have a shell, we need to forget about any FontFace
6315 // objects for @font-face rules that came from the style set. There's no need
6316 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
6317 // no point on it.
6318 MarkUserFontSetDirty();
6320 if (mResizeObserverController) {
6321 mResizeObserverController->ShellDetachedFromDocument();
6324 if (IsEditingOn()) {
6325 TurnEditingOff();
6328 PresShell* oldPresShell = mPresShell;
6329 mPresShell = nullptr;
6330 UpdateFrameRequestCallbackSchedulingState(oldPresShell);
6332 ClearStaleServoData();
6333 AssertNoStaleServoDataIn(*this);
6335 mStyleSet->ShellDetachedFromDocument();
6336 mStyleSetFilled = false;
6337 mQuirkSheetAdded = false;
6338 mContentEditableSheetAdded = false;
6339 mDesignModeSheetAdded = false;
6342 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
6343 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
6345 if (mPresShell) {
6346 if (aEntry) {
6347 mPresShell->StopObservingRefreshDriver();
6348 } else if (mBFCacheEntry) {
6349 mPresShell->StartObservingRefreshDriver();
6352 mBFCacheEntry = aEntry;
6355 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
6356 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
6358 NS_RELEASE(e->mKey);
6359 if (e->mSubDocument) {
6360 e->mSubDocument->SetParentDocument(nullptr);
6361 NS_RELEASE(e->mSubDocument);
6365 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
6366 SubDocMapEntry* e =
6367 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
6369 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
6370 NS_ADDREF(e->mKey);
6372 e->mSubDocument = nullptr;
6375 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
6376 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
6378 if (!aSubDoc) {
6379 // aSubDoc is nullptr, remove the mapping
6381 if (mSubDocuments) {
6382 if (Document* subDoc = GetSubDocumentFor(aElement)) {
6383 subDoc->SetAllowPaymentRequest(false);
6385 mSubDocuments->Remove(aElement);
6387 } else {
6388 if (!mSubDocuments) {
6389 // Create a new hashtable
6391 static const PLDHashTableOps hash_table_ops = {
6392 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
6393 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
6395 mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
6398 // Add a mapping to the hash table
6399 auto entry =
6400 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
6402 if (!entry) {
6403 return NS_ERROR_OUT_OF_MEMORY;
6406 if (entry->mSubDocument) {
6407 entry->mSubDocument->SetAllowPaymentRequest(false);
6408 entry->mSubDocument->SetParentDocument(nullptr);
6410 // Release the old sub document
6411 NS_RELEASE(entry->mSubDocument);
6414 entry->mSubDocument = aSubDoc;
6415 NS_ADDREF(entry->mSubDocument);
6417 // set allowpaymentrequest for the binding subdocument
6418 if (!mAllowPaymentRequest) {
6419 aSubDoc->SetAllowPaymentRequest(false);
6420 } else {
6421 nsresult rv = nsContentUtils::CheckSameOrigin(aElement, aSubDoc);
6422 if (NS_SUCCEEDED(rv)) {
6423 aSubDoc->SetAllowPaymentRequest(true);
6424 } else {
6425 if (aElement->IsHTMLElement(nsGkAtoms::iframe) &&
6426 aElement->GetBoolAttr(nsGkAtoms::allowpaymentrequest)) {
6427 aSubDoc->SetAllowPaymentRequest(true);
6428 } else {
6429 aSubDoc->SetAllowPaymentRequest(false);
6434 aSubDoc->SetParentDocument(this);
6437 return NS_OK;
6440 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
6441 if (mSubDocuments && aContent->IsElement()) {
6442 auto entry = static_cast<SubDocMapEntry*>(
6443 mSubDocuments->Search(aContent->AsElement()));
6445 if (entry) {
6446 return entry->mSubDocument;
6450 return nullptr;
6453 Element* Document::FindContentForSubDocument(Document* aDocument) const {
6454 NS_ENSURE_TRUE(aDocument, nullptr);
6456 if (!mSubDocuments) {
6457 return nullptr;
6460 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
6461 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
6462 if (entry->mSubDocument == aDocument) {
6463 return entry->mKey;
6466 return nullptr;
6469 bool Document::IsNodeOfType(uint32_t aFlags) const { return false; }
6471 Element* Document::GetRootElement() const {
6472 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
6473 ? mCachedRootElement
6474 : GetRootElementInternal();
6477 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
6479 Element* Document::GetRootElementInternal() const {
6480 // We invoke GetRootElement() immediately before the servo traversal, so we
6481 // should always have a cache hit from Servo.
6482 MOZ_ASSERT(NS_IsMainThread());
6484 // Loop backwards because any non-elements, such as doctypes and PIs
6485 // are likely to appear before the root element.
6486 for (nsIContent* child = GetLastChild(); child;
6487 child = child->GetPreviousSibling()) {
6488 if (Element* element = Element::FromNode(child)) {
6489 const_cast<Document*>(this)->mCachedRootElement = element;
6490 return element;
6494 const_cast<Document*>(this)->mCachedRootElement = nullptr;
6495 return nullptr;
6498 nsresult Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
6499 bool aNotify) {
6500 if (aKid->IsElement() && GetRootElement()) {
6501 NS_WARNING("Inserting root element when we already have one");
6502 return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
6505 return nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify);
6508 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
6509 Maybe<mozAutoDocUpdate> updateBatch;
6510 if (aKid->IsElement()) {
6511 updateBatch.emplace(this, aNotify);
6512 // Destroy the link map up front before we mess with the child list.
6513 DestroyElementMaps();
6516 // Preemptively clear mCachedRootElement, since we may be about to remove it
6517 // from our child list, and we don't want to return this maybe-obsolete value
6518 // from any GetRootElement() calls that happen inside of RemoveChildNode().
6519 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
6520 // GetRootElement() calls until after it's removed the child from mChildren.
6521 // Any call before that point would restore this soon-to-be-obsolete cached
6522 // answer, and our clearing here would be fruitless.)
6523 mCachedRootElement = nullptr;
6524 nsINode::RemoveChildNode(aKid, aNotify);
6525 MOZ_ASSERT(mCachedRootElement != aKid,
6526 "Stale pointer in mCachedRootElement, after we tried to clear it "
6527 "(maybe somebody called GetRootElement() too early?)");
6530 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
6531 if (mStyleSetFilled) {
6532 mStyleSet->AddDocStyleSheet(aSheet);
6533 ApplicableStylesChanged();
6537 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
6538 mStyleSet->RecordShadowStyleChange(aShadowRoot);
6539 ApplicableStylesChanged();
6542 void Document::ApplicableStylesChanged() {
6543 // TODO(emilio): if we decide to resolve style in display: none iframes, then
6544 // we need to always track style changes and remove the mStyleSetFilled.
6545 if (!mStyleSetFilled) {
6546 return;
6549 MarkUserFontSetDirty();
6550 PresShell* ps = GetPresShell();
6551 if (!ps) {
6552 return;
6555 ps->EnsureStyleFlush();
6556 nsPresContext* pc = ps->GetPresContext();
6557 if (!pc) {
6558 return;
6561 pc->MarkCounterStylesDirty();
6562 pc->MarkFontFeatureValuesDirty();
6563 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
6566 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
6567 if (mStyleSetFilled) {
6568 mStyleSet->RemoveStyleSheet(aSheet);
6569 ApplicableStylesChanged();
6573 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
6574 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
6576 if (aSheet.IsApplicable()) {
6577 AddStyleSheetToStyleSets(aSheet);
6581 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
6582 const bool applicable = aSheet.IsApplicable();
6583 // If we're actually in the document style sheet list
6584 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
6585 if (applicable) {
6586 AddStyleSheetToStyleSets(aSheet);
6587 } else {
6588 RemoveStyleSheetFromStyleSets(aSheet);
6592 PostStyleSheetApplicableStateChangeEvent(aSheet);
6594 if (!mSSApplicableStateNotificationPending) {
6595 MOZ_RELEASE_ASSERT(NS_IsMainThread());
6596 nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(
6597 "Document::NotifyStyleSheetApplicableStateChanged", this,
6598 &Document::NotifyStyleSheetApplicableStateChanged);
6599 mSSApplicableStateNotificationPending =
6600 NS_SUCCEEDED(Dispatch(TaskCategory::Other, notification.forget()));
6604 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
6605 if (!StyleSheetChangeEventsEnabled()) {
6606 return;
6609 StyleSheetApplicableStateChangeEventInit init;
6610 init.mBubbles = true;
6611 init.mCancelable = true;
6612 init.mStylesheet = &aSheet;
6613 init.mApplicable = aSheet.IsApplicable();
6615 RefPtr<StyleSheetApplicableStateChangeEvent> event =
6616 StyleSheetApplicableStateChangeEvent::Constructor(
6617 this, u"StyleSheetApplicableStateChanged"_ns, init);
6618 event->SetTrusted(true);
6619 event->SetTarget(this);
6620 RefPtr<AsyncEventDispatcher> asyncDispatcher =
6621 new AsyncEventDispatcher(this, event);
6622 asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
6623 asyncDispatcher->PostDOMEvent();
6626 void Document::NotifyStyleSheetApplicableStateChanged() {
6627 mSSApplicableStateNotificationPending = false;
6628 nsCOMPtr<nsIObserverService> observerService =
6629 mozilla::services::GetObserverService();
6630 if (observerService) {
6631 observerService->NotifyObservers(
6632 ToSupports(this), "style-sheet-applicable-state-changed", nullptr);
6636 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
6637 nsIURI* aSheetURI) {
6638 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
6639 bool bEqual;
6640 nsIURI* uri = aSheets[i]->GetSheetURI();
6642 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
6643 return i;
6646 return -1;
6649 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
6650 nsIURI* aSheetURI) {
6651 MOZ_ASSERT(aSheetURI, "null arg");
6653 // Checking if we have loaded this one already.
6654 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
6655 return NS_ERROR_INVALID_ARG;
6657 // Loading the sheet sync.
6658 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
6660 css::SheetParsingMode parsingMode;
6661 switch (aType) {
6662 case Document::eAgentSheet:
6663 parsingMode = css::eAgentSheetFeatures;
6664 break;
6666 case Document::eUserSheet:
6667 parsingMode = css::eUserSheetFeatures;
6668 break;
6670 case Document::eAuthorSheet:
6671 parsingMode = css::eAuthorSheetFeatures;
6672 break;
6674 default:
6675 MOZ_CRASH("impossible value for aType");
6678 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
6679 css::Loader::UseSystemPrincipal::Yes);
6680 if (result.isErr()) {
6681 return result.unwrapErr();
6684 RefPtr<StyleSheet> sheet = result.unwrap();
6686 sheet->SetAssociatedDocumentOrShadowRoot(this);
6687 MOZ_ASSERT(sheet->IsApplicable());
6689 return AddAdditionalStyleSheet(aType, sheet);
6692 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
6693 StyleSheet* aSheet) {
6694 if (mAdditionalSheets[aType].Contains(aSheet)) {
6695 return NS_ERROR_INVALID_ARG;
6698 if (!aSheet->IsApplicable()) {
6699 return NS_ERROR_INVALID_ARG;
6702 mAdditionalSheets[aType].AppendElement(aSheet);
6704 if (mStyleSetFilled) {
6705 mStyleSet->AppendStyleSheet(*aSheet);
6706 ApplicableStylesChanged();
6708 return NS_OK;
6711 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
6712 nsIURI* aSheetURI) {
6713 MOZ_ASSERT(aSheetURI);
6715 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
6717 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
6718 if (i >= 0) {
6719 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
6720 sheets.RemoveElementAt(i);
6722 if (!mIsGoingAway) {
6723 MOZ_ASSERT(sheetRef->IsApplicable());
6724 if (mStyleSetFilled) {
6725 mStyleSet->RemoveStyleSheet(*sheetRef);
6726 ApplicableStylesChanged();
6729 sheetRef->ClearAssociatedDocumentOrShadowRoot();
6733 nsIGlobalObject* Document::GetScopeObject() const {
6734 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
6735 return scope;
6738 DocGroup* Document::GetDocGroupOrCreate() {
6739 if (!mDocGroup) {
6740 bool crossOriginIsolated = GetBrowsingContext()
6741 ? GetBrowsingContext()->CrossOriginIsolated()
6742 : false;
6743 nsAutoCString docGroupKey;
6744 nsresult rv = mozilla::dom::DocGroup::GetKey(
6745 NodePrincipal(), crossOriginIsolated, docGroupKey);
6746 if (NS_SUCCEEDED(rv) && mDocumentContainer) {
6747 BrowsingContextGroup* group = GetBrowsingContext()->Group();
6748 if (group) {
6749 mDocGroup = group->AddDocument(docGroupKey, this);
6753 return mDocGroup;
6756 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
6757 mScopeObject = do_GetWeakReference(aGlobal);
6758 if (aGlobal) {
6759 mHasHadScriptHandlingObject = true;
6761 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
6762 if (!window) {
6763 return;
6765 BrowsingContextGroup* browsingContextGroup =
6766 window->GetBrowsingContextGroup();
6768 bool crossOriginIsolated = GetBrowsingContext()
6769 ? GetBrowsingContext()->CrossOriginIsolated()
6770 : false;
6772 // We should already have the principal, and now that we have been added
6773 // to a window, we should be able to join a DocGroup!
6774 nsAutoCString docGroupKey;
6775 nsresult rv = mozilla::dom::DocGroup::GetKey(
6776 NodePrincipal(), crossOriginIsolated, docGroupKey);
6777 if (mDocGroup) {
6778 if (NS_SUCCEEDED(rv)) {
6779 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
6781 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
6782 browsingContextGroup);
6783 } else {
6784 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
6786 MOZ_ASSERT(mDocGroup);
6789 MOZ_ASSERT_IF(
6790 mNodeInfoManager->GetArenaAllocator(),
6791 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
6795 bool Document::ContainsEMEContent() {
6796 bool containsEME = false;
6798 auto check = [&containsEME](nsISupports* aSupports) {
6799 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
6800 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
6801 if (mediaElem->GetMediaKeys()) {
6802 containsEME = true;
6807 EnumerateActivityObservers(check);
6808 return containsEME;
6811 bool Document::ContainsMSEContent() {
6812 bool containsMSE = false;
6814 auto check = [&containsMSE](nsISupports* aSupports) {
6815 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
6816 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
6817 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
6818 if (ms) {
6819 containsMSE = true;
6824 EnumerateActivityObservers(check);
6825 return containsMSE;
6828 static void NotifyActivityChanged(nsISupports* aSupports) {
6829 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
6830 if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
6831 mediaElem->NotifyOwnerDocumentActivityChanged();
6833 nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
6834 do_QueryInterface(aSupports));
6835 if (objectLoadingContent) {
6836 nsObjectLoadingContent* olc =
6837 static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
6838 olc->NotifyOwnerDocumentActivityChanged();
6840 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
6841 do_QueryInterface(aSupports));
6842 if (objectDocumentActivity) {
6843 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
6844 } else {
6845 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
6846 do_QueryInterface(aSupports));
6847 if (imageLoadingContent) {
6848 auto ilc = static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
6849 ilc->NotifyOwnerDocumentActivityChanged();
6854 bool Document::IsTopLevelWindowInactive() const {
6855 nsCOMPtr<nsIDocShellTreeItem> treeItem = GetDocShell();
6856 if (!treeItem) {
6857 return false;
6860 nsCOMPtr<nsIDocShellTreeItem> rootItem;
6861 treeItem->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
6862 if (!rootItem) {
6863 return false;
6866 nsCOMPtr<nsPIDOMWindowOuter> domWindow = rootItem->GetWindow();
6867 return domWindow && !domWindow->IsActive();
6870 void Document::SetContainer(nsDocShell* aContainer) {
6871 if (aContainer) {
6872 mDocumentContainer = aContainer;
6873 } else {
6874 mDocumentContainer = WeakPtr<nsDocShell>();
6877 mInChromeDocShell =
6878 aContainer && aContainer->GetBrowsingContext()->IsChrome();
6880 EnumerateActivityObservers(NotifyActivityChanged);
6882 // IsTopLevelWindowInactive depends on the docshell, so
6883 // update the cached value now that it's available.
6884 UpdateDocumentStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE, false);
6885 if (!aContainer) {
6886 return;
6889 BrowsingContext* context = aContainer->GetBrowsingContext();
6890 if (context && context->IsContent()) {
6891 if (context->IsTopContent()) {
6892 SetIsTopLevelContentDocument(true);
6894 SetIsContentDocument(true);
6898 nsISupports* Document::GetContainer() const {
6899 return static_cast<nsIDocShell*>(mDocumentContainer);
6902 void Document::SetScriptGlobalObject(
6903 nsIScriptGlobalObject* aScriptGlobalObject) {
6904 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
6905 mAnimationController->IsPausedByType(
6906 SMILTimeContainer::PAUSE_PAGEHIDE |
6907 SMILTimeContainer::PAUSE_BEGIN),
6908 "Clearing window pointer while animations are unpaused");
6910 if (mScriptGlobalObject && !aScriptGlobalObject) {
6911 // We're detaching from the window. We need to grab a pointer to
6912 // our layout history state now.
6913 mLayoutHistoryState = GetLayoutHistoryState();
6915 // Also make sure to remove our onload blocker now if we haven't done it yet
6916 if (mOnloadBlockCount != 0) {
6917 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
6918 if (loadGroup) {
6919 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
6923 if (GetController().isSome()) {
6924 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
6925 loader->ClearCacheForControlledDocument(this);
6928 // We may become controlled again if this document comes back out
6929 // of bfcache. Clear our state to allow that to happen. Only
6930 // clear this flag if we are actually controlled, though, so pages
6931 // that were force reloaded don't become controlled when they
6932 // come out of bfcache.
6933 mMaybeServiceWorkerControlled = false;
6937 // BlockOnload() might be called before mScriptGlobalObject is set.
6938 // We may need to add the blocker once mScriptGlobalObject is set.
6939 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
6941 mScriptGlobalObject = aScriptGlobalObject;
6943 if (needOnloadBlocker) {
6944 EnsureOnloadBlocker();
6947 UpdateFrameRequestCallbackSchedulingState();
6949 if (aScriptGlobalObject) {
6950 // Go back to using the docshell for the layout history state
6951 mLayoutHistoryState = nullptr;
6952 SetScopeObject(aScriptGlobalObject);
6953 mHasHadDefaultView = true;
6955 if (mAllowDNSPrefetch) {
6956 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
6957 if (docShell) {
6958 #ifdef DEBUG
6959 nsCOMPtr<nsIWebNavigation> webNav =
6960 do_GetInterface(aScriptGlobalObject);
6961 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
6962 "Unexpected container or script global?");
6963 #endif
6964 bool allowDNSPrefetch;
6965 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
6966 mAllowDNSPrefetch = allowDNSPrefetch;
6970 // If we are set in a window that is already focused we should remember this
6971 // as the time the document gained focus.
6972 if (HasFocus(IgnoreErrors())) {
6973 SetLastFocusTime(TimeStamp::Now());
6977 // Remember the pointer to our window (or lack there of), to avoid
6978 // having to QI every time it's asked for.
6979 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
6980 mWindow = window;
6982 // Now that we know what our window is, we can flush the CSP errors to the
6983 // Web Console. We are flushing all messages that occurred and were stored in
6984 // the queue prior to this point.
6985 if (mCSP) {
6986 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
6989 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
6990 do_QueryInterface(GetChannel());
6991 if (internalChannel) {
6992 nsCOMArray<nsISecurityConsoleMessage> messages;
6993 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
6994 MOZ_ASSERT(NS_SUCCEEDED(rv));
6995 SendToConsole(messages);
6998 // Set our visibility state, but do not fire the event. This is correct
6999 // because either we're coming out of bfcache (in which case IsVisible() will
7000 // still test false at this point and no state change will happen) or we're
7001 // doing the initial document load and don't want to fire the event for this
7002 // change.
7003 dom::VisibilityState oldState = mVisibilityState;
7004 mVisibilityState = ComputeVisibilityState();
7005 // When the visibility is changed, notify it to observers.
7006 // Some observers need the notification, for example HTMLMediaElement uses
7007 // it to update internal media resource allocation.
7008 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
7009 // creation are already done before Document::SetScriptGlobalObject() call.
7010 // MediaDecoder decides whether starting decoding is decided based on
7011 // document's visibility. When the MediaDecoder is created,
7012 // Document::SetScriptGlobalObject() is not yet called and document is
7013 // hidden state. Therefore the MediaDecoder decides that decoding is
7014 // not yet necessary. But soon after Document::SetScriptGlobalObject()
7015 // call, the document becomes not hidden. At the time, MediaDecoder needs
7016 // to know it and needs to start updating decoding.
7017 if (oldState != mVisibilityState) {
7018 EnumerateActivityObservers(NotifyActivityChanged);
7021 // The global in the template contents owner document should be the same.
7022 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
7023 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
7026 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
7027 mScriptGlobalObject && GetChannel()) {
7028 // If we are shift-reloaded, don't associate with a ServiceWorker.
7029 if (mDocumentContainer->IsForceReloading()) {
7030 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
7031 return;
7034 mMaybeServiceWorkerControlled = true;
7038 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
7039 MOZ_ASSERT(!mScriptGlobalObject,
7040 "Do not call this when mScriptGlobalObject is set!");
7041 if (mHasHadDefaultView) {
7042 return nullptr;
7045 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
7046 do_QueryReferent(mScopeObject);
7047 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
7048 if (win) {
7049 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
7050 if (!outer || outer->GetCurrentInnerWindow() != win) {
7051 NS_WARNING("Wrong inner/outer window combination!");
7052 return nullptr;
7055 return scriptHandlingObject;
7057 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
7058 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
7059 "Wrong script object!");
7060 if (aScriptObject) {
7061 SetScopeObject(aScriptObject);
7062 mHasHadDefaultView = false;
7066 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
7067 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
7068 // Let's use mScriptGlobalObject. Even if the document is already removed from
7069 // the docshell, the outer window might be still obtainable from the it.
7070 nsCOMPtr<nsPIDOMWindowOuter> win;
7071 if (mRemovedFromDocShell) {
7072 // The docshell returns the outer window we are done.
7073 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
7074 if (kungFuDeathGrip) {
7075 win = kungFuDeathGrip->GetWindow();
7077 } else {
7078 if (nsCOMPtr<nsPIDOMWindowInner> inner =
7079 do_QueryInterface(mScriptGlobalObject)) {
7080 // mScriptGlobalObject is always the inner window, let's get the outer.
7081 win = inner->GetOuterWindow();
7085 return win;
7088 bool Document::InternalAllowXULXBL() {
7089 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
7090 mAllowXULXBL = eTriTrue;
7091 return true;
7094 mAllowXULXBL = eTriFalse;
7095 return false;
7098 // Note: We don't hold a reference to the document observer; we assume
7099 // that it has a live reference to the document.
7100 void Document::AddObserver(nsIDocumentObserver* aObserver) {
7101 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
7102 "Observer already in the list");
7103 mObservers.AppendElement(aObserver);
7104 AddMutationObserver(aObserver);
7107 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
7108 // If we're in the process of destroying the document (and we're
7109 // informing the observers of the destruction), don't remove the
7110 // observers from the list. This is not a big deal, since we
7111 // don't hold a live reference to the observers.
7112 if (!mInDestructor) {
7113 RemoveMutationObserver(aObserver);
7114 return mObservers.RemoveElement(aObserver);
7117 return mObservers.Contains(aObserver);
7120 void Document::BeginUpdate() {
7121 ++mUpdateNestLevel;
7122 nsContentUtils::AddScriptBlocker();
7123 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
7126 void Document::EndUpdate() {
7127 const bool reset = !mPendingMaybeEditingStateChanged;
7128 mPendingMaybeEditingStateChanged = true;
7130 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
7132 --mUpdateNestLevel;
7134 nsContentUtils::RemoveScriptBlocker();
7136 if (mXULBroadcastManager) {
7137 mXULBroadcastManager->MaybeBroadcast();
7140 if (reset) {
7141 mPendingMaybeEditingStateChanged = false;
7143 MaybeEditingStateChanged();
7146 void Document::BeginLoad() {
7147 if (IsEditingOn()) {
7148 // Reset() blows away all event listeners in the document, and our
7149 // editor relies heavily on those. Midas is turned on, to make it
7150 // work, re-initialize it to give it a chance to add its event
7151 // listeners again.
7153 TurnEditingOff();
7154 EditingStateChanged();
7157 MOZ_ASSERT(!mDidCallBeginLoad);
7158 mDidCallBeginLoad = true;
7160 // Block onload here to prevent having to deal with blocking and
7161 // unblocking it while we know the document is loading.
7162 BlockOnload();
7163 mDidFireDOMContentLoaded = false;
7164 BlockDOMContentLoaded();
7166 if (mScriptLoader) {
7167 mScriptLoader->BeginDeferringScripts();
7170 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
7173 void Document::MozSetImageElement(const nsAString& aImageElementId,
7174 Element* aElement) {
7175 if (aImageElementId.IsEmpty()) return;
7177 // Hold a script blocker while calling SetImageElement since that can call
7178 // out to id-observers
7179 nsAutoScriptBlocker scriptBlocker;
7181 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
7182 if (entry) {
7183 entry->SetImageElement(aElement);
7184 if (entry->IsEmpty()) {
7185 mIdentifierMap.RemoveEntry(entry);
7190 void Document::DispatchContentLoadedEvents() {
7191 // If you add early returns from this method, make sure you're
7192 // calling UnblockOnload properly.
7194 // Unpin references to preloaded images
7195 mPreloadingImages.Clear();
7197 // DOM manipulation after content loaded should not care if the element
7198 // came from the preloader.
7199 mPreloadedPreconnects.Clear();
7201 if (mTiming) {
7202 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
7205 // Dispatch observer notification to notify observers document is interactive.
7206 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
7207 if (os) {
7208 nsIPrincipal* principal = NodePrincipal();
7209 os->NotifyObservers(ToSupports(this),
7210 principal->IsSystemPrincipal()
7211 ? "chrome-document-interactive"
7212 : "content-document-interactive",
7213 nullptr);
7216 // Fire a DOM event notifying listeners that this document has been
7217 // loaded (excluding images and other loads initiated by this
7218 // document).
7219 nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
7220 u"DOMContentLoaded"_ns, CanBubble::eYes,
7221 Cancelable::eNo);
7223 if (auto* const window = GetInnerWindow()) {
7224 const RefPtr<ServiceWorkerContainer> serviceWorker =
7225 window->Navigator()->ServiceWorker();
7227 // This could cause queued messages from a service worker to get
7228 // dispatched on serviceWorker.
7229 serviceWorker->StartMessages();
7232 if (MayStartLayout()) {
7233 MaybeResolveReadyForIdle();
7236 RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
7237 nsIDocShell* docShell = this->GetDocShell();
7239 if (timelines && timelines->HasConsumer(docShell)) {
7240 timelines->AddMarkerForDocShell(
7241 docShell,
7242 MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
7245 if (mTiming) {
7246 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
7249 // If this document is a [i]frame, fire a DOMFrameContentLoaded
7250 // event on all parent documents notifying that the HTML (excluding
7251 // other external files such as images and stylesheets) in a frame
7252 // has finished loading.
7254 // target_frame is the [i]frame element that will be used as the
7255 // target for the event. It's the [i]frame whose content is done
7256 // loading.
7257 nsCOMPtr<EventTarget> target_frame;
7259 if (mParentDocument) {
7260 target_frame = mParentDocument->FindContentForSubDocument(this);
7263 if (target_frame) {
7264 nsCOMPtr<Document> parent = mParentDocument;
7265 do {
7266 RefPtr<Event> event;
7267 if (parent) {
7268 IgnoredErrorResult ignored;
7269 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
7272 if (event) {
7273 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
7275 event->SetTarget(target_frame);
7276 event->SetTrusted(true);
7278 // To dispatch this event we must manually call
7279 // EventDispatcher::Dispatch() on the ancestor document since the
7280 // target is not in the same document, so the event would never reach
7281 // the ancestor document if we used the normal event
7282 // dispatching code.
7284 WidgetEvent* innerEvent = event->WidgetEventPtr();
7285 if (innerEvent) {
7286 nsEventStatus status = nsEventStatus_eIgnore;
7288 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
7289 EventDispatcher::Dispatch(ToSupports(parent), context, innerEvent,
7290 event, &status);
7295 parent = parent->GetInProcessParentDocument();
7296 } while (parent);
7299 // If the document has a manifest attribute, fire a MozApplicationManifest
7300 // event.
7301 Element* root = GetRootElement();
7302 if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::manifest)) {
7303 nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
7304 u"MozApplicationManifest"_ns,
7305 CanBubble::eYes, Cancelable::eYes);
7308 nsPIDOMWindowInner* inner = GetInnerWindow();
7309 if (inner) {
7310 inner->NoteDOMContentLoaded();
7313 // TODO
7314 if (mMaybeServiceWorkerControlled) {
7315 using mozilla::dom::ServiceWorkerManager;
7316 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
7317 if (swm) {
7318 Maybe<ClientInfo> clientInfo = GetClientInfo();
7319 if (clientInfo.isSome()) {
7320 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
7325 if (mSetCompleteAfterDOMContentLoaded) {
7326 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
7327 mSetCompleteAfterDOMContentLoaded = false;
7330 UnblockOnload(true);
7333 void Document::EndLoad() {
7334 bool turnOnEditing =
7335 mParser && (HasFlag(NODE_IS_EDITABLE) || mContentEditableCount > 0);
7337 #if defined(DEBUG)
7338 // only assert if nothing stopped the load on purpose
7339 if (!mParserAborted) {
7340 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
7342 #endif
7344 // EndLoad may have been called without a matching call to BeginLoad, in the
7345 // case of a failed parse (for example, due to timeout). In such a case, we
7346 // still want to execute part of this code to do appropriate cleanup, but we
7347 // gate part of it because it is intended to match 1-for-1 with calls to
7348 // BeginLoad. We have an explicit flag bit for this purpose, since it's
7349 // complicated and error prone to derive this condition from other related
7350 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
7352 // Part 1: Code that always executes to cleanup end of parsing, whether
7353 // that parsing was successful or not.
7355 // Drop the ref to our parser, if any, but keep hold of the sink so that we
7356 // can flush it from FlushPendingNotifications as needed. We might have to
7357 // do that to get a StartLayout() to happen.
7358 if (mParser) {
7359 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
7360 mParser = nullptr;
7363 // Update the attributes on the PerformanceNavigationTiming before notifying
7364 // the onload observers.
7365 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
7366 window->QueuePerformanceNavigationTiming();
7369 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
7371 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
7373 if (!mDidCallBeginLoad) {
7374 return;
7376 mDidCallBeginLoad = false;
7378 UnblockDOMContentLoaded();
7380 if (turnOnEditing) {
7381 EditingStateChanged();
7384 if (!GetWindow()) {
7385 // This is a document that's not in a window. For example, this could be an
7386 // XMLHttpRequest responseXML document, or a document created via DOMParser
7387 // or DOMImplementation. We don't reach this code normally for such
7388 // documents (which is not obviously correct), but can reach it via
7389 // document.open()/document.close().
7391 // Such documents don't fire load events, but per spec should set their
7392 // readyState to "complete" when parsing and all loading of subresources is
7393 // done. Parsing is done now, and documents not in a window don't load
7394 // subresources, so just go ahead and mark ourselves as complete.
7395 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
7396 /* updateTimingInformation = */ false);
7398 // Reset mSkipLoadEventAfterClose just in case.
7399 mSkipLoadEventAfterClose = false;
7403 void Document::UnblockDOMContentLoaded() {
7404 MOZ_ASSERT(mBlockDOMContentLoaded);
7405 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
7406 return;
7409 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
7410 ("DOCUMENT %p UnblockDOMContentLoaded", this));
7412 mDidFireDOMContentLoaded = true;
7413 if (PresShell* presShell = GetPresShell()) {
7414 presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
7417 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
7418 if (!mSynchronousDOMContentLoaded) {
7419 MOZ_RELEASE_ASSERT(NS_IsMainThread());
7420 nsCOMPtr<nsIRunnable> ev =
7421 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
7422 &Document::DispatchContentLoadedEvents);
7423 Dispatch(TaskCategory::Other, ev.forget());
7424 } else {
7425 DispatchContentLoadedEvents();
7429 void Document::ContentStateChanged(nsIContent* aContent,
7430 EventStates aStateMask) {
7431 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
7432 "Someone forgot a scriptblocker");
7433 NS_DOCUMENT_NOTIFY_OBSERVERS(ContentStateChanged,
7434 (this, aContent, aStateMask));
7437 void Document::RuleChanged(StyleSheet& aSheet, css::Rule* aRule) {
7438 if (aSheet.IsApplicable()) {
7439 ApplicableStylesChanged();
7443 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
7444 if (aRule.IsIncompleteImportRule()) {
7445 return;
7448 if (aSheet.IsApplicable()) {
7449 ApplicableStylesChanged();
7453 void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
7454 if (aSheet.IsApplicable()) {
7455 ApplicableStylesChanged();
7459 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
7460 if (aSheet.IsApplicable()) {
7461 ApplicableStylesChanged();
7465 static Element* GetCustomContentContainer(PresShell* aPresShell) {
7466 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
7467 return nullptr;
7470 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
7473 static void InsertAnonContentIntoCanvas(AnonymousContent& aAnonContent,
7474 PresShell* aPresShell) {
7475 Element* container = GetCustomContentContainer(aPresShell);
7476 if (!container) {
7477 return;
7480 nsresult rv = container->AppendChildTo(&aAnonContent.ContentNode(), true);
7481 if (NS_FAILED(rv)) {
7482 return;
7485 aPresShell->GetCanvasFrame()->ShowCustomContentContainer();
7488 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
7489 Element& aElement, ErrorResult& aRv) {
7490 nsAutoScriptBlocker scriptBlocker;
7492 // Clone the node to avoid returning a direct reference.
7493 nsCOMPtr<nsINode> clone = aElement.CloneNode(true, aRv);
7494 if (aRv.Failed()) {
7495 return nullptr;
7498 auto anonContent =
7499 MakeRefPtr<AnonymousContent>(clone.forget().downcast<Element>());
7500 mAnonymousContents.AppendElement(anonContent);
7502 InsertAnonContentIntoCanvas(*anonContent, GetPresShell());
7504 return anonContent.forget();
7507 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
7508 PresShell* aPresShell) {
7509 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
7510 if (!container) {
7511 return;
7513 container->RemoveChild(aAnonContent.ContentNode(), IgnoreErrors());
7516 void Document::RemoveAnonymousContent(AnonymousContent& aContent,
7517 ErrorResult& aRv) {
7518 nsAutoScriptBlocker scriptBlocker;
7520 auto index = mAnonymousContents.IndexOf(&aContent);
7521 if (index == mAnonymousContents.NoIndex) {
7522 return;
7525 mAnonymousContents.RemoveElementAt(index);
7526 RemoveAnonContentFromCanvas(aContent, GetPresShell());
7528 if (mAnonymousContents.IsEmpty() &&
7529 GetCustomContentContainer(GetPresShell())) {
7530 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
7534 Element* Document::GetAnonRootIfInAnonymousContentContainer(
7535 nsINode* aNode) const {
7536 if (!aNode->IsInNativeAnonymousSubtree()) {
7537 return nullptr;
7540 PresShell* presShell = GetPresShell();
7541 if (!presShell || !presShell->GetCanvasFrame()) {
7542 return nullptr;
7545 nsAutoScriptBlocker scriptBlocker;
7546 nsCOMPtr<Element> customContainer =
7547 presShell->GetCanvasFrame()->GetCustomContentContainer();
7548 if (!customContainer) {
7549 return nullptr;
7552 // An arbitrary number of elements can be inserted as children of the custom
7553 // container frame. We want the one that was added that contains aNode, so
7554 // we need to keep track of the last child separately using |child| here.
7555 nsINode* child = aNode;
7556 nsINode* parent = aNode->GetParentNode();
7557 while (parent && parent->IsInNativeAnonymousSubtree()) {
7558 if (parent == customContainer) {
7559 return Element::FromNode(child);
7561 child = parent;
7562 parent = child->GetParentNode();
7564 return nullptr;
7567 Maybe<ClientInfo> Document::GetClientInfo() const {
7568 if (const Document* orig = GetOriginalDocument()) {
7569 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
7570 return info;
7574 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
7575 return inner->GetClientInfo();
7578 return Maybe<ClientInfo>();
7581 Maybe<ClientState> Document::GetClientState() const {
7582 if (const Document* orig = GetOriginalDocument()) {
7583 if (Maybe<ClientState> state = orig->GetClientState()) {
7584 return state;
7588 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
7589 return inner->GetClientState();
7592 return Maybe<ClientState>();
7595 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
7596 if (const Document* orig = GetOriginalDocument()) {
7597 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
7598 return controller;
7602 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
7603 return inner->GetController();
7606 return Maybe<ServiceWorkerDescriptor>();
7610 // Document interface
7612 DocumentType* Document::GetDoctype() const {
7613 for (nsIContent* child = GetFirstChild(); child;
7614 child = child->GetNextSibling()) {
7615 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
7616 return static_cast<DocumentType*>(child);
7619 return nullptr;
7622 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
7623 if (!mDOMImplementation) {
7624 nsCOMPtr<nsIURI> uri;
7625 NS_NewURI(getter_AddRefs(uri), "about:blank");
7626 if (!uri) {
7627 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
7628 return nullptr;
7630 bool hasHadScriptObject = true;
7631 nsIScriptGlobalObject* scriptObject =
7632 GetScriptHandlingObject(hasHadScriptObject);
7633 if (!scriptObject && hasHadScriptObject) {
7634 rv.Throw(NS_ERROR_UNEXPECTED);
7635 return nullptr;
7637 mDOMImplementation = new DOMImplementation(
7638 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
7641 return mDOMImplementation;
7644 bool IsLowercaseASCII(const nsAString& aValue) {
7645 int32_t len = aValue.Length();
7646 for (int32_t i = 0; i < len; ++i) {
7647 char16_t c = aValue[i];
7648 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
7649 return false;
7652 return true;
7655 // We only support pseudo-elements with two colons in this function.
7656 static PseudoStyleType GetPseudoElementType(const nsString& aString,
7657 ErrorResult& aRv) {
7658 MOZ_ASSERT(!aString.IsEmpty(),
7659 "GetPseudoElementType aString should be non-null");
7660 if (aString.Length() <= 2 || aString[0] != ':' || aString[1] != ':') {
7661 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
7662 return PseudoStyleType::NotPseudo;
7664 RefPtr<nsAtom> pseudo = NS_Atomize(Substring(aString, 1));
7665 return nsCSSPseudoElements::GetPseudoType(pseudo,
7666 CSSEnabledState::InUASheets);
7669 already_AddRefed<Element> Document::CreateElement(
7670 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
7671 ErrorResult& rv) {
7672 rv = nsContentUtils::CheckQName(aTagName, false);
7673 if (rv.Failed()) {
7674 return nullptr;
7677 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
7678 nsAutoString lcTagName;
7679 if (needsLowercase) {
7680 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
7683 const nsString* is = nullptr;
7684 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
7685 if (aOptions.IsElementCreationOptions()) {
7686 const ElementCreationOptions& options =
7687 aOptions.GetAsElementCreationOptions();
7689 if (options.mIs.WasPassed()) {
7690 is = &options.mIs.Value();
7693 // Check 'pseudo' and throw an exception if it's not one allowed
7694 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
7695 if (options.mPseudo.WasPassed()) {
7696 pseudoType = GetPseudoElementType(options.mPseudo.Value(), rv);
7697 if (rv.Failed() || pseudoType == PseudoStyleType::NotPseudo ||
7698 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(pseudoType)) {
7699 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
7700 return nullptr;
7705 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
7706 nullptr, mDefaultElementType, is);
7708 if (pseudoType != PseudoStyleType::NotPseudo) {
7709 elem->SetPseudoElementType(pseudoType);
7712 return elem.forget();
7715 already_AddRefed<Element> Document::CreateElementNS(
7716 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
7717 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
7718 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
7719 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
7720 mNodeInfoManager, ELEMENT_NODE,
7721 getter_AddRefs(nodeInfo));
7722 if (rv.Failed()) {
7723 return nullptr;
7726 const nsString* is = nullptr;
7727 if (aOptions.IsElementCreationOptions()) {
7728 const ElementCreationOptions& options =
7729 aOptions.GetAsElementCreationOptions();
7730 if (options.mIs.WasPassed()) {
7731 is = &options.mIs.Value();
7735 nsCOMPtr<Element> element;
7736 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
7737 NOT_FROM_PARSER, is);
7738 if (rv.Failed()) {
7739 return nullptr;
7742 return element.forget();
7745 already_AddRefed<Element> Document::CreateXULElement(
7746 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
7747 ErrorResult& aRv) {
7748 aRv = nsContentUtils::CheckQName(aTagName, false);
7749 if (aRv.Failed()) {
7750 return nullptr;
7753 const nsString* is = nullptr;
7754 if (aOptions.IsElementCreationOptions()) {
7755 const ElementCreationOptions& options =
7756 aOptions.GetAsElementCreationOptions();
7757 if (options.mIs.WasPassed()) {
7758 is = &options.mIs.Value();
7762 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
7763 if (!elem) {
7764 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
7765 return nullptr;
7767 return elem.forget();
7770 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
7771 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
7772 return text.forget();
7775 already_AddRefed<nsTextNode> Document::CreateTextNode(
7776 const nsAString& aData) const {
7777 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
7778 // Don't notify; this node is still being created.
7779 text->SetText(aData, false);
7780 return text.forget();
7783 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
7784 RefPtr<DocumentFragment> frag =
7785 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
7786 return frag.forget();
7789 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
7790 already_AddRefed<dom::Comment> Document::CreateComment(
7791 const nsAString& aData) const {
7792 RefPtr<dom::Comment> comment =
7793 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
7795 // Don't notify; this node is still being created.
7796 comment->SetText(aData, false);
7797 return comment.forget();
7800 already_AddRefed<CDATASection> Document::CreateCDATASection(
7801 const nsAString& aData, ErrorResult& rv) {
7802 if (IsHTMLDocument()) {
7803 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
7804 return nullptr;
7807 if (FindInReadable(u"]]>"_ns, aData)) {
7808 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
7809 return nullptr;
7812 RefPtr<CDATASection> cdata =
7813 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
7815 // Don't notify; this node is still being created.
7816 cdata->SetText(aData, false);
7818 return cdata.forget();
7821 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
7822 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
7823 nsresult res = nsContentUtils::CheckQName(aTarget, false);
7824 if (NS_FAILED(res)) {
7825 rv.Throw(res);
7826 return nullptr;
7829 if (FindInReadable(u"?>"_ns, aData)) {
7830 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
7831 return nullptr;
7834 RefPtr<ProcessingInstruction> pi =
7835 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
7837 return pi.forget();
7840 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
7841 ErrorResult& rv) {
7842 if (!mNodeInfoManager) {
7843 rv.Throw(NS_ERROR_NOT_INITIALIZED);
7844 return nullptr;
7847 nsresult res = nsContentUtils::CheckQName(aName, false);
7848 if (NS_FAILED(res)) {
7849 rv.Throw(res);
7850 return nullptr;
7853 nsAutoString name;
7854 if (IsHTMLDocument()) {
7855 nsContentUtils::ASCIIToLower(aName, name);
7856 } else {
7857 name = aName;
7860 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
7861 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
7862 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
7863 if (NS_FAILED(res)) {
7864 rv.Throw(res);
7865 return nullptr;
7868 RefPtr<Attr> attribute =
7869 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), EmptyString());
7870 return attribute.forget();
7873 already_AddRefed<Attr> Document::CreateAttributeNS(
7874 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
7875 ErrorResult& rv) {
7876 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
7877 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
7878 mNodeInfoManager, ATTRIBUTE_NODE,
7879 getter_AddRefs(nodeInfo));
7880 if (rv.Failed()) {
7881 return nullptr;
7884 RefPtr<Attr> attribute =
7885 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), EmptyString());
7886 return attribute.forget();
7889 void Document::ResolveScheduledSVGPresAttrs() {
7890 for (auto iter = mLazySVGPresElements.Iter(); !iter.Done(); iter.Next()) {
7891 SVGElement* svg = iter.Get()->GetKey();
7892 svg->UpdateContentDeclarationBlock();
7894 mLazySVGPresElements.Clear();
7897 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
7898 const {
7899 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
7901 const nsTArray<nsWeakPtr> blockedNodes = mBlockedNodesByClassifier.Clone();
7903 for (unsigned long i = 0; i < blockedNodes.Length(); i++) {
7904 nsWeakPtr weakNode = blockedNodes[i];
7905 nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode);
7906 // Consider only nodes to which we have managed to get strong references.
7907 // Coping with nullptrs since it's expected for nodes to disappear when
7908 // nobody else is referring to them.
7909 if (node) {
7910 list->AppendElement(node);
7914 return list.forget();
7917 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
7918 aSheetSet.Truncate();
7920 // Look through our sheets, find the selected set title
7921 size_t count = SheetCount();
7922 nsAutoString title;
7923 for (size_t index = 0; index < count; index++) {
7924 StyleSheet* sheet = SheetAt(index);
7925 NS_ASSERTION(sheet, "Null sheet in sheet list!");
7927 if (sheet->Disabled()) {
7928 // Disabled sheets don't affect the currently selected set
7929 continue;
7932 sheet->GetTitle(title);
7934 if (aSheetSet.IsEmpty()) {
7935 aSheetSet = title;
7936 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
7937 // Sheets from multiple sets enabled; return null string, per spec.
7938 SetDOMStringToNull(aSheetSet);
7939 return;
7944 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
7945 if (DOMStringIsNull(aSheetSet)) {
7946 return;
7949 // Must update mLastStyleSheetSet before doing anything else with stylesheets
7950 // or CSSLoaders.
7951 mLastStyleSheetSet = aSheetSet;
7952 EnableStyleSheetsForSetInternal(aSheetSet, true);
7955 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
7956 mPreferredStyleSheetSet = aSheetSet;
7957 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
7958 // spec.
7959 if (DOMStringIsNull(mLastStyleSheetSet)) {
7960 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
7961 // per spec. The idea here is that we're changing our preferred set and
7962 // that shouldn't change the value of lastStyleSheetSet. Also, we're
7963 // using the Internal version so we can update the CSSLoader and not have
7964 // to worry about null strings.
7965 EnableStyleSheetsForSetInternal(aSheetSet, true);
7969 DOMStringList* Document::StyleSheetSets() {
7970 if (!mStyleSheetSetList) {
7971 mStyleSheetSetList = new DOMStyleSheetSetList(this);
7973 return mStyleSheetSetList;
7976 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
7977 // Per spec, passing in null is a no-op.
7978 if (!DOMStringIsNull(aSheetSet)) {
7979 // Note: must make sure to not change the CSSLoader's preferred sheet --
7980 // that value should be equal to either our lastStyleSheetSet (if that's
7981 // non-null) or to our preferredStyleSheetSet. And this method doesn't
7982 // change either of those.
7983 EnableStyleSheetsForSetInternal(aSheetSet, false);
7987 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
7988 bool aUpdateCSSLoader) {
7989 size_t count = SheetCount();
7990 nsAutoString title;
7991 for (size_t index = 0; index < count; index++) {
7992 StyleSheet* sheet = SheetAt(index);
7993 NS_ASSERTION(sheet, "Null sheet in sheet list!");
7995 sheet->GetTitle(title);
7996 if (!title.IsEmpty()) {
7997 sheet->SetEnabled(title.Equals(aSheetSet));
8000 if (aUpdateCSSLoader) {
8001 CSSLoader()->DocumentStyleSheetSetChanged();
8003 if (mStyleSet->StyleSheetsHaveChanged()) {
8004 ApplicableStylesChanged();
8008 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
8009 nsAutoCString charset;
8010 GetDocumentCharacterSet()->Name(charset);
8011 CopyASCIItoUTF16(charset, aCharacterSet);
8014 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
8015 ErrorResult& rv) const {
8016 nsINode* imported = &aNode;
8018 switch (imported->NodeType()) {
8019 case DOCUMENT_NODE: {
8020 break;
8022 case DOCUMENT_FRAGMENT_NODE:
8023 case ATTRIBUTE_NODE:
8024 case ELEMENT_NODE:
8025 case PROCESSING_INSTRUCTION_NODE:
8026 case TEXT_NODE:
8027 case CDATA_SECTION_NODE:
8028 case COMMENT_NODE:
8029 case DOCUMENT_TYPE_NODE: {
8030 return imported->Clone(aDeep, mNodeInfoManager, rv);
8032 default: {
8033 NS_WARNING("Don't know how to clone this nodetype for importNode.");
8037 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8038 return nullptr;
8041 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
8042 return nsRange::Create(this, 0, this, 0, rv);
8045 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
8046 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
8047 ErrorResult& rv) const {
8048 RefPtr<NodeIterator> iterator =
8049 new NodeIterator(&aRoot, aWhatToShow, aFilter);
8050 return iterator.forget();
8053 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
8054 uint32_t aWhatToShow,
8055 NodeFilter* aFilter,
8056 ErrorResult& rv) const {
8057 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
8058 return walker.forget();
8061 already_AddRefed<Location> Document::GetLocation() const {
8062 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
8064 if (!w) {
8065 return nullptr;
8068 return do_AddRef(w->Location());
8071 already_AddRefed<nsIURI> Document::GetDomainURI() {
8072 nsIPrincipal* principal = NodePrincipal();
8074 nsCOMPtr<nsIURI> uri;
8075 principal->GetDomain(getter_AddRefs(uri));
8076 if (uri) {
8077 return uri.forget();
8079 auto* basePrin = BasePrincipal::Cast(principal);
8080 basePrin->GetURI(getter_AddRefs(uri));
8081 return uri.forget();
8084 void Document::GetDomain(nsAString& aDomain) {
8085 nsCOMPtr<nsIURI> uri = GetDomainURI();
8087 if (!uri) {
8088 aDomain.Truncate();
8089 return;
8092 nsAutoCString hostName;
8093 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
8094 if (NS_SUCCEEDED(rv)) {
8095 CopyUTF8toUTF16(hostName, aDomain);
8096 } else {
8097 // If we can't get the host from the URI (e.g. about:, javascript:,
8098 // etc), just return an empty string.
8099 aDomain.Truncate();
8103 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
8104 if (!GetBrowsingContext()) {
8105 // If our browsing context is null; disallow setting domain
8106 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8107 return;
8110 if (mSandboxFlags & SANDBOXED_DOMAIN) {
8111 // We're sandboxed; disallow setting domain
8112 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8113 return;
8116 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
8117 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8118 return;
8121 if (aDomain.IsEmpty()) {
8122 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8123 return;
8126 nsCOMPtr<nsIURI> uri = GetDomainURI();
8127 if (!uri) {
8128 rv.Throw(NS_ERROR_FAILURE);
8129 return;
8132 // Check new domain - must be a superdomain of the current host
8133 // For example, a page from foo.bar.com may set domain to bar.com,
8134 // but not to ar.com, baz.com, or fi.foo.bar.com.
8136 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
8137 if (!newURI) {
8138 // Error: illegal domain
8139 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8140 return;
8143 if (StaticPrefs::
8144 dom_postMessage_sharedArrayBuffer_withCOOP_COEP_AtStartup() &&
8145 GetBrowsingContext() && GetBrowsingContext()->CrossOriginIsolated()) {
8146 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
8147 return;
8150 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
8151 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
8154 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
8155 const nsACString& aHostString) {
8156 if (aHostString.IsEmpty()) {
8157 return nullptr;
8160 // Create new URI
8161 nsCOMPtr<nsIURI> uri = GetDomainURI();
8162 if (!uri) {
8163 return nullptr;
8166 nsresult rv;
8167 rv = NS_MutateURI(uri)
8168 .SetUserPass(EmptyCString())
8169 .SetPort(-1) // we want to reset the port number if needed.
8170 .SetHostPort(aHostString)
8171 .Finalize(uri);
8172 if (NS_FAILED(rv)) {
8173 return nullptr;
8176 return uri.forget();
8179 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
8180 const nsAString& aNewDomain, nsIURI* aOrigHost) {
8181 if (NS_WARN_IF(!aOrigHost)) {
8182 return nullptr;
8185 nsCOMPtr<nsIURI> newURI =
8186 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
8187 if (!newURI) {
8188 // Error: failed to parse input domain
8189 return nullptr;
8192 // Check new domain - must be a superdomain of the current host
8193 // For example, a page from foo.bar.com may set domain to bar.com,
8194 // but not to ar.com, baz.com, or fi.foo.bar.com.
8195 nsAutoCString current;
8196 nsAutoCString domain;
8197 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
8198 current.Truncate();
8200 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
8201 domain.Truncate();
8204 bool ok = current.Equals(domain);
8205 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
8206 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
8207 // We're golden if the new domain is the current page's base domain or a
8208 // subdomain of it.
8209 nsCOMPtr<nsIEffectiveTLDService> tldService =
8210 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
8211 if (!tldService) {
8212 return nullptr;
8215 nsAutoCString currentBaseDomain;
8216 ok = NS_SUCCEEDED(
8217 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
8218 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
8219 (domain.Length() >= currentBaseDomain.Length()),
8220 "uh-oh! slight optimization wasn't valid somehow!");
8221 ok = ok && domain.Length() >= currentBaseDomain.Length();
8224 if (!ok) {
8225 // Error: illegal domain
8226 return nullptr;
8229 return CreateInheritingURIForHost(domain);
8232 Element* Document::GetHtmlElement() const {
8233 Element* rootElement = GetRootElement();
8234 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
8235 return rootElement;
8236 return nullptr;
8239 Element* Document::GetHtmlChildElement(nsAtom* aTag) {
8240 Element* html = GetHtmlElement();
8241 if (!html) return nullptr;
8243 // Look for the element with aTag inside html. This needs to run
8244 // forwards to find the first such element.
8245 for (nsIContent* child = html->GetFirstChild(); child;
8246 child = child->GetNextSibling()) {
8247 if (child->IsHTMLElement(aTag)) return child->AsElement();
8249 return nullptr;
8252 nsGenericHTMLElement* Document::GetBody() {
8253 Element* html = GetHtmlElement();
8254 if (!html) {
8255 return nullptr;
8258 for (nsIContent* child = html->GetFirstChild(); child;
8259 child = child->GetNextSibling()) {
8260 if (child->IsHTMLElement(nsGkAtoms::body) ||
8261 child->IsHTMLElement(nsGkAtoms::frameset)) {
8262 return static_cast<nsGenericHTMLElement*>(child);
8266 return nullptr;
8269 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
8270 nsCOMPtr<Element> root = GetRootElement();
8272 // The body element must be either a body tag or a frameset tag. And we must
8273 // have a root element to be able to add kids to it.
8274 if (!newBody ||
8275 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset) ||
8276 !root) {
8277 rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
8278 return;
8281 // Use DOM methods so that we pass through the appropriate security checks.
8282 nsCOMPtr<Element> currentBody = GetBody();
8283 if (currentBody) {
8284 root->ReplaceChild(*newBody, *currentBody, rv);
8285 } else {
8286 root->AppendChild(*newBody, rv);
8290 HTMLSharedElement* Document::GetHead() {
8291 return static_cast<HTMLSharedElement*>(GetHeadElement());
8294 Element* Document::GetTitleElement() {
8295 // mMayHaveTitleElement will have been set to true if any HTML or SVG
8296 // <title> element has been bound to this document. So if it's false,
8297 // we know there is nothing to do here. This avoids us having to search
8298 // the whole DOM if someone calls document.title on a large document
8299 // without a title.
8300 if (!mMayHaveTitleElement) return nullptr;
8302 Element* root = GetRootElement();
8303 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
8304 // In SVG, the document's title must be a child
8305 for (nsIContent* child = root->GetFirstChild(); child;
8306 child = child->GetNextSibling()) {
8307 if (child->IsSVGElement(nsGkAtoms::title)) {
8308 return child->AsElement();
8311 return nullptr;
8314 // We check the HTML namespace even for non-HTML documents, except SVG. This
8315 // matches the spec and the behavior of all tested browsers.
8316 // We avoid creating a live nsContentList since we don't need to watch for DOM
8317 // tree mutations.
8318 RefPtr<nsContentList> list = new nsContentList(
8319 this, kNameSpaceID_XHTML, nsGkAtoms::title, nsGkAtoms::title,
8320 /* aDeep = */ true,
8321 /* aLiveList = */ false);
8323 nsIContent* first = list->Item(0, false);
8325 return first ? first->AsElement() : nullptr;
8328 void Document::GetTitle(nsAString& aTitle) {
8329 aTitle.Truncate();
8331 Element* rootElement = GetRootElement();
8332 if (!rootElement) {
8333 return;
8336 nsAutoString tmp;
8338 #ifdef MOZ_XUL
8339 if (rootElement->IsXULElement()) {
8340 rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, tmp);
8341 } else
8342 #endif
8344 Element* title = GetTitleElement();
8345 if (!title) {
8346 return;
8348 nsContentUtils::GetNodeTextContent(title, false, tmp);
8351 tmp.CompressWhitespace();
8352 aTitle = tmp;
8355 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
8356 Element* rootElement = GetRootElement();
8357 if (!rootElement) {
8358 return;
8361 #ifdef MOZ_XUL
8362 if (rootElement->IsXULElement()) {
8363 aRv =
8364 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
8365 return;
8367 #endif
8369 Maybe<mozAutoDocUpdate> updateBatch;
8370 nsCOMPtr<Element> title = GetTitleElement();
8371 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
8372 if (!title) {
8373 // Batch updates so that mutation events don't change "the title
8374 // element" under us
8375 updateBatch.emplace(this, true);
8376 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
8377 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
8378 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
8379 NOT_FROM_PARSER);
8380 if (!title) {
8381 return;
8383 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true);
8385 } else if (rootElement->IsHTMLElement()) {
8386 if (!title) {
8387 // Batch updates so that mutation events don't change "the title
8388 // element" under us
8389 updateBatch.emplace(this, true);
8390 Element* head = GetHeadElement();
8391 if (!head) {
8392 return;
8395 RefPtr<mozilla::dom::NodeInfo> titleInfo;
8396 titleInfo = mNodeInfoManager->GetNodeInfo(
8397 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
8398 title = NS_NewHTMLTitleElement(titleInfo.forget());
8399 if (!title) {
8400 return;
8403 head->AppendChildTo(title, true);
8405 } else {
8406 return;
8409 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
8412 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
8413 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
8414 "Setting a title while unlinking or destroying the element?");
8415 if (mInUnlinkOrDeletion) {
8416 return;
8419 if (aBoundTitleElement) {
8420 mMayHaveTitleElement = true;
8422 if (mPendingTitleChangeEvent.IsPending()) return;
8424 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8425 RefPtr<nsRunnableMethod<Document, void, false>> event =
8426 NewNonOwningRunnableMethod("Document::DoNotifyPossibleTitleChange", this,
8427 &Document::DoNotifyPossibleTitleChange);
8428 nsresult rv = Dispatch(TaskCategory::Other, do_AddRef(event));
8429 if (NS_SUCCEEDED(rv)) {
8430 mPendingTitleChangeEvent = std::move(event);
8434 void Document::DoNotifyPossibleTitleChange() {
8435 mPendingTitleChangeEvent.Forget();
8436 mHaveFiredTitleChange = true;
8438 nsAutoString title;
8439 GetTitle(title);
8441 RefPtr<PresShell> presShell = GetPresShell();
8442 if (presShell) {
8443 nsCOMPtr<nsISupports> container =
8444 presShell->GetPresContext()->GetContainerWeak();
8445 if (container) {
8446 nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container);
8447 if (docShellWin) {
8448 docShellWin->SetTitle(title);
8453 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8454 if (WindowGlobalChild* child = inner->GetWindowGlobalChild()) {
8455 child->SendUpdateDocumentTitle(title);
8459 // Fire a DOM event for the title change.
8460 nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
8461 u"DOMTitleChanged"_ns, CanBubble::eYes,
8462 Cancelable::eYes);
8465 already_AddRefed<MediaQueryList> Document::MatchMedia(
8466 const nsAString& aMediaQueryList, CallerType aCallerType) {
8467 RefPtr<MediaQueryList> result =
8468 new MediaQueryList(this, aMediaQueryList, aCallerType);
8470 mDOMMediaQueryLists.insertBack(result);
8472 return result.forget();
8475 void Document::SetMayStartLayout(bool aMayStartLayout) {
8476 mMayStartLayout = aMayStartLayout;
8477 if (MayStartLayout()) {
8478 // Before starting layout, check whether we're a toplevel chrome
8479 // window. If we are, setup some state so that we don't have to restyle
8480 // the whole tree after StartLayout.
8481 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
8482 // We're the chrome document!
8483 win->BeforeStartLayout();
8485 ReadyState state = GetReadyStateEnum();
8486 if (state >= READYSTATE_INTERACTIVE) {
8487 // DOMContentLoaded has fired already.
8488 MaybeResolveReadyForIdle();
8492 MaybeEditingStateChanged();
8495 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
8496 mInitializableFrameLoaders.RemoveElement(aLoader);
8497 // Don't even try to initialize.
8498 if (mInDestructor) {
8499 NS_WARNING(
8500 "Trying to initialize a frame loader while"
8501 "document is being deleted");
8502 return NS_ERROR_FAILURE;
8505 mInitializableFrameLoaders.AppendElement(aLoader);
8506 if (!mFrameLoaderRunner) {
8507 mFrameLoaderRunner =
8508 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
8509 &Document::MaybeInitializeFinalizeFrameLoaders);
8510 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
8511 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
8513 return NS_OK;
8516 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
8517 nsIRunnable* aFinalizer) {
8518 mInitializableFrameLoaders.RemoveElement(aLoader);
8519 if (mInDestructor) {
8520 return NS_ERROR_FAILURE;
8523 mFrameLoaderFinalizers.AppendElement(aFinalizer);
8524 if (!mFrameLoaderRunner) {
8525 mFrameLoaderRunner =
8526 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
8527 &Document::MaybeInitializeFinalizeFrameLoaders);
8528 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
8529 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
8531 return NS_OK;
8534 void Document::MaybeInitializeFinalizeFrameLoaders() {
8535 if (mDelayFrameLoaderInitialization) {
8536 // This method will be recalled when !mDelayFrameLoaderInitialization.
8537 mFrameLoaderRunner = nullptr;
8538 return;
8541 // We're not in an update, but it is not safe to run scripts, so
8542 // postpone frameloader initialization and finalization.
8543 if (!nsContentUtils::IsSafeToRunScript()) {
8544 if (!mInDestructor && !mFrameLoaderRunner &&
8545 (mInitializableFrameLoaders.Length() ||
8546 mFrameLoaderFinalizers.Length())) {
8547 mFrameLoaderRunner = NewRunnableMethod(
8548 "Document::MaybeInitializeFinalizeFrameLoaders", this,
8549 &Document::MaybeInitializeFinalizeFrameLoaders);
8550 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
8552 return;
8554 mFrameLoaderRunner = nullptr;
8556 // Don't use a temporary array for mInitializableFrameLoaders, because
8557 // loading a frame may cause some other frameloader to be removed from the
8558 // array. But be careful to keep the loader alive when starting the load!
8559 while (mInitializableFrameLoaders.Length()) {
8560 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
8561 mInitializableFrameLoaders.RemoveElementAt(0);
8562 NS_ASSERTION(loader, "null frameloader in the array?");
8563 loader->ReallyStartLoading();
8566 uint32_t length = mFrameLoaderFinalizers.Length();
8567 if (length > 0) {
8568 nsTArray<nsCOMPtr<nsIRunnable>> finalizers;
8569 mFrameLoaderFinalizers.SwapElements(finalizers);
8570 for (uint32_t i = 0; i < length; ++i) {
8571 finalizers[i]->Run();
8576 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
8577 uint32_t length = mInitializableFrameLoaders.Length();
8578 for (uint32_t i = 0; i < length; ++i) {
8579 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
8580 mInitializableFrameLoaders.RemoveElementAt(i);
8581 return;
8586 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
8587 mPrototypeDocument = aPrototype;
8588 mSynchronousDOMContentLoaded = true;
8591 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
8592 return GetPermissionDelegateHandler();
8595 Document* Document::RequestExternalResource(
8596 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
8597 ExternalResourceLoad** aPendingLoad) {
8598 MOZ_ASSERT(aURI, "Must have a URI");
8599 MOZ_ASSERT(aRequestingNode, "Must have a node");
8600 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
8601 if (mDisplayDocument) {
8602 return mDisplayDocument->RequestExternalResource(
8603 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
8606 return mExternalResourceMap.RequestResource(
8607 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
8610 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
8611 mExternalResourceMap.EnumerateResources(aCallback);
8614 SMILAnimationController* Document::GetAnimationController() {
8615 // We create the animation controller lazily because most documents won't want
8616 // one and only SVG documents and the like will call this
8617 if (mAnimationController) return mAnimationController;
8618 // Refuse to create an Animation Controller for data documents.
8619 if (mLoadedAsData) return nullptr;
8621 mAnimationController = new SMILAnimationController(this);
8623 // If there's a presContext then check the animation mode and pause if
8624 // necessary.
8625 nsPresContext* context = GetPresContext();
8626 if (mAnimationController && context &&
8627 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
8628 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
8631 // If we're hidden (or being hidden), notify the newly-created animation
8632 // controller. (Skip this check for SVG-as-an-image documents, though,
8633 // because they don't get OnPageShow / OnPageHide calls).
8634 if (!mIsShowing && !mIsBeingUsedAsImage) {
8635 mAnimationController->OnPageHide();
8638 return mAnimationController;
8641 PendingAnimationTracker* Document::GetOrCreatePendingAnimationTracker() {
8642 if (!mPendingAnimationTracker) {
8643 mPendingAnimationTracker = new PendingAnimationTracker(this);
8646 return mPendingAnimationTracker;
8650 * Retrieve the "direction" property of the document.
8652 * @lina 01/09/2001
8654 void Document::GetDir(nsAString& aDirection) const {
8655 aDirection.Truncate();
8656 Element* rootElement = GetHtmlElement();
8657 if (rootElement) {
8658 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
8663 * Set the "direction" property of the document.
8665 * @lina 01/09/2001
8667 void Document::SetDir(const nsAString& aDirection) {
8668 Element* rootElement = GetHtmlElement();
8669 if (rootElement) {
8670 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
8674 nsIHTMLCollection* Document::Images() {
8675 if (!mImages) {
8676 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
8677 nsGkAtoms::img);
8679 return mImages;
8682 nsIHTMLCollection* Document::Embeds() {
8683 if (!mEmbeds) {
8684 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
8685 nsGkAtoms::embed);
8687 return mEmbeds;
8690 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
8691 void* aData) {
8692 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
8693 aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href);
8696 nsIHTMLCollection* Document::Links() {
8697 if (!mLinks) {
8698 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
8700 return mLinks;
8703 nsIHTMLCollection* Document::Forms() {
8704 if (!mForms) {
8705 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
8706 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
8707 nsGkAtoms::form);
8710 return mForms;
8713 nsIHTMLCollection* Document::Scripts() {
8714 if (!mScripts) {
8715 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
8716 nsGkAtoms::script);
8718 return mScripts;
8721 nsIHTMLCollection* Document::Applets() {
8722 if (!mApplets) {
8723 mApplets = new nsEmptyContentList(this);
8725 return mApplets;
8728 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
8729 void* aData) {
8730 return aElement->IsHTMLElement(nsGkAtoms::a) &&
8731 aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
8734 nsIHTMLCollection* Document::Anchors() {
8735 if (!mAnchors) {
8736 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
8738 return mAnchors;
8741 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
8742 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
8743 ErrorResult& rv) {
8744 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
8745 "XOW should have caught this!");
8747 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
8748 if (!window) {
8749 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
8750 return nullptr;
8752 nsCOMPtr<nsPIDOMWindowOuter> outer =
8753 nsPIDOMWindowOuter::GetFromCurrentInner(window);
8754 if (!outer) {
8755 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8756 return nullptr;
8758 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
8759 RefPtr<BrowsingContext> newBC;
8760 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
8761 if (!newBC) {
8762 return nullptr;
8764 return WindowProxyHolder(std::move(newBC));
8767 Document* Document::Open(const Optional<nsAString>& /* unused */,
8768 const Optional<nsAString>& /* unused */,
8769 ErrorResult& aError) {
8770 // Implements
8771 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
8773 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
8774 "XOW should have caught this!");
8776 // Step 1 -- throw if we're an XML document.
8777 if (!IsHTMLDocument() || mDisableDocWrite) {
8778 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
8779 return nullptr;
8782 // Step 2 -- throw if dynamic markup insertion should throw.
8783 if (ShouldThrowOnDynamicMarkupInsertion()) {
8784 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
8785 return nullptr;
8788 // Step 3 -- get the entry document, so we can use it for security checks.
8789 nsCOMPtr<Document> callerDoc = GetEntryDocument();
8790 if (!callerDoc) {
8791 // If we're called from C++ or in some other way without an originating
8792 // document we can't do a document.open w/o changing the principal of the
8793 // document to something like about:blank (as that's the only sane thing to
8794 // do when we don't know the origin of this call), and since we can't
8795 // change the principals of a document for security reasons we'll have to
8796 // refuse to go ahead with this call.
8798 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
8799 return nullptr;
8802 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
8803 // the entry document.
8804 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
8805 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
8806 return nullptr;
8809 // Step 5 -- if we have an active parser with a nonzero script nesting level,
8810 // just no-op.
8811 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
8812 return this;
8815 // Step 6 -- check for open() during unload. Per spec, this is just a check
8816 // of the ignore-opens-during-unload counter, but our unload event code
8817 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
8818 // do), so we check for unload directly.
8819 if (ShouldIgnoreOpens()) {
8820 return this;
8823 nsCOMPtr<nsIDocShell> shell(mDocumentContainer);
8824 if (shell) {
8825 bool inUnload;
8826 shell->GetIsInUnload(&inUnload);
8827 if (inUnload) {
8828 return this;
8832 // document.open() inherits the CSP from the opening document.
8833 // Please create an actual copy of the CSP (do not share the same
8834 // reference) otherwise appending a new policy within the opened
8835 // document will be incorrectly propagated to the opening doc.
8836 nsCOMPtr<nsIContentSecurityPolicy> csp = callerDoc->GetCsp();
8837 if (csp) {
8838 RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
8839 cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
8840 mCSP = cspToInherit;
8843 // At this point we know this is a valid-enough document.open() call
8844 // and not a no-op. Increment our use counter.
8845 SetUseCounter(eUseCounter_custom_DocumentOpen);
8847 // Step 7 -- stop existing navigation of our browsing context (and all other
8848 // loads it's doing) if we're the active document of our browsing context.
8849 // Note that we do not want to stop anything if there is no existing
8850 // navigation.
8851 if (shell && IsCurrentActiveDocument() &&
8852 shell->GetIsAttemptingToNavigate()) {
8853 nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(shell));
8854 webnav->Stop(nsIWebNavigation::STOP_NETWORK);
8856 // The Stop call may have cancelled the onload blocker request or
8857 // prevented it from getting added, so we need to make sure it gets added
8858 // to the document again otherwise the document could have a non-zero
8859 // onload block count without the onload blocker request being in the
8860 // loadgroup.
8861 EnsureOnloadBlocker();
8864 // Step 8 -- clear event listeners out of our DOM tree
8865 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
8866 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
8867 elm->RemoveAllListeners();
8871 // Step 9 -- clear event listeners from our window, if we have one.
8873 // Note that we explicitly want the inner window, and only if we're its
8874 // document. We want to do this (per spec) even when we're not the "active
8875 // document", so we can't go through GetWindow(), because it might forward to
8876 // the wrong inner.
8877 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
8878 if (win->GetExtantDoc() == this) {
8879 if (EventListenerManager* elm =
8880 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
8881 elm->RemoveAllListeners();
8886 // If we have a parser that has a zero script nesting level, we need to
8887 // properly terminate it. We do that after we've removed all the event
8888 // listeners (so termination won't trigger event listeners if it does
8889 // something to the DOM), but before we remove all elements from the document
8890 // (so if termination does modify the DOM in some way we will just blow it
8891 // away immediately. See the similar code in WriteCommon that handles the
8892 // !IsInsertionPointDefined() case and should stay in sync with this code.
8893 if (mParser) {
8894 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
8895 "Why didn't we take the early return?");
8896 // Make sure we don't re-enter.
8897 IgnoreOpensDuringUnload ignoreOpenGuard(this);
8898 mParser->Terminate();
8899 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
8902 // Step 10 -- remove all our DOM kids without firing any mutation events.
8904 // We want to ignore any recursive calls to Open() that happen while
8905 // disconnecting the node tree. The spec doesn't say to do this, but the
8906 // spec also doesn't envision unload events on subframes firing while we do
8907 // this, while all browsers fire them in practice. See
8908 // <https://github.com/whatwg/html/issues/4611>.
8909 IgnoreOpensDuringUnload ignoreOpenGuard(this);
8910 DisconnectNodeTree();
8913 // Step 11 -- if we're the current document in our docshell, do the
8914 // equivalent of pushState() with the new URL we should have.
8915 if (shell && IsCurrentActiveDocument()) {
8916 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
8917 if (callerDoc != this) {
8918 nsCOMPtr<nsIURI> noFragmentURI;
8919 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
8920 if (NS_WARN_IF(NS_FAILED(rv))) {
8921 aError.Throw(rv);
8922 return nullptr;
8924 newURI = std::move(noFragmentURI);
8927 // UpdateURLAndHistory might do various member-setting, so make sure we're
8928 // holding strong refs to all the refcounted args on the stack. We can
8929 // assume that our caller is holding on to "this" already.
8930 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
8931 bool equalURIs;
8932 nsresult rv = currentURI->Equals(newURI, &equalURIs);
8933 if (NS_WARN_IF(NS_FAILED(rv))) {
8934 aError.Throw(rv);
8935 return nullptr;
8937 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
8938 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, EmptyString(),
8939 /* aReplace = */ true, currentURI,
8940 equalURIs);
8941 if (NS_WARN_IF(NS_FAILED(rv))) {
8942 aError.Throw(rv);
8943 return nullptr;
8946 // And use the security info of the caller document as well, since
8947 // it's the thing providing our data.
8948 mSecurityInfo = callerDoc->GetSecurityInfo();
8950 // This is not mentioned in the spec, but I think that's a spec bug. See
8951 // <https://github.com/whatwg/html/issues/4299>. In any case, since our
8952 // URL may be changing away from about:blank here, we really want to unset
8953 // this flag no matter what, since only about:blank can be an initial
8954 // document.
8955 SetIsInitialDocument(false);
8957 // And let our docloader know that it will need to track our load event.
8958 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
8961 // Per spec nothing happens with our URI in other cases, though note
8962 // <https://github.com/whatwg/html/issues/4286>.
8964 // Note that we don't need to do anything here with base URIs per spec.
8965 // That said, this might be assuming that we implement
8966 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
8967 // correctly, which we don't right now for the about:blank case.
8969 // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
8970 mSkipLoadEventAfterClose = mLoadEventFiring;
8972 // Preliminary to steps 13-16. Set our ready state to uninitialized before
8973 // we do anything else, so we can then proceed to later ready state levels.
8974 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
8975 /* updateTimingInformation = */ false);
8977 // Step 13 -- set our compat mode to standards.
8978 SetCompatibilityMode(eCompatibility_FullStandards);
8980 // Step 14 -- create a new parser associated with document. This also does
8981 // step 16 implicitly.
8982 mParserAborted = false;
8983 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
8984 mParser = parser;
8985 parser->Initialize(this, GetDocumentURI(), shell, nullptr);
8986 nsresult rv = parser->StartExecutor();
8987 if (NS_WARN_IF(NS_FAILED(rv))) {
8988 aError.Throw(rv);
8989 return nullptr;
8992 // Clear out our form control state, because the state of controls
8993 // in the pre-open() document should not affect the state of
8994 // controls that are now going to be written.
8995 mLayoutHistoryState = nullptr;
8997 if (shell) {
8998 // Prepare the docshell and the document viewer for the impending
8999 // out-of-band document.write()
9000 shell->PrepareForNewContentModel();
9002 nsCOMPtr<nsIContentViewer> cv;
9003 shell->GetContentViewer(getter_AddRefs(cv));
9004 if (cv) {
9005 cv->LoadStart(this);
9009 // Step 15.
9010 SetReadyStateInternal(Document::READYSTATE_LOADING,
9011 /* updateTimingInformation = */ false);
9013 // Step 16 happened with step 14 above.
9015 // Step 17.
9016 return this;
9019 void Document::Close(ErrorResult& rv) {
9020 if (!IsHTMLDocument()) {
9021 // No calling document.close() on XHTML!
9023 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9024 return;
9027 if (ShouldThrowOnDynamicMarkupInsertion()) {
9028 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9029 return;
9032 if (!mParser || !mParser->IsScriptCreated()) {
9033 return;
9036 ++mWriteLevel;
9037 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
9038 ->Parse(EmptyString(), nullptr, true);
9039 --mWriteLevel;
9042 void Document::WriteCommon(const Sequence<nsString>& aText,
9043 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
9044 // Fast path the common case
9045 if (aText.Length() == 1) {
9046 WriteCommon(aText[0], aNewlineTerminate, rv);
9047 } else {
9048 // XXXbz it would be nice if we could pass all the strings to the parser
9049 // without having to do all this copying and then ask it to start
9050 // parsing....
9051 nsString text;
9052 for (size_t i = 0; i < aText.Length(); ++i) {
9053 text.Append(aText[i]);
9055 WriteCommon(text, aNewlineTerminate, rv);
9059 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
9060 ErrorResult& aRv) {
9061 mTooDeepWriteRecursion =
9062 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
9063 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
9064 aRv.Throw(NS_ERROR_UNEXPECTED);
9065 return;
9068 if (!IsHTMLDocument() || mDisableDocWrite) {
9069 // No calling document.write*() on XHTML!
9071 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9072 return;
9075 if (ShouldThrowOnDynamicMarkupInsertion()) {
9076 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9077 return;
9080 if (mParserAborted) {
9081 // Hixie says aborting the parser doesn't undefine the insertion point.
9082 // However, since we null out mParser in that case, we track the
9083 // theoretically defined insertion point using mParserAborted.
9084 return;
9087 // Implement Step 4.1 of:
9088 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
9089 if (ShouldIgnoreOpens()) {
9090 return;
9093 void* key = GenerateParserKey();
9094 if (mParser && !mParser->IsInsertionPointDefined()) {
9095 if (mIgnoreDestructiveWritesCounter) {
9096 // Instead of implying a call to document.open(), ignore the call.
9097 nsContentUtils::ReportToConsole(
9098 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9099 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9100 return;
9102 // The spec doesn't tell us to ignore opens from here, but we need to
9103 // ensure opens are ignored here. See similar code in Open() that handles
9104 // the case of an existing parser which is not currently running script and
9105 // should stay in sync with this code.
9106 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9107 mParser->Terminate();
9108 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9111 if (!mParser) {
9112 if (mIgnoreDestructiveWritesCounter) {
9113 // Instead of implying a call to document.open(), ignore the call.
9114 nsContentUtils::ReportToConsole(
9115 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9116 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9117 return;
9120 Open({}, {}, aRv);
9122 // If Open() fails, or if it didn't create a parser (as it won't
9123 // if the user chose to not discard the current document through
9124 // onbeforeunload), don't write anything.
9125 if (aRv.Failed() || !mParser) {
9126 return;
9130 static constexpr auto new_line = u"\n"_ns;
9132 ++mWriteLevel;
9134 // This could be done with less code, but for performance reasons it
9135 // makes sense to have the code for two separate Parse() calls here
9136 // since the concatenation of strings costs more than we like. And
9137 // why pay that price when we don't need to?
9138 if (aNewlineTerminate) {
9139 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
9140 ->Parse(aText + new_line, key, false);
9141 } else {
9142 aRv =
9143 (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
9146 --mWriteLevel;
9148 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
9151 void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
9152 WriteCommon(aText, false, rv);
9155 void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
9156 WriteCommon(aText, true, rv);
9159 void* Document::GenerateParserKey(void) {
9160 if (!mScriptLoader) {
9161 // If we don't have a script loader, then the parser probably isn't parsing
9162 // anything anyway, so just return null.
9163 return nullptr;
9166 // The script loader provides us with the currently executing script element,
9167 // which is guaranteed to be unique per script.
9168 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
9169 if (script && mParser && mParser->IsScriptCreated()) {
9170 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
9171 if (creatorParser != mParser) {
9172 // Make scripts that aren't inserted by the active parser of this document
9173 // participate in the context of the script that document.open()ed
9174 // this document.
9175 return nullptr;
9178 return script;
9181 /* static */
9182 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
9183 nsAtom* aAtom, void* aData) {
9184 MOZ_ASSERT(aElement, "Must have element to work with!");
9186 if (!aElement->HasName()) {
9187 return false;
9190 nsString* elementName = static_cast<nsString*>(aData);
9191 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
9192 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
9193 eCaseMatters);
9196 /* static */
9197 void* Document::UseExistingNameString(nsINode* aRootNode,
9198 const nsString* aName) {
9199 return const_cast<nsString*>(aName);
9202 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
9203 if (mDocumentURI) {
9204 nsAutoCString uri;
9205 nsresult rv = mDocumentURI->GetSpec(uri);
9206 NS_ENSURE_SUCCESS(rv, rv);
9208 CopyUTF8toUTF16(uri, aDocumentURI);
9209 } else {
9210 aDocumentURI.Truncate();
9213 return NS_OK;
9216 // Alias of above
9217 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
9219 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
9220 CallerType aCallerType,
9221 ErrorResult& aRv) const {
9222 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
9223 aRv = GetDocumentURI(aDocumentURI);
9224 return;
9227 nsAutoCString uri;
9228 nsresult res = mChromeXHRDocURI->GetSpec(uri);
9229 if (NS_FAILED(res)) {
9230 aRv.Throw(res);
9231 return;
9233 CopyUTF8toUTF16(uri, aDocumentURI);
9236 nsIURI* Document::GetDocumentURIObject() const {
9237 if (!mChromeXHRDocURI) {
9238 return GetDocumentURI();
9241 return mChromeXHRDocURI;
9244 void Document::GetCompatMode(nsString& aCompatMode) const {
9245 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
9246 mCompatMode == eCompatibility_AlmostStandards ||
9247 mCompatMode == eCompatibility_FullStandards,
9248 "mCompatMode is neither quirks nor strict for this document");
9250 if (mCompatMode == eCompatibility_NavQuirks) {
9251 aCompatMode.AssignLiteral("BackCompat");
9252 } else {
9253 aCompatMode.AssignLiteral("CSS1Compat");
9257 } // namespace dom
9258 } // namespace mozilla
9260 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
9261 if (Element* element = Element::FromNode(aNode)) {
9262 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
9263 while (true) {
9264 RefPtr<Attr> attr;
9266 // Use an iterator to get an arbitrary attribute from the
9267 // cache. The iterator must be destroyed before any other
9268 // operations on mAttributeCache, to avoid hash table
9269 // assertions.
9270 auto iter = map->mAttributeCache.ConstIter();
9271 if (iter.Done()) {
9272 break;
9274 attr = iter.UserData();
9277 BlastSubtreeToPieces(attr);
9279 mozilla::DebugOnly<nsresult> rv =
9280 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
9281 attr->NodeInfo()->NameAtom(), false);
9283 // XXX Should we abort here?
9284 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
9288 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
9289 BlastSubtreeToPieces(shadow);
9290 element->UnattachShadow();
9294 while (aNode->HasChildren()) {
9295 nsIContent* node = aNode->GetFirstChild();
9296 BlastSubtreeToPieces(node);
9297 aNode->RemoveChildNode(node, false);
9301 namespace mozilla {
9302 namespace dom {
9304 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) {
9305 nsINode* adoptedNode = &aAdoptedNode;
9307 // Scope firing mutation events so that we don't carry any state that
9308 // might be stale
9310 nsINode* parent = adoptedNode->GetParentNode();
9311 if (parent) {
9312 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
9316 nsAutoScriptBlocker scriptBlocker;
9318 switch (adoptedNode->NodeType()) {
9319 case ATTRIBUTE_NODE: {
9320 // Remove from ownerElement.
9321 RefPtr<Attr> adoptedAttr = static_cast<Attr*>(adoptedNode);
9323 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement(rv);
9324 if (rv.Failed()) {
9325 return nullptr;
9328 if (ownerElement) {
9329 RefPtr<Attr> newAttr =
9330 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
9331 if (rv.Failed()) {
9332 return nullptr;
9335 newAttr.swap(adoptedAttr);
9338 break;
9340 case DOCUMENT_FRAGMENT_NODE: {
9341 if (adoptedNode->IsShadowRoot()) {
9342 rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
9343 return nullptr;
9345 [[fallthrough]];
9347 case ELEMENT_NODE:
9348 case PROCESSING_INSTRUCTION_NODE:
9349 case TEXT_NODE:
9350 case CDATA_SECTION_NODE:
9351 case COMMENT_NODE:
9352 case DOCUMENT_TYPE_NODE: {
9353 // Don't allow adopting a node's anonymous subtree out from under it.
9354 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
9355 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
9356 return nullptr;
9359 // We don't want to adopt an element into its own contentDocument or into
9360 // a descendant contentDocument, so we check if the frameElement of this
9361 // document or any of its parents is the adopted node or one of its
9362 // descendants.
9363 Document* doc = this;
9364 do {
9365 if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
9366 nsCOMPtr<nsINode> node = win->GetFrameElementInternal();
9367 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
9368 rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
9369 return nullptr;
9372 } while ((doc = doc->GetInProcessParentDocument()));
9374 // Remove from parent.
9375 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
9376 if (parent) {
9377 parent->RemoveChildNode(adoptedNode->AsContent(), true);
9378 } else {
9379 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
9382 break;
9384 case DOCUMENT_NODE: {
9385 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
9386 return nullptr;
9388 default: {
9389 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
9391 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
9392 return nullptr;
9396 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
9397 bool sameDocument = oldDocument == this;
9399 AutoJSContext cx;
9400 JS::Rooted<JSObject*> newScope(cx, nullptr);
9401 if (!sameDocument) {
9402 newScope = GetWrapper();
9403 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
9404 // Make sure cx is in a semi-sane compartment before we call WrapNative.
9405 // It's kind of irrelevant, given that we're passing aAllowWrapping =
9406 // false, and documents should always insist on being wrapped in an
9407 // canonical scope. But we try to pass something sane anyway.
9408 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
9409 JSAutoRealm ar(cx, globalObject);
9410 JS::Rooted<JS::Value> v(cx);
9411 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
9412 /* aAllowWrapping = */ false);
9413 if (rv.Failed()) return nullptr;
9414 newScope = &v.toObject();
9418 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
9419 if (rv.Failed()) {
9420 // Disconnect all nodes from their parents, since some have the old document
9421 // as their ownerDocument and some have this as their ownerDocument.
9422 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
9423 return nullptr;
9426 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
9427 "Should still be in the document we just got adopted into");
9429 return adoptedNode;
9432 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
9434 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
9435 const nsString& aScaleString) {
9436 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
9437 if (aScaleString.EqualsLiteral("device-width") ||
9438 aScaleString.EqualsLiteral("device-height")) {
9439 return Some(LayoutDeviceToScreenScale(10.0f));
9440 } else if (aScaleString.EqualsLiteral("yes")) {
9441 return Some(LayoutDeviceToScreenScale(1.0f));
9442 } else if (aScaleString.EqualsLiteral("no")) {
9443 return Some(LayoutDeviceToScreenScale(kViewportMinScale));
9444 } else if (aScaleString.IsEmpty()) {
9445 return Nothing();
9448 nsresult scaleErrorCode;
9449 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
9450 if (NS_FAILED(scaleErrorCode)) {
9451 return Some(LayoutDeviceToScreenScale(kViewportMinScale));
9454 if (scale < 0) {
9455 return Nothing();
9457 return Some(clamped(LayoutDeviceToScreenScale(scale), kViewportMinScale,
9458 kViewportMaxScale));
9461 bool Document::ParseScalesInViewportMetaData(
9462 const ViewportMetaData& aViewportMetaData) {
9463 Maybe<LayoutDeviceToScreenScale> scale;
9465 scale = ParseScaleString(aViewportMetaData.mInitialScale);
9466 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
9467 mValidScaleFloat = scale.isSome();
9469 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
9470 // Chrome uses '5' for the fallback value of maximum-scale, we might
9471 // consider matching it in future.
9472 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
9473 mScaleMaxFloat = scale.valueOr(kViewportMaxScale);
9474 mValidMaxScale = scale.isSome();
9476 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
9477 mScaleMinFloat = scale.valueOr(kViewportMinScale);
9478 mValidMinScale = scale.isSome();
9480 // Resolve min-zoom and max-zoom values.
9481 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
9482 if (mValidMaxScale && mValidMinScale) {
9483 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
9485 return mValidScaleFloat || mValidMaxScale || mValidMinScale;
9488 bool Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
9489 const nsAString& aHeightString,
9490 bool aHasValidScale) {
9491 // The width and height properties
9492 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
9494 // The width and height viewport <META> properties are translated into width
9495 // and height descriptors, setting the min-width/min-height value to
9496 // extend-to-zoom and the max-width/max-height value to the length from the
9497 // viewport <META> property as follows:
9499 // 1. Non-negative number values are translated to pixel lengths, clamped to
9500 // the range: [1px, 10000px]
9501 // 2. Negative number values are dropped
9502 // 3. device-width and device-height translate to 100vw and 100vh respectively
9503 // 4. Other keywords and unknown values are also dropped
9504 mMinWidth = nsViewportInfo::Auto;
9505 mMaxWidth = nsViewportInfo::Auto;
9506 if (!aWidthString.IsEmpty()) {
9507 mMinWidth = nsViewportInfo::ExtendToZoom;
9508 if (aWidthString.EqualsLiteral("device-width")) {
9509 mMaxWidth = nsViewportInfo::DeviceSize;
9510 } else {
9511 nsresult widthErrorCode;
9512 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
9513 if (NS_FAILED(widthErrorCode)) {
9514 mMaxWidth = nsViewportInfo::Auto;
9515 } else if (mMaxWidth >= 0.0f) {
9516 mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
9517 } else {
9518 mMaxWidth = nsViewportInfo::Auto;
9521 } else if (aHasValidScale) {
9522 if (aHeightString.IsEmpty()) {
9523 mMinWidth = nsViewportInfo::ExtendToZoom;
9524 mMaxWidth = nsViewportInfo::ExtendToZoom;
9526 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
9527 mMinWidth = nsViewportInfo::ExtendToZoom;
9528 mMaxWidth = nsViewportInfo::DeviceSize;
9531 mMinHeight = nsViewportInfo::Auto;
9532 mMaxHeight = nsViewportInfo::Auto;
9533 if (!aHeightString.IsEmpty()) {
9534 mMinHeight = nsViewportInfo::ExtendToZoom;
9535 if (aHeightString.EqualsLiteral("device-height")) {
9536 mMaxHeight = nsViewportInfo::DeviceSize;
9537 } else {
9538 nsresult heightErrorCode;
9539 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
9540 if (NS_FAILED(heightErrorCode)) {
9541 mMaxHeight = nsViewportInfo::Auto;
9542 } else if (mMaxHeight >= 0.0f) {
9543 mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
9544 } else {
9545 mMaxHeight = nsViewportInfo::Auto;
9550 return !aWidthString.IsEmpty() || !aHeightString.IsEmpty();
9553 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
9554 MOZ_ASSERT(mPresShell);
9556 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
9557 // widget scale and the full zoom.
9558 nsPresContext* context = mPresShell->GetPresContext();
9559 // When querying the full zoom, get it from the device context rather than
9560 // directly from the pres context, because the device context's value can
9561 // include an adjustment necessay to keep the number of app units per device
9562 // pixel an integer, and we want the adjusted value.
9563 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
9564 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
9565 CSSToLayoutDeviceScale layoutDeviceScale =
9566 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
9568 CSSToScreenScale defaultScale =
9569 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
9571 // Special behaviour for desktop mode, provided we are not on an about: page
9572 nsPIDOMWindowOuter* win = GetWindow();
9573 if (win && win->IsDesktopModeViewport() && !IsAboutPage()) {
9574 CSSCoord viewportWidth =
9575 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
9576 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
9577 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
9578 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
9579 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
9580 return nsViewportInfo(fakeDesktopSize, scaleToFit,
9581 nsViewportInfo::ZoomFlag::AllowZoom,
9582 nsViewportInfo::ZoomBehaviour::Mobile);
9585 if (!nsLayoutUtils::ShouldHandleMetaViewport(this)) {
9586 return nsViewportInfo(aDisplaySize, defaultScale,
9587 nsLayoutUtils::AllowZoomingForDocument(this)
9588 ? nsViewportInfo::ZoomFlag::AllowZoom
9589 : nsViewportInfo::ZoomFlag::DisallowZoom,
9590 StaticPrefs::apz_allow_zooming_out()
9591 ? nsViewportInfo::ZoomBehaviour::Mobile
9592 : nsViewportInfo::ZoomBehaviour::Desktop);
9595 // In cases where the width of the CSS viewport is less than or equal to the
9596 // width of the display (i.e. width <= device-width) then we disable
9597 // double-tap-to-zoom behaviour. See bug 941995 for details.
9599 switch (mViewportType) {
9600 case DisplayWidthHeight:
9601 return nsViewportInfo(aDisplaySize, defaultScale,
9602 nsViewportInfo::ZoomFlag::AllowZoom,
9603 nsViewportInfo::ZoomBehaviour::Mobile);
9604 case Unknown: {
9605 nsAutoString viewport;
9606 GetHeaderData(nsGkAtoms::viewport, viewport);
9607 // We might early exit if the viewport is empty. Even if we don't,
9608 // at the end of this case we'll note that it was empty. Later, when
9609 // we're using the cached values, this will trigger alternate code paths.
9610 if (viewport.IsEmpty()) {
9611 // If the docType specifies that we are on a site optimized for mobile,
9612 // then we want to return specially crafted defaults for the viewport
9613 // info.
9614 RefPtr<DocumentType> docType = GetDoctype();
9615 if (docType) {
9616 nsAutoString docId;
9617 docType->GetPublicId(docId);
9618 if ((docId.Find("WAP") != -1) || (docId.Find("Mobile") != -1) ||
9619 (docId.Find("WML") != -1)) {
9620 // We're making an assumption that the docType can't change here
9621 mViewportType = DisplayWidthHeight;
9622 return nsViewportInfo(aDisplaySize, defaultScale,
9623 nsViewportInfo::ZoomFlag::AllowZoom,
9624 nsViewportInfo::ZoomBehaviour::Mobile);
9628 nsAutoString handheldFriendly;
9629 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
9630 if (handheldFriendly.EqualsLiteral("true")) {
9631 mViewportType = DisplayWidthHeight;
9632 return nsViewportInfo(aDisplaySize, defaultScale,
9633 nsViewportInfo::ZoomFlag::AllowZoom,
9634 nsViewportInfo::ZoomBehaviour::Mobile);
9638 ViewportMetaData metaData = GetViewportMetaData();
9640 // Parse initial-scale, minimum-scale and maximum-scale.
9641 bool hasValidContents = ParseScalesInViewportMetaData(metaData);
9643 // Parse width and height properties
9644 // This function sets m{Min,Max}{Width,Height}.
9645 if (ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
9646 mValidScaleFloat)) {
9647 hasValidContents = true;
9650 mAllowZoom = true;
9651 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
9652 (metaData.mUserScalable.EqualsLiteral("no")) ||
9653 (metaData.mUserScalable.EqualsLiteral("false"))) {
9654 mAllowZoom = false;
9656 if (!metaData.mUserScalable.IsEmpty()) {
9657 hasValidContents = true;
9660 // Resolve viewport-fit value.
9661 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
9662 mViewportFit = ViewportFitType::Auto;
9663 if (!metaData.mViewportFit.IsEmpty()) {
9664 if (metaData.mViewportFit.EqualsLiteral("contain")) {
9665 mViewportFit = ViewportFitType::Contain;
9666 hasValidContents = true;
9667 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
9668 mViewportFit = ViewportFitType::Cover;
9669 hasValidContents = true;
9670 } else if (metaData.mViewportFit.EqualsLiteral("auto")) {
9671 hasValidContents = true;
9675 mWidthStrEmpty = metaData.mWidth.IsEmpty();
9677 mViewportType = hasValidContents ? Specified : NoValidContent;
9678 [[fallthrough]];
9680 case Specified:
9681 case NoValidContent:
9682 default:
9683 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
9684 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
9685 bool effectiveValidMaxScale = mValidMaxScale;
9687 nsViewportInfo::ZoomFlag effectiveZoomFlag =
9688 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
9689 : nsViewportInfo::ZoomFlag::DisallowZoom;
9690 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
9691 // If the pref to force user-scalable is enabled, we ignore the values
9692 // from the meta-viewport tag for these properties and just assume they
9693 // allow the page to be scalable. Note in particular that this code is
9694 // in the "Specified" branch of the enclosing switch statement, so that
9695 // calls to GetViewportInfo always use the latest value of the
9696 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
9697 // return nsViewportInfo instances are all consistent with
9698 // browser_ui_zoom_force_user_scalable() already.
9699 effectiveMinScale = kViewportMinScale;
9700 effectiveMaxScale = kViewportMaxScale;
9701 effectiveValidMaxScale = true;
9702 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
9705 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
9706 auto ComputeExtendZoom = [&]() -> float {
9707 if (mValidScaleFloat && effectiveValidMaxScale) {
9708 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
9710 if (mValidScaleFloat) {
9711 return mScaleFloat.scale;
9713 if (effectiveValidMaxScale) {
9714 return effectiveMaxScale.scale;
9716 return nsViewportInfo::Auto;
9719 // Resolving 'extend-to-zoom'
9720 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
9721 float extendZoom = ComputeExtendZoom();
9723 CSSCoord minWidth = mMinWidth;
9724 CSSCoord maxWidth = mMaxWidth;
9725 CSSCoord minHeight = mMinHeight;
9726 CSSCoord maxHeight = mMaxHeight;
9728 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
9729 // viewport size. We need to use this scaled size for any clamping of
9730 // width or height.
9731 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
9733 // Our min and max width and height values are mostly as specified by
9734 // the viewport declaration, but we make an exception for max width.
9735 // Max width, if auto, and if there's no initial-scale, will be set
9736 // to a default size. This is to support legacy site design with no
9737 // viewport declaration, and to do that using the same scheme as
9738 // Chrome does, in order to maintain web compatibility. Since the
9739 // default size has a complicated calculation, we fixup the maxWidth
9740 // value after setting it, above.
9741 if (maxWidth == nsViewportInfo::Auto && !mValidScaleFloat) {
9742 BrowsingContext* bc = GetBrowsingContext();
9743 nsIDocShell* docShell = GetDocShell();
9744 if (docShell &&
9745 docShell->GetTouchEventsOverride() ==
9746 nsIDocShell::TOUCHEVENTS_OVERRIDE_ENABLED &&
9747 bc && bc->InRDMPane()) {
9748 // If RDM and touch simulation are active, then use the simulated
9749 // screen width to accomodate for cases where the screen width is
9750 // larger than the desktop viewport default.
9751 maxWidth = nsViewportInfo::Max(
9752 displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
9753 } else {
9754 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
9756 // Divide by fullZoom to stretch CSS pixel size of viewport in order
9757 // to keep device pixel size unchanged after full zoom applied.
9758 // See bug 974242.
9759 maxWidth /= fullZoom;
9761 // We set minWidth to ExtendToZoom, which will cause our later width
9762 // calculation to expand to maxWidth, if scale restrictions allow it.
9763 minWidth = nsViewportInfo::ExtendToZoom;
9766 // Resolve device-width and device-height first.
9767 if (maxWidth == nsViewportInfo::DeviceSize) {
9768 maxWidth = displaySize.width;
9770 if (maxHeight == nsViewportInfo::DeviceSize) {
9771 maxHeight = displaySize.height;
9773 if (extendZoom == nsViewportInfo::Auto) {
9774 if (maxWidth == nsViewportInfo::ExtendToZoom) {
9775 maxWidth = nsViewportInfo::Auto;
9777 if (maxHeight == nsViewportInfo::ExtendToZoom) {
9778 maxHeight = nsViewportInfo::Auto;
9780 if (minWidth == nsViewportInfo::ExtendToZoom) {
9781 minWidth = maxWidth;
9783 if (minHeight == nsViewportInfo::ExtendToZoom) {
9784 minHeight = maxHeight;
9786 } else {
9787 CSSSize extendSize = displaySize / extendZoom;
9788 if (maxWidth == nsViewportInfo::ExtendToZoom) {
9789 maxWidth = extendSize.width;
9791 if (maxHeight == nsViewportInfo::ExtendToZoom) {
9792 maxHeight = extendSize.height;
9794 if (minWidth == nsViewportInfo::ExtendToZoom) {
9795 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
9797 if (minHeight == nsViewportInfo::ExtendToZoom) {
9798 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
9801 // Resolve initial width and height from min/max descriptors
9802 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
9803 CSSCoord width = nsViewportInfo::Auto;
9804 if (minWidth != nsViewportInfo::Auto ||
9805 maxWidth != nsViewportInfo::Auto) {
9806 width = nsViewportInfo::Max(
9807 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
9809 CSSCoord height = nsViewportInfo::Auto;
9810 if (minHeight != nsViewportInfo::Auto ||
9811 maxHeight != nsViewportInfo::Auto) {
9812 height = nsViewportInfo::Max(
9813 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
9816 // Resolve width value
9817 // https://drafts.csswg.org/css-device-adapt/#resolve-width
9818 if (width == nsViewportInfo::Auto) {
9819 if (height == nsViewportInfo::Auto || aDisplaySize.height == 0) {
9820 width = displaySize.width;
9821 } else {
9822 width = height * aDisplaySize.width / aDisplaySize.height;
9826 // Resolve height value
9827 // https://drafts.csswg.org/css-device-adapt/#resolve-height
9828 if (height == nsViewportInfo::Auto) {
9829 if (aDisplaySize.width == 0) {
9830 height = displaySize.height;
9831 } else {
9832 height = width * aDisplaySize.height / aDisplaySize.width;
9835 MOZ_ASSERT(width != nsViewportInfo::Auto &&
9836 height != nsViewportInfo::Auto);
9838 CSSSize size(width, height);
9840 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
9841 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
9842 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
9844 nsViewportInfo::AutoSizeFlag sizeFlag =
9845 nsViewportInfo::AutoSizeFlag::FixedSize;
9846 if (mMaxWidth == nsViewportInfo::DeviceSize ||
9847 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::DeviceSize ||
9848 mScaleFloat.scale == 1.0f)) ||
9849 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::Auto &&
9850 mMaxHeight < 0)) {
9851 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
9854 // FIXME: Resolving width and height should be done above 'Resolve width
9855 // value' and 'Resolve height value'.
9856 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
9857 size = displaySize;
9860 // The purpose of clamping the viewport width to a minimum size is to
9861 // prevent page authors from setting it to a ridiculously small value.
9862 // If the page is actually being rendered in a very small area (as might
9863 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
9864 // prevent the viewport from taking on that size.
9865 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
9867 size.width = clamped(size.width, effectiveMinSize.width,
9868 float(kViewportMaxSize.width));
9870 // Also recalculate the default zoom, if it wasn't specified in the
9871 // metadata, and the width is specified.
9872 if (!mValidScaleFloat && !mWidthStrEmpty) {
9873 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
9874 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
9877 size.height = clamped(size.height, effectiveMinSize.height,
9878 float(kViewportMaxSize.height));
9880 // In cases of user-scalable=no, if we have a positive scale, clamp it to
9881 // min and max, and then use the clamped value for the scale, the min, and
9882 // the max. If we don't have a positive scale, assert that we are setting
9883 // the auto scale flag.
9884 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
9885 scaleFloat > CSSToScreenScale(0.0f)) {
9886 scaleFloat = scaleMinFloat = scaleMaxFloat =
9887 clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
9889 MOZ_ASSERT(
9890 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
9891 "If we don't have a positive scale, we should be using auto scale.");
9893 // We need to perform a conversion, but only if the initial or maximum
9894 // scale were set explicitly by the user.
9895 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
9896 scaleFloat <= scaleMaxFloat) {
9897 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
9898 size.width = std::max(size.width, displaySize.width);
9899 size.height = std::max(size.height, displaySize.height);
9900 } else if (effectiveValidMaxScale) {
9901 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
9902 size.width = std::max(size.width, displaySize.width);
9903 size.height = std::max(size.height, displaySize.height);
9906 return nsViewportInfo(
9907 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
9908 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
9909 : nsViewportInfo::AutoScaleFlag::AutoScale,
9910 effectiveZoomFlag, mViewportFit);
9914 ViewportMetaData Document::GetViewportMetaData() const {
9915 // The order of mMetaViewport is first-modified is first. We want the last
9916 // modified since Chrome uses the last one.
9917 // See https://webcompat.com/issues/20701#issuecomment-436054739
9918 return !mMetaViewports.IsEmpty() ? mMetaViewports.LastElement().mData
9919 : ViewportMetaData();
9922 void Document::AddMetaViewportElement(HTMLMetaElement* aElement,
9923 ViewportMetaData&& aData) {
9924 for (size_t i = 0; i < mMetaViewports.Length(); i++) {
9925 MetaViewportElementAndData& viewport = mMetaViewports[i];
9926 if (viewport.mElement == aElement) {
9927 if (viewport.mData == aData) {
9928 return;
9930 // Move the existing one to the tail since Chrome uses the last modified
9931 // viewport meta tag.
9932 mMetaViewports.RemoveElementAt(i);
9933 break;
9937 mMetaViewports.AppendElement(MetaViewportElementAndData{aElement, aData});
9938 // Trigger recomputation of the nsViewportInfo the next time it's queried.
9939 mViewportType = Unknown;
9941 RefPtr<AsyncEventDispatcher> asyncDispatcher =
9942 new AsyncEventDispatcher(this, u"DOMMetaViewportFitChanged"_ns,
9943 CanBubble::eYes, ChromeOnlyDispatch::eYes);
9944 asyncDispatcher->RunDOMEventWhenSafe();
9947 void Document::RemoveMetaViewportElement(HTMLMetaElement* aElement) {
9948 for (MetaViewportElementAndData& viewport : mMetaViewports) {
9949 if (viewport.mElement == aElement) {
9950 mMetaViewports.RemoveElement(viewport);
9951 // Trigger recomputation of the nsViewportInfo the next time it's queried.
9952 mViewportType = Unknown;
9954 RefPtr<AsyncEventDispatcher> asyncDispatcher =
9955 new AsyncEventDispatcher(this, u"DOMMetaViewportFitChanged"_ns,
9956 CanBubble::eYes, ChromeOnlyDispatch::eYes);
9957 asyncDispatcher->RunDOMEventWhenSafe();
9958 return;
9963 void Document::UpdateForScrollAnchorAdjustment(nscoord aLength) {
9964 mScrollAnchorAdjustmentLength += abs(aLength);
9965 mScrollAnchorAdjustmentCount += 1;
9968 EventListenerManager* Document::GetOrCreateListenerManager() {
9969 if (!mListenerManager) {
9970 mListenerManager =
9971 new EventListenerManager(static_cast<EventTarget*>(this));
9972 SetFlags(NODE_HAS_LISTENERMANAGER);
9975 return mListenerManager;
9978 EventListenerManager* Document::GetExistingListenerManager() const {
9979 return mListenerManager;
9982 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
9983 aVisitor.mCanHandle = true;
9984 // FIXME! This is a hack to make middle mouse paste working also in Editor.
9985 // Bug 329119
9986 aVisitor.mForceContentDispatch = true;
9988 // Load events must not propagate to |window| object, see bug 335251.
9989 if (aVisitor.mEvent->mMessage != eLoad) {
9990 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
9991 aVisitor.SetParentTarget(
9992 window ? window->GetTargetForEventTargetChain() : nullptr, false);
9996 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
9997 CallerType aCallerType,
9998 ErrorResult& rv) const {
9999 nsPresContext* presContext = GetPresContext();
10001 // Create event even without presContext.
10002 RefPtr<Event> ev =
10003 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
10004 nullptr, aEventType, aCallerType);
10005 if (!ev) {
10006 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10007 return nullptr;
10009 WidgetEvent* e = ev->WidgetEventPtr();
10010 e->mFlags.mBubbles = false;
10011 e->mFlags.mCancelable = false;
10012 return ev.forget();
10015 void Document::FlushPendingNotifications(FlushType aType) {
10016 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
10017 FlushPendingNotifications(flush);
10020 class nsDocumentOnStack {
10021 public:
10022 explicit nsDocumentOnStack(Document* aDoc) : mDoc(aDoc) {
10023 mDoc->IncreaseStackRefCnt();
10025 ~nsDocumentOnStack() { mDoc->DecreaseStackRefCnt(); }
10027 private:
10028 Document* mDoc;
10031 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
10032 FlushType flushType = aFlush.mFlushType;
10034 nsDocumentOnStack dos(this);
10036 // We need to flush the sink for non-HTML documents (because the XML
10037 // parser still does insertion with deferred notifications). We
10038 // also need to flush the sink if this is a layout-related flush, to
10039 // make sure that layout is started as needed. But we can skip that
10040 // part if we have no presshell or if it's already done an initial
10041 // reflow.
10042 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
10043 mPresShell && !mPresShell->DidInitialize())) &&
10044 (mParser || mWeakSink)) {
10045 nsCOMPtr<nsIContentSink> sink;
10046 if (mParser) {
10047 sink = mParser->GetContentSink();
10048 } else {
10049 sink = do_QueryReferent(mWeakSink);
10050 if (!sink) {
10051 mWeakSink = nullptr;
10054 // Determine if it is safe to flush the sink notifications
10055 // by determining if it safe to flush all the presshells.
10056 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
10057 sink->FlushPendingNotifications(flushType);
10061 // Should we be flushing pending binding constructors in here?
10063 if (flushType <= FlushType::ContentAndNotify) {
10064 // Nothing to do here
10065 return;
10068 // If we have a parent we must flush the parent too to ensure that our
10069 // container is reflowed if its size was changed.
10071 // We do it only if the subdocument and the parent can observe each other
10072 // synchronously (that is, if we're not cross-origin), to avoid work that is
10073 // not observable, and if the parent document has finished loading all its
10074 // render-blocking stylesheets and may start laying out the document, to avoid
10075 // unnecessary flashes of unstyled content on the parent document. Note that
10076 // this last bit means that size-dependent media queries in this document may
10077 // produce incorrect results temporarily.
10079 // But if it's not safe to flush ourselves, then don't flush the parent, since
10080 // that can cause things like resizes of our frame's widget, which we can't
10081 // handle while flushing is unsafe.
10082 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
10083 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
10084 ChangesToFlush parentFlush = aFlush;
10085 if (flushType >= FlushType::Style) {
10086 // Since media queries mean that a size change of our container can affect
10087 // style, we need to promote a style flush on ourself to a layout flush on
10088 // our parent, since we need our container to be the correct size to
10089 // determine the correct style.
10090 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
10092 mParentDocument->FlushPendingNotifications(parentFlush);
10095 if (RefPtr<PresShell> presShell = GetPresShell()) {
10096 presShell->FlushPendingNotifications(aFlush);
10100 void Document::FlushExternalResources(FlushType aType) {
10101 NS_ASSERTION(
10102 aType >= FlushType::Style,
10103 "should only need to flush for style or higher in external resources");
10104 if (GetDisplayDocument()) {
10105 return;
10108 auto flush = [aType](Document& aDoc) {
10109 aDoc.FlushPendingNotifications(aType);
10110 return CallState::Continue;
10113 EnumerateExternalResources(flush);
10116 void Document::SetXMLDeclaration(const char16_t* aVersion,
10117 const char16_t* aEncoding,
10118 const int32_t aStandalone) {
10119 if (!aVersion || *aVersion == '\0') {
10120 mXMLDeclarationBits = 0;
10121 return;
10124 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
10126 if (aEncoding && *aEncoding != '\0') {
10127 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
10130 if (aStandalone == 1) {
10131 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
10132 XML_DECLARATION_BITS_STANDALONE_YES;
10133 } else if (aStandalone == 0) {
10134 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
10138 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
10139 nsAString& aStandalone) {
10140 aVersion.Truncate();
10141 aEncoding.Truncate();
10142 aStandalone.Truncate();
10144 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
10145 return;
10148 // always until we start supporting 1.1 etc.
10149 aVersion.AssignLiteral("1.0");
10151 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
10152 // This is what we have stored, not necessarily what was written
10153 // in the original
10154 GetCharacterSet(aEncoding);
10157 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
10158 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
10159 aStandalone.AssignLiteral("yes");
10160 } else {
10161 aStandalone.AssignLiteral("no");
10166 bool Document::IsScriptEnabled() {
10167 // If this document is sandboxed without 'allow-scripts'
10168 // script is not enabled
10169 if (HasScriptsBlockedBySandbox()) {
10170 return false;
10173 nsCOMPtr<nsIScriptGlobalObject> globalObject =
10174 do_QueryInterface(GetInnerWindow());
10175 if (!globalObject || !globalObject->HasJSGlobal()) {
10176 return false;
10179 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
10180 .Allowed();
10183 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
10184 PRTime modDate = 0;
10185 nsresult rv;
10187 nsCOMPtr<nsIHttpChannel> httpChannel;
10188 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
10189 if (NS_WARN_IF(NS_FAILED(rv))) {
10190 return;
10193 if (httpChannel) {
10194 nsAutoCString tmp;
10195 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
10197 if (NS_SUCCEEDED(rv)) {
10198 PRTime time;
10199 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
10200 if (st == PR_SUCCESS) {
10201 modDate = time;
10205 static const char* const headers[] = {
10206 "default-style", "content-style-type", "content-language",
10207 "content-disposition", "refresh", "x-dns-prefetch-control",
10208 "x-frame-options",
10209 // add more http headers if you need
10210 // XXXbz don't add content-location support without reading bug
10211 // 238654 and its dependencies/dups first.
10214 nsAutoCString headerVal;
10215 const char* const* name = headers;
10216 while (*name) {
10217 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
10218 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
10219 RefPtr<nsAtom> key = NS_Atomize(*name);
10220 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
10222 ++name;
10224 } else {
10225 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
10226 if (fileChannel) {
10227 nsCOMPtr<nsIFile> file;
10228 fileChannel->GetFile(getter_AddRefs(file));
10229 if (file) {
10230 PRTime msecs;
10231 rv = file->GetLastModifiedTime(&msecs);
10233 if (NS_SUCCEEDED(rv)) {
10234 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
10237 } else {
10238 nsAutoCString contentDisp;
10239 rv = aChannel->GetContentDispositionHeader(contentDisp);
10240 if (NS_SUCCEEDED(rv)) {
10241 SetHeaderData(nsGkAtoms::headerContentDisposition,
10242 NS_ConvertASCIItoUTF16(contentDisp));
10247 mLastModified.Truncate();
10248 if (modDate != 0) {
10249 GetFormattedTimeString(modDate, mLastModified);
10253 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
10254 nsAtom* aPrefix,
10255 int32_t aNamespaceID,
10256 const nsAString* aIs) {
10257 #ifdef DEBUG
10258 nsAutoString qName;
10259 if (aPrefix) {
10260 aPrefix->ToString(qName);
10261 qName.Append(':');
10263 qName.Append(aName);
10265 // Note: "a:b:c" is a valid name in non-namespaces XML, and
10266 // Document::CreateElement can call us with such a name and no prefix,
10267 // which would cause an error if we just used true here.
10268 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
10269 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
10270 "Don't pass invalid prefixes to Document::CreateElem, "
10271 "check caller.");
10272 #endif
10274 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
10275 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
10276 getter_AddRefs(nodeInfo));
10277 NS_ENSURE_TRUE(nodeInfo, nullptr);
10279 nsCOMPtr<Element> element;
10280 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
10281 NOT_FROM_PARSER, aIs);
10282 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
10285 bool Document::IsSafeToFlush() const {
10286 PresShell* presShell = GetPresShell();
10287 if (!presShell) {
10288 return true;
10290 return presShell->IsSafeToFlush();
10293 void Document::Sanitize() {
10294 // Sanitize the document by resetting all (current and former) password fields
10295 // and any form fields with autocomplete=off to their default values. We do
10296 // this now, instead of when the presentation is restored, to offer some
10297 // protection in case there is ever an exploit that allows a cached document
10298 // to be accessed from a different document.
10300 // First locate all input elements, regardless of whether they are
10301 // in a form, and reset the password and autocomplete=off elements.
10303 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
10305 nsAutoString value;
10307 uint32_t length = nodes->Length(true);
10308 for (uint32_t i = 0; i < length; ++i) {
10309 NS_ASSERTION(nodes->Item(i), "null item in node list!");
10311 RefPtr<HTMLInputElement> input =
10312 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
10313 if (!input) continue;
10315 input->GetAttr(nsGkAtoms::autocomplete, value);
10316 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
10317 input->Reset();
10321 // Now locate all _form_ elements that have autocomplete=off and reset them
10322 nodes = GetElementsByTagName(u"form"_ns);
10324 length = nodes->Length(true);
10325 for (uint32_t i = 0; i < length; ++i) {
10326 // Reset() may change the list dynamically.
10327 RefPtr<HTMLFormElement> form =
10328 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
10329 if (!form) continue;
10331 form->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, value);
10332 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
10336 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
10337 if (!mSubDocuments) {
10338 return;
10341 // PLDHashTable::Iterator can't handle modifications while iterating so we
10342 // copy all entries to an array first before calling any callbacks.
10343 AutoTArray<RefPtr<Document>, 8> subdocs;
10344 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
10345 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
10346 if (Document* subdoc = entry->mSubDocument) {
10347 subdocs.AppendElement(subdoc);
10350 for (auto& subdoc : subdocs) {
10351 if (aCallback(*subdoc) == CallState::Stop) {
10352 break;
10357 void Document::CollectDescendantDocuments(
10358 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
10359 if (!mSubDocuments) {
10360 return;
10363 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
10364 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
10365 const Document* subdoc = entry->mSubDocument;
10366 if (subdoc) {
10367 if (aCallback(subdoc)) {
10368 aDescendants.AppendElement(entry->mSubDocument);
10370 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
10375 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
10376 uint16_t& aBFCacheCombo) {
10377 bool ret = true;
10379 if (!IsBFCachingAllowed()) {
10380 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
10381 ret = false;
10384 nsAutoCString uri;
10385 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
10386 if (mDocumentURI) {
10387 mDocumentURI->GetSpec(uri);
10391 if (EventHandlingSuppressed()) {
10392 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
10393 ("Save of %s blocked on event handling suppression", uri.get()));
10394 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
10395 ret = false;
10398 // Do not allow suspended windows to be placed in the
10399 // bfcache. This method is also used to verify a document
10400 // coming out of the bfcache is ok to restore, though. So
10401 // we only want to block suspend windows that aren't also
10402 // frozen.
10403 nsPIDOMWindowInner* win = GetInnerWindow();
10404 if (win && win->IsSuspended() && !win->IsFrozen()) {
10405 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
10406 ("Save of %s blocked on suspended Window", uri.get()));
10407 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
10408 ret = false;
10411 // Check our event listener manager for unload/beforeunload listeners.
10412 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
10413 if (piTarget) {
10414 EventListenerManager* manager = piTarget->GetExistingListenerManager();
10415 if (manager && manager->HasUnloadListeners()) {
10416 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
10417 ("Save of %s blocked due to unload handlers", uri.get()));
10418 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
10419 ret = false;
10423 // Check if we have pending network requests
10424 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
10425 if (loadGroup) {
10426 nsCOMPtr<nsISimpleEnumerator> requests;
10427 loadGroup->GetRequests(getter_AddRefs(requests));
10429 bool hasMore = false;
10431 // We want to bail out if we have any requests other than aNewRequest (or
10432 // in the case when aNewRequest is a part of a multipart response the base
10433 // channel the multipart response is coming in on).
10434 nsCOMPtr<nsIChannel> baseChannel;
10435 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
10436 if (part) {
10437 part->GetBaseChannel(getter_AddRefs(baseChannel));
10440 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
10441 nsCOMPtr<nsISupports> elem;
10442 requests->GetNext(getter_AddRefs(elem));
10444 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
10445 if (request && request != aNewRequest && request != baseChannel) {
10446 // Favicon loads don't need to block caching.
10447 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
10448 if (channel) {
10449 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
10450 if (li->InternalContentPolicyType() ==
10451 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
10452 continue;
10456 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
10457 nsAutoCString requestName;
10458 request->GetName(requestName);
10459 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
10460 ("Save of %s blocked because document has request %s",
10461 uri.get(), requestName.get()));
10463 aBFCacheCombo |= BFCacheStatus::REQUEST;
10464 ret = false;
10469 // Check if we have active GetUserMedia use
10470 if (MediaManager::Exists() && win &&
10471 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
10472 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
10473 ("Save of %s blocked due to GetUserMedia", uri.get()));
10474 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
10475 ret = false;
10478 #ifdef MOZ_WEBRTC
10479 // Check if we have active PeerConnections
10480 if (win && win->HasActivePeerConnections()) {
10481 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
10482 ("Save of %s blocked due to PeerConnection", uri.get()));
10483 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
10484 ret = false;
10486 #endif // MOZ_WEBRTC
10488 // Don't save presentations for documents containing EME content, so that
10489 // CDMs reliably shutdown upon user navigation.
10490 if (ContainsEMEContent()) {
10491 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
10492 ret = false;
10495 // Don't save presentations for documents containing MSE content, to
10496 // reduce memory usage.
10497 if (ContainsMSEContent()) {
10498 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
10499 ("Save of %s blocked due to MSE use", uri.get()));
10500 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
10501 ret = false;
10504 if (mSubDocuments) {
10505 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
10506 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
10507 Document* subdoc = entry->mSubDocument;
10509 uint16_t subDocBFCacheCombo = 0;
10510 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
10511 bool canCache =
10512 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo)
10513 : false;
10514 if (!canCache) {
10515 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
10516 ("Save of %s blocked due to subdocument blocked", uri.get()));
10517 aBFCacheCombo |= subDocBFCacheCombo;
10518 ret = false;
10523 // BFCache is currently not compatible with remote subframes (bug 1609324)
10524 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
10525 for (auto& child : browsingContext->Children()) {
10526 if (!child->IsInProcess()) {
10527 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
10528 ret = false;
10529 break;
10534 if (win) {
10535 auto* globalWindow = nsGlobalWindowInner::Cast(win);
10536 #ifdef MOZ_WEBSPEECH
10537 if (globalWindow->HasActiveSpeechSynthesis()) {
10538 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
10539 ("Save of %s blocked due to Speech use", uri.get()));
10540 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
10541 ret = false;
10543 #endif
10544 if (globalWindow->HasUsedVR()) {
10545 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
10546 ("Save of %s blocked due to having used VR", uri.get()));
10547 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
10548 ret = false;
10552 return ret;
10555 void Document::Destroy() {
10556 // The ContentViewer wants to release the document now. So, tell our content
10557 // to drop any references to the document so that it can be destroyed.
10558 if (mIsGoingAway) return;
10560 ReportUseCounters();
10562 mIsGoingAway = true;
10564 ScriptLoader()->Destroy();
10565 SetScriptGlobalObject(nullptr);
10566 RemovedFromDocShell();
10568 bool oldVal = mInUnlinkOrDeletion;
10569 mInUnlinkOrDeletion = true;
10571 #ifdef DEBUG
10572 uint32_t oldChildCount = GetChildCount();
10573 #endif
10575 for (nsIContent* child = GetFirstChild(); child;
10576 child = child->GetNextSibling()) {
10577 child->DestroyContent();
10578 MOZ_ASSERT(child->GetParentNode() == this);
10580 MOZ_ASSERT(oldChildCount == GetChildCount());
10581 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
10583 mInUnlinkOrDeletion = oldVal;
10585 mLayoutHistoryState = nullptr;
10587 if (mOriginalDocument) {
10588 mOriginalDocument->mLatestStaticClone = nullptr;
10591 // Shut down our external resource map. We might not need this for
10592 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
10593 // tearing down all those frame trees right now is the right thing to do.
10594 mExternalResourceMap.Shutdown();
10596 // Manually break cycles via promise's global object pointer.
10597 mReadyForIdle = nullptr;
10598 mOrientationPendingPromise = nullptr;
10600 // To break cycles.
10601 mPreloadService.ClearAllPreloads();
10603 if (mDocumentL10n) {
10604 mDocumentL10n->Destroy();
10608 void Document::RemovedFromDocShell() {
10609 mEditingState = EditingState::eOff;
10611 if (mRemovedFromDocShell) return;
10613 mRemovedFromDocShell = true;
10614 EnumerateActivityObservers(NotifyActivityChanged);
10616 for (nsIContent* child = GetFirstChild(); child;
10617 child = child->GetNextSibling()) {
10618 child->SaveSubtreeState();
10621 nsIDocShell* docShell = GetDocShell();
10622 if (docShell) {
10623 docShell->SynchronizeLayoutHistoryState();
10627 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
10628 const {
10629 nsCOMPtr<nsILayoutHistoryState> state;
10630 if (!mScriptGlobalObject) {
10631 state = mLayoutHistoryState;
10632 } else {
10633 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
10634 if (docShell) {
10635 docShell->GetLayoutHistoryState(getter_AddRefs(state));
10639 return state.forget();
10642 void Document::EnsureOnloadBlocker() {
10643 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
10644 // -- it's not ours.
10645 if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
10646 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
10647 if (loadGroup) {
10648 // Check first to see if mOnloadBlocker is in the loadgroup.
10649 nsCOMPtr<nsISimpleEnumerator> requests;
10650 loadGroup->GetRequests(getter_AddRefs(requests));
10652 bool hasMore = false;
10653 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
10654 nsCOMPtr<nsISupports> elem;
10655 requests->GetNext(getter_AddRefs(elem));
10656 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
10657 if (request && request == mOnloadBlocker) {
10658 return;
10662 // Not in the loadgroup, so add it.
10663 loadGroup->AddRequest(mOnloadBlocker, nullptr);
10668 void Document::AsyncBlockOnload() {
10669 while (mAsyncOnloadBlockCount) {
10670 --mAsyncOnloadBlockCount;
10671 BlockOnload();
10675 void Document::BlockOnload() {
10676 if (mDisplayDocument) {
10677 mDisplayDocument->BlockOnload();
10678 return;
10681 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
10682 // -- it's not ours.
10683 if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
10684 if (!nsContentUtils::IsSafeToRunScript()) {
10685 // Because AddRequest may lead to OnStateChange calls in chrome,
10686 // block onload only when there are no script blockers.
10687 ++mAsyncOnloadBlockCount;
10688 if (mAsyncOnloadBlockCount == 1) {
10689 nsContentUtils::AddScriptRunner(NewRunnableMethod(
10690 "Document::AsyncBlockOnload", this, &Document::AsyncBlockOnload));
10692 return;
10694 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
10695 if (loadGroup) {
10696 loadGroup->AddRequest(mOnloadBlocker, nullptr);
10699 ++mOnloadBlockCount;
10702 void Document::UnblockOnload(bool aFireSync) {
10703 if (mDisplayDocument) {
10704 mDisplayDocument->UnblockOnload(aFireSync);
10705 return;
10708 if (mOnloadBlockCount == 0 && mAsyncOnloadBlockCount == 0) {
10709 MOZ_ASSERT_UNREACHABLE(
10710 "More UnblockOnload() calls than BlockOnload() "
10711 "calls; dropping call");
10712 return;
10715 --mOnloadBlockCount;
10717 if (mOnloadBlockCount == 0) {
10718 if (mScriptGlobalObject) {
10719 // Only manipulate the loadgroup in this case, because if
10720 // mScriptGlobalObject is null, it's not ours.
10721 if (aFireSync && mAsyncOnloadBlockCount == 0) {
10722 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
10723 ++mOnloadBlockCount;
10724 DoUnblockOnload();
10725 } else {
10726 PostUnblockOnloadEvent();
10728 } else if (mIsBeingUsedAsImage) {
10729 // To correctly unblock onload for a document that contains an SVG
10730 // image, we need to know when all of the SVG document's resources are
10731 // done loading, in a way comparable to |window.onload|. We fire this
10732 // event to indicate that the SVG should be considered fully loaded.
10733 // Because scripting is disabled on SVG-as-image documents, this event
10734 // is not accessible to content authors. (See bug 837315.)
10735 RefPtr<AsyncEventDispatcher> asyncDispatcher =
10736 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
10737 CanBubble::eNo, ChromeOnlyDispatch::eNo);
10738 asyncDispatcher->PostDOMEvent();
10743 class nsUnblockOnloadEvent : public Runnable {
10744 public:
10745 explicit nsUnblockOnloadEvent(Document* aDoc)
10746 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
10747 NS_IMETHOD Run() override {
10748 mDoc->DoUnblockOnload();
10749 return NS_OK;
10752 private:
10753 RefPtr<Document> mDoc;
10756 void Document::PostUnblockOnloadEvent() {
10757 MOZ_RELEASE_ASSERT(NS_IsMainThread());
10758 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
10759 nsresult rv = Dispatch(TaskCategory::Other, evt.forget());
10760 if (NS_SUCCEEDED(rv)) {
10761 // Stabilize block count so we don't post more events while this one is up
10762 ++mOnloadBlockCount;
10763 } else {
10764 NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
10768 void Document::DoUnblockOnload() {
10769 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
10770 MOZ_ASSERT(mOnloadBlockCount != 0,
10771 "Shouldn't have a count of zero here, since we stabilized in "
10772 "PostUnblockOnloadEvent");
10774 --mOnloadBlockCount;
10776 if (mOnloadBlockCount != 0) {
10777 // We blocked again after the last unblock. Nothing to do here. We'll
10778 // post a new event when we unblock again.
10779 return;
10782 if (mAsyncOnloadBlockCount != 0) {
10783 // We need to wait until the async onload block has been handled.
10785 // FIXME(emilio): Shouldn't we return here??
10786 PostUnblockOnloadEvent();
10789 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
10790 // -- it's not ours.
10791 if (mScriptGlobalObject) {
10792 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
10793 if (loadGroup) {
10794 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
10799 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
10800 for (nsIFrame* f = aFrame; f;
10801 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
10802 nsIContent* content = f->GetContent();
10803 if (!content || content->IsInNativeAnonymousSubtree()) continue;
10805 if (content->OwnerDoc() == this) {
10806 return content;
10808 // We must be in a subdocument so jump directly to the root frame.
10809 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
10810 // the containing document.
10811 f = f->PresContext()->GetPresShell()->GetRootFrame();
10814 return nullptr;
10817 void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
10818 const nsAString& aType, bool aInFrameSwap,
10819 bool aPersisted, bool aOnlySystemGroup) {
10820 if (!aDispatchTarget) {
10821 return;
10824 PageTransitionEventInit init;
10825 init.mBubbles = true;
10826 init.mCancelable = true;
10827 init.mPersisted = aPersisted;
10828 init.mInFrameSwap = aInFrameSwap;
10830 RefPtr<PageTransitionEvent> event =
10831 PageTransitionEvent::Constructor(this, aType, init);
10833 event->SetTrusted(true);
10834 event->SetTarget(this);
10835 if (aOnlySystemGroup) {
10836 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
10838 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
10839 nullptr);
10842 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
10843 bool aOnlySystemGroup) {
10844 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
10845 MOZ_DIAGNOSTIC_ASSERT(
10846 inFrameLoaderSwap ==
10847 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
10849 Element* root = GetRootElement();
10850 if (aPersisted && root) {
10851 // Send out notifications that our <link> elements are attached.
10852 RefPtr<nsContentList> links =
10853 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
10855 uint32_t linkCount = links->Length(true);
10856 for (uint32_t i = 0; i < linkCount; ++i) {
10857 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
10861 // See Document
10862 if (!inFrameLoaderSwap) {
10863 if (aPersisted) {
10864 ImageTracker()->SetAnimatingState(true);
10867 // Set mIsShowing before firing events, in case those event handlers
10868 // move us around.
10869 mIsShowing = true;
10870 mVisible = true;
10872 UpdateVisibilityState();
10875 EnumerateActivityObservers(NotifyActivityChanged);
10877 auto notifyExternal = [aPersisted](Document& aExternalResource) {
10878 aExternalResource.OnPageShow(aPersisted, nullptr);
10879 return CallState::Continue;
10881 EnumerateExternalResources(notifyExternal);
10883 if (mAnimationController) {
10884 mAnimationController->OnPageShow();
10887 if (!mIsBeingUsedAsImage) {
10888 // Dispatch observer notification to notify observers page is shown.
10889 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
10890 if (os) {
10891 nsIPrincipal* principal = NodePrincipal();
10892 os->NotifyObservers(ToSupports(this),
10893 principal->IsSystemPrincipal() ? "chrome-page-shown"
10894 : "content-page-shown",
10895 nullptr);
10898 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
10899 if (!target) {
10900 target = do_QueryInterface(GetWindow());
10902 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
10903 aPersisted, aOnlySystemGroup);
10907 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
10908 if (nsPresContext* presContext = aDocument.GetPresContext()) {
10909 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
10910 FullscreenEventType::Change, &aDocument, aTarget);
10911 presContext->RefreshDriver()->ScheduleFullscreenEvent(
10912 std::move(pendingEvent));
10916 static void ClearPendingFullscreenRequests(Document* aDoc);
10918 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
10919 bool aOnlySystemGroup) {
10920 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
10921 MOZ_DIAGNOSTIC_ASSERT(
10922 inFrameLoaderSwap ==
10923 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
10925 // Send out notifications that our <link> elements are detached,
10926 // but only if this is not a full unload.
10927 Element* root = GetRootElement();
10928 if (aPersisted && root) {
10929 RefPtr<nsContentList> links =
10930 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
10932 uint32_t linkCount = links->Length(true);
10933 for (uint32_t i = 0; i < linkCount; ++i) {
10934 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved();
10938 if (mAnimationController) {
10939 mAnimationController->OnPageHide();
10942 if (!inFrameLoaderSwap) {
10943 if (aPersisted) {
10944 // We do not stop the animations (bug 1024343) when the page is refreshing
10945 // while being dragged out.
10946 ImageTracker()->SetAnimatingState(false);
10949 // Set mIsShowing before firing events, in case those event handlers
10950 // move us around.
10951 mIsShowing = false;
10952 mVisible = false;
10955 ExitPointerLock();
10957 if (!mIsBeingUsedAsImage) {
10958 // Dispatch observer notification to notify observers page is hidden.
10959 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
10960 if (os) {
10961 nsIPrincipal* principal = NodePrincipal();
10962 os->NotifyObservers(ToSupports(this),
10963 principal->IsSystemPrincipal()
10964 ? "chrome-page-hidden"
10965 : "content-page-hidden",
10966 nullptr);
10969 // Now send out a PageHide event.
10970 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
10971 if (!target) {
10972 target = do_QueryInterface(GetWindow());
10975 PageUnloadingEventTimeStamp timeStamp(this);
10976 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
10977 aPersisted, aOnlySystemGroup);
10981 if (!inFrameLoaderSwap) {
10982 UpdateVisibilityState();
10985 auto notifyExternal = [aPersisted](Document& aExternalResource) {
10986 aExternalResource.OnPageHide(aPersisted, nullptr);
10987 return CallState::Continue;
10989 EnumerateExternalResources(notifyExternal);
10990 EnumerateActivityObservers(NotifyActivityChanged);
10992 ClearPendingFullscreenRequests(this);
10993 if (GetUnretargetedFullScreenElement()) {
10994 // If this document was fullscreen, we should exit fullscreen in this
10995 // doctree branch. This ensures that if the user navigates while in
10996 // fullscreen mode we don't leave its still visible ancestor documents
10997 // in fullscreen mode. So exit fullscreen in the document's fullscreen
10998 // root document, as this will exit fullscreen in all the root's
10999 // descendant documents. Note that documents are removed from the
11000 // doctree by the time OnPageHide() is called, so we must store a
11001 // reference to the root (in Document::mFullscreenRoot) since we can't
11002 // just traverse the doctree to get the root.
11003 Document::ExitFullscreenInDocTree(this);
11005 // Since the document is removed from the doctree before OnPageHide() is
11006 // called, ExitFullscreen() can't traverse from the root down to *this*
11007 // document, so we must manually call CleanupFullscreenState() below too.
11008 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
11009 // so we *must* call it after ExitFullscreen(), not before.
11010 // OnPageHide() is called in every hidden (i.e. descendant) document,
11011 // so calling CleanupFullscreenState() here will ensure all hidden
11012 // documents have their fullscreen state reset.
11013 CleanupFullscreenState();
11015 // The fullscreenchange event is to be queued in the refresh driver,
11016 // however a hidden page wouldn't trigger that again, so it makes no
11017 // sense to dispatch such event here.
11021 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
11022 NS_ASSERTION(
11023 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
11024 "mSubtreeModifiedTargets not cleared after dispatching?");
11025 ++mSubtreeModifiedDepth;
11026 if (aTarget) {
11027 // MayDispatchMutationEvent is often called just before this method,
11028 // so it has already appended the node to mSubtreeModifiedTargets.
11029 int32_t count = mSubtreeModifiedTargets.Count();
11030 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
11031 mSubtreeModifiedTargets.AppendObject(aTarget);
11036 void Document::MutationEventDispatched(nsINode* aTarget) {
11037 --mSubtreeModifiedDepth;
11038 if (mSubtreeModifiedDepth == 0) {
11039 int32_t count = mSubtreeModifiedTargets.Count();
11040 if (!count) {
11041 return;
11044 nsPIDOMWindowInner* window = GetInnerWindow();
11045 if (window &&
11046 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
11047 mSubtreeModifiedTargets.Clear();
11048 return;
11051 nsCOMArray<nsINode> realTargets;
11052 for (int32_t i = 0; i < count; ++i) {
11053 nsINode* possibleTarget = mSubtreeModifiedTargets[i];
11054 nsCOMPtr<nsIContent> content = do_QueryInterface(possibleTarget);
11055 if (content && content->ChromeOnlyAccess()) {
11056 continue;
11059 nsINode* commonAncestor = nullptr;
11060 int32_t realTargetCount = realTargets.Count();
11061 for (int32_t j = 0; j < realTargetCount; ++j) {
11062 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
11063 possibleTarget, realTargets[j]);
11064 if (commonAncestor) {
11065 realTargets.ReplaceObjectAt(commonAncestor, j);
11066 break;
11069 if (!commonAncestor) {
11070 realTargets.AppendObject(possibleTarget);
11074 mSubtreeModifiedTargets.Clear();
11076 int32_t realTargetCount = realTargets.Count();
11077 for (int32_t k = 0; k < realTargetCount; ++k) {
11078 InternalMutationEvent mutation(true, eLegacySubtreeModified);
11079 (new AsyncEventDispatcher(realTargets[k], mutation))
11080 ->RunDOMEventWhenSafe();
11085 void Document::DestroyElementMaps() {
11086 #ifdef DEBUG
11087 mStyledLinksCleared = true;
11088 #endif
11089 mStyledLinks.Clear();
11090 // Notify ID change listeners before clearing the identifier map.
11091 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
11092 iter.Get()->ClearAndNotify();
11094 mIdentifierMap.Clear();
11095 mComposedShadowRoots.Clear();
11096 mResponsiveContent.Clear();
11097 IncrementExpandoGeneration(*this);
11100 void Document::RefreshLinkHrefs() {
11101 // Get a list of all links we know about. We will reset them, which will
11102 // remove them from the document, so we need a copy of what is in the
11103 // hashtable.
11104 LinkArray linksToNotify(mStyledLinks.Count());
11105 for (auto iter = mStyledLinks.ConstIter(); !iter.Done(); iter.Next()) {
11106 linksToNotify.AppendElement(iter.Get()->GetKey());
11109 // Reset all of our styled links.
11110 nsAutoScriptBlocker scriptBlocker;
11111 for (LinkArray::size_type i = 0; i < linksToNotify.Length(); i++) {
11112 linksToNotify[i]->ResetLinkState(true, linksToNotify[i]->ElementHasHref());
11116 nsresult Document::CloneDocHelper(Document* clone) const {
11117 clone->mIsStaticDocument = mCreatingStaticClone;
11119 // Init document
11120 nsresult rv = clone->Init();
11121 NS_ENSURE_SUCCESS(rv, rv);
11123 if (mCreatingStaticClone) {
11124 if (mOriginalDocument) {
11125 clone->mOriginalDocument = mOriginalDocument;
11126 } else {
11127 clone->mOriginalDocument = const_cast<Document*>(this);
11129 clone->mOriginalDocument->mLatestStaticClone = clone;
11130 clone->mOriginalDocument->mStaticCloneCount++;
11132 nsCOMPtr<nsILoadGroup> loadGroup;
11134 // |mDocumentContainer| is the container of the document that is being
11135 // created and not the original container. See CreateStaticClone function().
11136 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
11137 if (docLoader) {
11138 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
11140 nsCOMPtr<nsIChannel> channel = GetChannel();
11141 nsCOMPtr<nsIURI> uri;
11142 if (channel) {
11143 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
11144 } else {
11145 uri = Document::GetDocumentURI();
11147 clone->mChannel = channel;
11148 if (uri) {
11149 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
11152 clone->SetContainer(mDocumentContainer);
11154 // Setup the navigation time. This will be needed by any animations in the
11155 // document, even if they are only paused.
11156 MOZ_ASSERT(!clone->GetNavigationTiming(),
11157 "Navigation time was already set?");
11158 MOZ_ASSERT(mTiming,
11159 "Timing should have been setup before making a static clone");
11160 RefPtr<nsDOMNavigationTiming> timing =
11161 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
11162 clone->SetNavigationTiming(timing);
11163 clone->SetCsp(mCSP);
11166 // Now ensure that our clone has the same URI, base URI, and principal as us.
11167 // We do this after the mCreatingStaticClone block above, because that block
11168 // can set the base URI to an incorrect value in cases when base URI
11169 // information came from the channel. So we override explicitly, and do it
11170 // for all these properties, in case ResetToURI messes with any of the rest of
11171 // them.
11172 clone->SetDocumentURI(Document::GetDocumentURI());
11173 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
11174 clone->SetPrincipals(NodePrincipal(), mPartitionedPrincipal);
11175 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
11176 clone->mDocumentBaseURI = mDocumentBaseURI;
11177 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
11178 clone->mReferrerInfo =
11179 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
11180 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
11182 bool hasHadScriptObject = true;
11183 nsIScriptGlobalObject* scriptObject =
11184 GetScriptHandlingObject(hasHadScriptObject);
11185 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
11186 if (mCreatingStaticClone) {
11187 // If we're doing a static clone (print, print preview), then we're going to
11188 // be setting a scope object after the clone. It's better to set it only
11189 // once, so we don't do that here. However, we do want to act as if there is
11190 // a script handling object. So we set mHasHadScriptHandlingObject.
11191 clone->mHasHadScriptHandlingObject = true;
11192 } else if (scriptObject) {
11193 clone->SetScriptHandlingObject(scriptObject);
11194 } else {
11195 clone->SetScopeObject(GetScopeObject());
11197 // Make the clone a data document
11198 clone->SetLoadedAsData(true);
11200 // Misc state
11202 // State from Document
11203 clone->mCharacterSet = mCharacterSet;
11204 clone->mCharacterSetSource = mCharacterSetSource;
11205 clone->SetCompatibilityMode(mCompatMode);
11206 clone->mBidiOptions = mBidiOptions;
11207 clone->mContentLanguage = mContentLanguage;
11208 clone->SetContentTypeInternal(GetContentTypeInternal());
11209 clone->mSecurityInfo = mSecurityInfo;
11211 // State from Document
11212 clone->mType = mType;
11213 clone->mXMLDeclarationBits = mXMLDeclarationBits;
11214 clone->mBaseTarget = mBaseTarget;
11216 return NS_OK;
11219 void Document::NotifyLoading(bool aNewParentIsLoading,
11220 const ReadyState& aCurrentState,
11221 ReadyState aNewState) {
11222 // Mirror the top-level loading state down to all subdocuments
11223 bool was_loading = mAncestorIsLoading ||
11224 aCurrentState == READYSTATE_LOADING ||
11225 aCurrentState == READYSTATE_INTERACTIVE;
11226 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
11227 aNewState == READYSTATE_INTERACTIVE; // new value for state
11228 bool set_load_state = was_loading != is_loading;
11230 MOZ_LOG(
11231 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
11232 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
11233 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
11234 "set_load_state: %d",
11235 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
11236 (int)aNewState, was_loading, is_loading, set_load_state));
11238 mAncestorIsLoading = aNewParentIsLoading;
11239 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
11240 // Tell our innerwindow (and thus TimeoutManager)
11241 nsPIDOMWindowInner* inner = GetInnerWindow();
11242 if (inner) {
11243 inner->SetActiveLoadingState(is_loading);
11245 BrowsingContext* context = GetBrowsingContext();
11246 if (context) {
11247 // Don't use PreOrderWalk to mirror this down; go down one level as a
11248 // time so we can set mAncestorIsLoading and take into account the
11249 // readystates of the subdocument. In the child process it will call
11250 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
11251 // iterate it's children
11252 for (auto& child : context->Children()) {
11253 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
11254 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
11255 child->SetAncestorLoading(is_loading);
11261 void Document::SetReadyStateInternal(ReadyState aReadyState,
11262 bool aUpdateTimingInformation) {
11263 if (aReadyState == READYSTATE_UNINITIALIZED) {
11264 // Transition back to uninitialized happens only to keep assertions happy
11265 // right before readyState transitions to something else. Make this
11266 // transition undetectable by Web content.
11267 mReadyState = aReadyState;
11268 return;
11271 if (IsTopLevelContentDocument()) {
11272 if (aReadyState == READYSTATE_LOADING) {
11273 AddToplevelLoadingDocument(this);
11274 } else if (aReadyState == READYSTATE_COMPLETE) {
11275 RemoveToplevelLoadingDocument(this);
11279 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
11280 mLoadingTimeStamp = TimeStamp::Now();
11282 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
11283 mReadyState = aReadyState;
11284 if (aUpdateTimingInformation && mTiming) {
11285 switch (aReadyState) {
11286 case READYSTATE_LOADING:
11287 mTiming->NotifyDOMLoading(GetDocumentURI());
11288 break;
11289 case READYSTATE_INTERACTIVE:
11290 mTiming->NotifyDOMInteractive(GetDocumentURI());
11291 break;
11292 case READYSTATE_COMPLETE:
11293 mTiming->NotifyDOMComplete(GetDocumentURI());
11294 break;
11295 default:
11296 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
11297 break;
11300 // At the time of loading start, we don't have timing object, record time.
11302 if (READYSTATE_INTERACTIVE == aReadyState &&
11303 NodePrincipal()->IsSystemPrincipal()) {
11304 if (!mXULPersist) {
11305 mXULPersist = new XULPersist(this);
11306 mXULPersist->Init();
11308 if (!mChromeObserver) {
11309 mChromeObserver = new ChromeObserver(this);
11310 mChromeObserver->Init();
11314 if (aUpdateTimingInformation) {
11315 RecordNavigationTiming(aReadyState);
11318 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
11319 this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
11320 asyncDispatcher->RunDOMEventWhenSafe();
11323 void Document::GetReadyState(nsAString& aReadyState) const {
11324 switch (mReadyState) {
11325 case READYSTATE_LOADING:
11326 aReadyState.AssignLiteral(u"loading");
11327 break;
11328 case READYSTATE_INTERACTIVE:
11329 aReadyState.AssignLiteral(u"interactive");
11330 break;
11331 case READYSTATE_COMPLETE:
11332 aReadyState.AssignLiteral(u"complete");
11333 break;
11334 default:
11335 aReadyState.AssignLiteral(u"uninitialized");
11339 void Document::SuppressEventHandling(uint32_t aIncrease) {
11340 mEventsSuppressed += aIncrease;
11341 UpdateFrameRequestCallbackSchedulingState();
11342 for (uint32_t i = 0; i < aIncrease; ++i) {
11343 ScriptLoader()->AddExecuteBlocker();
11346 auto suppressInSubDoc = [aIncrease](Document& aSubDoc) {
11347 aSubDoc.SuppressEventHandling(aIncrease);
11348 return CallState::Continue;
11351 EnumerateSubDocuments(suppressInSubDoc);
11354 void Document::NotifyAbortedLoad() {
11355 // If we still have outstanding work blocking DOMContentLoaded,
11356 // then don't try to change the readystate now, but wait until
11357 // they finish and then do so.
11358 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
11359 mSetCompleteAfterDOMContentLoaded = true;
11360 return;
11363 // Otherwise we're fully done at this point, so set the
11364 // readystate to complete.
11365 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
11366 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
11370 static void FireOrClearDelayedEvents(nsTArray<nsCOMPtr<Document>>& aDocuments,
11371 bool aFireEvents) {
11372 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
11373 if (!fm) return;
11375 for (uint32_t i = 0; i < aDocuments.Length(); ++i) {
11376 // NB: Don't bother trying to fire delayed events on documents that were
11377 // closed before this event ran.
11378 if (!aDocuments[i]->EventHandlingSuppressed()) {
11379 fm->FireDelayedEvents(aDocuments[i]);
11380 RefPtr<PresShell> presShell = aDocuments[i]->GetPresShell();
11381 if (presShell) {
11382 // Only fire events for active documents.
11383 bool fire = aFireEvents && aDocuments[i]->GetInnerWindow() &&
11384 aDocuments[i]->GetInnerWindow()->IsCurrentInnerWindow();
11385 presShell->FireOrClearDelayedEvents(fire);
11387 aDocuments[i]->FireOrClearPostMessageEvents(aFireEvents);
11392 void Document::PreloadPictureClosed() {
11393 MOZ_ASSERT(mPreloadPictureDepth > 0);
11394 mPreloadPictureDepth--;
11395 if (mPreloadPictureDepth == 0) {
11396 mPreloadPictureFoundSource.SetIsVoid(true);
11400 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
11401 const nsAString& aSizesAttr,
11402 const nsAString& aTypeAttr,
11403 const nsAString& aMediaAttr) {
11404 // Nested pictures are not valid syntax, so while we'll eventually load them,
11405 // it's not worth tracking sources mixed between nesting levels to preload
11406 // them effectively.
11407 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
11408 // <picture> selects the first matching source, so if this returns a URI we
11409 // needn't consider new sources until a new <picture> is encountered.
11410 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
11411 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
11412 aMediaAttr, mPreloadPictureFoundSource);
11413 if (found && mPreloadPictureFoundSource.IsVoid()) {
11414 // Found an empty source, which counts
11415 mPreloadPictureFoundSource.SetIsVoid(false);
11420 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
11421 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
11422 const nsAString& aSizesAttr, bool* aIsImgSet) {
11423 nsString sourceURL;
11424 bool isImgSet;
11425 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
11426 // We're in a <picture> element and found a URI from a source previous to
11427 // this image, use it.
11428 sourceURL = mPreloadPictureFoundSource;
11429 isImgSet = true;
11430 } else {
11431 // Otherwise try to use this <img> as a source
11432 HTMLImageElement::SelectSourceForTagWithAttrs(
11433 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
11434 VoidString(), sourceURL);
11435 isImgSet = !aSrcsetAttr.IsEmpty();
11438 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
11439 if (sourceURL.IsEmpty()) {
11440 return nullptr;
11443 // Construct into URI using passed baseURI (the parser may know of base URI
11444 // changes that have not reached us)
11445 nsresult rv;
11446 nsCOMPtr<nsIURI> uri;
11447 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
11448 this, aBaseURI);
11449 if (NS_FAILED(rv)) {
11450 return nullptr;
11453 *aIsImgSet = isImgSet;
11455 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
11456 // this this <picture> share the same <sources> (though this is not valid per
11457 // spec)
11458 return uri.forget();
11461 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
11462 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
11463 bool aLinkPreload) {
11464 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
11465 nsContentUtils::CORSModeToLoadImageFlags(
11466 Element::StringToCORSMode(aCrossOriginAttr));
11468 nsContentPolicyType policyType =
11469 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
11470 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
11472 nsCOMPtr<nsIReferrerInfo> referrerInfo =
11473 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
11475 RefPtr<imgRequestProxy> request;
11476 nsresult rv = nsContentUtils::LoadImage(
11477 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
11478 nullptr /* no observer */, loadFlags,
11479 aLinkPreload ? u"link"_ns : u"img"_ns, getter_AddRefs(request),
11480 policyType, false /* urgent */, aLinkPreload);
11482 // Pin image-reference to avoid evicting it from the img-cache before
11483 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
11484 // unlink
11485 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
11486 mPreloadingImages.Put(aUri, std::move(request));
11490 void Document::MaybePreLoadImage(nsIURI* aUri,
11491 const nsAString& aCrossOriginAttr,
11492 ReferrerPolicyEnum aReferrerPolicy,
11493 bool aIsImgSet, bool aLinkPreload) {
11494 if (aLinkPreload) {
11495 // Check if the image was already preloaded in this document to avoid
11496 // duplicate preloading.
11497 PreloadHashKey key = PreloadHashKey::CreateAsImage(
11498 aUri, NodePrincipal(),
11499 dom::Element::StringToCORSMode(aCrossOriginAttr));
11500 if (!mPreloadService.PreloadExists(key)) {
11501 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
11502 aLinkPreload);
11504 return;
11507 // Early exit if the img is already present in the img-cache
11508 // which indicates that the "real" load has already started and
11509 // that we shouldn't preload it.
11510 if (nsContentUtils::IsImageInCache(aUri, this)) {
11511 return;
11514 // Image not in cache - trigger preload
11515 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
11516 aLinkPreload);
11519 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
11520 NS_MutateURI mutator(aOrigURI);
11521 if (NS_FAILED(mutator.GetStatus())) {
11522 return;
11525 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
11526 // which ignores the path and uses only the origin. The other is for the
11527 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
11528 // non-Anonymous preconnects create different connections on the wire and
11529 // therefore should not be considred duplicates of each other and we
11530 // normalize the path before putting it in the hash to accomplish that.
11532 if (aCORSMode == CORS_ANONYMOUS) {
11533 mutator.SetPathQueryRef("/anonymous"_ns);
11534 } else {
11535 mutator.SetPathQueryRef("/"_ns);
11538 nsCOMPtr<nsIURI> uri;
11539 nsresult rv = mutator.Finalize(uri);
11540 if (NS_FAILED(rv)) {
11541 return;
11544 auto entry = mPreloadedPreconnects.LookupForAdd(uri);
11545 if (entry) {
11546 return; // we found an existing entry
11548 entry.OrInsert([]() { return true; });
11550 nsCOMPtr<nsISpeculativeConnect> speculator(
11551 do_QueryInterface(nsContentUtils::GetIOService()));
11552 if (!speculator) {
11553 return;
11556 if (aCORSMode == CORS_ANONYMOUS) {
11557 speculator->SpeculativeAnonymousConnect(uri, NodePrincipal(), nullptr);
11558 } else {
11559 speculator->SpeculativeConnect(uri, NodePrincipal(), nullptr);
11563 void Document::ForgetImagePreload(nsIURI* aURI) {
11564 // Checking count is faster than hashing the URI in the common
11565 // case of empty table.
11566 if (mPreloadingImages.Count() != 0) {
11567 nsCOMPtr<imgIRequest> req;
11568 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
11569 if (req) {
11570 // Make sure to cancel the request so imagelib knows it's gone.
11571 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
11576 void Document::UpdateDocumentStates(EventStates aMaybeChangedStates,
11577 bool aNotify) {
11578 EventStates oldStates = mDocumentState;
11579 if (aMaybeChangedStates.HasState(NS_DOCUMENT_STATE_RTL_LOCALE)) {
11580 if (IsDocumentRightToLeft()) {
11581 mDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
11582 } else {
11583 mDocumentState &= ~NS_DOCUMENT_STATE_RTL_LOCALE;
11587 if (aMaybeChangedStates.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
11588 if (IsTopLevelWindowInactive()) {
11589 mDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
11590 } else {
11591 mDocumentState &= ~NS_DOCUMENT_STATE_WINDOW_INACTIVE;
11595 EventStates changedStates = oldStates ^ mDocumentState;
11596 if (aNotify && !changedStates.IsEmpty()) {
11597 if (PresShell* ps = GetObservingPresShell()) {
11598 ps->DocumentStatesChanged(changedStates);
11603 namespace {
11606 * Stub for LoadSheet(), since all we want is to get the sheet into
11607 * the CSSLoader's style cache
11609 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
11610 ~StubCSSLoaderObserver() = default;
11612 public:
11613 NS_IMETHOD
11614 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
11615 NS_DECL_ISUPPORTS
11617 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
11619 } // namespace
11621 SheetPreloadStatus Document::PreloadStyle(
11622 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
11623 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aIntegrity,
11624 bool aIsLinkPreload) {
11625 // The CSSLoader will retain this object after we return.
11626 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
11628 nsCOMPtr<nsIReferrerInfo> referrerInfo =
11629 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
11631 auto preloadType = aIsLinkPreload ? css::Loader::IsPreload::FromLink
11632 : css::Loader::IsPreload::FromParser;
11634 // Charset names are always ASCII.
11635 auto result = CSSLoader()->LoadSheet(
11636 uri, preloadType, aEncoding, referrerInfo, obs,
11637 Element::StringToCORSMode(aCrossOriginAttr), aIntegrity);
11638 if (result.isErr()) {
11639 return SheetPreloadStatus::Errored;
11641 RefPtr<StyleSheet> sheet = result.unwrap();
11642 if (sheet->IsComplete()) {
11643 return SheetPreloadStatus::AlreadyComplete;
11645 return SheetPreloadStatus::InProgress;
11648 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
11649 return CSSLoader()
11650 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
11651 .unwrapOr(nullptr);
11654 void Document::ResetDocumentDirection() {
11655 if (!nsContentUtils::IsChromeDoc(this)) {
11656 return;
11658 UpdateDocumentStates(NS_DOCUMENT_STATE_RTL_LOCALE, true);
11661 bool Document::IsDocumentRightToLeft() {
11662 if (!nsContentUtils::IsChromeDoc(this)) {
11663 return false;
11665 // setting the localedir attribute on the root element forces a
11666 // specific direction for the document.
11667 Element* element = GetRootElement();
11668 if (element) {
11669 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
11670 nullptr};
11671 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
11672 strings, eCaseMatters)) {
11673 case 0:
11674 return false;
11675 case 1:
11676 return true;
11677 default:
11678 break; // otherwise, not a valid value, so fall through
11682 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
11683 !mDocumentURI->SchemeIs("resource")) {
11684 return false;
11687 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
11690 class nsDelayedEventDispatcher : public Runnable {
11691 public:
11692 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>& aDocuments)
11693 : mozilla::Runnable("nsDelayedEventDispatcher") {
11694 mDocuments.SwapElements(aDocuments);
11696 virtual ~nsDelayedEventDispatcher() = default;
11698 NS_IMETHOD Run() override {
11699 FireOrClearDelayedEvents(mDocuments, true);
11700 return NS_OK;
11703 private:
11704 nsTArray<nsCOMPtr<Document>> mDocuments;
11707 static void GetAndUnsuppressSubDocuments(
11708 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
11709 if (aDocument.EventHandlingSuppressed() > 0) {
11710 aDocument.DecreaseEventSuppression();
11711 aDocument.ScriptLoader()->RemoveExecuteBlocker();
11713 aDocuments.AppendElement(&aDocument);
11714 auto recurse = [&aDocuments](Document& aSubDoc) {
11715 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
11716 return CallState::Continue;
11718 aDocument.EnumerateSubDocuments(recurse);
11721 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
11722 nsTArray<nsCOMPtr<Document>> documents;
11723 GetAndUnsuppressSubDocuments(*this, documents);
11725 if (aFireEvents) {
11726 MOZ_RELEASE_ASSERT(NS_IsMainThread());
11727 nsCOMPtr<nsIRunnable> ded = new nsDelayedEventDispatcher(documents);
11728 Dispatch(TaskCategory::Other, ded.forget());
11729 } else {
11730 FireOrClearDelayedEvents(documents, false);
11733 if (!EventHandlingSuppressed()) {
11734 MOZ_ASSERT(NS_IsMainThread());
11735 nsTArray<RefPtr<net::ChannelEventQueue>> queues;
11736 mSuspendedQueues.SwapElements(queues);
11737 for (net::ChannelEventQueue* queue : queues) {
11738 queue->Resume();
11741 // If there have been any events driven by the refresh driver which were
11742 // delayed due to events being suppressed in this document, make sure there
11743 // is a refresh scheduled soon so the events will run.
11744 if (mHasDelayedRefreshEvent) {
11745 mHasDelayedRefreshEvent = false;
11747 if (mPresShell) {
11748 nsRefreshDriver* rd = mPresShell->GetPresContext()->RefreshDriver();
11749 rd->RunDelayedEventsSoon();
11755 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
11756 MOZ_ASSERT(NS_IsMainThread());
11757 MOZ_ASSERT(EventHandlingSuppressed());
11758 mSuspendedQueues.AppendElement(aQueue);
11761 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
11762 MOZ_ASSERT(NS_IsMainThread());
11764 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
11765 mSuspendedPostMessageEvents.AppendElement(aEvent);
11766 return true;
11768 return false;
11771 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
11772 nsTArray<RefPtr<PostMessageEvent>> events;
11773 events.SwapElements(mSuspendedPostMessageEvents);
11775 if (aFireEvents) {
11776 for (PostMessageEvent* event : events) {
11777 event->Run();
11782 void Document::SetSuppressedEventListener(EventListener* aListener) {
11783 mSuppressedEventListener = aListener;
11784 auto setOnSubDocs = [&](Document& aDocument) {
11785 aDocument.SetSuppressedEventListener(aListener);
11786 return CallState::Continue;
11788 EnumerateSubDocuments(setOnSubDocs);
11791 nsISupports* Document::GetCurrentContentSink() {
11792 return mParser ? mParser->GetContentSink() : nullptr;
11795 Document* Document::GetTemplateContentsOwner() {
11796 if (!mTemplateContentsOwner) {
11797 bool hasHadScriptObject = true;
11798 nsIScriptGlobalObject* scriptObject =
11799 GetScriptHandlingObject(hasHadScriptObject);
11801 nsCOMPtr<Document> document;
11802 nsresult rv = NS_NewDOMDocument(getter_AddRefs(document),
11803 EmptyString(), // aNamespaceURI
11804 EmptyString(), // aQualifiedName
11805 nullptr, // aDoctype
11806 Document::GetDocumentURI(),
11807 Document::GetDocBaseURI(), NodePrincipal(),
11808 true, // aLoadedAsData
11809 scriptObject, // aEventObject
11810 DocumentFlavorHTML);
11811 NS_ENSURE_SUCCESS(rv, nullptr);
11813 mTemplateContentsOwner = document;
11814 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
11816 if (!scriptObject) {
11817 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
11820 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
11822 // Set |mTemplateContentsOwner| as the template contents owner of itself so
11823 // that it is the template contents owner of nested template elements.
11824 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
11827 return mTemplateContentsOwner;
11830 static already_AddRefed<nsPIDOMWindowOuter> FindTopWindowForElement(
11831 Element* element) {
11832 Document* document = element->OwnerDoc();
11833 if (!document) {
11834 return nullptr;
11837 nsCOMPtr<nsPIDOMWindowOuter> window = document->GetWindow();
11838 if (!window) {
11839 return nullptr;
11842 // Trying to find the top window (equivalent to window.top).
11843 if (nsCOMPtr<nsPIDOMWindowOuter> top = window->GetInProcessTop()) {
11844 window = std::move(top);
11846 return window.forget();
11850 * nsAutoFocusEvent is used to dispatch a focus event for an
11851 * nsGenericHTMLFormElement with the autofocus attribute enabled.
11853 class nsAutoFocusEvent : public Runnable {
11854 public:
11855 explicit nsAutoFocusEvent(nsCOMPtr<Element>&& aElement,
11856 nsCOMPtr<nsPIDOMWindowOuter>&& aTopWindow)
11857 : mozilla::Runnable("nsAutoFocusEvent"),
11858 mElement(std::move(aElement)),
11859 mTopWindow(std::move(aTopWindow)) {}
11861 NS_IMETHOD Run() override {
11862 nsCOMPtr<nsPIDOMWindowOuter> currentTopWindow =
11863 FindTopWindowForElement(mElement);
11864 if (currentTopWindow != mTopWindow) {
11865 // The element's top window changed from when the event was queued.
11866 // Don't take away focus from an unrelated window.
11867 return NS_OK;
11870 if (Document* doc = mTopWindow->GetExtantDoc()) {
11871 if (doc->IsAutoFocusFired()) {
11872 return NS_OK;
11874 doc->SetAutoFocusFired();
11877 // Don't steal focus from the user.
11878 if (mTopWindow->GetFocusedElement()) {
11879 return NS_OK;
11882 FocusOptions options;
11883 ErrorResult rv;
11884 mElement->Focus(options, CallerType::System, rv);
11885 return rv.StealNSResult();
11888 private:
11889 nsCOMPtr<Element> mElement;
11890 nsCOMPtr<nsPIDOMWindowOuter> mTopWindow;
11893 void Document::SetAutoFocusElement(Element* aAutoFocusElement) {
11894 if (mAutoFocusFired) {
11895 // Too late.
11896 return;
11899 if (mAutoFocusElement) {
11900 // The spec disallows multiple autofocus elements, so we consider only the
11901 // first one to preserve the old behavior.
11902 return;
11905 mAutoFocusElement = do_GetWeakReference(aAutoFocusElement);
11906 TriggerAutoFocus();
11909 void Document::SetAutoFocusFired() { mAutoFocusFired = true; }
11911 bool Document::IsAutoFocusFired() { return mAutoFocusFired; }
11913 void Document::TriggerAutoFocus() {
11914 if (mAutoFocusFired) {
11915 return;
11918 if (!mPresShell || !mPresShell->DidInitialize()) {
11919 // Delay autofocus until frames are constructed so that we don't thrash
11920 // style and layout calculations.
11921 return;
11924 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(mAutoFocusElement);
11925 if (autoFocusElement && autoFocusElement->OwnerDoc() == this) {
11926 nsCOMPtr<nsPIDOMWindowOuter> topWindow =
11927 FindTopWindowForElement(autoFocusElement);
11928 if (!topWindow) {
11929 return;
11932 // NOTE: This may be removed in the future since the spec technically
11933 // allows autofocus after load.
11934 nsCOMPtr<Document> topDoc = topWindow->GetExtantDoc();
11935 if (topDoc &&
11936 topDoc->GetReadyStateEnum() == Document::READYSTATE_COMPLETE) {
11937 return;
11940 nsCOMPtr<nsIRunnable> event =
11941 new nsAutoFocusEvent(std::move(autoFocusElement), topWindow.forget());
11942 nsresult rv = NS_DispatchToCurrentThread(event.forget());
11943 NS_ENSURE_SUCCESS_VOID(rv);
11947 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
11948 if (!aDocumentURI) {
11949 return;
11952 nsAutoCString ref;
11954 // Since all URI's that pass through here aren't URL's we can't
11955 // rely on the nsIURI implementation for providing a way for
11956 // finding the 'ref' part of the URI, we'll haveto revert to
11957 // string routines for finding the data past '#'
11959 nsresult rv = aDocumentURI->GetSpec(ref);
11960 if (NS_FAILED(rv)) {
11961 Unused << aDocumentURI->GetRef(mScrollToRef);
11962 return;
11965 nsReadingIterator<char> start, end;
11967 ref.BeginReading(start);
11968 ref.EndReading(end);
11970 if (FindCharInReadable('#', start, end)) {
11971 ++start; // Skip over the '#'
11973 mScrollToRef = Substring(start, end);
11977 void Document::ScrollToRef() {
11978 if (mScrolledToRefAlready) {
11979 RefPtr<PresShell> presShell = GetPresShell();
11980 if (presShell) {
11981 presShell->ScrollToAnchor();
11983 return;
11986 if (mScrollToRef.IsEmpty()) {
11987 return;
11990 RefPtr<PresShell> presShell = GetPresShell();
11991 if (presShell) {
11992 nsresult rv = NS_ERROR_FAILURE;
11993 // We assume that the bytes are in UTF-8, as it says in the spec:
11994 // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
11995 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
11996 // Check an empty string which might be caused by the UTF-8 conversion
11997 if (!ref.IsEmpty()) {
11998 // Note that GoToAnchor will handle flushing layout as needed.
11999 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
12000 } else {
12001 rv = NS_ERROR_FAILURE;
12004 if (NS_FAILED(rv)) {
12005 nsAutoCString buff;
12006 const bool unescaped =
12007 NS_UnescapeURL(mScrollToRef.BeginReading(), mScrollToRef.Length(),
12008 /*aFlags =*/0, buff);
12010 // This attempt is only necessary if characters were unescaped.
12011 if (unescaped) {
12012 NS_ConvertUTF8toUTF16 utf16Str(buff);
12013 if (!utf16Str.IsEmpty()) {
12014 rv = presShell->GoToAnchor(utf16Str,
12015 mChangeScrollPosWhenScrollingToRef);
12019 // If UTF-8 URI failed then try to assume the string as a
12020 // document's charset.
12021 if (NS_FAILED(rv)) {
12022 const Encoding* encoding = GetDocumentCharacterSet();
12023 rv = encoding->DecodeWithoutBOMHandling(unescaped ? buff : mScrollToRef,
12024 ref);
12025 if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
12026 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
12030 if (NS_SUCCEEDED(rv)) {
12031 mScrolledToRefAlready = true;
12036 void Document::RegisterActivityObserver(nsISupports* aSupports) {
12037 if (!mActivityObservers) {
12038 mActivityObservers = MakeUnique<nsTHashtable<nsPtrHashKey<nsISupports>>>();
12040 mActivityObservers->PutEntry(aSupports);
12043 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
12044 if (!mActivityObservers) {
12045 return false;
12047 nsPtrHashKey<nsISupports>* entry = mActivityObservers->GetEntry(aSupports);
12048 if (!entry) {
12049 return false;
12051 mActivityObservers->RemoveEntry(entry);
12052 return true;
12055 void Document::EnumerateActivityObservers(
12056 ActivityObserverEnumerator aEnumerator) {
12057 if (!mActivityObservers) {
12058 return;
12061 nsTArray<nsCOMPtr<nsISupports>> observers(mActivityObservers->Count());
12062 for (auto iter = mActivityObservers->ConstIter(); !iter.Done(); iter.Next()) {
12063 observers.AppendElement(iter.Get()->GetKey());
12066 for (auto& observer : observers) {
12067 aEnumerator(observer.get());
12071 void Document::RegisterPendingLinkUpdate(Link* aLink) {
12072 if (aLink->HasPendingLinkUpdate()) {
12073 return;
12076 aLink->SetHasPendingLinkUpdate();
12078 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
12079 nsCOMPtr<nsIRunnable> event =
12080 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
12081 &Document::FlushPendingLinkUpdates);
12082 // Do this work in a second in the worst case.
12083 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
12084 EventQueuePriority::Idle);
12085 if (NS_FAILED(rv)) {
12086 // If during shutdown posting a runnable doesn't succeed, we probably
12087 // don't need to update link states.
12088 return;
12090 mHasLinksToUpdateRunnable = true;
12093 mLinksToUpdate.InfallibleAppend(aLink);
12096 void Document::FlushPendingLinkUpdates() {
12097 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
12098 MOZ_ASSERT(mHasLinksToUpdateRunnable);
12099 mHasLinksToUpdateRunnable = false;
12101 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
12102 mFlushingPendingLinkUpdates = true;
12104 while (!mLinksToUpdate.IsEmpty()) {
12105 LinksToUpdateList links(std::move(mLinksToUpdate));
12106 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
12107 Link* link = iter.Get();
12108 Element* element = link->GetElement();
12109 if (element->OwnerDoc() == this) {
12110 link->ClearHasPendingLinkUpdate();
12111 if (element->IsInComposedDoc()) {
12112 element->UpdateLinkState(link->LinkState());
12119 already_AddRefed<Document> Document::CreateStaticClone(
12120 nsIDocShell* aCloneContainer) {
12121 MOZ_ASSERT(!mCreatingStaticClone);
12122 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
12123 mCreatingStaticClone = true;
12124 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
12125 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
12127 auto raii = MakeScopeExit([&] {
12128 RemoveProperty(nsGkAtoms::adoptedsheetclones);
12129 mCreatingStaticClone = false;
12132 // Make document use different container during cloning.
12133 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
12134 SetContainer(static_cast<nsDocShell*>(aCloneContainer));
12135 IgnoredErrorResult rv;
12136 nsCOMPtr<nsINode> clonedNode = this->CloneNode(true, rv);
12137 SetContainer(originalShell);
12138 if (rv.Failed()) {
12139 return nullptr;
12142 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
12143 if (clonedDoc) {
12144 size_t sheetsCount = SheetCount();
12145 for (size_t i = 0; i < sheetsCount; ++i) {
12146 RefPtr<StyleSheet> sheet = SheetAt(i);
12147 if (sheet) {
12148 if (sheet->IsApplicable()) {
12149 RefPtr<StyleSheet> clonedSheet =
12150 sheet->Clone(nullptr, nullptr, clonedDoc, nullptr);
12151 NS_WARNING_ASSERTION(clonedSheet,
12152 "Cloning a stylesheet didn't work!");
12153 if (clonedSheet) {
12154 clonedDoc->AddStyleSheet(clonedSheet);
12159 clonedDoc->CloneAdoptedSheetsFrom(*this);
12161 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
12162 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
12163 for (StyleSheet* sheet : sheets) {
12164 if (sheet->IsApplicable()) {
12165 RefPtr<StyleSheet> clonedSheet =
12166 sheet->Clone(nullptr, nullptr, clonedDoc, nullptr);
12167 NS_WARNING_ASSERTION(clonedSheet,
12168 "Cloning a stylesheet didn't work!");
12169 if (clonedSheet) {
12170 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
12171 clonedSheet);
12177 // Font faces created with the JS API will not be reflected in the
12178 // stylesheets and need to be copied over to the cloned document.
12179 if (const FontFaceSet* set = GetFonts()) {
12180 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
12183 clonedDoc->mReferrerInfo =
12184 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
12185 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
12188 return clonedDoc.forget();
12191 void Document::UnlinkOriginalDocumentIfStatic() {
12192 if (IsStaticDocument() && mOriginalDocument) {
12193 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
12194 mOriginalDocument->mStaticCloneCount--;
12195 mOriginalDocument = nullptr;
12197 MOZ_ASSERT(!mOriginalDocument);
12200 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
12201 int32_t* aHandle) {
12202 if (mFrameRequestCallbackCounter == INT32_MAX) {
12203 // Can't increment without overflowing; bail out
12204 return NS_ERROR_NOT_AVAILABLE;
12206 int32_t newHandle = ++mFrameRequestCallbackCounter;
12208 mFrameRequestCallbacks.AppendElement(FrameRequest(aCallback, newHandle));
12209 UpdateFrameRequestCallbackSchedulingState();
12211 *aHandle = newHandle;
12212 return NS_OK;
12215 void Document::CancelFrameRequestCallback(int32_t aHandle) {
12216 // mFrameRequestCallbacks is stored sorted by handle
12217 if (mFrameRequestCallbacks.RemoveElementSorted(aHandle)) {
12218 UpdateFrameRequestCallbackSchedulingState();
12219 } else {
12220 Unused << mCanceledFrameRequestCallbacks.put(aHandle);
12224 bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
12225 return !mCanceledFrameRequestCallbacks.empty() &&
12226 mCanceledFrameRequestCallbacks.has(aHandle);
12229 nsresult Document::GetStateObject(nsIVariant** aState) {
12230 // Get the document's current state object. This is the object backing both
12231 // history.state and popStateEvent.state.
12233 // mStateObjectContainer may be null; this just means that there's no
12234 // current state object.
12236 if (!mStateObjectCached && mStateObjectContainer) {
12237 AutoJSAPI jsapi;
12238 // Init with null is "OK" in the sense that it will just fail.
12239 if (!jsapi.Init(GetScopeObject())) {
12240 return NS_ERROR_UNEXPECTED;
12242 mStateObjectContainer->DeserializeToVariant(
12243 jsapi.cx(), getter_AddRefs(mStateObjectCached));
12246 NS_IF_ADDREF(*aState = mStateObjectCached);
12247 return NS_OK;
12250 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
12251 mTiming = aTiming;
12252 if (!mLoadingTimeStamp.IsNull() && mTiming) {
12253 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(), mLoadingTimeStamp);
12257 nsContentList* Document::ImageMapList() {
12258 if (!mImageMaps) {
12259 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
12260 nsGkAtoms::map);
12263 return mImageMaps;
12266 #define DEPRECATED_OPERATION(_op) #_op "Warning",
12267 static const char* kDeprecationWarnings[] = {
12268 #include "nsDeprecatedOperationList.h"
12269 nullptr};
12270 #undef DEPRECATED_OPERATION
12272 #define DOCUMENT_WARNING(_op) #_op "Warning",
12273 static const char* kDocumentWarnings[] = {
12274 #include "nsDocumentWarningList.h"
12275 nullptr};
12276 #undef DOCUMENT_WARNING
12278 static UseCounter OperationToUseCounter(
12279 Document::DeprecatedOperations aOperation) {
12280 switch (aOperation) {
12281 #define DEPRECATED_OPERATION(_op) \
12282 case Document::e##_op: \
12283 return eUseCounter_##_op;
12284 #include "nsDeprecatedOperationList.h"
12285 #undef DEPRECATED_OPERATION
12286 default:
12287 MOZ_CRASH();
12291 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
12292 return mDeprecationWarnedAbout[aOperation];
12295 void Document::WarnOnceAbout(
12296 DeprecatedOperations aOperation, bool asError /* = false */,
12297 const nsTArray<nsString>& aParams /* = empty array */) const {
12298 MOZ_ASSERT(NS_IsMainThread());
12299 if (HasWarnedAbout(aOperation)) {
12300 return;
12302 mDeprecationWarnedAbout[aOperation] = true;
12303 // Don't count deprecated operations for about pages since those pages
12304 // are almost in our control, and we always need to remove uses there
12305 // before we remove the operation itself anyway.
12306 if (!IsAboutPage()) {
12307 const_cast<Document*>(this)->SetUseCounter(
12308 OperationToUseCounter(aOperation));
12310 uint32_t flags =
12311 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
12312 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
12313 nsContentUtils::eDOM_PROPERTIES,
12314 kDeprecationWarnings[aOperation], aParams);
12317 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
12318 return mDocWarningWarnedAbout[aWarning];
12321 void Document::WarnOnceAbout(
12322 DocumentWarnings aWarning, bool asError /* = false */,
12323 const nsTArray<nsString>& aParams /* = empty array */) const {
12324 MOZ_ASSERT(NS_IsMainThread());
12325 if (HasWarnedAbout(aWarning)) {
12326 return;
12328 mDocWarningWarnedAbout[aWarning] = true;
12329 uint32_t flags =
12330 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
12331 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
12332 nsContentUtils::eDOM_PROPERTIES,
12333 kDocumentWarnings[aWarning], aParams);
12336 mozilla::dom::ImageTracker* Document::ImageTracker() {
12337 if (!mImageTracker) {
12338 mImageTracker = new mozilla::dom::ImageTracker;
12340 return mImageTracker;
12343 void Document::GetPlugins(nsTArray<nsIObjectLoadingContent*>& aPlugins) {
12344 aPlugins.SetCapacity(aPlugins.Length() + mPlugins.Count());
12345 for (auto iter = mPlugins.ConstIter(); !iter.Done(); iter.Next()) {
12346 aPlugins.AppendElement(iter.Get()->GetKey());
12348 auto recurse = [&aPlugins](Document& aSubDoc) {
12349 aSubDoc.GetPlugins(aPlugins);
12350 return CallState::Continue;
12352 EnumerateSubDocuments(recurse);
12355 void Document::ScheduleSVGUseElementShadowTreeUpdate(
12356 SVGUseElement& aUseElement) {
12357 MOZ_ASSERT(aUseElement.IsInComposedDoc());
12359 mSVGUseElementsNeedingShadowTreeUpdate.PutEntry(&aUseElement);
12361 if (PresShell* presShell = GetPresShell()) {
12362 presShell->EnsureStyleFlush();
12366 void Document::DoUpdateSVGUseElementShadowTrees() {
12367 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
12368 nsTArray<RefPtr<SVGUseElement>> useElementsToUpdate;
12370 do {
12371 useElementsToUpdate.Clear();
12372 useElementsToUpdate.SetCapacity(
12373 mSVGUseElementsNeedingShadowTreeUpdate.Count());
12376 for (auto iter = mSVGUseElementsNeedingShadowTreeUpdate.ConstIter();
12377 !iter.Done(); iter.Next()) {
12378 useElementsToUpdate.AppendElement(iter.Get()->GetKey());
12380 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
12383 for (auto& useElement : useElementsToUpdate) {
12384 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
12385 // The element was in another <use> shadow tree which we processed
12386 // already and also needed an update, and is removed from the document
12387 // now, so nothing to do here.
12388 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
12389 continue;
12391 useElement->UpdateShadowTree();
12393 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
12396 void Document::NotifyMediaFeatureValuesChanged() {
12397 for (auto iter = mResponsiveContent.ConstIter(); !iter.Done(); iter.Next()) {
12398 RefPtr<HTMLImageElement> imageElement = iter.Get()->GetKey();
12399 imageElement->MediaFeatureValuesChanged();
12403 already_AddRefed<Touch> Document::CreateTouch(
12404 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
12405 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
12406 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
12407 float aRotationAngle, float aForce) {
12408 RefPtr<Touch> touch =
12409 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
12410 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
12411 return touch.forget();
12414 already_AddRefed<TouchList> Document::CreateTouchList() {
12415 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
12416 return retval.forget();
12419 already_AddRefed<TouchList> Document::CreateTouchList(
12420 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
12421 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
12422 retval->Append(&aTouch);
12423 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
12424 retval->Append(aTouches[i].get());
12426 return retval.forget();
12429 already_AddRefed<TouchList> Document::CreateTouchList(
12430 const Sequence<OwningNonNull<Touch>>& aTouches) {
12431 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
12432 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
12433 retval->Append(aTouches[i].get());
12435 return retval.forget();
12438 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
12439 float aX, float aY) {
12440 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
12442 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
12443 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
12444 nsPoint pt(x, y);
12446 FlushPendingNotifications(FlushType::Layout);
12448 PresShell* presShell = GetPresShell();
12449 if (!presShell) {
12450 return nullptr;
12453 nsIFrame* rootFrame = presShell->GetRootFrame();
12455 // XUL docs, unlike HTML, have no frame tree until everything's done loading
12456 if (!rootFrame) {
12457 return nullptr;
12460 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
12461 RelativeTo{rootFrame}, pt,
12462 {FrameForPointOption::IgnorePaintSuppression,
12463 FrameForPointOption::IgnoreCrossDoc});
12464 if (!ptFrame) {
12465 return nullptr;
12468 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
12469 nsPoint aOffset;
12470 nsCOMPtr<nsIWidget> widget = nsContentUtils::GetWidget(presShell, &aOffset);
12471 LayoutDeviceIntPoint refPoint = nsContentUtils::ToWidgetPoint(
12472 CSSPoint(aX, aY), aOffset, GetPresContext());
12473 nsPoint adjustedPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
12474 widget, refPoint, RelativeTo{ptFrame});
12476 nsIFrame::ContentOffsets offsets =
12477 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
12479 nsCOMPtr<nsIContent> node = offsets.content;
12480 uint32_t offset = offsets.offset;
12481 nsCOMPtr<nsIContent> anonNode = node;
12482 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
12483 if (nodeIsAnonymous) {
12484 node = ptFrame->GetContent();
12485 nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
12486 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
12487 nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
12488 if (textFrame) {
12489 // If the anonymous content node has a child, then we need to make sure
12490 // that we get the appropriate child, as otherwise the offset may not be
12491 // correct when we construct a range for it.
12492 nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
12493 if (firstChild) {
12494 anonNode = firstChild;
12497 if (textArea) {
12498 offset =
12499 nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
12502 node = nonanon;
12503 } else {
12504 node = nullptr;
12505 offset = 0;
12509 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
12510 if (nodeIsAnonymous) {
12511 aCaretPos->SetAnonymousContentNode(anonNode);
12513 return aCaretPos.forget();
12516 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
12517 // We rely on correct frame information here, so need to flush frames.
12518 FlushPendingNotifications(FlushType::Frames);
12520 // An element that is the HTML body element is potentially scrollable if all
12521 // of the following conditions are true:
12523 // The element has an associated CSS layout box.
12524 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
12525 if (!bodyFrame) {
12526 return false;
12529 // The element's parent element's computed value of the overflow-x or
12530 // overflow-y properties is neither visible nor clip.
12531 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
12532 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
12533 if (parentFrame && !parentFrame->StyleDisplay()->IsScrollableOverflow()) {
12534 return false;
12537 // The element's computed value of the overflow-x or overflow-y properties is
12538 // neither visible nor clip.
12539 if (!bodyFrame->StyleDisplay()->IsScrollableOverflow()) {
12540 return false;
12543 return true;
12546 Element* Document::GetScrollingElement() {
12547 // Keep this in sync with IsScrollingElement.
12548 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
12549 RefPtr<HTMLBodyElement> body = GetBodyElement();
12550 if (body && !IsPotentiallyScrollable(body)) {
12551 return body;
12554 return nullptr;
12557 return GetRootElement();
12560 bool Document::IsScrollingElement(Element* aElement) {
12561 // Keep this in sync with GetScrollingElement.
12562 MOZ_ASSERT(aElement);
12564 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
12565 return aElement == GetRootElement();
12568 // In the common case when aElement != body, avoid refcounting.
12569 HTMLBodyElement* body = GetBodyElement();
12570 if (aElement != body) {
12571 return false;
12574 // Now we know body is non-null, since aElement is not null. It's the
12575 // scrolling element for the document if it itself is not potentially
12576 // scrollable.
12577 RefPtr<HTMLBodyElement> strongBody(body);
12578 return !IsPotentiallyScrollable(strongBody);
12581 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
12582 public:
12583 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
12584 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
12586 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
12587 const BlockParsingOptions& aOptions)
12588 : mPromise(aPromise) {
12589 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
12590 if (parser &&
12591 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
12592 parser->BlockParser();
12593 mParser = do_GetWeakReference(parser);
12594 mDocument = aDocument;
12595 mDocument->BlockOnload();
12596 mDocument->BlockDOMContentLoaded();
12600 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
12601 MaybeUnblockParser();
12603 mPromise->MaybeResolve(aValue);
12606 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
12607 MaybeUnblockParser();
12609 mPromise->MaybeReject(aValue);
12612 protected:
12613 virtual ~UnblockParsingPromiseHandler() {
12614 // If we're being cleaned up by the cycle collector, our mDocument reference
12615 // may have been unlinked while our mParser weak reference is still alive.
12616 if (mDocument) {
12617 MaybeUnblockParser();
12621 private:
12622 void MaybeUnblockParser() {
12623 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
12624 if (parser) {
12625 MOZ_DIAGNOSTIC_ASSERT(mDocument);
12626 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
12627 if (parser == docParser) {
12628 parser->UnblockParser();
12629 parser->ContinueInterruptedParsingAsync();
12632 if (mDocument) {
12633 // We blocked DOMContentLoaded and load events on this document. Unblock
12634 // them. Note that we want to do that no matter what's going on with the
12635 // parser state for this document. Maybe someone caused it to stop being
12636 // parsed, so CreatorParserOrNull() is returning null, but we still want
12637 // to unblock these.
12638 mDocument->UnblockDOMContentLoaded();
12639 mDocument->UnblockOnload(false);
12641 mParser = nullptr;
12642 mDocument = nullptr;
12645 nsWeakPtr mParser;
12646 RefPtr<Promise> mPromise;
12647 RefPtr<Document> mDocument;
12650 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
12652 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
12653 NS_INTERFACE_MAP_ENTRY(nsISupports)
12654 NS_INTERFACE_MAP_END
12656 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
12657 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
12659 already_AddRefed<Promise> Document::BlockParsing(
12660 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
12661 RefPtr<Promise> resultPromise =
12662 Promise::Create(aPromise.GetParentObject(), aRv);
12663 if (aRv.Failed()) {
12664 return nullptr;
12667 RefPtr<PromiseNativeHandler> promiseHandler =
12668 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
12669 aPromise.AppendNativeHandler(promiseHandler);
12671 return resultPromise.forget();
12674 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
12675 if (mFailedChannel) {
12676 nsCOMPtr<nsIURI> failedURI;
12677 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
12678 return failedURI.forget();
12682 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
12683 if (!uri) {
12684 return nullptr;
12687 return uri.forget();
12690 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
12691 if (mIsGoingAway) {
12692 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
12693 return nullptr;
12696 if (!mReadyForIdle) {
12697 nsIGlobalObject* global = GetScopeObject();
12698 if (!global) {
12699 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
12700 return nullptr;
12703 mReadyForIdle = Promise::Create(global, aRv);
12704 if (aRv.Failed()) {
12705 return nullptr;
12709 return mReadyForIdle;
12712 void Document::MaybeResolveReadyForIdle() {
12713 IgnoredErrorResult rv;
12714 Promise* readyPromise = GetDocumentReadyForIdle(rv);
12715 if (readyPromise) {
12716 readyPromise->MaybeResolve(this);
12720 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
12721 // The policy is created when the document is initialized. We _must_ have a
12722 // policy here even if the featurePolicy pref is off. If this assertion fails,
12723 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
12724 MOZ_ASSERT(mFeaturePolicy);
12725 return mFeaturePolicy;
12728 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
12729 // Only chrome documents are allowed to use command dispatcher.
12730 if (!nsContentUtils::IsChromeDoc(this)) {
12731 return nullptr;
12733 if (!mCommandDispatcher) {
12734 // Create our command dispatcher and hook it up.
12735 mCommandDispatcher = new nsXULCommandDispatcher(this);
12737 return mCommandDispatcher;
12740 void Document::InitializeXULBroadcastManager() {
12741 if (mXULBroadcastManager) {
12742 return;
12744 mXULBroadcastManager = new XULBroadcastManager(this);
12747 static bool NodeHasScopeObject(nsINode* node) {
12748 MOZ_ASSERT(node, "Must not be called with null.");
12750 // Window root occasionally keeps alive a node of a document whose
12751 // window is already dead. If in this brief period someone calls
12752 // GetPopupNode and we return that node, we can end up creating a
12753 // reflector for the node in the wrong global (the current global,
12754 // not the document global, because we won't know what the document
12755 // global is). Returning an orphan node like that to JS would be a
12756 // bug anyway, so to avoid this, let's do the same check as fetching
12757 // GetParentObject() on the document does to determine the scope and
12758 // if there is no usable scope object let's report that and return
12759 // null from Document::GetPopupNode instead of returning a node that
12760 // will get a reflector in the wrong scope.
12761 Document* doc = node->OwnerDoc();
12762 MOZ_ASSERT(doc, "This should never happen.");
12764 nsIGlobalObject* global = doc->GetScopeObject();
12765 return global ? global->HasJSGlobal() : false;
12768 already_AddRefed<nsPIWindowRoot> Document::GetWindowRoot() {
12769 if (!mDocumentContainer) {
12770 return nullptr;
12772 // XXX It's unclear why this can't just use GetWindow().
12773 nsCOMPtr<nsPIDOMWindowOuter> piWin = mDocumentContainer->GetWindow();
12774 return piWin ? piWin->GetTopWindowRoot() : nullptr;
12777 already_AddRefed<nsINode> Document::GetPopupNode() {
12778 nsCOMPtr<nsINode> node;
12779 nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
12780 if (rootWin) {
12781 node = rootWin->GetPopupNode(); // addref happens here
12784 if (!node) {
12785 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
12786 if (pm) {
12787 node = pm->GetLastTriggerPopupNode(this);
12791 if (node && NodeHasScopeObject(node)) {
12792 return node.forget();
12795 return nullptr;
12798 void Document::SetPopupNode(nsINode* aNode) {
12799 nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
12800 if (rootWin) {
12801 rootWin->SetPopupNode(aNode);
12805 // Returns the rangeOffset element from the XUL Popup Manager. This is for
12806 // chrome callers only.
12807 nsINode* Document::GetPopupRangeParent(ErrorResult& aRv) {
12808 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
12809 if (!pm) {
12810 aRv.Throw(NS_ERROR_FAILURE);
12811 return nullptr;
12814 return pm->GetMouseLocationParent();
12817 // Returns the rangeOffset element from the XUL Popup Manager.
12818 int32_t Document::GetPopupRangeOffset(ErrorResult& aRv) {
12819 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
12820 if (!pm) {
12821 aRv.Throw(NS_ERROR_FAILURE);
12822 return 0;
12825 return pm->MouseLocationOffset();
12828 already_AddRefed<nsINode> Document::GetTooltipNode() {
12829 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
12830 if (pm) {
12831 nsCOMPtr<nsINode> node = pm->GetLastTriggerTooltipNode(this);
12832 if (node) {
12833 return node.forget();
12837 return nullptr;
12840 void Document::MaybeWarnAboutZoom() {
12841 if (mHasWarnedAboutZoom) {
12842 return;
12844 const bool usedZoom =
12845 mStyleUseCounters && Servo_IsPropertyIdRecordedInUseCounter(
12846 mStyleUseCounters.get(), eCSSProperty_zoom);
12847 if (!usedZoom) {
12848 return;
12851 mHasWarnedAboutZoom = true;
12852 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
12853 this, nsContentUtils::eLAYOUT_PROPERTIES,
12854 "ZoomPropertyWarning");
12857 nsIHTMLCollection* Document::Children() {
12858 if (!mChildrenCollection) {
12859 mChildrenCollection =
12860 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
12861 nsGkAtoms::_asterisk, false);
12864 return mChildrenCollection;
12867 uint32_t Document::ChildElementCount() { return Children()->Length(); }
12869 // Singleton class to manage the list of fullscreen documents which are the
12870 // root of a branch which contains fullscreen documents. We maintain this list
12871 // so that we can easily exit all windows from fullscreen when the user
12872 // presses the escape key.
12873 class FullscreenRoots {
12874 public:
12875 // Adds the root of given document to the manager. Calling this method
12876 // with a document whose root is already contained has no effect.
12877 static void Add(Document* aDoc);
12879 // Iterates over every root in the root list, and calls aFunction, passing
12880 // each root once to aFunction. It is safe to call Add() and Remove() while
12881 // iterating over the list (i.e. in aFunction). Documents that are removed
12882 // from the manager during traversal are not traversed, and documents that
12883 // are added to the manager during traversal are also not traversed.
12884 static void ForEach(void (*aFunction)(Document* aDoc));
12886 // Removes the root of a specific document from the manager.
12887 static void Remove(Document* aDoc);
12889 // Returns true if all roots added to the list have been removed.
12890 static bool IsEmpty();
12892 private:
12893 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
12894 MOZ_COUNTED_DTOR(FullscreenRoots)
12896 enum { NotFound = uint32_t(-1) };
12897 // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
12898 static uint32_t Find(Document* aRoot);
12900 // Returns true if aRoot is in the list of fullscreen roots.
12901 static bool Contains(Document* aRoot);
12903 // Singleton instance of the FullscreenRoots. This is instantiated when a
12904 // root is added, and it is deleted when the last root is removed.
12905 static FullscreenRoots* sInstance;
12907 // List of weak pointers to roots.
12908 nsTArray<nsWeakPtr> mRoots;
12911 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
12913 /* static */
12914 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
12915 if (!sInstance) {
12916 return;
12918 // Create a copy of the roots array, and iterate over the copy. This is so
12919 // that if an element is removed from mRoots we don't mess up our iteration.
12920 nsTArray<nsWeakPtr> roots(sInstance->mRoots.Clone());
12921 // Call aFunction on all entries.
12922 for (uint32_t i = 0; i < roots.Length(); i++) {
12923 nsCOMPtr<Document> root = do_QueryReferent(roots[i]);
12924 // Check that the root isn't in the manager. This is so that new additions
12925 // while we were running don't get traversed.
12926 if (root && FullscreenRoots::Contains(root)) {
12927 aFunction(root);
12932 /* static */
12933 bool FullscreenRoots::Contains(Document* aRoot) {
12934 return FullscreenRoots::Find(aRoot) != NotFound;
12937 /* static */
12938 void FullscreenRoots::Add(Document* aDoc) {
12939 nsCOMPtr<Document> root = nsContentUtils::GetRootDocument(aDoc);
12940 if (!FullscreenRoots::Contains(root)) {
12941 if (!sInstance) {
12942 sInstance = new FullscreenRoots();
12944 sInstance->mRoots.AppendElement(do_GetWeakReference(root));
12948 /* static */
12949 uint32_t FullscreenRoots::Find(Document* aRoot) {
12950 if (!sInstance) {
12951 return NotFound;
12953 nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
12954 for (uint32_t i = 0; i < roots.Length(); i++) {
12955 nsCOMPtr<Document> otherRoot(do_QueryReferent(roots[i]));
12956 if (otherRoot == aRoot) {
12957 return i;
12960 return NotFound;
12963 /* static */
12964 void FullscreenRoots::Remove(Document* aDoc) {
12965 nsCOMPtr<Document> root = nsContentUtils::GetRootDocument(aDoc);
12966 uint32_t index = Find(root);
12967 NS_ASSERTION(index != NotFound,
12968 "Should only try to remove roots which are still added!");
12969 if (index == NotFound || !sInstance) {
12970 return;
12972 sInstance->mRoots.RemoveElementAt(index);
12973 if (sInstance->mRoots.IsEmpty()) {
12974 delete sInstance;
12975 sInstance = nullptr;
12979 /* static */
12980 bool FullscreenRoots::IsEmpty() { return !sInstance; }
12982 // Any fullscreen change waiting for the widget to finish transition
12983 // is queued here. This is declared static instead of a member of
12984 // Document because in the majority of time, there would be at most
12985 // one document requesting or exiting fullscreen. We shouldn't waste
12986 // the space to hold for it in every document.
12987 class PendingFullscreenChangeList {
12988 public:
12989 PendingFullscreenChangeList() = delete;
12991 template <typename T>
12992 static void Add(UniquePtr<T> aChange) {
12993 sList.insertBack(aChange.release());
12996 static const FullscreenChange* GetLast() { return sList.getLast(); }
12998 enum IteratorOption {
12999 // When we are committing fullscreen changes or preparing for
13000 // that, we generally want to iterate all requests in the same
13001 // window with eDocumentsWithSameRoot option.
13002 eDocumentsWithSameRoot,
13003 // If we are removing a document from the tree, we would only
13004 // want to remove the requests from the given document and its
13005 // descendants. For that case, use eInclusiveDescendants.
13006 eInclusiveDescendants
13009 template <typename T>
13010 class Iterator {
13011 public:
13012 explicit Iterator(Document* aDoc, IteratorOption aOption)
13013 : mCurrent(PendingFullscreenChangeList::sList.getFirst()),
13014 mRootShellForIteration(aDoc->GetDocShell()) {
13015 if (mCurrent) {
13016 if (mRootShellForIteration && aOption == eDocumentsWithSameRoot) {
13017 // Use a temporary to avoid undefined behavior from passing
13018 // mRootShellForIteration.
13019 nsCOMPtr<nsIDocShellTreeItem> root;
13020 mRootShellForIteration->GetInProcessRootTreeItem(
13021 getter_AddRefs(root));
13022 mRootShellForIteration = std::move(root);
13024 SkipToNextMatch();
13028 UniquePtr<T> TakeAndNext() {
13029 auto thisChange = TakeAndNextInternal();
13030 SkipToNextMatch();
13031 return thisChange;
13033 bool AtEnd() const { return mCurrent == nullptr; }
13035 private:
13036 UniquePtr<T> TakeAndNextInternal() {
13037 FullscreenChange* thisChange = mCurrent;
13038 MOZ_ASSERT(thisChange->Type() == T::kType);
13039 mCurrent = mCurrent->removeAndGetNext();
13040 return WrapUnique(static_cast<T*>(thisChange));
13042 void SkipToNextMatch() {
13043 while (mCurrent) {
13044 if (mCurrent->Type() == T::kType) {
13045 nsCOMPtr<nsIDocShellTreeItem> docShell =
13046 mCurrent->Document()->GetDocShell();
13047 if (!docShell) {
13048 // Always automatically drop fullscreen changes which are
13049 // from a document detached from the doc shell.
13050 UniquePtr<T> change = TakeAndNextInternal();
13051 change->MayRejectPromise("Document is not active");
13052 continue;
13054 while (docShell && docShell != mRootShellForIteration) {
13055 nsCOMPtr<nsIDocShellTreeItem> parent;
13056 docShell->GetInProcessParent(getter_AddRefs(parent));
13057 docShell = std::move(parent);
13059 if (docShell) {
13060 break;
13063 // The current one either don't have matched type, or isn't
13064 // inside the given subtree, so skip this item.
13065 mCurrent = mCurrent->getNext();
13069 FullscreenChange* mCurrent;
13070 nsCOMPtr<nsIDocShellTreeItem> mRootShellForIteration;
13073 private:
13074 static LinkedList<FullscreenChange> sList;
13077 /* static */
13078 LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
13080 Document* Document::GetFullscreenRoot() {
13081 nsCOMPtr<Document> root = do_QueryReferent(mFullscreenRoot);
13082 return root;
13085 size_t Document::CountFullscreenElements() const {
13086 size_t count = 0;
13087 for (const nsWeakPtr& ptr : mTopLayer) {
13088 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
13089 if (elem->State().HasState(NS_EVENT_STATE_FULLSCREEN)) {
13090 count++;
13094 return count;
13097 void Document::SetFullscreenRoot(Document* aRoot) {
13098 mFullscreenRoot = do_GetWeakReference(aRoot);
13101 void Document::TryCancelDialog() {
13102 // Check if the document is blocked by modal dialog
13103 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
13104 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
13105 if (HTMLDialogElement* dialog =
13106 HTMLDialogElement::FromNodeOrNull(element)) {
13107 dialog->QueueCancelDialog();
13108 break;
13113 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
13114 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
13115 RefPtr<Promise> promise = exit->GetPromise();
13116 RestorePreviousFullscreenState(std::move(exit));
13117 return promise.forget();
13120 static void AskWindowToExitFullscreen(Document* aDoc) {
13121 if (XRE_GetProcessType() == GeckoProcessType_Content) {
13122 nsContentUtils::DispatchEventOnlyToChrome(
13123 aDoc, ToSupports(aDoc), u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
13124 Cancelable::eNo, /* DefaultAction */ nullptr);
13125 } else {
13126 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
13127 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
13132 class nsCallExitFullscreen : public Runnable {
13133 public:
13134 explicit nsCallExitFullscreen(Document* aDoc)
13135 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
13137 NS_IMETHOD Run() final {
13138 if (!mDoc) {
13139 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
13140 } else {
13141 AskWindowToExitFullscreen(mDoc);
13143 return NS_OK;
13146 private:
13147 nsCOMPtr<Document> mDoc;
13150 /* static */
13151 void Document::AsyncExitFullscreen(Document* aDoc) {
13152 MOZ_RELEASE_ASSERT(NS_IsMainThread());
13153 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
13154 if (aDoc) {
13155 aDoc->Dispatch(TaskCategory::Other, exit.forget());
13156 } else {
13157 NS_DispatchToCurrentThread(exit.forget());
13161 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
13162 uint32_t count = 0;
13163 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
13164 auto subDoc = [&count](Document& aSubDoc) {
13165 if (aSubDoc.GetUnretargetedFullScreenElement()) {
13166 count++;
13168 return CallState::Continue;
13170 aDoc.EnumerateSubDocuments(subDoc);
13171 return count;
13174 bool Document::IsFullscreenLeaf() {
13175 // A fullscreen leaf document is fullscreen, and has no fullscreen
13176 // subdocuments.
13177 if (!GetUnretargetedFullScreenElement()) {
13178 return false;
13180 return CountFullscreenSubDocuments(*this) == 0;
13183 static Document* GetFullscreenLeaf(Document& aDoc) {
13184 if (aDoc.IsFullscreenLeaf()) {
13185 return &aDoc;
13187 if (!aDoc.GetUnretargetedFullScreenElement()) {
13188 return nullptr;
13190 Document* leaf = nullptr;
13191 auto recurse = [&leaf](Document& aSubDoc) {
13192 leaf = GetFullscreenLeaf(aSubDoc);
13193 return leaf ? CallState::Stop : CallState::Continue;
13195 aDoc.EnumerateSubDocuments(recurse);
13196 return leaf;
13199 static Document* GetFullscreenLeaf(Document* aDoc) {
13200 if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
13201 return leaf;
13203 // Otherwise we could be either in a non-fullscreen doc tree, or we're
13204 // below the fullscreen doc. Start the search from the root.
13205 Document* root = nsContentUtils::GetRootDocument(aDoc);
13206 return GetFullscreenLeaf(*root);
13209 static CallState ResetFullscreen(Document& aDocument) {
13210 if (Element* fsElement = aDocument.GetUnretargetedFullScreenElement()) {
13211 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
13212 "Should have at most 1 fullscreen subdocument.");
13213 aDocument.CleanupFullscreenState();
13214 NS_ASSERTION(!aDocument.GetUnretargetedFullScreenElement(),
13215 "Should reset fullscreen");
13216 DispatchFullscreenChange(aDocument, fsElement);
13217 aDocument.EnumerateSubDocuments(ResetFullscreen);
13219 return CallState::Continue;
13222 // Since Document::ExitFullscreenInDocTree() could be called from
13223 // Element::UnbindFromTree() where it is not safe to synchronously run
13224 // script. This runnable is the script part of that function.
13225 class ExitFullscreenScriptRunnable : public Runnable {
13226 public:
13227 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
13228 : mozilla::Runnable("ExitFullscreenScriptRunnable"),
13229 mRoot(aRoot),
13230 mLeaf(aLeaf) {}
13232 NS_IMETHOD Run() override {
13233 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
13234 // document since we want this event to follow the same path that
13235 // MozDOMFullscreen:Entered was dispatched.
13236 nsContentUtils::DispatchEventOnlyToChrome(
13237 mLeaf, ToSupports(mLeaf), u"MozDOMFullscreen:Exited"_ns,
13238 CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
13239 // Ensure the window exits fullscreen.
13240 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
13241 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
13242 false);
13244 return NS_OK;
13247 private:
13248 nsCOMPtr<Document> mRoot;
13249 nsCOMPtr<Document> mLeaf;
13252 /* static */
13253 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
13254 MOZ_ASSERT(aMaybeNotARootDoc);
13256 // Unlock the pointer
13257 UnlockPointer();
13259 // Resolve all promises which waiting for exit fullscreen.
13260 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
13261 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
13262 while (!iter.AtEnd()) {
13263 UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
13264 exit->MayResolvePromise();
13267 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
13268 if (!root || !root->GetUnretargetedFullScreenElement()) {
13269 // If a document was detached before exiting from fullscreen, it is
13270 // possible that the root had left fullscreen state. In this case,
13271 // we would not get anything from the ResetFullscreen() call. Root's
13272 // not being a fullscreen doc also means the widget should have
13273 // exited fullscreen state. It means even if we do not return here,
13274 // we would actually do nothing below except crashing ourselves via
13275 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
13276 // document.
13277 return;
13280 // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
13281 // See ExitFullscreenScriptRunnable::Run for details. We have to
13282 // record it here because we don't have such information after we
13283 // reset the fullscreen state below.
13284 Document* fullscreenLeaf = GetFullscreenLeaf(root);
13286 // Walk the tree of fullscreen documents, and reset their fullscreen state.
13287 ResetFullscreen(*root);
13289 NS_ASSERTION(!root->GetUnretargetedFullScreenElement(),
13290 "Fullscreen root should no longer be a fullscreen doc...");
13292 // Move the top-level window out of fullscreen mode.
13293 FullscreenRoots::Remove(root);
13295 nsContentUtils::AddScriptRunner(
13296 new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
13299 static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
13300 RefPtr<AsyncEventDispatcher> asyncDispatcher =
13301 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
13302 CanBubble::eYes, ChromeOnlyDispatch::eYes);
13303 asyncDispatcher->PostDOMEvent();
13306 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
13307 NS_ASSERTION(
13308 !GetUnretargetedFullScreenElement() || !FullscreenRoots::IsEmpty(),
13309 "Should have at least 1 fullscreen root when fullscreen!");
13311 if (!GetWindow()) {
13312 aExit->MayRejectPromise("No active window");
13313 return;
13315 if (!GetUnretargetedFullScreenElement() || FullscreenRoots::IsEmpty()) {
13316 aExit->MayRejectPromise("Not in fullscreen mode");
13317 return;
13320 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
13321 AutoTArray<Element*, 8> exitElements;
13323 Document* doc = fullScreenDoc;
13324 // Collect all subdocuments.
13325 for (; doc != this; doc = doc->GetInProcessParentDocument()) {
13326 Element* fsElement = doc->GetUnretargetedFullScreenElement();
13327 MOZ_ASSERT(fsElement,
13328 "Parent document of "
13329 "a fullscreen document without fullscreen element?");
13330 exitElements.AppendElement(fsElement);
13332 MOZ_ASSERT(doc == this, "Must have reached this doc");
13333 // Collect all ancestor documents which we are going to change.
13334 for (; doc; doc = doc->GetInProcessParentDocument()) {
13335 Element* fsElement = doc->GetUnretargetedFullScreenElement();
13336 MOZ_ASSERT(fsElement,
13337 "Ancestor of fullscreen document must also be in fullscreen");
13338 if (doc != this) {
13339 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
13340 if (iframe->FullscreenFlag()) {
13341 // If this is an iframe, and it explicitly requested
13342 // fullscreen, don't rollback it automatically.
13343 break;
13347 exitElements.AppendElement(fsElement);
13348 if (doc->CountFullscreenElements() > 1) {
13349 break;
13353 Document* lastDoc = exitElements.LastElement()->OwnerDoc();
13354 size_t fullscreenCount = lastDoc->CountFullscreenElements();
13355 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
13356 // If we are fully exiting fullscreen, don't touch anything here,
13357 // just wait for the window to get out from fullscreen first.
13358 PendingFullscreenChangeList::Add(std::move(aExit));
13359 AskWindowToExitFullscreen(this);
13360 return;
13363 // If fullscreen mode is updated the pointer should be unlocked
13364 UnlockPointer();
13365 // All documents listed in the array except the last one are going to
13366 // completely exit from the fullscreen state.
13367 for (auto i : IntegerRange(exitElements.Length() - 1)) {
13368 exitElements[i]->OwnerDoc()->CleanupFullscreenState();
13370 // The last document will either rollback one fullscreen element, or
13371 // completely exit from the fullscreen state as well.
13372 Document* newFullscreenDoc;
13373 if (fullscreenCount > 1) {
13374 lastDoc->UnsetFullscreenElement();
13375 newFullscreenDoc = lastDoc;
13376 } else {
13377 lastDoc->CleanupFullscreenState();
13378 newFullscreenDoc = lastDoc->GetInProcessParentDocument();
13380 // Dispatch the fullscreenchange event to all document listed. Note
13381 // that the loop order is reversed so that events are dispatched in
13382 // the tree order as indicated in the spec.
13383 for (Element* e : Reversed(exitElements)) {
13384 DispatchFullscreenChange(*e->OwnerDoc(), e);
13386 aExit->MayResolvePromise();
13388 MOZ_ASSERT(newFullscreenDoc,
13389 "If we were going to exit from fullscreen on "
13390 "all documents in this doctree, we should've asked the window to "
13391 "exit first instead of reaching here.");
13392 if (fullScreenDoc != newFullscreenDoc &&
13393 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
13394 // We've popped so enough off the stack that we've rolled back to
13395 // a fullscreen element in a parent document. If this document is
13396 // cross origin, dispatch an event to chrome so it knows to show
13397 // the warning UI.
13398 DispatchFullscreenNewOriginEvent(newFullscreenDoc);
13402 class nsCallRequestFullscreen : public Runnable {
13403 public:
13404 explicit nsCallRequestFullscreen(UniquePtr<FullscreenRequest> aRequest)
13405 : mozilla::Runnable("nsCallRequestFullscreen"),
13406 mRequest(std::move(aRequest)) {}
13408 NS_IMETHOD Run() override {
13409 Document* doc = mRequest->Document();
13410 doc->RequestFullscreen(std::move(mRequest));
13411 return NS_OK;
13414 UniquePtr<FullscreenRequest> mRequest;
13417 void Document::AsyncRequestFullscreen(UniquePtr<FullscreenRequest> aRequest) {
13418 // Request fullscreen asynchronously.
13419 MOZ_RELEASE_ASSERT(NS_IsMainThread());
13420 nsCOMPtr<nsIRunnable> event =
13421 new nsCallRequestFullscreen(std::move(aRequest));
13422 Dispatch(TaskCategory::Other, event.forget());
13425 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
13426 if (nsPresContext* presContext = aDoc->GetPresContext()) {
13427 presContext->UpdateViewportScrollStylesOverride();
13431 static void NotifyFullScreenChangedForMediaControl(Element* aElement,
13432 bool aIsInFullScreen) {
13433 // When a media element enters the fullscreen, we would like to notify that
13434 // to the media controller in order to update its status.
13435 if (!aElement->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
13436 return;
13438 RefPtr<BrowsingContext> bc = aElement->OwnerDoc()->GetBrowsingContext();
13439 if (!bc) {
13440 return;
13442 if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(bc)) {
13443 updater->NotifyMediaFullScreenState(bc->Id(), aIsInFullScreen);
13447 static void ClearFullscreenStateOnElement(Element* aElement) {
13448 // Remove styles from existing top element.
13449 EventStateManager::SetFullscreenState(aElement, false);
13450 NotifyFullScreenChangedForMediaControl(aElement, false);
13451 // Reset iframe fullscreen flag.
13452 if (aElement->IsHTMLElement(nsGkAtoms::iframe)) {
13453 static_cast<HTMLIFrameElement*>(aElement)->SetFullscreenFlag(false);
13457 void Document::CleanupFullscreenState() {
13458 // Iterate the top layer and clear the fullscreen states.
13459 // Since we also need to clear the fullscreen-ancestor state, and
13460 // currently fullscreen elements can only be placed in hierarchy
13461 // order in the stack, reversely iterating the stack could be more
13462 // efficient. NOTE that fullscreen-ancestor state would be removed
13463 // in bug 1199529, and the elements may not in hierarchy order
13464 // after bug 1195213.
13465 mTopLayer.RemoveElementsBy([&](const nsWeakPtr& weakPtr) {
13466 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
13467 if (!element || !element->IsInComposedDoc() ||
13468 element->OwnerDoc() != this) {
13469 return true;
13472 if (element->State().HasState(NS_EVENT_STATE_FULLSCREEN)) {
13473 ClearFullscreenStateOnElement(element);
13474 return true;
13476 return false;
13479 mFullscreenRoot = nullptr;
13481 // Restore the zoom level that was in place prior to entering fullscreen.
13482 if (PresShell* presShell = GetPresShell()) {
13483 if (presShell->GetMobileViewportManager()) {
13484 presShell->SetResolutionAndScaleTo(
13485 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
13489 UpdateViewportScrollbarOverrideForFullscreen(this);
13492 void Document::UnsetFullscreenElement() {
13493 Element* removedElement = TopLayerPop([](Element* element) -> bool {
13494 return element->State().HasState(NS_EVENT_STATE_FULLSCREEN);
13497 MOZ_ASSERT(removedElement->State().HasState(NS_EVENT_STATE_FULLSCREEN));
13498 ClearFullscreenStateOnElement(removedElement);
13499 UpdateViewportScrollbarOverrideForFullscreen(this);
13502 bool Document::SetFullscreenElement(Element* aElement) {
13503 if (TopLayerPush(aElement)) {
13504 EventStateManager::SetFullscreenState(aElement, true);
13505 NotifyFullScreenChangedForMediaControl(aElement, true);
13506 UpdateViewportScrollbarOverrideForFullscreen(this);
13507 return true;
13509 return false;
13512 bool Document::TopLayerPush(Element* aElement) {
13513 NS_ASSERTION(aElement, "Must pass non-null to TopLayerPush()");
13514 auto predictFunc = [&aElement](Element* element) {
13515 return element == aElement;
13517 TopLayerPop(predictFunc);
13519 mTopLayer.AppendElement(do_GetWeakReference(aElement));
13520 NS_ASSERTION(GetTopLayerTop() == aElement, "Should match");
13521 return true;
13524 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicateFunc) {
13525 if (mTopLayer.IsEmpty()) {
13526 return nullptr;
13529 // Remove the topmost element that qualifies aPredicate; This
13530 // is required is because the top layer contains not only
13531 // fullscreen elements, but also dialog elements.
13532 Element* removedElement;
13533 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
13534 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
13535 if (element && aPredicateFunc(element)) {
13536 removedElement = element;
13537 mTopLayer.RemoveElementAt(i);
13538 break;
13542 // Pop from the stack null elements (references to elements which have
13543 // been GC'd since they were added to the stack) and elements which are
13544 // no longer in this document.
13546 // FIXME(emilio): If this loop does something, it'd violate the assertions
13547 // from GetTopLayerTop()... What gives?
13548 while (!mTopLayer.IsEmpty()) {
13549 Element* element = GetTopLayerTop();
13550 if (!element || element->GetComposedDoc() != this) {
13551 mTopLayer.RemoveLastElement();
13552 } else {
13553 // The top element of the stack is now an in-doc element. Return here.
13554 break;
13558 return removedElement;
13561 Element* Document::GetTopLayerTop() {
13562 if (mTopLayer.IsEmpty()) {
13563 return nullptr;
13565 uint32_t last = mTopLayer.Length() - 1;
13566 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
13567 NS_ASSERTION(element, "Should have a top layer element!");
13568 NS_ASSERTION(element->IsInComposedDoc(),
13569 "Top layer element should be in doc");
13570 NS_ASSERTION(element->OwnerDoc() == this,
13571 "Top layer element should be in this doc");
13572 return element;
13575 Element* Document::GetUnretargetedFullScreenElement() {
13576 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
13577 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
13578 // Per spec, the fullscreen element is the topmost element in the document’s
13579 // top layer whose fullscreen flag is set, if any, and null otherwise.
13580 if (element && element->State().HasState(NS_EVENT_STATE_FULLSCREEN)) {
13581 return element;
13584 return nullptr;
13587 nsTArray<Element*> Document::GetTopLayer() const {
13588 nsTArray<Element*> elements;
13589 for (const nsWeakPtr& ptr : mTopLayer) {
13590 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
13591 elements.AppendElement(elem);
13594 return elements;
13597 // Returns true if aDoc is in the focused tab in the active window.
13598 bool IsInActiveTab(Document* aDoc) {
13599 nsCOMPtr<nsIDocShell> docshell = aDoc->GetDocShell();
13600 if (!docshell) {
13601 return false;
13604 bool isActive = false;
13605 docshell->GetIsActive(&isActive);
13606 if (!isActive) {
13607 return false;
13610 nsFocusManager* fm = nsFocusManager::GetFocusManager();
13611 if (!fm) {
13612 return false;
13615 if (XRE_IsParentProcess()) {
13616 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
13617 // by retaining the old code path for the parent process.
13619 nsCOMPtr<nsIDocShellTreeItem> rootItem;
13620 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
13621 if (!rootItem) {
13622 return false;
13624 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
13625 if (!rootWin) {
13626 return false;
13629 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
13630 activeWindow = fm->GetActiveWindow();
13631 if (!activeWindow) {
13632 return false;
13635 return activeWindow == rootWin;
13638 BrowsingContext* bc = aDoc->GetBrowsingContext();
13639 if (!bc) {
13640 return false;
13643 BrowsingContext* activeBrowsingContext = fm->GetActiveBrowsingContext();
13644 if (!activeBrowsingContext) {
13645 return false;
13648 return activeBrowsingContext == bc->Top();
13651 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
13652 // Ensure the frame element is the fullscreen element in this document.
13653 // If the frame element is already the fullscreen element in this document,
13654 // this has no effect.
13655 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
13656 RequestFullscreen(std::move(request), XRE_IsContentProcess());
13659 void Document::RemoteFrameFullscreenReverted() {
13660 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
13661 RestorePreviousFullscreenState(std::move(exit));
13664 static bool HasFullscreenSubDocument(Document& aDoc) {
13665 uint32_t count = CountFullscreenSubDocuments(aDoc);
13666 NS_ASSERTION(count <= 1,
13667 "Fullscreen docs should have at most 1 fullscreen child!");
13668 return count >= 1;
13671 // Returns nullptr if a request for Fullscreen API is currently enabled
13672 // in the given document. Returns a static string indicates the reason
13673 // why it is not enabled otherwise.
13674 const char* Document::GetFullscreenError(CallerType aCallerType) {
13675 if (!StaticPrefs::full_screen_api_enabled()) {
13676 return "FullscreenDeniedDisabled";
13679 if (aCallerType == CallerType::System) {
13680 // Chrome code can always use the fullscreen API, provided it's not
13681 // explicitly disabled.
13682 return nullptr;
13685 if (!IsVisible()) {
13686 return "FullscreenDeniedHidden";
13689 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
13690 return "FullscreenDeniedFeaturePolicy";
13693 // Ensure that all containing elements are <iframe> and have allowfullscreen
13694 // attribute set.
13695 BrowsingContext* bc = GetBrowsingContext();
13696 if (!bc || !bc->FullscreenAllowed()) {
13697 return "FullscreenDeniedContainerNotAllowed";
13700 return nullptr;
13703 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
13704 Element* elem = aRequest.Element();
13705 // Strictly speaking, this isn't part of the fullscreen element ready
13706 // check in the spec, but per steps in the spec, when an element which
13707 // is already the fullscreen element requests fullscreen, nothing
13708 // should change and no event should be dispatched, but we still need
13709 // to resolve the returned promise.
13710 Element* fullscreenElement = GetUnretargetedFullScreenElement();
13711 if (elem == fullscreenElement) {
13712 aRequest.MayResolvePromise();
13713 return false;
13715 if (!elem->IsInComposedDoc()) {
13716 aRequest.Reject("FullscreenDeniedNotInDocument");
13717 return false;
13719 if (elem->OwnerDoc() != this) {
13720 aRequest.Reject("FullscreenDeniedMovedDocument");
13721 return false;
13723 if (!GetWindow()) {
13724 aRequest.Reject("FullscreenDeniedLostWindow");
13725 return false;
13727 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
13728 aRequest.Reject(msg);
13729 return false;
13731 if (HasFullscreenSubDocument(*this)) {
13732 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
13733 return false;
13735 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
13736 aRequest.Reject("FullscreenDeniedHTMLDialog");
13737 return false;
13739 // XXXsmaug Note, we don't follow the latest fullscreen spec here.
13740 // This whole check could be probably removed.
13741 if (fullscreenElement && !nsContentUtils::ContentIsHostIncludingDescendantOf(
13742 elem, fullscreenElement)) {
13743 // If this document is fullscreen, only grant fullscreen requests from
13744 // a descendant of the current fullscreen element.
13745 aRequest.Reject("FullscreenDeniedNotDescendant");
13746 return false;
13748 if (!nsContentUtils::IsChromeDoc(this) && !IsInActiveTab(this)) {
13749 aRequest.Reject("FullscreenDeniedNotFocusedTab");
13750 return false;
13752 // Deny requests when a windowed plugin is focused.
13753 nsFocusManager* fm = nsFocusManager::GetFocusManager();
13754 if (!fm) {
13755 NS_WARNING("Failed to retrieve focus manager in fullscreen request.");
13756 aRequest.MayRejectPromise("An unexpected error occurred");
13757 return false;
13759 if (nsContentUtils::HasPluginWithUncontrolledEventDispatch(
13760 fm->GetFocusedElement())) {
13761 aRequest.Reject("FullscreenDeniedFocusedPlugin");
13762 return false;
13764 return true;
13767 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
13768 nsIDocShell* docShell = aDoc->GetDocShell();
13769 if (!docShell) {
13770 return nullptr;
13772 nsCOMPtr<nsIDocShellTreeItem> rootItem;
13773 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
13774 return rootItem ? rootItem->GetWindow() : nullptr;
13777 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
13778 nsPIDOMWindowOuter* aRootWin) {
13779 MOZ_ASSERT(XRE_IsParentProcess());
13780 // If we are in the chrome process, and the window has not been in
13781 // fullscreen, we certainly need to make that fullscreen first.
13782 if (!aRootWin->GetFullScreen()) {
13783 return false;
13785 // The iterator not being at end indicates there is still some
13786 // pending fullscreen request relates to this document. We have to
13787 // push the request to the pending queue so requests are handled
13788 // in the correct order.
13789 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
13790 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
13791 if (!iter.AtEnd()) {
13792 return false;
13794 // We have to apply the fullscreen state directly in this case,
13795 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
13796 // if it is already in fullscreen. If we do not apply the state but
13797 // instead add it to the queue and wait for the window as normal,
13798 // we would get stuck.
13799 return true;
13802 static bool CheckFullscreenAllowedElementType(const Element* elem) {
13803 // Per spec only HTML, <svg>, and <math> should be allowed, but
13804 // we also need to allow XUL elements right now.
13805 return elem->IsHTMLElement() || elem->IsXULElement() ||
13806 elem->IsSVGElement(nsGkAtoms::svg) ||
13807 elem->IsMathMLElement(nsGkAtoms::math);
13810 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
13811 bool applyFullScreenDirectly) {
13812 if (XRE_IsContentProcess()) {
13813 RequestFullscreenInContentProcess(std::move(aRequest),
13814 applyFullScreenDirectly);
13815 } else {
13816 RequestFullscreenInParentProcess(std::move(aRequest),
13817 applyFullScreenDirectly);
13821 void Document::RequestFullscreenInContentProcess(
13822 UniquePtr<FullscreenRequest> aRequest, bool applyFullScreenDirectly) {
13823 MOZ_ASSERT(XRE_IsContentProcess());
13825 // If we are in the content process, we can apply the fullscreen
13826 // state directly only if we have been in DOM fullscreen, because
13827 // otherwise we always need to notify the chrome.
13828 if (applyFullScreenDirectly || !!nsContentUtils::GetRootDocument(this)
13829 ->GetUnretargetedFullScreenElement()) {
13830 ApplyFullscreen(std::move(aRequest));
13831 return;
13834 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
13835 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
13836 return;
13839 // We don't need to check element ready before this point, because
13840 // if we called ApplyFullscreen, it would check that for us.
13841 if (!FullscreenElementReadyCheck(*aRequest)) {
13842 return;
13845 PendingFullscreenChangeList::Add(std::move(aRequest));
13846 // If we are not the top level process, dispatch an event to make
13847 // our parent process go fullscreen first.
13848 nsContentUtils::DispatchEventOnlyToChrome(
13849 this, ToSupports(this), u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
13850 Cancelable::eNo, /* DefaultAction */ nullptr);
13853 void Document::RequestFullscreenInParentProcess(
13854 UniquePtr<FullscreenRequest> aRequest, bool applyFullScreenDirectly) {
13855 MOZ_ASSERT(XRE_IsParentProcess());
13856 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
13857 if (!rootWin) {
13858 aRequest->MayRejectPromise("No active window");
13859 return;
13862 if (applyFullScreenDirectly || ShouldApplyFullscreenDirectly(this, rootWin)) {
13863 ApplyFullscreen(std::move(aRequest));
13864 return;
13867 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
13868 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
13869 return;
13872 // We don't need to check element ready before this point, because
13873 // if we called ApplyFullscreen, it would check that for us.
13874 if (!FullscreenElementReadyCheck(*aRequest)) {
13875 return;
13878 PendingFullscreenChangeList::Add(std::move(aRequest));
13879 // Make the window fullscreen.
13880 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
13883 /* static */
13884 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
13885 bool handled = false;
13886 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
13887 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
13888 while (!iter.AtEnd()) {
13889 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
13890 Document* doc = request->Document();
13891 if (doc->ApplyFullscreen(std::move(request))) {
13892 handled = true;
13895 return handled;
13898 static void ClearPendingFullscreenRequests(Document* aDoc) {
13899 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
13900 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
13901 while (!iter.AtEnd()) {
13902 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
13903 request->MayRejectPromise("Fullscreen request aborted");
13907 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
13908 if (!FullscreenElementReadyCheck(*aRequest)) {
13909 return false;
13912 // Stash a reference to any existing fullscreen doc, we'll use this later
13913 // to detect if the origin which is fullscreen has changed.
13914 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
13916 // Stores a list of documents which we must dispatch "fullscreenchange"
13917 // too. We're required by the spec to dispatch the events in root-to-leaf
13918 // order, but we traverse the doctree in a leaf-to-root order, so we save
13919 // references to the documents we must dispatch to so that we get the order
13920 // as specified.
13921 AutoTArray<Document*, 8> changed;
13923 // Remember the root document, so that if a fullscreen document is hidden
13924 // we can reset fullscreen state in the remaining visible fullscreen
13925 // documents.
13926 Document* fullScreenRootDoc = nsContentUtils::GetRootDocument(this);
13928 // If a document is already in fullscreen, then unlock the mouse pointer
13929 // before setting a new document to fullscreen
13930 UnlockPointer();
13932 // Set the fullscreen element. This sets the fullscreen style on the
13933 // element, and the fullscreen-ancestor styles on ancestors of the element
13934 // in this document.
13935 Element* elem = aRequest->Element();
13936 DebugOnly<bool> x = SetFullscreenElement(elem);
13937 MOZ_ASSERT(x, "Fullscreen state of requesting doc should always change!");
13938 // Set the iframe fullscreen flag.
13939 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
13940 iframe->SetFullscreenFlag(true);
13942 changed.AppendElement(this);
13944 // Propagate up the document hierarchy, setting the fullscreen element as
13945 // the element's container in ancestor documents. This also sets the
13946 // appropriate css styles as well. Note we don't propagate down the
13947 // document hierarchy, the fullscreen element (or its container) is not
13948 // visible there. Stop when we reach the root document.
13949 Document* child = this;
13950 while (true) {
13951 child->SetFullscreenRoot(fullScreenRootDoc);
13953 // When entering fullscreen, reset the RCD's resolution to the intrinsic
13954 // resolution, otherwise the fullscreen content could be sized larger than
13955 // the screen (since fullscreen is implemented using position:fixed and
13956 // fixed elements are sized to the layout viewport).
13957 // This also ensures that things like video controls aren't zoomed in
13958 // when in fullscreen mode.
13959 if (PresShell* presShell = child->GetPresShell()) {
13960 if (RefPtr<MobileViewportManager> manager =
13961 presShell->GetMobileViewportManager()) {
13962 // Save the previous resolution so it can be restored.
13963 child->mSavedResolution = presShell->GetResolution();
13964 presShell->SetResolutionAndScaleTo(
13965 manager->ComputeIntrinsicResolution(),
13966 ResolutionChangeOrigin::MainThreadRestore);
13970 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
13971 "Fullscreen root should be set!");
13972 if (child == fullScreenRootDoc) {
13973 break;
13975 Document* parent = child->GetInProcessParentDocument();
13976 Element* element = parent->FindContentForSubDocument(child);
13977 if (parent->SetFullscreenElement(element)) {
13978 changed.AppendElement(parent);
13979 child = parent;
13980 } else {
13981 // We've reached either the root, or a point in the doctree where the
13982 // new fullscreen element container is the same as the previous
13983 // fullscreen element's container. No more changes need to be made
13984 // to the top layer of documents further up the tree.
13985 break;
13989 FullscreenRoots::Add(this);
13991 // If it is the first entry of the fullscreen, trigger an event so
13992 // that the UI can response to this change, e.g. hide chrome, or
13993 // notifying parent process to enter fullscreen. Note that chrome
13994 // code may also want to listen to MozDOMFullscreen:NewOrigin event
13995 // to pop up warning UI.
13996 if (!previousFullscreenDoc) {
13997 nsContentUtils::DispatchEventOnlyToChrome(
13998 this, ToSupports(elem), u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
13999 Cancelable::eNo, /* DefaultAction */ nullptr);
14002 // The origin which is fullscreen gets changed. Trigger an event so
14003 // that the chrome knows to pop up a warning UI. Note that
14004 // previousFullscreenDoc == nullptr upon first entry, so we always
14005 // take this path on the first entry. Also note that, in a multi-
14006 // process browser, the code in content process is responsible for
14007 // sending message with the origin to its parent, and the parent
14008 // shouldn't rely on this event itself.
14009 if (aRequest->mShouldNotifyNewOrigin &&
14010 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
14011 DispatchFullscreenNewOriginEvent(this);
14014 // Dispatch "fullscreenchange" events. Note that the loop order is
14015 // reversed so that events are dispatched in the tree order as
14016 // indicated in the spec.
14017 for (Document* d : Reversed(changed)) {
14018 DispatchFullscreenChange(*d, d->GetUnretargetedFullScreenElement());
14020 aRequest->MayResolvePromise();
14021 return true;
14024 void Document::ClearOrientationPendingPromise() {
14025 mOrientationPendingPromise = nullptr;
14028 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
14029 if (mIsGoingAway) {
14030 return false;
14033 mOrientationPendingPromise = aPromise;
14034 return true;
14037 static void DispatchPointerLockChange(Document* aTarget) {
14038 if (!aTarget) {
14039 return;
14042 RefPtr<AsyncEventDispatcher> asyncDispatcher =
14043 new AsyncEventDispatcher(aTarget, u"pointerlockchange"_ns,
14044 CanBubble::eYes, ChromeOnlyDispatch::eNo);
14045 asyncDispatcher->PostDOMEvent();
14048 static void DispatchPointerLockError(Document* aTarget, const char* aMessage) {
14049 if (!aTarget) {
14050 return;
14053 RefPtr<AsyncEventDispatcher> asyncDispatcher =
14054 new AsyncEventDispatcher(aTarget, u"pointerlockerror"_ns, CanBubble::eYes,
14055 ChromeOnlyDispatch::eNo);
14056 asyncDispatcher->PostDOMEvent();
14057 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
14058 aTarget, nsContentUtils::eDOM_PROPERTIES,
14059 aMessage);
14062 class PointerLockRequest final : public Runnable {
14063 public:
14064 PointerLockRequest(Element* aElement, bool aUserInputOrChromeCaller)
14065 : mozilla::Runnable("PointerLockRequest"),
14066 mElement(do_GetWeakReference(aElement)),
14067 mDocument(do_GetWeakReference(aElement->OwnerDoc())),
14068 mUserInputOrChromeCaller(aUserInputOrChromeCaller) {}
14070 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() final;
14072 private:
14073 nsWeakPtr mElement;
14074 nsWeakPtr mDocument;
14075 bool mUserInputOrChromeCaller;
14078 static const char* GetPointerLockError(Element* aElement, Element* aCurrentLock,
14079 bool aNoFocusCheck = false) {
14080 // Check if pointer lock pref is enabled
14081 if (!StaticPrefs::full_screen_api_pointer_lock_enabled()) {
14082 return "PointerLockDeniedDisabled";
14085 nsCOMPtr<Document> ownerDoc = aElement->OwnerDoc();
14086 if (aCurrentLock && aCurrentLock->OwnerDoc() != ownerDoc) {
14087 return "PointerLockDeniedInUse";
14090 if (!aElement->IsInComposedDoc()) {
14091 return "PointerLockDeniedNotInDocument";
14094 if (ownerDoc->GetSandboxFlags() & SANDBOXED_POINTER_LOCK) {
14095 return "PointerLockDeniedSandboxed";
14098 // Check if the element is in a document with a docshell.
14099 if (!ownerDoc->GetContainer()) {
14100 return "PointerLockDeniedHidden";
14102 nsCOMPtr<nsPIDOMWindowOuter> ownerWindow = ownerDoc->GetWindow();
14103 if (!ownerWindow) {
14104 return "PointerLockDeniedHidden";
14106 nsCOMPtr<nsPIDOMWindowInner> ownerInnerWindow = ownerDoc->GetInnerWindow();
14107 if (!ownerInnerWindow) {
14108 return "PointerLockDeniedHidden";
14110 if (ownerWindow->GetCurrentInnerWindow() != ownerInnerWindow) {
14111 return "PointerLockDeniedHidden";
14114 nsCOMPtr<nsPIDOMWindowOuter> top = ownerWindow->GetInProcessScriptableTop();
14115 if (!top || !top->GetExtantDoc() || top->GetExtantDoc()->Hidden()) {
14116 return "PointerLockDeniedHidden";
14119 if (!aNoFocusCheck) {
14120 mozilla::ErrorResult rv;
14121 if (!top->GetExtantDoc()->HasFocus(rv)) {
14122 return "PointerLockDeniedNotFocused";
14126 return nullptr;
14129 static void ChangePointerLockedElement(Element* aElement, Document* aDocument,
14130 Element* aPointerLockedElement) {
14131 // aDocument here is not really necessary, as it is the uncomposed
14132 // document of both aElement and aPointerLockedElement as far as one
14133 // is not nullptr, and they wouldn't both be nullptr in any case.
14134 // But since the caller of this function should have known what the
14135 // document is, we just don't try to figure out what it should be.
14136 MOZ_ASSERT(aDocument);
14137 MOZ_ASSERT(aElement != aPointerLockedElement);
14138 if (aPointerLockedElement) {
14139 MOZ_ASSERT(aPointerLockedElement->GetComposedDoc() == aDocument);
14140 aPointerLockedElement->ClearPointerLock();
14142 if (aElement) {
14143 MOZ_ASSERT(aElement->GetComposedDoc() == aDocument);
14144 aElement->SetPointerLock();
14145 EventStateManager::sPointerLockedElement = do_GetWeakReference(aElement);
14146 EventStateManager::sPointerLockedDoc = do_GetWeakReference(aDocument);
14147 NS_ASSERTION(EventStateManager::sPointerLockedElement &&
14148 EventStateManager::sPointerLockedDoc,
14149 "aElement and this should support weak references!");
14150 } else {
14151 EventStateManager::sPointerLockedElement = nullptr;
14152 EventStateManager::sPointerLockedDoc = nullptr;
14154 // Retarget all events to aElement via capture or
14155 // stop retargeting if aElement is nullptr.
14156 PresShell::SetCapturingContent(aElement, CaptureFlags::PointerLock);
14157 DispatchPointerLockChange(aDocument);
14160 NS_IMETHODIMP
14161 PointerLockRequest::Run() {
14162 nsCOMPtr<Element> e = do_QueryReferent(mElement);
14163 nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
14164 const char* error = nullptr;
14165 if (!e || !doc || !e->GetComposedDoc()) {
14166 error = "PointerLockDeniedNotInDocument";
14167 } else if (e->GetComposedDoc() != doc) {
14168 error = "PointerLockDeniedMovedDocument";
14170 if (!error) {
14171 nsCOMPtr<Element> pointerLockedElement =
14172 do_QueryReferent(EventStateManager::sPointerLockedElement);
14173 if (e == pointerLockedElement) {
14174 DispatchPointerLockChange(doc);
14175 return NS_OK;
14177 // Note, we must bypass focus change, so pass true as the last parameter!
14178 error = GetPointerLockError(e, pointerLockedElement, true);
14179 // Another element in the same document is requesting pointer lock,
14180 // just grant it without user input check.
14181 if (!error && pointerLockedElement) {
14182 ChangePointerLockedElement(e, doc, pointerLockedElement);
14183 return NS_OK;
14186 // If it is neither user input initiated, nor requested in fullscreen,
14187 // it should be rejected.
14188 if (!error && !mUserInputOrChromeCaller &&
14189 !doc->GetUnretargetedFullScreenElement()) {
14190 error = "PointerLockDeniedNotInputDriven";
14192 if (!error && !doc->SetPointerLock(e, StyleCursorKind::None)) {
14193 error = "PointerLockDeniedFailedToLock";
14195 if (error) {
14196 DispatchPointerLockError(doc, error);
14197 return NS_OK;
14200 ChangePointerLockedElement(e, doc, nullptr);
14201 nsContentUtils::DispatchEventOnlyToChrome(
14202 doc, ToSupports(e), u"MozDOMPointerLock:Entered"_ns, CanBubble::eYes,
14203 Cancelable::eNo, /* DefaultAction */ nullptr);
14204 return NS_OK;
14207 void Document::RequestPointerLock(Element* aElement, CallerType aCallerType) {
14208 NS_ASSERTION(aElement,
14209 "Must pass non-null element to Document::RequestPointerLock");
14211 nsCOMPtr<Element> pointerLockedElement =
14212 do_QueryReferent(EventStateManager::sPointerLockedElement);
14213 if (aElement == pointerLockedElement) {
14214 DispatchPointerLockChange(this);
14215 return;
14218 if (const char* msg = GetPointerLockError(aElement, pointerLockedElement)) {
14219 DispatchPointerLockError(this, msg);
14220 return;
14223 bool userInputOrSystemCaller = HasValidTransientUserGestureActivation() ||
14224 aCallerType == CallerType::System;
14225 nsCOMPtr<nsIRunnable> request =
14226 new PointerLockRequest(aElement, userInputOrSystemCaller);
14227 Dispatch(TaskCategory::Other, request.forget());
14230 bool Document::SetPointerLock(Element* aElement, StyleCursorKind aCursorStyle) {
14231 MOZ_ASSERT(!aElement || aElement->OwnerDoc() == this,
14232 "We should be either unlocking pointer (aElement is nullptr), "
14233 "or locking pointer to an element in this document");
14234 #ifdef DEBUG
14235 if (!aElement) {
14236 nsCOMPtr<Document> pointerLockedDoc =
14237 do_QueryReferent(EventStateManager::sPointerLockedDoc);
14238 MOZ_ASSERT(pointerLockedDoc == this);
14240 #endif
14242 PresShell* presShell = GetPresShell();
14243 if (!presShell) {
14244 NS_WARNING("SetPointerLock(): No PresShell");
14245 if (!aElement) {
14246 // If we are unlocking pointer lock, but for some reason the doc
14247 // has already detached from the presshell, just ask the event
14248 // state manager to release the pointer.
14249 EventStateManager::SetPointerLock(nullptr, nullptr);
14250 return true;
14252 return false;
14254 nsPresContext* presContext = presShell->GetPresContext();
14255 if (!presContext) {
14256 NS_WARNING("SetPointerLock(): Unable to get PresContext");
14257 return false;
14260 nsCOMPtr<nsIWidget> widget;
14261 nsIFrame* rootFrame = presShell->GetRootFrame();
14262 if (!NS_WARN_IF(!rootFrame)) {
14263 widget = rootFrame->GetNearestWidget();
14264 NS_WARNING_ASSERTION(widget,
14265 "SetPointerLock(): Unable to find widget in "
14266 "presShell->GetRootFrame()->GetNearestWidget();");
14267 if (aElement && !widget) {
14268 return false;
14272 // Hide the cursor and set pointer lock for future mouse events
14273 RefPtr<EventStateManager> esm = presContext->EventStateManager();
14274 esm->SetCursor(aCursorStyle, nullptr, Nothing(), widget, true);
14275 EventStateManager::SetPointerLock(widget, aElement);
14277 return true;
14280 void Document::UnlockPointer(Document* aDoc) {
14281 if (!EventStateManager::sIsPointerLocked) {
14282 return;
14285 nsCOMPtr<Document> pointerLockedDoc =
14286 do_QueryReferent(EventStateManager::sPointerLockedDoc);
14287 if (!pointerLockedDoc || (aDoc && aDoc != pointerLockedDoc)) {
14288 return;
14290 if (!pointerLockedDoc->SetPointerLock(nullptr, StyleCursorKind::Auto)) {
14291 return;
14294 nsCOMPtr<Element> pointerLockedElement =
14295 do_QueryReferent(EventStateManager::sPointerLockedElement);
14296 ChangePointerLockedElement(nullptr, pointerLockedDoc, pointerLockedElement);
14298 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
14299 pointerLockedElement, u"MozDOMPointerLock:Exited"_ns, CanBubble::eYes,
14300 ChromeOnlyDispatch::eYes);
14301 asyncDispatcher->RunDOMEventWhenSafe();
14304 void Document::UpdateVisibilityState() {
14305 dom::VisibilityState oldState = mVisibilityState;
14306 mVisibilityState = ComputeVisibilityState();
14307 if (oldState != mVisibilityState) {
14308 nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
14309 u"visibilitychange"_ns,
14310 CanBubble::eYes, Cancelable::eNo);
14311 EnumerateActivityObservers(NotifyActivityChanged);
14314 if (mVisibilityState == dom::VisibilityState::Visible) {
14315 MaybeActiveMediaComponents();
14319 VisibilityState Document::ComputeVisibilityState() const {
14320 // We have to check a few pieces of information here:
14321 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
14322 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
14323 // want to use GetWindow here because it does weird groveling for windows
14324 // in some cases.
14325 // 3) Is our outer window background? If so, we're hidden.
14326 // Otherwise, we're visible.
14327 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
14328 mWindow->GetOuterWindow()->IsBackground()) {
14329 return dom::VisibilityState::Hidden;
14332 return dom::VisibilityState::Visible;
14335 void Document::PostVisibilityUpdateEvent() {
14336 nsCOMPtr<nsIRunnable> event =
14337 NewRunnableMethod("Document::UpdateVisibilityState", this,
14338 &Document::UpdateVisibilityState);
14339 Dispatch(TaskCategory::Other, event.forget());
14342 void Document::MaybeActiveMediaComponents() {
14343 if (!mWindow) {
14344 return;
14347 GetWindow()->MaybeActiveMediaComponents();
14350 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
14351 nsINode::AddSizeOfExcludingThis(aWindowSizes, &aWindowSizes.mDOMOtherSize);
14353 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
14354 AddSizeOfNodeTree(*kid, aWindowSizes);
14357 // IMPORTANT: for our ComputedValues measurements, we want to measure
14358 // ComputedValues accessible from DOM elements before ComputedValues not
14359 // accessible from DOM elements (i.e. accessible only from the frame tree).
14361 // Therefore, the measurement of the Document superclass must happen after
14362 // the measurement of DOM nodes (above), because Document contains the
14363 // PresShell, which contains the frame tree.
14364 if (mPresShell) {
14365 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
14368 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
14370 aWindowSizes.mPropertyTablesSize +=
14371 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
14373 if (EventListenerManager* elm = GetExistingListenerManager()) {
14374 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
14377 if (mNodeInfoManager) {
14378 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
14381 aWindowSizes.mDOMMediaQueryLists += mDOMMediaQueryLists.sizeOfExcludingThis(
14382 aWindowSizes.mState.mMallocSizeOf);
14384 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
14385 aWindowSizes.mDOMMediaQueryLists +=
14386 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
14389 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
14391 for (auto& sheetArray : mAdditionalSheets) {
14392 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
14394 // Lumping in the loader with the style-sheets size is not ideal,
14395 // but most of the things in there are in fact stylesheets, so it
14396 // doesn't seem worthwhile to separate it out.
14397 // This can be null if we've already been unlinked.
14398 if (mCSSLoader) {
14399 aWindowSizes.mLayoutStyleSheetsSize +=
14400 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
14403 if (mResizeObserverController) {
14404 mResizeObserverController->AddSizeOfIncludingThis(aWindowSizes);
14407 aWindowSizes.mDOMOtherSize += mAttrStyleSheet
14408 ? mAttrStyleSheet->DOMSizeOfIncludingThis(
14409 aWindowSizes.mState.mMallocSizeOf)
14410 : 0;
14412 aWindowSizes.mDOMOtherSize += mStyledLinks.ShallowSizeOfExcludingThis(
14413 aWindowSizes.mState.mMallocSizeOf);
14415 // Measurement of the following members may be added later if DMD finds it
14416 // is worthwhile:
14417 // - mMidasCommandManager
14418 // - many!
14421 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
14422 aWindowSizes.mDOMOtherSize += aWindowSizes.mState.mMallocSizeOf(this);
14423 DocAddSizeOfExcludingThis(aWindowSizes);
14426 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
14427 size_t* aNodeSize) const {
14428 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
14429 // nsDocuments can only appear at the top of the DOM tree, and we use the
14430 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
14431 // be called.
14432 MOZ_CRASH();
14435 /* static */
14436 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
14437 size_t nodeSize = 0;
14438 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
14440 // This is where we transfer the nodeSize obtained from
14441 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
14442 switch (aNode.NodeType()) {
14443 case nsINode::ELEMENT_NODE:
14444 aWindowSizes.mDOMElementNodesSize += nodeSize;
14445 break;
14446 case nsINode::TEXT_NODE:
14447 aWindowSizes.mDOMTextNodesSize += nodeSize;
14448 break;
14449 case nsINode::CDATA_SECTION_NODE:
14450 aWindowSizes.mDOMCDATANodesSize += nodeSize;
14451 break;
14452 case nsINode::COMMENT_NODE:
14453 aWindowSizes.mDOMCommentNodesSize += nodeSize;
14454 break;
14455 default:
14456 aWindowSizes.mDOMOtherSize += nodeSize;
14457 break;
14460 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
14461 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
14464 if (aNode.IsContent()) {
14465 nsTArray<nsIContent*> anonKids;
14466 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
14467 nsIContent::eAllChildren);
14468 for (nsIContent* anonKid : anonKids) {
14469 AddSizeOfNodeTree(*anonKid, aWindowSizes);
14472 if (auto* element = Element::FromNode(aNode)) {
14473 if (ShadowRoot* shadow = element->GetShadowRoot()) {
14474 AddSizeOfNodeTree(*shadow, aWindowSizes);
14479 // NOTE(emilio): If you feel smart and want to change this function to use
14480 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
14481 // sane way, and kids of <content> won't point to the parent, so we'd never
14482 // find the root node where we should stop at.
14483 for (nsIContent* kid = aNode.GetFirstChild(); kid;
14484 kid = kid->GetNextSibling()) {
14485 AddSizeOfNodeTree(*kid, aWindowSizes);
14489 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
14490 ErrorResult& rv) {
14491 nsCOMPtr<nsIScriptGlobalObject> global =
14492 do_QueryInterface(aGlobal.GetAsSupports());
14493 if (!global) {
14494 rv.Throw(NS_ERROR_UNEXPECTED);
14495 return nullptr;
14498 nsCOMPtr<nsIScriptObjectPrincipal> prin =
14499 do_QueryInterface(aGlobal.GetAsSupports());
14500 if (!prin) {
14501 rv.Throw(NS_ERROR_UNEXPECTED);
14502 return nullptr;
14505 nsCOMPtr<nsIURI> uri;
14506 NS_NewURI(getter_AddRefs(uri), "about:blank");
14507 if (!uri) {
14508 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
14509 return nullptr;
14512 nsCOMPtr<Document> doc;
14513 nsresult res = NS_NewDOMDocument(
14514 getter_AddRefs(doc), VoidString(), EmptyString(), nullptr, uri, uri,
14515 prin->GetPrincipal(), true, global, DocumentFlavorPlain);
14516 if (NS_FAILED(res)) {
14517 rv.Throw(res);
14518 return nullptr;
14521 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
14523 return doc.forget();
14526 XPathExpression* Document::CreateExpression(const nsAString& aExpression,
14527 XPathNSResolver* aResolver,
14528 ErrorResult& rv) {
14529 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
14532 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
14533 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
14536 already_AddRefed<XPathResult> Document::Evaluate(
14537 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
14538 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
14539 ErrorResult& rv) {
14540 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
14541 aType, aResult, rv);
14544 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
14545 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
14546 if (!item) {
14547 return nullptr;
14549 nsCOMPtr<nsIDocShellTreeOwner> owner;
14550 item->GetTreeOwner(getter_AddRefs(owner));
14551 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
14552 if (!appWin) {
14553 return nullptr;
14555 nsCOMPtr<nsIDocShell> appWinShell;
14556 appWin->GetDocShell(getter_AddRefs(appWinShell));
14557 if (!SameCOMIdentity(appWinShell, item)) {
14558 return nullptr;
14560 return appWin.forget();
14563 WindowContext* Document::GetTopLevelWindowContext() const {
14564 WindowContext* windowContext = GetWindowContext();
14565 return windowContext ? windowContext->TopWindowContext() : nullptr;
14568 Document* Document::GetTopLevelContentDocument() {
14569 Document* parent;
14571 if (!mLoadedAsData) {
14572 parent = this;
14573 } else {
14574 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
14575 if (!window) {
14576 return nullptr;
14579 parent = window->GetExtantDoc();
14580 if (!parent) {
14581 return nullptr;
14585 do {
14586 if (parent->IsTopLevelContentDocument()) {
14587 break;
14590 // If we ever have a non-content parent before we hit a toplevel content
14591 // parent, then we're never going to find one. Just bail.
14592 if (!parent->IsContentDocument()) {
14593 return nullptr;
14596 parent = parent->GetInProcessParentDocument();
14597 } while (parent);
14599 return parent;
14602 const Document* Document::GetTopLevelContentDocument() const {
14603 const Document* parent;
14605 if (!mLoadedAsData) {
14606 parent = this;
14607 } else {
14608 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
14609 if (!window) {
14610 return nullptr;
14613 parent = window->GetExtantDoc();
14614 if (!parent) {
14615 return nullptr;
14619 do {
14620 if (parent->IsTopLevelContentDocument()) {
14621 break;
14624 // If we ever have a non-content parent before we hit a toplevel content
14625 // parent, then we're never going to find one. Just bail.
14626 if (!parent->IsContentDocument()) {
14627 return nullptr;
14630 parent = parent->GetInProcessParentDocument();
14631 } while (parent);
14633 return parent;
14636 void Document::PropagateUseCounters(Document* aParentDocument) {
14637 MOZ_ASSERT(this != aParentDocument);
14639 // Don't count chrome resources, even in the web content.
14640 if (NodePrincipal()->SchemeIs("chrome")) {
14641 return;
14644 // What really matters here is that our use counters get propagated as
14645 // high up in the content document hierarchy as possible. So,
14646 // starting with aParentDocument, we need to find the toplevel content
14647 // document, and propagate our use counters into its
14648 // mChildDocumentUseCounters.
14649 Document* contentParent = aParentDocument->GetTopLevelContentDocument();
14650 if (!contentParent) {
14651 return;
14654 SetCssUseCounterBits();
14655 contentParent->mChildDocumentUseCounters |= mUseCounters;
14656 contentParent->mChildDocumentUseCounters |= mChildDocumentUseCounters;
14659 bool Document::HasScriptsBlockedBySandbox() {
14660 return mSandboxFlags & SANDBOXED_SCRIPTS;
14663 // Some use-counter sanity-checking.
14664 static_assert(size_t(eUseCounter_EndCSSProperties) -
14665 size_t(eUseCounter_FirstCSSProperty) ==
14666 size_t(eCSSProperty_COUNT_with_aliases),
14667 "We should have the right amount of CSS property use counters");
14668 static_assert(size_t(eUseCounter_Count) -
14669 size_t(eUseCounter_FirstCountedUnknownProperty) ==
14670 size_t(CountedUnknownProperty::Count),
14671 "We should have the right amount of counted unknown properties"
14672 " use counters");
14673 static_assert(size_t(eUseCounter_Count) * 2 ==
14674 size_t(Telemetry::HistogramUseCounterCount),
14675 "There should be two histograms (document and page)"
14676 " for each use counter");
14678 #define ASSERT_CSS_COUNTER(id_, method_) \
14679 static_assert(size_t(eUseCounter_property_##method_) - \
14680 size_t(eUseCounter_FirstCSSProperty) == \
14681 size_t(id_), \
14682 "Order for CSS counters and CSS property id should match");
14683 #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
14684 #define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
14685 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
14686 #define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
14687 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
14688 #define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
14689 ASSERT_CSS_COUNTER(eCSSPropertyAlias_##aliasid_, method_)
14690 #include "mozilla/ServoCSSPropList.h"
14691 #undef CSS_PROP_ALIAS
14692 #undef CSS_PROP_SHORTHAND
14693 #undef CSS_PROP_LONGHAND
14694 #undef CSS_PROP_PUBLIC_OR_PRIVATE
14695 #undef ASSERT_CSS_COUNTER
14697 void Document::SetCssUseCounterBits() {
14698 auto* docCounters = mStyleUseCounters.get();
14699 if (!docCounters) {
14700 return;
14703 if (StaticPrefs::layout_css_use_counters_enabled()) {
14704 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
14705 auto id = nsCSSPropertyID(i);
14706 if (Servo_IsPropertyIdRecordedInUseCounter(docCounters, id)) {
14707 SetUseCounter(nsCSSProps::UseCounterFor(id));
14712 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
14713 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
14714 auto id = CountedUnknownProperty(i);
14715 if (Servo_IsUnknownPropertyRecordedInUseCounter(docCounters, id)) {
14716 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
14722 void Document::PropagateUseCountersToPage() {
14723 if (mDisplayDocument) {
14724 // If we are a resource document, we won't have a docshell and so we won't
14725 // record any page use counters on this document. Instead, we should
14726 // forward it up to the document that loaded us.
14727 MOZ_ASSERT(!mDocumentContainer);
14728 return PropagateUseCounters(mDisplayDocument);
14731 if (IsBeingUsedAsImage()) {
14732 // If this is an SVG image document, we also won't have a docshell.
14734 // But we do report the use counters via PropagateUseCounters() from the
14735 // image code.
14736 MOZ_ASSERT(!mDocumentContainer);
14737 return;
14740 // We only care about use counters in content. If we're already a toplevel
14741 // content document, then we should have already set the use counter on
14742 // ourselves, and we are done.
14743 Document* contentParent = GetTopLevelContentDocument();
14744 if (!contentParent || this == contentParent) {
14745 return;
14748 PropagateUseCounters(contentParent);
14751 void Document::ReportUseCounters() {
14752 static const bool kDebugUseCounters = false;
14754 if (mReportedUseCounters) {
14755 return;
14758 mReportedUseCounters = true;
14759 SetCssUseCounterBits();
14761 // Call ReportUseCounters in all our outstanding subdocuments and resources
14762 // and such. This needs to be here so that all our sub documents propagate our
14763 // counters to us if needed.
14765 // We need to do this explicitly (rather than, e.g., moving the
14766 // ReportUseCounters() call after DestroyContent(), which destroys the frame
14767 // loaders) because subdocument destruction may be (and is generally) async
14768 // (why?), and thus we have no guarantee of Document::Destroy being called on
14769 // children before us. Children will just early-return above.
14771 // This is a bit racy in the sense that we may miss some of the children use
14772 // counters that happen in between, but that isn't probably a huge deal at
14773 // this point where we're destroying the parent document already.
14775 // An alternative would be to move the ReportUseCounters() call to a script
14776 // runner or something after calling DestroyContent(), but that looks a bit
14777 // fishy as well.
14779 if (mSubDocuments) {
14780 for (auto iter = mSubDocuments->ConstIter(); !iter.Done(); iter.Next()) {
14781 auto* entry = static_cast<SubDocMapEntry*>(iter.Get());
14782 entry->mSubDocument->ReportUseCounters();
14785 if (Document* doc = GetLatestStaticClone()) {
14786 doc->ReportUseCounters();
14788 EnumerateExternalResources([](Document& aDoc) {
14789 aDoc.ReportUseCounters();
14790 return CallState::Continue;
14794 PropagateUseCountersToPage();
14796 if (Telemetry::HistogramUseCounterCount > 0 &&
14797 (IsContentDocument() || IsResourceDoc())) {
14798 if (NodePrincipal()->SchemeIs("about") ||
14799 NodePrincipal()->SchemeIs("chrome") ||
14800 NodePrincipal()->SchemeIs("resource")) {
14801 return;
14804 if (kDebugUseCounters) {
14805 nsCString spec;
14806 NodePrincipal()->GetAsciiSpec(spec);
14808 // URIs can be rather long for data documents, so truncate them to
14809 // some reasonable length.
14810 spec.Truncate(std::min(128U, spec.Length()));
14811 printf("-- Use counters for %s --\n", spec.get());
14814 // We keep separate counts for individual documents and top-level
14815 // pages to more accurately track how many web pages might break if
14816 // certain features were removed. Consider the case of a single
14817 // HTML document with several SVG images and/or iframes with
14818 // sub-documents of their own. If we maintained a single set of use
14819 // counters and all the sub-documents use a particular feature, then
14820 // telemetry would indicate that we would be breaking N documents if
14821 // that feature were removed. Whereas with a document/top-level
14822 // page split, we can see that N documents would be affected, but
14823 // only a single web page would be affected.
14825 // The difference between the values of these two histograms and the
14826 // related use counters below tell us how many pages did *not* use
14827 // the feature in question. For instance, if we see that a given
14828 // session has destroyed 30 content documents, but a particular use
14829 // counter shows only a count of 5, we can infer that the use
14830 // counter was *not* used in 25 of those 30 documents.
14832 // We do things this way, rather than accumulating a boolean flag
14833 // for each use counter, to avoid sending histograms for features
14834 // that don't get widely used. Doing things in this fashion means
14835 // smaller telemetry payloads and faster processing on the server
14836 // side.
14837 Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
14838 if (IsTopLevelContentDocument()) {
14839 Telemetry::Accumulate(Telemetry::TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED,
14843 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
14844 UseCounter uc = static_cast<UseCounter>(c);
14846 Telemetry::HistogramID id = static_cast<Telemetry::HistogramID>(
14847 Telemetry::HistogramFirstUseCounter + uc * 2);
14848 bool value = GetUseCounter(uc);
14850 if (value) {
14851 if (kDebugUseCounters) {
14852 const char* name = Telemetry::GetHistogramName(id);
14853 if (name) {
14854 printf(" %s", name);
14855 } else {
14856 printf(" #%d", id);
14858 printf(": %d\n", value);
14861 Telemetry::Accumulate(id, 1);
14864 if (IsTopLevelContentDocument()) {
14865 id = static_cast<Telemetry::HistogramID>(
14866 Telemetry::HistogramFirstUseCounter + uc * 2 + 1);
14867 value = GetUseCounter(uc) || GetChildDocumentUseCounter(uc);
14869 if (value) {
14870 if (kDebugUseCounters) {
14871 const char* name = Telemetry::GetHistogramName(id);
14872 if (name) {
14873 printf(" %s", name);
14874 } else {
14875 printf(" #%d", id);
14877 printf(": %d\n", value);
14880 Telemetry::Accumulate(id, 1);
14886 if (IsTopLevelContentDocument()) {
14887 CSSIntCoord adjustmentLength =
14888 CSSPixel::FromAppUnits(mScrollAnchorAdjustmentLength).Rounded();
14889 Telemetry::Accumulate(Telemetry::SCROLL_ANCHOR_ADJUSTMENT_LENGTH,
14890 adjustmentLength);
14891 Telemetry::Accumulate(Telemetry::SCROLL_ANCHOR_ADJUSTMENT_COUNT,
14892 mScrollAnchorAdjustmentCount);
14896 void Document::UpdateIntersectionObservations() {
14897 if (mIntersectionObservers.IsEmpty()) {
14898 return;
14901 DOMHighResTimeStamp time = 0;
14902 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
14903 Performance* perf = window->GetPerformance();
14904 if (perf) {
14905 time = perf->Now();
14908 nsTArray<RefPtr<DOMIntersectionObserver>> observers(
14909 mIntersectionObservers.Count());
14910 for (auto iter = mIntersectionObservers.Iter(); !iter.Done(); iter.Next()) {
14911 DOMIntersectionObserver* observer = iter.Get()->GetKey();
14912 observers.AppendElement(observer);
14914 for (const auto& observer : observers) {
14915 if (observer) {
14916 observer->Update(this, time);
14921 void Document::ScheduleIntersectionObserverNotification() {
14922 if (mIntersectionObservers.IsEmpty()) {
14923 return;
14925 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14926 nsCOMPtr<nsIRunnable> notification =
14927 NewRunnableMethod("Document::NotifyIntersectionObservers", this,
14928 &Document::NotifyIntersectionObservers);
14929 Dispatch(TaskCategory::Other, notification.forget());
14932 void Document::NotifyIntersectionObservers() {
14933 nsTArray<RefPtr<DOMIntersectionObserver>> observers(
14934 mIntersectionObservers.Count());
14935 for (auto iter = mIntersectionObservers.Iter(); !iter.Done(); iter.Next()) {
14936 DOMIntersectionObserver* observer = iter.Get()->GetKey();
14937 observers.AppendElement(observer);
14939 for (const auto& observer : observers) {
14940 if (observer) {
14941 // MOZ_KnownLive because 'observers' is guaranteed to
14942 // keep it alive.
14944 // Even with https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 fixed
14945 // this might need to stay, because 'observers' is not const, so it's not
14946 // obvious how to prove via static analysis that it won't change and
14947 // release us.
14948 MOZ_KnownLive(observer)->Notify();
14953 DOMIntersectionObserver& Document::EnsureLazyLoadImageObserver() {
14954 if (!mLazyLoadImageObserver) {
14955 mLazyLoadImageObserver =
14956 DOMIntersectionObserver::CreateLazyLoadObserver(*this);
14958 return *mLazyLoadImageObserver;
14961 void Document::NotifyLayerManagerRecreated() {
14962 EnumerateActivityObservers(NotifyActivityChanged);
14963 EnumerateSubDocuments([](Document& aSubDoc) {
14964 aSubDoc.NotifyLayerManagerRecreated();
14965 return CallState::Continue;
14969 XPathEvaluator* Document::XPathEvaluator() {
14970 if (!mXPathEvaluator) {
14971 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
14973 return mXPathEvaluator.get();
14976 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
14977 return mCachedEncoder.forget();
14980 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
14981 mCachedEncoder = aEncoder;
14984 void Document::SetContentTypeInternal(const nsACString& aType) {
14985 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
14986 aType.EqualsLiteral("application/xhtml+xml")) {
14987 mDefaultElementType = kNameSpaceID_XHTML;
14990 mCachedEncoder = nullptr;
14991 mContentType = aType;
14994 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
14996 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
14998 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
14999 mStateObjectContainer = scContainer;
15000 mStateObjectCached = nullptr;
15003 Document::DocumentTheme Document::GetDocumentLWTheme() {
15004 if (mDocLWTheme == Doc_Theme_Uninitialized) {
15005 mDocLWTheme = ThreadSafeGetDocumentLWTheme();
15007 return mDocLWTheme;
15010 Document::DocumentTheme Document::ThreadSafeGetDocumentLWTheme() const {
15011 if (!NodePrincipal()->IsSystemPrincipal()) {
15012 return Doc_Theme_None;
15015 if (mDocLWTheme != Doc_Theme_Uninitialized) {
15016 return mDocLWTheme;
15019 DocumentTheme theme = Doc_Theme_None; // No lightweight theme by default
15020 Element* element = GetRootElement();
15021 if (element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
15022 nsGkAtoms::_true, eCaseMatters)) {
15023 theme = Doc_Theme_Neutral;
15024 nsAutoString lwTheme;
15025 element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwthemetextcolor, lwTheme);
15026 if (lwTheme.EqualsLiteral("dark")) {
15027 theme = Doc_Theme_Dark;
15028 } else if (lwTheme.EqualsLiteral("bright")) {
15029 theme = Doc_Theme_Bright;
15033 return theme;
15036 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
15037 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
15038 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
15039 ELEMENT_NODE);
15040 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
15042 nsCOMPtr<Element> element;
15043 DebugOnly<nsresult> rv =
15044 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
15045 mozilla::dom::NOT_FROM_PARSER);
15047 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
15048 return element.forget();
15051 static CallState MarkDocumentTreeToBeInSyncOperation(
15052 Document& aDoc, nsTArray<RefPtr<Document>>& aDocuments) {
15053 aDoc.SetIsInSyncOperation(true);
15054 if (nsCOMPtr<nsPIDOMWindowInner> window = aDoc.GetInnerWindow()) {
15055 window->TimeoutManager().BeginSyncOperation();
15057 aDocuments.AppendElement(&aDoc);
15058 auto recurse = [&aDocuments](Document& aSubDoc) {
15059 return MarkDocumentTreeToBeInSyncOperation(aSubDoc, aDocuments);
15061 aDoc.EnumerateSubDocuments(recurse);
15062 return CallState::Continue;
15065 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc) {
15066 mMicroTaskLevel = 0;
15067 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
15068 if (ccjs) {
15069 mMicroTaskLevel = ccjs->MicroTaskLevel();
15070 ccjs->SetMicroTaskLevel(0);
15072 if (aDoc) {
15073 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
15074 if (nsCOMPtr<nsPIDOMWindowOuter> top = win->GetInProcessTop()) {
15075 if (RefPtr<Document> doc = top->GetExtantDoc()) {
15076 MarkDocumentTreeToBeInSyncOperation(*doc, mDocuments);
15083 nsAutoSyncOperation::~nsAutoSyncOperation() {
15084 for (RefPtr<Document>& doc : mDocuments) {
15085 if (nsCOMPtr<nsPIDOMWindowInner> window = doc->GetInnerWindow()) {
15086 window->TimeoutManager().EndSyncOperation();
15088 doc->SetIsInSyncOperation(false);
15090 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
15091 if (ccjs) {
15092 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
15096 gfxUserFontSet* Document::GetUserFontSet() {
15097 if (!mFontFaceSet) {
15098 return nullptr;
15101 return mFontFaceSet->GetUserFontSet();
15104 void Document::FlushUserFontSet() {
15105 if (!mFontFaceSetDirty) {
15106 return;
15109 mFontFaceSetDirty = false;
15111 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
15112 nsTArray<nsFontFaceRuleContainer> rules;
15113 RefPtr<PresShell> presShell = GetPresShell();
15114 if (presShell) {
15115 MOZ_ASSERT(mStyleSetFilled);
15116 mStyleSet->AppendFontFaceRules(rules);
15119 if (!mFontFaceSet && !rules.IsEmpty()) {
15120 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15121 mFontFaceSet = new FontFaceSet(window, this);
15124 bool changed = false;
15125 if (mFontFaceSet) {
15126 changed = mFontFaceSet->UpdateRules(rules);
15129 // We need to enqueue a style change reflow (for later) to
15130 // reflect that we're modifying @font-face rules. (However,
15131 // without a reflow, nothing will happen to start any downloads
15132 // that are needed.)
15133 if (changed && presShell) {
15134 if (nsPresContext* presContext = presShell->GetPresContext()) {
15135 presContext->UserFontSetUpdated();
15141 void Document::MarkUserFontSetDirty() {
15142 if (mFontFaceSetDirty) {
15143 return;
15145 mFontFaceSetDirty = true;
15146 if (PresShell* presShell = GetPresShell()) {
15147 presShell->EnsureStyleFlush();
15151 FontFaceSet* Document::Fonts() {
15152 if (!mFontFaceSet) {
15153 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15154 mFontFaceSet = new FontFaceSet(window, this);
15155 FlushUserFontSet();
15157 return mFontFaceSet;
15160 void Document::ReportHasScrollLinkedEffect() {
15161 if (mHasScrollLinkedEffect) {
15162 // We already did this once for this document, don't do it again.
15163 return;
15165 mHasScrollLinkedEffect = true;
15166 nsContentUtils::ReportToConsole(
15167 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
15168 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound2");
15171 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
15172 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
15173 topWc->SetSHEntryHasUserInteraction(aHasInteraction);
15177 bool Document::GetSHEntryHasUserInteraction() {
15178 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
15179 return topWc->GetSHEntryHasUserInteraction();
15181 return false;
15184 void Document::SetUserHasInteracted() {
15185 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
15186 ("Document %p has been interacted by user.", this));
15188 // We maybe need to update the user-interaction permission.
15189 MaybeStoreUserInteractionAsPermission();
15191 // For purposes of reducing irrelevant session history entries on
15192 // the back button, we annotate entries with whether they had user
15193 // interaction. This is gated on its own flag on the WindowContext
15194 // (instead of mUserHasInteracted) to account for the fact that multiple
15195 // top-level SH entries can be associated with the same document.
15196 // Thus, whenever we create a new SH entry for this document,
15197 // this flag is reset.
15198 if (!GetSHEntryHasUserInteraction()) {
15199 nsIDocShell* docShell = this->GetDocShell();
15200 if (docShell) {
15201 nsCOMPtr<nsISHEntry> currentEntry;
15202 bool oshe;
15203 nsresult rv =
15204 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
15205 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
15206 currentEntry->SetHasUserInteraction(true);
15209 SetSHEntryHasUserInteraction(true);
15212 if (mUserHasInteracted) {
15213 return;
15216 mUserHasInteracted = true;
15218 if (mChannel) {
15219 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
15220 loadInfo->SetDocumentHasUserInteracted(true);
15222 // Tell the parent process about user interaction
15223 if (auto* wgc = GetWindowGlobalChild()) {
15224 wgc->SendUpdateDocumentHasUserInteracted(true);
15227 MaybeAllowStorageForOpenerAfterUserInteraction();
15230 BrowsingContext* Document::GetBrowsingContext() const {
15231 nsCOMPtr<nsIDocShell> docshell(mDocumentContainer);
15232 return docshell ? docshell->GetBrowsingContext() : nullptr;
15235 void Document::NotifyUserGestureActivation() {
15236 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
15237 bc->PreOrderWalk([&](BrowsingContext* aContext) {
15238 nsIDocShell* docShell = aContext->GetDocShell();
15239 if (!docShell) {
15240 return;
15243 Document* document = docShell->GetDocument();
15244 if (!document) {
15245 return;
15248 // XXXedgar we probably could just check `IsInProcess()` after fission
15249 // enable.
15250 if (NodePrincipal()->Equals(document->NodePrincipal())) {
15251 aContext->NotifyUserGestureActivation();
15255 for (bc = bc->GetParent(); bc; bc = bc->GetParent()) {
15256 bc->NotifyUserGestureActivation();
15261 bool Document::HasBeenUserGestureActivated() {
15262 RefPtr<BrowsingContext> bc = GetBrowsingContext();
15263 return bc && bc->HasBeenUserGestureActivated();
15266 void Document::ClearUserGestureActivation() {
15267 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
15268 bc = bc->Top();
15269 bc->PreOrderWalk([&](BrowsingContext* aContext) {
15270 aContext->NotifyResetUserGestureActivation();
15275 bool Document::HasValidTransientUserGestureActivation() {
15276 RefPtr<BrowsingContext> bc = GetBrowsingContext();
15277 return bc && bc->HasValidTransientUserGestureActivation();
15280 bool Document::ConsumeTransientUserGestureActivation() {
15281 RefPtr<BrowsingContext> bc = GetBrowsingContext();
15282 return bc && bc->ConsumeTransientUserGestureActivation();
15285 void Document::SetDocTreeHadAudibleMedia() {
15286 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
15287 if (topWc && !topWc->GetDocTreeHadAudibleMedia()) {
15288 topWc->SetDocTreeHadAudibleMedia(true);
15292 DocumentAutoplayPolicy Document::AutoplayPolicy() const {
15293 return AutoplayPolicy::IsAllowedToPlay(*this);
15296 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
15297 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
15298 return;
15301 // This will probably change for project fission, but currently this document
15302 // and the opener are on the same process. In the future, we should make this
15303 // part async.
15304 nsPIDOMWindowInner* inner = GetInnerWindow();
15305 if (NS_WARN_IF(!inner)) {
15306 return;
15309 uint32_t cookieBehavior = CookieJarSettings()->GetCookieBehavior();
15310 if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
15311 cookieBehavior ==
15312 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
15313 // We care about first-party tracking resources only.
15314 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
15315 return;
15317 } else {
15318 MOZ_ASSERT(net::CookieJarSettings::IsRejectThirdPartyWithExceptions(
15319 cookieBehavior));
15322 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
15323 if (NS_WARN_IF(!outer)) {
15324 return;
15327 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
15328 if (!openerBC) {
15329 // No opener.
15330 return;
15333 // We want to ensure the following check works for both fission mode
15334 // and non-fission mode:
15335 // "If the opener is not a 3rd party and if this window is not a 3rd
15336 // party with respect to the opener, we should not continue."
15338 // In non-fission mode, the opener and the opened window are in the same
15339 // process, we can use nsContentUtils::IsThirdPartyWindowOrChannel to
15340 // do the check.
15341 // In fission mode, if this window is not a 3rd party with respect to
15342 // the opener, they must be in the same process, so we can still use
15343 // IsThirdPartyWindowOrChannel(openerInner) to continue to check if
15344 // the opener is a 3rd party.
15345 if (openerBC->IsInProcess()) {
15346 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
15347 if (NS_WARN_IF(!outerOpener)) {
15348 return;
15351 nsCOMPtr<nsPIDOMWindowInner> openerInner =
15352 outerOpener->GetCurrentInnerWindow();
15353 if (NS_WARN_IF(!openerInner)) {
15354 return;
15357 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
15358 if (NS_WARN_IF(!openerDocument)) {
15359 return;
15362 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
15363 if (NS_WARN_IF(!openerURI)) {
15364 return;
15367 // If the opener is not a 3rd party and if this window is not
15368 // a 3rd party with respect to the opener, we should not continue.
15369 if (!nsContentUtils::IsThirdPartyWindowOrChannel(inner, nullptr,
15370 openerURI) &&
15371 !nsContentUtils::IsThirdPartyWindowOrChannel(openerInner, nullptr,
15372 nullptr)) {
15373 return;
15377 // We don't care when the asynchronous work finishes here.
15378 Unused << ContentBlocking::AllowAccessFor(
15379 NodePrincipal(), openerBC,
15380 ContentBlockingNotifier::eOpenerAfterUserInteraction);
15383 namespace {
15385 // Documents can stay alive for days. We don't want to update the permission
15386 // value at any user-interaction, and, using a timer triggered any X seconds
15387 // should be good enough. 'X' is taken from
15388 // privacy.userInteraction.document.interval pref.
15389 // We also want to store the user-interaction before shutting down, and, for
15390 // this reason, this class implements nsIAsyncShutdownBlocker interface.
15391 class UserIntractionTimer final : public Runnable,
15392 public nsITimerCallback,
15393 public nsIAsyncShutdownBlocker {
15394 public:
15395 NS_DECL_ISUPPORTS_INHERITED
15397 explicit UserIntractionTimer(Document* aDocument)
15398 : Runnable("UserIntractionTimer"),
15399 mPrincipal(aDocument->NodePrincipal()),
15400 mDocument(do_GetWeakReference(aDocument)) {
15401 static int32_t userInteractionTimerId = 0;
15402 // Blocker names must be unique. Let's create it now because when needed,
15403 // the document could be already gone.
15404 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
15405 ++userInteractionTimerId, aDocument);
15408 // Runnable interface
15410 NS_IMETHOD
15411 Run() override {
15412 uint32_t interval =
15413 StaticPrefs::privacy_userInteraction_document_interval();
15414 if (!interval) {
15415 return NS_OK;
15418 RefPtr<UserIntractionTimer> self = this;
15419 auto raii =
15420 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
15422 nsresult rv = NS_NewTimerWithCallback(
15423 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
15424 NS_ENSURE_SUCCESS(rv, NS_OK);
15426 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
15427 NS_ENSURE_TRUE(!!phase, NS_OK);
15429 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
15430 __LINE__, u"UserIntractionTimer shutdown"_ns);
15431 NS_ENSURE_SUCCESS(rv, NS_OK);
15433 raii.release();
15434 return NS_OK;
15437 // nsITimerCallback interface
15439 NS_IMETHOD
15440 Notify(nsITimer* aTimer) override {
15441 StoreUserInteraction();
15442 return NS_OK;
15445 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
15446 using nsINamed::GetName;
15447 #endif
15449 // nsIAsyncShutdownBlocker interface
15451 NS_IMETHOD
15452 GetName(nsAString& aName) override {
15453 aName = mBlockerName;
15454 return NS_OK;
15457 NS_IMETHOD
15458 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
15459 CancelTimerAndStoreUserInteraction();
15460 return NS_OK;
15463 NS_IMETHOD
15464 GetState(nsIPropertyBag**) override { return NS_OK; }
15466 private:
15467 ~UserIntractionTimer() = default;
15469 void StoreUserInteraction() {
15470 // Remove the shutting down blocker
15471 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
15472 if (phase) {
15473 phase->RemoveBlocker(this);
15476 // If the document is not gone, let's reset its timer flag.
15477 nsCOMPtr<Document> document = do_QueryReferent(mDocument);
15478 if (document) {
15479 ContentBlockingUserInteraction::Observe(mPrincipal);
15480 document->ResetUserInteractionTimer();
15484 void CancelTimerAndStoreUserInteraction() {
15485 if (mTimer) {
15486 mTimer->Cancel();
15487 mTimer = nullptr;
15490 StoreUserInteraction();
15493 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
15494 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
15495 NS_ENSURE_TRUE(!!svc, nullptr);
15497 nsCOMPtr<nsIAsyncShutdownClient> phase;
15498 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
15499 NS_ENSURE_SUCCESS(rv, nullptr);
15501 return phase.forget();
15504 nsCOMPtr<nsIPrincipal> mPrincipal;
15505 nsWeakPtr mDocument;
15507 nsCOMPtr<nsITimer> mTimer;
15509 nsString mBlockerName;
15512 NS_IMPL_ISUPPORTS_INHERITED(UserIntractionTimer, Runnable, nsITimerCallback,
15513 nsIAsyncShutdownBlocker)
15515 } // namespace
15517 void Document::MaybeStoreUserInteractionAsPermission() {
15518 // We care about user-interaction stored only for top-level documents.
15519 if (!IsTopLevelContentDocument()) {
15520 return;
15523 if (!mUserHasInteracted) {
15524 // First interaction, let's store this info now.
15525 ContentBlockingUserInteraction::Observe(NodePrincipal());
15526 return;
15529 if (mHasUserInteractionTimerScheduled) {
15530 return;
15533 nsCOMPtr<nsIRunnable> task = new UserIntractionTimer(this);
15534 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
15535 EventQueuePriority::Idle);
15536 if (NS_WARN_IF(NS_FAILED(rv))) {
15537 return;
15540 // This value will be reset by the timer.
15541 mHasUserInteractionTimerScheduled = true;
15544 void Document::ResetUserInteractionTimer() {
15545 mHasUserInteractionTimerScheduled = false;
15548 bool Document::IsExtensionPage() const {
15549 return Preferences::GetBool("media.autoplay.allow-extension-background-pages",
15550 true) &&
15551 BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
15554 void Document::TraceProtos(JSTracer* aTrc) {
15555 if (mPrototypeDocument) {
15556 mPrototypeDocument->TraceProtos(aTrc);
15561 * Retrieves the classification of the Flash plugins in the document based on
15562 * the classification lists. For more information, see
15563 * toolkit/components/url-classifier/flash-block-lists.rst
15565 FlashClassification Document::DocumentFlashClassification() {
15566 // Disable flash blocking when fission is enabled(See Bug 1584931).
15567 const auto fnIsFlashBlockingEnabled = [] {
15568 return StaticPrefs::plugins_flashBlock_enabled() && !FissionAutostart();
15571 // If neither pref is on, skip the null-principal and principal URI checks.
15572 if (!StaticPrefs::plugins_http_https_only() && !fnIsFlashBlockingEnabled()) {
15573 return FlashClassification::Unknown;
15576 if (!NodePrincipal()->GetIsContentPrincipal()) {
15577 return FlashClassification::Denied;
15580 if (StaticPrefs::plugins_http_https_only()) {
15581 // Only allow plugins for documents from an HTTP/HTTPS origin. This should
15582 // allow dependent data: URIs to load plugins, but not:
15583 // * chrome documents
15584 // * "bare" data: loads
15585 // * FTP/gopher/file
15587 if (!(NodePrincipal()->SchemeIs("http") ||
15588 NodePrincipal()->SchemeIs("https"))) {
15589 return FlashClassification::Denied;
15593 // If flash blocking is disabled, it is equivalent to all sites being
15594 // on neither list.
15595 if (!fnIsFlashBlockingEnabled()) {
15596 return FlashClassification::Unknown;
15599 if (mFlashClassification == FlashClassification::Unknown) {
15600 mFlashClassification = DocumentFlashClassificationInternal();
15603 return mFlashClassification;
15606 void Document::AddResizeObserver(ResizeObserver& aObserver) {
15607 if (!mResizeObserverController) {
15608 mResizeObserverController = MakeUnique<ResizeObserverController>(this);
15610 mResizeObserverController->AddResizeObserver(aObserver);
15613 void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
15614 MOZ_DIAGNOSTIC_ASSERT(mResizeObserverController, "No controller?");
15615 if (MOZ_UNLIKELY(!mResizeObserverController)) {
15616 return;
15618 mResizeObserverController->RemoveResizeObserver(aObserver);
15621 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
15622 if (!mPermissionDelegateHandler) {
15623 mPermissionDelegateHandler =
15624 mozilla::MakeAndAddRef<PermissionDelegateHandler>(this);
15627 if (!mPermissionDelegateHandler->Initialize()) {
15628 mPermissionDelegateHandler = nullptr;
15631 return mPermissionDelegateHandler;
15634 void Document::ScheduleResizeObserversNotification() const {
15635 if (!mResizeObserverController) {
15636 return;
15639 mResizeObserverController->ScheduleNotification();
15643 * Initializes |mIsThirdPartyForFlashClassifier| if necessary and returns its
15644 * value. The value returned represents whether this document should be
15645 * considered Third-Party.
15647 * A top-level document cannot be a considered Third-Party; only subdocuments
15648 * may. For a subdocument to be considered Third-Party, it must meet ANY ONE
15649 * of the following requirements:
15650 * - The document's parent is Third-Party
15651 * - The document has a different scheme (http/https) than its parent document
15652 * - The document's domain and subdomain do not match those of its parent
15653 * document.
15655 * If there is an error in determining whether the document is Third-Party,
15656 * it will be assumed to be Third-Party for security reasons.
15658 bool Document::IsThirdPartyForFlashClassifier() {
15659 if (mIsThirdPartyForFlashClassifier.isSome()) {
15660 return mIsThirdPartyForFlashClassifier.value();
15663 BrowsingContext* browsingContext = this->GetBrowsingContext();
15664 if (!browsingContext) {
15665 mIsThirdPartyForFlashClassifier.emplace(true);
15666 return mIsThirdPartyForFlashClassifier.value();
15669 if (browsingContext->IsTop()) {
15670 mIsThirdPartyForFlashClassifier.emplace(false);
15671 return mIsThirdPartyForFlashClassifier.value();
15674 nsCOMPtr<Document> parentDocument = GetInProcessParentDocument();
15675 if (!parentDocument) {
15676 // Failure
15677 mIsThirdPartyForFlashClassifier.emplace(true);
15678 return mIsThirdPartyForFlashClassifier.value();
15681 if (parentDocument->IsThirdPartyForFlashClassifier()) {
15682 mIsThirdPartyForFlashClassifier.emplace(true);
15683 return mIsThirdPartyForFlashClassifier.value();
15686 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
15687 nsCOMPtr<nsIPrincipal> parentPrincipal = parentDocument->GetPrincipal();
15689 bool principalsMatch = false;
15690 nsresult rv = principal->Equals(parentPrincipal, &principalsMatch);
15692 if (NS_WARN_IF(NS_FAILED(rv))) {
15693 // Failure
15694 mIsThirdPartyForFlashClassifier.emplace(true);
15695 return mIsThirdPartyForFlashClassifier.value();
15698 if (!principalsMatch) {
15699 mIsThirdPartyForFlashClassifier.emplace(true);
15700 return mIsThirdPartyForFlashClassifier.value();
15703 // Fall-through. Document is not a Third-Party Document.
15704 mIsThirdPartyForFlashClassifier.emplace(false);
15705 return mIsThirdPartyForFlashClassifier.value();
15708 FlashClassification Document::DocumentFlashClassificationInternal() {
15709 FlashClassification classification = FlashClassification::Unknown;
15711 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(GetChannel());
15712 if (httpChannel) {
15713 nsIHttpChannel::FlashPluginState state = nsIHttpChannel::FlashPluginUnknown;
15714 httpChannel->GetFlashPluginState(&state);
15716 // Allow unknown children to inherit allowed status from parent, but do not
15717 // allow denied children to do so.
15719 if (state == nsIHttpChannel::FlashPluginDeniedInSubdocuments &&
15720 IsThirdPartyForFlashClassifier()) {
15721 return FlashClassification::Denied;
15724 if (state == nsIHttpChannel::FlashPluginDenied) {
15725 return FlashClassification::Denied;
15728 if (state == nsIHttpChannel::FlashPluginAllowed) {
15729 classification = FlashClassification::Allowed;
15733 if (IsTopLevelContentDocument()) {
15734 return classification;
15737 Document* parentDocument = GetInProcessParentDocument();
15738 if (!parentDocument) {
15739 return FlashClassification::Denied;
15742 FlashClassification parentClassification =
15743 parentDocument->DocumentFlashClassification();
15745 if (parentClassification == FlashClassification::Denied) {
15746 return FlashClassification::Denied;
15749 // Allow unknown children to inherit allowed status from parent, but
15750 // do not allow denied children to do so.
15751 if (classification == FlashClassification::Unknown &&
15752 parentClassification == FlashClassification::Allowed) {
15753 return FlashClassification::Allowed;
15756 return classification;
15759 void Document::ClearStaleServoData() {
15760 DocumentStyleRootIterator iter(this);
15761 while (Element* root = iter.GetNextStyleRoot()) {
15762 RestyleManager::ClearServoDataFromSubtree(root);
15766 Selection* Document::GetSelection(ErrorResult& aRv) {
15767 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
15768 if (!window) {
15769 return nullptr;
15772 if (!window->IsCurrentInnerWindow()) {
15773 return nullptr;
15776 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
15779 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
15780 mozilla::ErrorResult& aRv) {
15781 nsIGlobalObject* global = GetScopeObject();
15782 if (!global) {
15783 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
15784 return nullptr;
15787 RefPtr<Promise> promise =
15788 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
15789 if (aRv.Failed()) {
15790 return nullptr;
15793 if (NodePrincipal()->GetIsNullPrincipal()) {
15794 promise->MaybeResolve(false);
15795 return promise.forget();
15798 if (IsTopLevelContentDocument()) {
15799 promise->MaybeResolve(true);
15800 return promise.forget();
15803 RefPtr<BrowsingContext> bc = GetBrowsingContext();
15804 if (!bc) {
15805 promise->MaybeResolve(false);
15806 return promise.forget();
15809 RefPtr<BrowsingContext> topBC = bc->Top();
15810 // We check if the document is a first-party document here by testing if the
15811 // top-level window is same-origin. In non-Fission mode, we can directly get
15812 // the top-level window through the top browsing context since it should be
15813 // in-process. And test their principals.
15815 // In Fission mode, we can also directly get the top-level window. If we
15816 // cannot get it, this means the top-level window is cross-origin. Then, we
15817 // know our answer.
15818 if (auto* topOuterWindow = topBC->GetDOMWindow()) {
15819 if (nsGlobalWindowOuter::Cast(topOuterWindow)
15820 ->GetPrincipal()
15821 ->Equals(NodePrincipal())) {
15822 promise->MaybeResolve(true);
15823 return promise.forget();
15827 nsPIDOMWindowInner* inner = GetInnerWindow();
15828 nsGlobalWindowOuter* outer = nullptr;
15829 if (inner) {
15830 outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
15831 promise->MaybeResolve(outer->IsStorageAccessPermissionGranted());
15832 } else {
15833 promise->MaybeRejectWithUndefined();
15835 return promise.forget();
15838 RefPtr<Document::GetContentBlockingEventsPromise>
15839 Document::GetContentBlockingEvents() {
15840 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
15841 if (!inner) {
15842 return nullptr;
15845 RefPtr<WindowGlobalChild> wgc = inner->GetWindowGlobalChild();
15846 if (!wgc) {
15847 return nullptr;
15850 return wgc->SendGetContentBlockingEvents()->Then(
15851 GetCurrentSerialEventTarget(), __func__,
15852 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
15853 ResolveOrRejectValue& aValue) {
15854 if (aValue.IsResolve()) {
15855 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
15856 aValue.ResolveValue(), __func__);
15859 return Document::GetContentBlockingEventsPromise::CreateAndReject(
15860 false, __func__);
15864 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
15865 mozilla::ErrorResult& aRv) {
15866 nsIGlobalObject* global = GetScopeObject();
15867 if (!global) {
15868 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
15869 return nullptr;
15872 // Propagate user input event handling to the resolve handler
15873 RefPtr<Promise> promise =
15874 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
15875 if (aRv.Failed()) {
15876 return nullptr;
15879 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
15880 if (!inner) {
15881 promise->MaybeRejectWithUndefined();
15882 return promise.forget();
15885 // Step 1. If the document already has been granted access, resolve.
15886 RefPtr<nsGlobalWindowOuter> outer =
15887 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
15888 if (!outer) {
15889 promise->MaybeRejectWithUndefined();
15890 return promise.forget();
15893 if (outer->IsStorageAccessPermissionGranted()) {
15894 promise->MaybeResolveWithUndefined();
15895 return promise.forget();
15898 // Step 2. If the document has a null origin, reject.
15899 if (NodePrincipal()->GetIsNullPrincipal()) {
15900 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
15901 nsLiteralCString("requestStorageAccess"),
15902 this, nsContentUtils::eDOM_PROPERTIES,
15903 "RequestStorageAccessNullPrincipal");
15904 promise->MaybeRejectWithUndefined();
15905 return promise.forget();
15908 RefPtr<BrowsingContext> bc = GetBrowsingContext();
15909 if (!bc) {
15910 promise->MaybeRejectWithUndefined();
15911 return promise.forget();
15914 // Only enforce third-party checks when there is a reason to enforce them.
15915 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
15916 // Step 3. If the document's frame is the main frame, resolve.
15917 if (IsTopLevelContentDocument()) {
15918 promise->MaybeResolveWithUndefined();
15919 return promise.forget();
15922 // Step 4. If the sub frame's origin is equal to the main frame's, resolve.
15924 // In fission, if the sub frame's origin differs from the main frame's
15925 // origin, they will be in different processes. We use IsInProcess()
15926 // check here to deterimine whether they have the same origin. In
15927 // non-fission mode, it is always in-process so we need to compare their
15928 // principals.
15929 if (bc->Top()->IsInProcess()) {
15930 nsCOMPtr<nsPIDOMWindowOuter> topOuter = bc->Top()->GetDOMWindow();
15931 if (!topOuter) {
15932 promise->MaybeRejectWithUndefined();
15933 return promise.forget();
15936 nsCOMPtr<Document> topLevelDoc = topOuter->GetExtantDoc();
15937 if (!topLevelDoc) {
15938 promise->MaybeRejectWithUndefined();
15939 return promise.forget();
15942 if (topLevelDoc->NodePrincipal()->Equals(NodePrincipal())) {
15943 promise->MaybeResolveWithUndefined();
15944 return promise.forget();
15949 // Step 5. If the sub frame is not sandboxed, skip to step 7.
15950 // Step 6. If the sub frame doesn't have the token
15951 // "allow-storage-access-by-user-activation", reject.
15952 if (StorageAccessSandboxed()) {
15953 nsContentUtils::ReportToConsole(
15954 nsIScriptError::errorFlag, nsLiteralCString("requestStorageAccess"),
15955 this, nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessSandboxed");
15956 promise->MaybeRejectWithUndefined();
15957 return promise.forget();
15960 // Step 7. If the sub frame's parent frame is not the top frame, reject.
15961 RefPtr<BrowsingContext> parentBC = bc->GetParent();
15962 if (parentBC && !parentBC->IsTopContent()) {
15963 nsContentUtils::ReportToConsole(
15964 nsIScriptError::errorFlag, nsLiteralCString("requestStorageAccess"),
15965 this, nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessNested");
15966 promise->MaybeRejectWithUndefined();
15967 return promise.forget();
15970 // Step 8. If the browser is not processing a user gesture, reject.
15971 if (!UserActivation::IsHandlingUserInput()) {
15972 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
15973 nsLiteralCString("requestStorageAccess"),
15974 this, nsContentUtils::eDOM_PROPERTIES,
15975 "RequestStorageAccessUserGesture");
15976 promise->MaybeRejectWithUndefined();
15977 return promise.forget();
15980 // Step 9. Check any additional rules that the browser has.
15981 // Examples: skip-lists, on-device classification,
15982 // user settings, anti-clickjacking heuristics, or prompting the
15983 // user for explicit permission. Reject if some rule is not fulfilled.
15984 if (CookieJarSettings()->GetRejectThirdPartyContexts()) {
15985 // Only do something special for third-party tracking content.
15986 if (StorageDisabledByAntiTracking(this, nullptr)) {
15987 // Note: If this has returned true, the top-level document is guaranteed
15988 // to not be on the Content Blocking allow list.
15989 MOZ_ASSERT(!CookieJarSettings()->GetIsOnContentBlockingAllowList());
15991 RefPtr<Document> self(this);
15993 auto performFinalChecks =
15994 [inner,
15995 self]() -> RefPtr<ContentBlocking::StorageAccessFinalCheckPromise> {
15996 RefPtr<ContentBlocking::StorageAccessFinalCheckPromise::Private> p =
15997 new ContentBlocking::StorageAccessFinalCheckPromise::Private(
15998 __func__);
15999 RefPtr<StorageAccessPermissionRequest> sapr =
16000 StorageAccessPermissionRequest::Create(
16001 inner,
16002 // Allow
16003 [p] {
16004 Telemetry::AccumulateCategorical(
16005 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
16006 p->Resolve(ContentBlocking::eAllow, __func__);
16008 // Block
16009 [p] {
16010 Telemetry::AccumulateCategorical(
16011 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
16012 p->Reject(false, __func__);
16015 typedef ContentPermissionRequestBase::PromptResult PromptResult;
16016 PromptResult pr = sapr->CheckPromptPrefs();
16018 if (pr == PromptResult::Pending) {
16019 // We're about to show a prompt, record the request attempt
16020 Telemetry::AccumulateCategorical(
16021 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
16024 self->AutomaticStorageAccessPermissionCanBeGranted()->Then(
16025 GetCurrentSerialEventTarget(), __func__,
16026 [p, pr, sapr,
16027 inner](const AutomaticStorageAccessPermissionGrantPromise::
16028 ResolveOrRejectValue& aValue) -> void {
16029 // Make a copy because we can't modified copy-captured lambda
16030 // variables.
16031 PromptResult pr2 = pr;
16033 bool storageAccessCanBeGrantedAutomatically =
16034 aValue.IsResolve() && aValue.ResolveValue();
16036 bool autoGrant = false;
16037 if (pr2 == PromptResult::Pending &&
16038 storageAccessCanBeGrantedAutomatically) {
16039 pr2 = PromptResult::Granted;
16040 autoGrant = true;
16042 Telemetry::AccumulateCategorical(
16043 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
16044 AllowAutomatically);
16047 if (pr2 != PromptResult::Pending) {
16048 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
16049 pr2 == PromptResult::Denied);
16050 if (pr2 == PromptResult::Granted) {
16051 ContentBlocking::StorageAccessPromptChoices choice =
16052 ContentBlocking::eAllow;
16053 if (autoGrant) {
16054 choice = ContentBlocking::eAllowAutoGrant;
16056 if (!autoGrant) {
16057 p->Resolve(choice, __func__);
16058 } else {
16059 sapr->MaybeDelayAutomaticGrants()->Then(
16060 GetCurrentSerialEventTarget(), __func__,
16061 [p, choice] { p->Resolve(choice, __func__); },
16062 [p] { p->Reject(false, __func__); });
16064 return;
16066 p->Reject(false, __func__);
16067 return;
16070 sapr->RequestDelayedTask(
16071 inner->EventTargetFor(TaskCategory::Other),
16072 ContentPermissionRequestBase::DelayedTaskType::Request);
16075 return std::move(p);
16077 ContentBlocking::AllowAccessFor(
16078 NodePrincipal(), bc, ContentBlockingNotifier::eStorageAccessAPI,
16079 performFinalChecks)
16080 ->Then(
16081 GetCurrentSerialEventTarget(), __func__,
16082 [outer, promise] {
16083 // Step 10. Grant the document access to cookies and store
16084 // that fact for
16085 // the purposes of future calls to
16086 // hasStorageAccess() and requestStorageAccess().
16087 outer->SetStorageAccessPermissionGranted(true);
16088 promise->MaybeResolveWithUndefined();
16090 [outer, promise] {
16091 outer->SetStorageAccessPermissionGranted(false);
16092 promise->MaybeRejectWithUndefined();
16095 return promise.forget();
16099 outer->SetStorageAccessPermissionGranted(true);
16100 promise->MaybeResolveWithUndefined();
16101 return promise.forget();
16104 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
16105 Document::AutomaticStorageAccessPermissionCanBeGranted() {
16106 if (XRE_IsContentProcess()) {
16107 // In the content process, we need to ask the parent process to compute
16108 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
16109 // isn't accessible in the content process.
16110 ContentChild* cc = ContentChild::GetSingleton();
16111 MOZ_ASSERT(cc);
16113 return cc
16114 ->SendAutomaticStorageAccessPermissionCanBeGranted(
16115 IPC::Principal(NodePrincipal()))
16116 ->Then(GetCurrentSerialEventTarget(), __func__,
16117 [](const ContentChild::
16118 AutomaticStorageAccessPermissionCanBeGrantedPromise::
16119 ResolveOrRejectValue& aValue) {
16120 if (aValue.IsResolve()) {
16121 return AutomaticStorageAccessPermissionGrantPromise::
16122 CreateAndResolve(aValue.ResolveValue(), __func__);
16125 return AutomaticStorageAccessPermissionGrantPromise::
16126 CreateAndReject(false, __func__);
16130 if (XRE_IsParentProcess()) {
16131 // In the parent process, we can directly compute this.
16132 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
16133 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
16134 __func__);
16137 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
16138 false, __func__);
16141 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
16142 nsIPrincipal* aPrincipal) {
16143 nsAutoCString prefix;
16144 AntiTrackingUtils::CreateStoragePermissionKey(aPrincipal, prefix);
16146 PermissionManager* permManager = PermissionManager::GetInstance();
16147 if (NS_WARN_IF(!permManager)) {
16148 return false;
16151 typedef nsTArray<RefPtr<nsIPermission>> Permissions;
16152 Permissions perms;
16153 nsresult rv = permManager->GetAllWithTypePrefix(prefix, perms);
16154 if (NS_WARN_IF(NS_FAILED(rv))) {
16155 return false;
16158 nsAutoCString prefix2(prefix);
16159 prefix2.Append('^');
16160 typedef nsTArray<nsCString> Origins;
16161 Origins origins;
16163 for (const auto& perm : perms) {
16164 nsAutoCString type;
16165 rv = perm->GetType(type);
16166 if (NS_WARN_IF(NS_FAILED(rv))) {
16167 return false;
16169 // Let's make sure that we're not looking at a permission for
16170 // https://exampletracker.company when we mean to look for the
16171 // permission for https://exampletracker.com!
16172 if (type != prefix && StringHead(type, prefix2.Length()) != prefix2) {
16173 continue;
16176 nsCOMPtr<nsIPrincipal> principal;
16177 rv = perm->GetPrincipal(getter_AddRefs(principal));
16178 if (NS_WARN_IF(NS_FAILED(rv))) {
16179 return false;
16182 nsAutoCString origin;
16183 rv = principal->GetOrigin(origin);
16184 if (NS_WARN_IF(NS_FAILED(rv))) {
16185 return false;
16188 ToLowerCase(origin);
16190 if (origins.IndexOf(origin) == Origins::NoIndex) {
16191 origins.AppendElement(origin);
16195 nsCOMPtr<nsIBrowserUsage> bu =
16196 do_ImportModule("resource:///modules/BrowserUsageTelemetry.jsm");
16197 if (NS_WARN_IF(!bu)) {
16198 return false;
16201 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
16202 rv = bu->GetUniqueDomainsVisitedInPast24Hours(
16203 &uniqueDomainsVisitedInPast24Hours);
16204 if (NS_WARN_IF(NS_FAILED(rv))) {
16205 return false;
16208 // one percent of the number of top-levels origins visited in the current
16209 // session (but not to exceed 24 hours), or the value of the
16210 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
16211 // higher.
16212 size_t maxConcurrentAutomaticGrants = std::max(
16213 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
16214 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
16217 size_t originsThirdPartyHasAccessTo = origins.Length();
16219 return StaticPrefs::dom_storage_access_auto_grants() &&
16220 originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
16223 void Document::RecordNavigationTiming(ReadyState aReadyState) {
16224 if (!XRE_IsContentProcess()) {
16225 return;
16227 if (!IsTopLevelContentDocument()) {
16228 return;
16230 // If we dont have the timing yet (mostly because the doc is still loading),
16231 // get it from docshell.
16232 RefPtr<nsDOMNavigationTiming> timing = mTiming;
16233 if (!timing) {
16234 if (!mDocumentContainer) {
16235 return;
16237 timing = mDocumentContainer->GetNavigationTiming();
16238 if (!timing) {
16239 return;
16242 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
16243 switch (aReadyState) {
16244 case READYSTATE_LOADING:
16245 if (!mDOMLoadingSet) {
16246 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
16247 startTime);
16248 mDOMLoadingSet = true;
16250 break;
16251 case READYSTATE_INTERACTIVE:
16252 if (!mDOMInteractiveSet) {
16253 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
16254 startTime);
16255 mDOMInteractiveSet = true;
16257 break;
16258 case READYSTATE_COMPLETE:
16259 if (!mDOMCompleteSet) {
16260 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
16261 startTime);
16262 mDOMCompleteSet = true;
16264 break;
16265 default:
16266 NS_WARNING("Unexpected ReadyState value");
16267 break;
16271 bool Document::ModuleScriptsEnabled() {
16272 return nsContentUtils::IsChromeDoc(this) ||
16273 StaticPrefs::dom_moduleScripts_enabled();
16276 void Document::ReportShadowDOMUsage() {
16277 nsPIDOMWindowInner* inner = GetInnerWindow();
16278 if (NS_WARN_IF(!inner)) {
16279 return;
16282 WindowContext* wc = inner->GetWindowContext();
16283 if (NS_WARN_IF(!wc)) {
16284 return;
16287 WindowContext* topWc = wc->TopWindowContext();
16288 if (topWc->GetHasReportedShadowDOMUsage()) {
16289 return;
16292 topWc->SetHasReportedShadowDOMUsage(true);
16295 // static
16296 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
16297 return StaticPrefs::dom_storage_access_enabled() &&
16298 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
16301 bool Document::StorageAccessSandboxed() const {
16302 return Document::StorageAccessSandboxed(GetSandboxFlags());
16305 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
16306 if (mCachedTabSizeGeneration == 0 ||
16307 GetGeneration() != mCachedTabSizeGeneration) {
16308 return false;
16310 aSizes->mDom += mCachedTabSizes.mDom;
16311 aSizes->mStyle += mCachedTabSizes.mStyle;
16312 aSizes->mOther += mCachedTabSizes.mOther;
16313 return true;
16316 void Document::SetCachedSizes(nsTabSizes* aSizes) {
16317 mCachedTabSizes.mDom = aSizes->mDom;
16318 mCachedTabSizes.mStyle = aSizes->mStyle;
16319 mCachedTabSizes.mOther = aSizes->mOther;
16320 mCachedTabSizeGeneration = GetGeneration();
16323 already_AddRefed<nsAtom> Document::GetContentLanguageAsAtomForStyle() const {
16324 nsAutoString contentLang;
16325 GetContentLanguage(contentLang);
16326 contentLang.StripWhitespace();
16328 // Content-Language may be a comma-separated list of language codes,
16329 // in which case the HTML5 spec says to treat it as unknown
16330 if (!contentLang.IsEmpty() && !contentLang.Contains(char16_t(','))) {
16331 return NS_Atomize(contentLang);
16334 return nullptr;
16337 already_AddRefed<nsAtom> Document::GetLanguageForStyle() const {
16338 RefPtr<nsAtom> lang = GetContentLanguageAsAtomForStyle();
16339 if (!lang) {
16340 lang = mLanguageFromCharset;
16342 return lang.forget();
16345 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
16346 nsAtom* aLanguage, bool* aNeedsToCache) const {
16347 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
16348 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
16351 void Document::DoCacheAllKnownLangPrefs() {
16352 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
16353 RefPtr<nsAtom> lang = GetLanguageForStyle();
16354 StaticPresData* data = StaticPresData::Get();
16355 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
16356 data->GetFontPrefsForLang(nsGkAtoms::x_math);
16357 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
16358 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
16359 for (auto iter = mLanguagesUsed.Iter(); !iter.Done(); iter.Next()) {
16360 data->GetFontPrefsForLang(iter.Get()->GetKey());
16362 mMayNeedFontPrefsUpdate = false;
16365 void Document::RecomputeLanguageFromCharset() {
16366 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
16367 RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
16368 if (language == nsGkAtoms::Unicode) {
16369 language = service->GetLocaleLanguage();
16372 if (language == mLanguageFromCharset) {
16373 return;
16376 mMayNeedFontPrefsUpdate = true;
16377 mLanguageFromCharset = std::move(language);
16380 nsICookieJarSettings* Document::CookieJarSettings() {
16381 // If we are here, this is probably a javascript: URL document. In any case,
16382 // we must have a nsCookieJarSettings. Let's create it.
16383 if (!mCookieJarSettings) {
16384 Document* inProcessParent = GetInProcessParentDocument();
16386 mCookieJarSettings =
16387 inProcessParent
16388 ? net::CookieJarSettings::Create(
16389 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
16390 mozilla::net::CookieJarSettings::Cast(
16391 inProcessParent->CookieJarSettings())
16392 ->GetPartitionKey(),
16393 inProcessParent->CookieJarSettings()
16394 ->GetIsFirstPartyIsolated(),
16395 inProcessParent->CookieJarSettings()
16396 ->GetIsOnContentBlockingAllowList())
16397 : net::CookieJarSettings::Create();
16399 if (auto* wgc = GetWindowGlobalChild()) {
16400 net::CookieJarSettingsArgs csArgs;
16401 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
16402 // Update cookie settings in the parent process
16403 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
16404 NS_WARNING(
16405 "Failed to update document's cookie jar settings on the "
16406 "WindowGlobalParent");
16411 return mCookieJarSettings;
16414 bool Document::HasStorageAccessPermissionGranted() {
16415 // The HasStoragePermission flag in LoadInfo remains fixed when
16416 // it is set in the parent process, so we need to check the cache
16417 // to see if the permission is granted afterwards.
16418 nsPIDOMWindowInner* inner = GetInnerWindow();
16419 if (inner && inner->HasStorageAccessPermissionGranted()) {
16420 return true;
16423 if (!mChannel) {
16424 return false;
16427 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16428 return loadInfo->GetHasStoragePermission();
16431 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
16432 nsPIDOMWindowInner* inner = GetInnerWindow();
16433 if (!inner) {
16434 return NodePrincipal();
16437 // Return our cached storage principal if one exists.
16438 if (mActiveStoragePrincipal) {
16439 return mActiveStoragePrincipal;
16442 // We use the lower-level ContentBlocking API here to ensure this
16443 // check doesn't send notifications.
16444 uint32_t rejectedReason = 0;
16445 if (ContentBlocking::ShouldAllowAccessFor(inner, GetDocumentURI(),
16446 &rejectedReason)) {
16447 return mActiveStoragePrincipal = NodePrincipal();
16450 // Let's use the storage principal only if we need to partition the cookie
16451 // jar. When the permission is granted, access will be different and the
16452 // normal principal will be used.
16453 if (ShouldPartitionStorage(rejectedReason) &&
16454 !StoragePartitioningEnabled(
16455 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
16456 return mActiveStoragePrincipal = NodePrincipal();
16459 return mActiveStoragePrincipal = mPartitionedPrincipal;
16462 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
16463 mIsInitialDocumentInWindow = aIsInitialDocument;
16465 // Asynchronously tell the parent process that we are, or are no longer, the
16466 // initial document. This happens async.
16467 if (auto* wgc = GetWindowGlobalChild()) {
16468 wgc->SendSetIsInitialDocument(aIsInitialDocument);
16472 // static
16473 void Document::AddToplevelLoadingDocument(Document* aDoc) {
16474 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
16475 // Currently we're interested in foreground documents only, so bail out early.
16476 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
16477 return;
16480 if (!sLoadingForegroundTopLevelContentDocument) {
16481 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
16482 mozilla::ipc::IdleSchedulerChild* idleScheduler =
16483 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
16484 if (idleScheduler) {
16485 idleScheduler->SendRunningPrioritizedOperation();
16488 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
16489 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
16493 // static
16494 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
16495 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
16496 if (sLoadingForegroundTopLevelContentDocument) {
16497 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
16498 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
16499 delete sLoadingForegroundTopLevelContentDocument;
16500 sLoadingForegroundTopLevelContentDocument = nullptr;
16502 mozilla::ipc::IdleSchedulerChild* idleScheduler =
16503 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
16504 if (idleScheduler) {
16505 idleScheduler->SendPrioritizedOperationDone();
16511 StylePrefersColorScheme Document::PrefersColorScheme(
16512 IgnoreRFP aIgnoreRFP) const {
16513 if (aIgnoreRFP == IgnoreRFP::No &&
16514 nsContentUtils::ShouldResistFingerprinting(this)) {
16515 return StylePrefersColorScheme::Light;
16518 if (nsPresContext* pc = GetPresContext()) {
16519 if (auto devtoolsOverride = pc->GetOverridePrefersColorScheme()) {
16520 return *devtoolsOverride;
16523 if (pc->IsPrintingOrPrintPreview()) {
16524 return StylePrefersColorScheme::Light;
16528 const bool dark =
16529 !!LookAndFeel::GetInt(LookAndFeel::IntID::SystemUsesDarkTheme, 0);
16530 return dark ? StylePrefersColorScheme::Dark : StylePrefersColorScheme::Light;
16533 // static
16534 bool Document::UseOverlayScrollbars(const Document* aDocument) {
16535 BrowsingContext* bc = aDocument ? aDocument->GetBrowsingContext() : nullptr;
16536 return LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) ||
16537 (bc && bc->InRDMPane());
16540 bool Document::HasRecentlyStartedForegroundLoads() {
16541 if (!sLoadingForegroundTopLevelContentDocument) {
16542 return false;
16545 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
16546 ++i) {
16547 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
16548 // A page loaded in foreground could be in background now.
16549 if (!doc->IsInBackgroundWindow()) {
16550 nsPIDOMWindowInner* win = doc->GetInnerWindow();
16551 if (win) {
16552 Performance* perf = win->GetPerformance();
16553 if (perf &&
16554 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
16555 return true;
16561 // Didn't find any loading foreground documents, just clear the array.
16562 delete sLoadingForegroundTopLevelContentDocument;
16563 sLoadingForegroundTopLevelContentDocument = nullptr;
16565 mozilla::ipc::IdleSchedulerChild* idleScheduler =
16566 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
16567 if (idleScheduler) {
16568 idleScheduler->SendPrioritizedOperationDone();
16570 return false;
16573 nsTArray<Document::PendingFrameStaticClone>
16574 Document::TakePendingFrameStaticClones() {
16575 MOZ_ASSERT(mIsStaticDocument,
16576 "Cannot have pending frame static clones in non-static documents");
16577 return std::move(mPendingFrameStaticClones);
16580 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
16581 nsFrameLoader* aStaticCloneOf) {
16582 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
16583 clone->mElement = aElement;
16584 clone->mStaticCloneOf = aStaticCloneOf;
16587 bool Document::UseRegularPrincipal() const {
16588 return EffectiveStoragePrincipal() == NodePrincipal();
16591 bool Document::HasThirdPartyChannel() {
16592 nsCOMPtr<nsIChannel> channel = GetChannel();
16593 if (channel) {
16594 // We assume that the channel is a third-party by default.
16595 bool thirdParty = true;
16597 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = services::GetThirdPartyUtil();
16598 if (!thirdPartyUtil) {
16599 return thirdParty;
16602 // Check that if the channel is a third-party to its parent.
16603 nsresult rv =
16604 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
16605 if (NS_FAILED(rv)) {
16606 // Assume third-party in case of failure
16607 thirdParty = true;
16610 return thirdParty;
16613 if (mParentDocument) {
16614 return mParentDocument->HasThirdPartyChannel();
16617 return false;
16620 } // namespace dom
16621 } // namespace mozilla