Bug 1838484 - Don't create module loader for template contents owner document r=smaug
[gecko.git] / dom / base / Document.cpp
bloba807f76a4adabee1a4ccbddee05b785ec63ffb4c
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 "mozilla/dom/Document.h"
12 #include "mozilla/dom/DocumentInlines.h"
14 #include <inttypes.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <algorithm>
18 #include <cstddef>
19 #include <cstdint>
20 #include <initializer_list>
21 #include <iterator>
22 #include <limits>
23 #include <type_traits>
24 #include "Attr.h"
25 #include "ErrorList.h"
26 #include "ExpandedPrincipal.h"
27 #include "MainThreadUtils.h"
28 #include "MobileViewportManager.h"
29 #include "NodeUbiReporting.h"
30 #include "PLDHashTable.h"
31 #include "StorageAccessPermissionRequest.h"
32 #include "ThirdPartyUtil.h"
33 #include "domstubs.h"
34 #include "gfxPlatform.h"
35 #include "imgIContainer.h"
36 #include "imgLoader.h"
37 #include "imgRequestProxy.h"
38 #include "js/Value.h"
39 #include "jsapi.h"
40 #include "mozAutoDocUpdate.h"
41 #include "mozIDOMWindow.h"
42 #include "mozIThirdPartyUtil.h"
43 #include "mozilla/AbstractTimelineMarker.h"
44 #include "mozilla/AntiTrackingUtils.h"
45 #include "mozilla/ArrayIterator.h"
46 #include "mozilla/ArrayUtils.h"
47 #include "mozilla/AsyncEventDispatcher.h"
48 #include "mozilla/Base64.h"
49 #include "mozilla/BasePrincipal.h"
50 #include "mozilla/CSSEnabledState.h"
51 #include "mozilla/ContentBlockingAllowList.h"
52 #include "mozilla/ContentBlockingNotifier.h"
53 #include "mozilla/ContentBlockingUserInteraction.h"
54 #include "mozilla/ContentPrincipal.h"
55 #include "mozilla/CycleCollectedJSContext.h"
56 #include "mozilla/DebugOnly.h"
57 #include "mozilla/DocLoadingTimelineMarker.h"
58 #include "mozilla/DocumentStyleRootIterator.h"
59 #include "mozilla/EditorBase.h"
60 #include "mozilla/EditorCommands.h"
61 #include "mozilla/Encoding.h"
62 #include "mozilla/ErrorResult.h"
63 #include "mozilla/EventDispatcher.h"
64 #include "mozilla/EventListenerManager.h"
65 #include "mozilla/EventQueue.h"
66 #include "mozilla/EventStateManager.h"
67 #include "mozilla/ExtensionPolicyService.h"
68 #include "mozilla/FullscreenChange.h"
69 #include "mozilla/GlobalStyleSheetCache.h"
70 #include "mozilla/HTMLEditor.h"
71 #include "mozilla/HoldDropJSObjects.h"
72 #include "mozilla/IdentifierMapEntry.h"
73 #include "mozilla/InputTaskManager.h"
74 #include "mozilla/IntegerRange.h"
75 #include "mozilla/InternalMutationEvent.h"
76 #include "mozilla/Likely.h"
77 #include "mozilla/Logging.h"
78 #include "mozilla/LookAndFeel.h"
79 #include "mozilla/MacroForEach.h"
80 #include "mozilla/Maybe.h"
81 #include "mozilla/MediaFeatureChange.h"
82 #include "mozilla/MediaManager.h"
83 #include "mozilla/MemoryReporting.h"
84 #include "mozilla/NullPrincipal.h"
85 #include "mozilla/OriginAttributes.h"
86 #include "mozilla/OwningNonNull.h"
87 #include "mozilla/PendingAnimationTracker.h"
88 #include "mozilla/PendingFullscreenEvent.h"
89 #include "mozilla/PermissionDelegateHandler.h"
90 #include "mozilla/PermissionManager.h"
91 #include "mozilla/Preferences.h"
92 #include "mozilla/PreloadHashKey.h"
93 #include "mozilla/PresShell.h"
94 #include "mozilla/PresShellForwards.h"
95 #include "mozilla/PresShellInlines.h"
96 #include "mozilla/PseudoStyleType.h"
97 #include "mozilla/RefCountType.h"
98 #include "mozilla/RejectForeignAllowList.h"
99 #include "mozilla/RelativeTo.h"
100 #include "mozilla/RestyleManager.h"
101 #include "mozilla/ReverseIterator.h"
102 #include "mozilla/ScrollTimelineAnimationTracker.h"
103 #include "mozilla/SMILAnimationController.h"
104 #include "mozilla/SMILTimeContainer.h"
105 #include "mozilla/ScopeExit.h"
106 #include "mozilla/Components.h"
107 #include "mozilla/ServoStyleConsts.h"
108 #include "mozilla/ServoStyleSet.h"
109 #include "mozilla/ServoTypes.h"
110 #include "mozilla/SizeOfState.h"
111 #include "mozilla/Span.h"
112 #include "mozilla/Sprintf.h"
113 #include "mozilla/StaticAnalysisFunctions.h"
114 #include "mozilla/StaticPrefs_apz.h"
115 #include "mozilla/StaticPrefs_browser.h"
116 #include "mozilla/StaticPrefs_docshell.h"
117 #include "mozilla/StaticPrefs_dom.h"
118 #include "mozilla/StaticPrefs_editor.h"
119 #include "mozilla/StaticPrefs_fission.h"
120 #include "mozilla/StaticPrefs_full_screen_api.h"
121 #include "mozilla/StaticPrefs_layout.h"
122 #include "mozilla/StaticPrefs_network.h"
123 #include "mozilla/StaticPrefs_page_load.h"
124 #include "mozilla/StaticPrefs_privacy.h"
125 #include "mozilla/StaticPrefs_security.h"
126 #include "mozilla/StaticPrefs_widget.h"
127 #include "mozilla/StaticPresData.h"
128 #include "mozilla/StorageAccess.h"
129 #include "mozilla/StoragePrincipalHelper.h"
130 #include "mozilla/StyleSheet.h"
131 #include "mozilla/Telemetry.h"
132 #include "mozilla/TelemetryHistogramEnums.h"
133 #include "mozilla/TelemetryScalarEnums.h"
134 #include "mozilla/TextControlElement.h"
135 #include "mozilla/TextEditor.h"
136 #include "mozilla/TimelineConsumers.h"
137 #include "mozilla/TypedEnumBits.h"
138 #include "mozilla/URLDecorationStripper.h"
139 #include "mozilla/URLExtraData.h"
140 #include "mozilla/Unused.h"
141 #include "mozilla/css/ImageLoader.h"
142 #include "mozilla/css/Loader.h"
143 #include "mozilla/css/Rule.h"
144 #include "mozilla/css/SheetParsingMode.h"
145 #include "mozilla/dom/AnonymousContent.h"
146 #include "mozilla/dom/BlobURLProtocolHandler.h"
147 #include "mozilla/dom/BrowserChild.h"
148 #include "mozilla/dom/BrowsingContext.h"
149 #include "mozilla/dom/BrowsingContextGroup.h"
150 #include "mozilla/dom/CDATASection.h"
151 #include "mozilla/dom/CSPDictionariesBinding.h"
152 #include "mozilla/dom/CanonicalBrowsingContext.h"
153 #include "mozilla/dom/ChromeObserver.h"
154 #include "mozilla/dom/ClientInfo.h"
155 #include "mozilla/dom/ClientState.h"
156 #include "mozilla/dom/Comment.h"
157 #include "mozilla/dom/ContentChild.h"
158 #include "mozilla/dom/DOMImplementation.h"
159 #include "mozilla/dom/DOMIntersectionObserver.h"
160 #include "mozilla/dom/DOMStringList.h"
161 #include "mozilla/dom/DocGroup.h"
162 #include "mozilla/dom/DocumentBinding.h"
163 #include "mozilla/dom/DocumentFragment.h"
164 #include "mozilla/dom/DocumentL10n.h"
165 #include "mozilla/dom/DocumentTimeline.h"
166 #include "mozilla/dom/DocumentType.h"
167 #include "mozilla/dom/ElementBinding.h"
168 #include "mozilla/dom/Event.h"
169 #include "mozilla/dom/EventListenerBinding.h"
170 #include "mozilla/dom/FailedCertSecurityInfoBinding.h"
171 #include "mozilla/dom/FeaturePolicy.h"
172 #include "mozilla/dom/FeaturePolicyUtils.h"
173 #include "mozilla/dom/FontFaceSet.h"
174 #include "mozilla/dom/FromParser.h"
175 #include "mozilla/dom/HighlightRegistry.h"
176 #include "mozilla/dom/HTMLAllCollection.h"
177 #include "mozilla/dom/HTMLBodyElement.h"
178 #include "mozilla/dom/HTMLCollectionBinding.h"
179 #include "mozilla/dom/HTMLDialogElement.h"
180 #include "mozilla/dom/HTMLFormElement.h"
181 #include "mozilla/dom/HTMLIFrameElement.h"
182 #include "mozilla/dom/HTMLImageElement.h"
183 #include "mozilla/dom/HTMLInputElement.h"
184 #include "mozilla/dom/HTMLLinkElement.h"
185 #include "mozilla/dom/HTMLMediaElement.h"
186 #include "mozilla/dom/HTMLMetaElement.h"
187 #include "mozilla/dom/HTMLSharedElement.h"
188 #include "mozilla/dom/HTMLTextAreaElement.h"
189 #include "mozilla/dom/ImageTracker.h"
190 #include "mozilla/dom/Link.h"
191 #include "mozilla/dom/MediaQueryList.h"
192 #include "mozilla/dom/MediaSource.h"
193 #include "mozilla/dom/MutationObservers.h"
194 #include "mozilla/dom/NameSpaceConstants.h"
195 #include "mozilla/dom/Navigator.h"
196 #include "mozilla/dom/NetErrorInfoBinding.h"
197 #include "mozilla/dom/NodeInfo.h"
198 #include "mozilla/dom/NodeIterator.h"
199 #include "mozilla/dom/PContentChild.h"
200 #include "mozilla/dom/PWindowGlobalChild.h"
201 #include "mozilla/dom/PageTransitionEvent.h"
202 #include "mozilla/dom/PageTransitionEventBinding.h"
203 #include "mozilla/dom/Performance.h"
204 #include "mozilla/dom/PermissionMessageUtils.h"
205 #include "mozilla/dom/PostMessageEvent.h"
206 #include "mozilla/dom/ProcessingInstruction.h"
207 #include "mozilla/dom/Promise.h"
208 #include "mozilla/dom/PromiseNativeHandler.h"
209 #include "mozilla/dom/ResizeObserverController.h"
210 #include "mozilla/dom/RustTypes.h"
211 #include "mozilla/dom/SVGElement.h"
212 #include "mozilla/dom/SVGDocument.h"
213 #include "mozilla/dom/SVGSVGElement.h"
214 #include "mozilla/dom/SVGUseElement.h"
215 #include "mozilla/dom/ScriptLoader.h"
216 #include "mozilla/dom/ScriptSettings.h"
217 #include "mozilla/dom/Selection.h"
218 #include "mozilla/dom/ServiceWorkerContainer.h"
219 #include "mozilla/dom/ServiceWorkerDescriptor.h"
220 #include "mozilla/dom/ServiceWorkerManager.h"
221 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
222 #include "mozilla/dom/ShadowRoot.h"
223 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
224 #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
225 #include "mozilla/dom/StyleSheetList.h"
226 #include "mozilla/dom/TimeoutManager.h"
227 #include "mozilla/dom/ToggleEvent.h"
228 #include "mozilla/dom/Touch.h"
229 #include "mozilla/dom/TouchEvent.h"
230 #include "mozilla/dom/TreeOrderedArrayInlines.h"
231 #include "mozilla/dom/TreeWalker.h"
232 #include "mozilla/dom/URL.h"
233 #include "mozilla/dom/UserActivation.h"
234 #include "mozilla/dom/WindowBinding.h"
235 #include "mozilla/dom/WindowContext.h"
236 #include "mozilla/dom/WindowGlobalChild.h"
237 #include "mozilla/dom/WindowProxyHolder.h"
238 #include "mozilla/dom/WorkerDocumentListener.h"
239 #include "mozilla/dom/XPathEvaluator.h"
240 #include "mozilla/dom/nsCSPContext.h"
241 #include "mozilla/dom/nsCSPUtils.h"
242 #include "mozilla/extensions/WebExtensionPolicy.h"
243 #include "mozilla/fallible.h"
244 #include "mozilla/gfx/BaseCoord.h"
245 #include "mozilla/gfx/BaseSize.h"
246 #include "mozilla/gfx/Coord.h"
247 #include "mozilla/gfx/Point.h"
248 #include "mozilla/gfx/ScaleFactor.h"
249 #include "mozilla/glean/GleanMetrics.h"
250 #include "mozilla/intl/LocaleService.h"
251 #include "mozilla/ipc/IdleSchedulerChild.h"
252 #include "mozilla/ipc/MessageChannel.h"
253 #include "mozilla/net/ChannelEventQueue.h"
254 #include "mozilla/net/CookieJarSettings.h"
255 #include "mozilla/net/NeckoChannelParams.h"
256 #include "mozilla/net/RequestContextService.h"
257 #include "nsAboutProtocolUtils.h"
258 #include "nsAlgorithm.h"
259 #include "nsAttrValue.h"
260 #include "nsAttrValueInlines.h"
261 #include "nsBaseHashtable.h"
262 #include "nsBidiUtils.h"
263 #include "nsCRT.h"
264 #include "nsCSSPropertyID.h"
265 #include "nsCSSProps.h"
266 #include "nsCSSPseudoElements.h"
267 #include "nsCSSRendering.h"
268 #include "nsCanvasFrame.h"
269 #include "nsCaseTreatment.h"
270 #include "nsCharsetSource.h"
271 #include "nsCommandManager.h"
272 #include "nsCommandParams.h"
273 #include "nsComponentManagerUtils.h"
274 #include "nsContentCreatorFunctions.h"
275 #include "nsContentList.h"
276 #include "nsContentPermissionHelper.h"
277 #include "nsContentSecurityUtils.h"
278 #include "nsContentUtils.h"
279 #include "nsCoord.h"
280 #include "nsCycleCollectionNoteChild.h"
281 #include "nsCycleCollectionTraversalCallback.h"
282 #include "nsDOMAttributeMap.h"
283 #include "nsDOMCaretPosition.h"
284 #include "nsDOMNavigationTiming.h"
285 #include "nsDOMString.h"
286 #include "nsDeviceContext.h"
287 #include "nsDocShell.h"
288 #include "nsDocShellLoadTypes.h"
289 #include "nsEffectiveTLDService.h"
290 #include "nsError.h"
291 #include "nsEscape.h"
292 #include "nsFocusManager.h"
293 #include "nsFrameLoader.h"
294 #include "nsFrameLoaderOwner.h"
295 #include "nsGenericHTMLElement.h"
296 #include "nsGlobalWindowInner.h"
297 #include "nsGlobalWindowOuter.h"
298 #include "nsHTMLCSSStyleSheet.h"
299 #include "nsHTMLDocument.h"
300 #include "nsHTMLStyleSheet.h"
301 #include "nsHtml5Module.h"
302 #include "nsHtml5Parser.h"
303 #include "nsHtml5TreeOpExecutor.h"
304 #include "nsIAsyncShutdown.h"
305 #include "nsIAuthPrompt.h"
306 #include "nsIAuthPrompt2.h"
307 #include "nsIBFCacheEntry.h"
308 #include "nsIBaseWindow.h"
309 #include "nsIBrowserChild.h"
310 #include "nsIBrowserUsage.h"
311 #include "nsICSSLoaderObserver.h"
312 #include "nsICategoryManager.h"
313 #include "nsICertOverrideService.h"
314 #include "nsIContent.h"
315 #include "nsIContentInlines.h"
316 #include "nsIContentPolicy.h"
317 #include "nsIContentSecurityPolicy.h"
318 #include "nsIContentSink.h"
319 #include "nsICookieJarSettings.h"
320 #include "nsICookieService.h"
321 #include "nsIDOMXULCommandDispatcher.h"
322 #include "nsIDocShell.h"
323 #include "nsIDocShellTreeItem.h"
324 #include "nsIDocumentActivity.h"
325 #include "nsIDocumentEncoder.h"
326 #include "nsIDocumentLoader.h"
327 #include "nsIDocumentLoaderFactory.h"
328 #include "nsIDocumentObserver.h"
329 #include "nsIDNSService.h"
330 #include "nsIEditingSession.h"
331 #include "nsIEditor.h"
332 #include "nsIEffectiveTLDService.h"
333 #include "nsIFile.h"
334 #include "nsIFileChannel.h"
335 #include "nsIFrame.h"
336 #include "nsIGlobalObject.h"
337 #include "nsIHTMLCollection.h"
338 #include "nsIHttpChannel.h"
339 #include "nsIHttpChannelInternal.h"
340 #include "nsIIOService.h"
341 #include "nsIImageLoadingContent.h"
342 #include "nsIInlineSpellChecker.h"
343 #include "nsIInputStreamChannel.h"
344 #include "nsIInterfaceRequestorUtils.h"
345 #include "nsILayoutHistoryState.h"
346 #include "nsIMultiPartChannel.h"
347 #include "nsIMutationObserver.h"
348 #include "nsINSSErrorsService.h"
349 #include "nsINamed.h"
350 #include "nsINodeList.h"
351 #include "nsIObjectLoadingContent.h"
352 #include "nsIObserverService.h"
353 #include "nsIPermission.h"
354 #include "nsIPrompt.h"
355 #include "nsIPropertyBag2.h"
356 #include "nsIPublicKeyPinningService.h"
357 #include "nsIReferrerInfo.h"
358 #include "nsIRefreshURI.h"
359 #include "nsIRequest.h"
360 #include "nsIRequestContext.h"
361 #include "nsIRunnable.h"
362 #include "nsISHEntry.h"
363 #include "nsIScriptElement.h"
364 #include "nsIScriptError.h"
365 #include "nsIScriptGlobalObject.h"
366 #include "nsIScriptSecurityManager.h"
367 #include "nsISecurityConsoleMessage.h"
368 #include "nsISelectionController.h"
369 #include "nsISerialEventTarget.h"
370 #include "nsISimpleEnumerator.h"
371 #include "nsISiteSecurityService.h"
372 #include "nsISocketProvider.h"
373 #include "nsISpeculativeConnect.h"
374 #include "nsIStructuredCloneContainer.h"
375 #include "nsIThread.h"
376 #include "nsITimedChannel.h"
377 #include "nsITimer.h"
378 #include "nsITransportSecurityInfo.h"
379 #include "nsIURIMutator.h"
380 #include "nsIVariant.h"
381 #include "nsIWeakReference.h"
382 #include "nsIWebNavigation.h"
383 #include "nsIWidget.h"
384 #include "nsIX509Cert.h"
385 #include "nsIX509CertValidity.h"
386 #include "nsIXMLContentSink.h"
387 #include "nsIHTMLContentSink.h"
388 #include "nsIXULRuntime.h"
389 #include "nsImageLoadingContent.h"
390 #include "nsImportModule.h"
391 #include "nsLanguageAtomService.h"
392 #include "nsLayoutUtils.h"
393 #include "nsNetCID.h"
394 #include "nsNetUtil.h"
395 #include "nsNodeInfoManager.h"
396 #include "nsObjectLoadingContent.h"
397 #include "nsPIDOMWindowInlines.h"
398 #include "nsPIWindowRoot.h"
399 #include "nsPoint.h"
400 #include "nsPointerHashKeys.h"
401 #include "nsPresContext.h"
402 #include "nsQueryFrame.h"
403 #include "nsQueryObject.h"
404 #include "nsRange.h"
405 #include "nsRect.h"
406 #include "nsRefreshDriver.h"
407 #include "nsSandboxFlags.h"
408 #include "nsSerializationHelper.h"
409 #include "nsServiceManagerUtils.h"
410 #include "nsStringFlags.h"
411 #include "nsStyleUtil.h"
412 #include "nsStringIterator.h"
413 #include "nsStyleSheetService.h"
414 #include "nsStyleStruct.h"
415 #include "nsTextNode.h"
416 #include "nsUnicharUtils.h"
417 #include "nsWrapperCache.h"
418 #include "nsWrapperCacheInlines.h"
419 #include "nsXPCOMCID.h"
420 #include "nsXULAppAPI.h"
421 #include "prthread.h"
422 #include "prtime.h"
423 #include "prtypes.h"
424 #include "xpcpublic.h"
426 // XXX Must be included after mozilla/Encoding.h
427 #include "encoding_rs.h"
429 #include "mozilla/dom/XULBroadcastManager.h"
430 #include "mozilla/dom/XULPersist.h"
431 #include "nsIAppWindow.h"
432 #include "nsXULPrototypeDocument.h"
433 #include "nsXULCommandDispatcher.h"
434 #include "nsXULPopupManager.h"
435 #include "nsIDocShellTreeOwner.h"
437 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
438 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
439 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
440 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
442 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
444 mozilla::LazyLogModule gPageCacheLog("PageCache");
445 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
446 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
447 mozilla::LazyLogModule gUseCountersLog("UseCounters");
449 namespace mozilla {
450 namespace dom {
452 class Document::HeaderData {
453 public:
454 HeaderData(nsAtom* aField, const nsAString& aData)
455 : mField(aField), mData(aData) {}
457 ~HeaderData() {
458 // Delete iteratively to avoid blowing up the stack, though it shouldn't
459 // happen in practice.
460 UniquePtr<HeaderData> next = std::move(mNext);
461 while (next) {
462 next = std::move(next->mNext);
466 RefPtr<nsAtom> mField;
467 nsString mData;
468 UniquePtr<HeaderData> mNext;
471 using LinkArray = nsTArray<Link*>;
473 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
474 nullptr;
476 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
477 static LazyLogModule gCspPRLog("CSP");
478 LazyLogModule gUserInteractionPRLog("UserInteraction");
480 static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
481 nsIHttpChannel** aHttpChannel) {
482 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
483 if (httpChannel) {
484 httpChannel.forget(aHttpChannel);
485 return NS_OK;
488 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
489 if (!multipart) {
490 *aHttpChannel = nullptr;
491 return NS_OK;
494 nsCOMPtr<nsIChannel> baseChannel;
495 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
496 if (NS_WARN_IF(NS_FAILED(rv))) {
497 return rv;
500 httpChannel = do_QueryInterface(baseChannel);
501 httpChannel.forget(aHttpChannel);
503 return NS_OK;
506 } // namespace dom
508 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
510 IdentifierMapEntry::IdentifierMapEntry(
511 const IdentifierMapEntry::DependentAtomOrString* aKey)
512 : mKey(aKey ? *aKey : nullptr) {}
514 void IdentifierMapEntry::Traverse(
515 nsCycleCollectionTraversalCallback* aCallback) {
516 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
517 "mIdentifierMap mNameContentList");
518 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
520 if (mImageElement) {
521 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
522 "mIdentifierMap mImageElement element");
523 nsIContent* imageElement = mImageElement;
524 aCallback->NoteXPCOMChild(imageElement);
528 bool IdentifierMapEntry::IsEmpty() {
529 return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
530 !mImageElement;
533 bool IdentifierMapEntry::HasNameElement() const {
534 return mNameContentList && mNameContentList->Length() != 0;
537 void IdentifierMapEntry::AddContentChangeCallback(
538 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
539 if (!mChangeCallbacks) {
540 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
543 ChangeCallback cc = {aCallback, aData, aForImage};
544 mChangeCallbacks->PutEntry(cc);
547 void IdentifierMapEntry::RemoveContentChangeCallback(
548 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
549 if (!mChangeCallbacks) return;
550 ChangeCallback cc = {aCallback, aData, aForImage};
551 mChangeCallbacks->RemoveEntry(cc);
552 if (mChangeCallbacks->Count() == 0) {
553 mChangeCallbacks = nullptr;
557 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
558 Element* aNewElement,
559 bool aImageOnly) {
560 if (!mChangeCallbacks) return;
562 for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) {
563 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
564 // Don't fire image changes for non-image observers, and don't fire element
565 // changes for image observers when an image override is active.
566 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
567 continue;
570 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
571 iter.Remove();
576 void IdentifierMapEntry::AddIdElement(Element* aElement) {
577 MOZ_ASSERT(aElement, "Must have element");
578 MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
580 size_t index = mIdContentList.Insert(*aElement);
581 if (index == 0) {
582 Element* oldElement = mIdContentList->SafeElementAt(1);
583 FireChangeCallbacks(oldElement, aElement);
587 void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
588 MOZ_ASSERT(aElement, "Missing element");
590 // This should only be called while the document is in an update.
591 // Assertions near the call to this method guarantee this.
593 // This could fire in OOM situations
594 // Only assert this in HTML documents for now as XUL does all sorts of weird
595 // crap.
596 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
597 mIdContentList->Contains(aElement),
598 "Removing id entry that doesn't exist");
600 // XXXbz should this ever Compact() I guess when all the content is gone
601 // we'll just get cleaned up in the natural order of things...
602 Element* currentElement = mIdContentList->SafeElementAt(0);
603 mIdContentList.RemoveElement(*aElement);
604 if (currentElement == aElement) {
605 FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
609 void IdentifierMapEntry::SetImageElement(Element* aElement) {
610 Element* oldElement = GetImageIdElement();
611 mImageElement = aElement;
612 Element* newElement = GetImageIdElement();
613 if (oldElement != newElement) {
614 FireChangeCallbacks(oldElement, newElement, true);
618 void IdentifierMapEntry::ClearAndNotify() {
619 Element* currentElement = mIdContentList->SafeElementAt(0);
620 mIdContentList.Clear();
621 if (currentElement) {
622 FireChangeCallbacks(currentElement, nullptr);
624 mNameContentList = nullptr;
625 if (mImageElement) {
626 SetImageElement(nullptr);
628 mChangeCallbacks = nullptr;
631 namespace dom {
633 class SimpleHTMLCollection final : public nsSimpleContentList,
634 public nsIHTMLCollection {
635 public:
636 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
638 NS_DECL_ISUPPORTS_INHERITED
640 virtual nsINode* GetParentObject() override {
641 return nsSimpleContentList::GetParentObject();
643 virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
644 virtual Element* GetElementAt(uint32_t aIndex) override {
645 return mElements.SafeElementAt(aIndex)->AsElement();
648 virtual Element* GetFirstNamedElement(const nsAString& aName,
649 bool& aFound) override {
650 aFound = false;
651 RefPtr<nsAtom> name = NS_Atomize(aName);
652 for (uint32_t i = 0; i < mElements.Length(); i++) {
653 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
654 Element* element = mElements[i]->AsElement();
655 if (element->GetID() == name ||
656 (element->HasName() &&
657 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
658 aFound = true;
659 return element;
662 return nullptr;
665 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
666 AutoTArray<nsAtom*, 8> atoms;
667 for (uint32_t i = 0; i < mElements.Length(); i++) {
668 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
669 Element* element = mElements[i]->AsElement();
671 nsAtom* id = element->GetID();
672 MOZ_ASSERT(id != nsGkAtoms::_empty);
673 if (id && !atoms.Contains(id)) {
674 atoms.AppendElement(id);
677 if (element->HasName()) {
678 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
679 MOZ_ASSERT(name && name != nsGkAtoms::_empty);
680 if (name && !atoms.Contains(name)) {
681 atoms.AppendElement(name);
686 nsString* names = aNames.AppendElements(atoms.Length());
687 for (uint32_t i = 0; i < atoms.Length(); i++) {
688 atoms[i]->ToString(names[i]);
692 virtual JSObject* GetWrapperPreserveColorInternal() override {
693 return nsWrapperCache::GetWrapperPreserveColor();
695 virtual void PreserveWrapperInternal(
696 nsISupports* aScriptObjectHolder) override {
697 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
699 virtual JSObject* WrapObject(JSContext* aCx,
700 JS::Handle<JSObject*> aGivenProto) override {
701 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
704 using nsBaseContentList::Item;
706 private:
707 virtual ~SimpleHTMLCollection() = default;
710 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
711 nsIHTMLCollection)
713 } // namespace dom
715 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
716 if (!mNameContentList) {
717 mNameContentList = new dom::SimpleHTMLCollection(aNode);
720 mNameContentList->AppendElement(aElement);
723 void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
724 if (mNameContentList) {
725 mNameContentList->RemoveElement(aElement);
729 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const {
730 Element* idElement = GetIdElement();
731 return idElement &&
732 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
735 size_t IdentifierMapEntry::SizeOfExcludingThis(
736 MallocSizeOf aMallocSizeOf) const {
737 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
740 // Helper structs for the content->subdoc map
742 class SubDocMapEntry : public PLDHashEntryHdr {
743 public:
744 // Both of these are strong references
745 dom::Element* mKey; // must be first, to look like PLDHashEntryStub
746 dom::Document* mSubDocument;
749 class OnloadBlocker final : public nsIRequest {
750 public:
751 OnloadBlocker() = default;
753 NS_DECL_ISUPPORTS
754 NS_DECL_NSIREQUEST
756 private:
757 ~OnloadBlocker() = default;
760 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
762 NS_IMETHODIMP
763 OnloadBlocker::GetName(nsACString& aResult) {
764 aResult.AssignLiteral("about:document-onload-blocker");
765 return NS_OK;
768 NS_IMETHODIMP
769 OnloadBlocker::IsPending(bool* _retval) {
770 *_retval = true;
771 return NS_OK;
774 NS_IMETHODIMP
775 OnloadBlocker::GetStatus(nsresult* status) {
776 *status = NS_OK;
777 return NS_OK;
780 NS_IMETHODIMP OnloadBlocker::SetCanceledReason(const nsACString& aReason) {
781 return SetCanceledReasonImpl(aReason);
784 NS_IMETHODIMP OnloadBlocker::GetCanceledReason(nsACString& aReason) {
785 return GetCanceledReasonImpl(aReason);
788 NS_IMETHODIMP OnloadBlocker::CancelWithReason(nsresult aStatus,
789 const nsACString& aReason) {
790 return CancelWithReasonImpl(aStatus, aReason);
792 NS_IMETHODIMP
793 OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
794 NS_IMETHODIMP
795 OnloadBlocker::Suspend(void) { return NS_OK; }
796 NS_IMETHODIMP
797 OnloadBlocker::Resume(void) { return NS_OK; }
799 NS_IMETHODIMP
800 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
801 *aLoadGroup = nullptr;
802 return NS_OK;
805 NS_IMETHODIMP
806 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
808 NS_IMETHODIMP
809 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
810 *aLoadFlags = nsIRequest::LOAD_NORMAL;
811 return NS_OK;
814 NS_IMETHODIMP
815 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
816 return GetTRRModeImpl(aTRRMode);
819 NS_IMETHODIMP
820 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
821 return SetTRRModeImpl(aTRRMode);
824 NS_IMETHODIMP
825 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
827 // ==================================================================
829 namespace dom {
831 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
833 Document* ExternalResourceMap::RequestResource(
834 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
835 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
836 // If we ever start allowing non-same-origin loads here, we might need to do
837 // something interesting with aRequestingPrincipal even for the hashtable
838 // gets.
839 MOZ_ASSERT(aURI, "Must have a URI");
840 MOZ_ASSERT(aRequestingNode, "Must have a node");
841 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
842 *aPendingLoad = nullptr;
843 if (mHaveShutDown) {
844 return nullptr;
847 // First, make sure we strip the ref from aURI.
848 nsCOMPtr<nsIURI> clone;
849 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
850 if (NS_FAILED(rv) || !clone) {
851 return nullptr;
854 ExternalResource* resource;
855 mMap.Get(clone, &resource);
856 if (resource) {
857 return resource->mDocument;
860 bool loadStartSucceeded =
861 mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
862 if (!loadEntry) {
863 loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
865 if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
866 aRequestingNode))) {
867 return false;
871 RefPtr<PendingLoad> load(loadEntry.Data());
872 load.forget(aPendingLoad);
873 return true;
875 if (!loadStartSucceeded) {
876 // Make sure we don't thrash things by trying this load again, since
877 // chances are it failed for good reasons (security check, etc).
878 // This must be done outside the WithEntryHandle functor, as it accesses
879 // mPendingLoads.
880 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
883 return nullptr;
886 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
887 nsTArray<RefPtr<Document>> docs(mMap.Count());
888 for (const auto& entry : mMap.Values()) {
889 if (Document* doc = entry->mDocument) {
890 docs.AppendElement(doc);
894 for (auto& doc : docs) {
895 if (aCallback(*doc) == CallState::Stop) {
896 return;
901 void ExternalResourceMap::Traverse(
902 nsCycleCollectionTraversalCallback* aCallback) const {
903 // mPendingLoads will get cleared out as the requests complete, so
904 // no need to worry about those here.
905 for (const auto& entry : mMap) {
906 ExternalResourceMap::ExternalResource* resource = entry.GetWeak();
908 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
909 "mExternalResourceMap.mMap entry"
910 "->mDocument");
911 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
913 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
914 "mExternalResourceMap.mMap entry"
915 "->mViewer");
916 aCallback->NoteXPCOMChild(resource->mViewer);
918 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
919 "mExternalResourceMap.mMap entry"
920 "->mLoadGroup");
921 aCallback->NoteXPCOMChild(resource->mLoadGroup);
925 void ExternalResourceMap::HideViewers() {
926 for (const auto& entry : mMap) {
927 nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
928 if (viewer) {
929 viewer->Hide();
934 void ExternalResourceMap::ShowViewers() {
935 for (const auto& entry : mMap) {
936 nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
937 if (viewer) {
938 viewer->Show();
943 void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
944 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
946 if (aFromDoc->IsShowing()) {
947 aToDoc->OnPageShow(true, nullptr);
951 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
952 nsIContentViewer* aViewer,
953 nsILoadGroup* aLoadGroup,
954 Document* aDisplayDocument) {
955 MOZ_ASSERT(aURI, "Unexpected call");
956 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
957 "Must have both or neither");
959 RefPtr<PendingLoad> load;
960 mPendingLoads.Remove(aURI, getter_AddRefs(load));
962 nsresult rv = NS_OK;
964 nsCOMPtr<Document> doc;
965 if (aViewer) {
966 doc = aViewer->GetDocument();
967 NS_ASSERTION(doc, "Must have a document");
969 doc->SetDisplayDocument(aDisplayDocument);
971 // Make sure that hiding our viewer will tear down its presentation.
972 aViewer->SetSticky(false);
974 rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
975 if (NS_SUCCEEDED(rv)) {
976 rv = aViewer->Open(nullptr, nullptr);
979 if (NS_FAILED(rv)) {
980 doc = nullptr;
981 aViewer = nullptr;
982 aLoadGroup = nullptr;
986 ExternalResource* newResource =
987 mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get();
989 newResource->mDocument = doc;
990 newResource->mViewer = aViewer;
991 newResource->mLoadGroup = aLoadGroup;
992 if (doc) {
993 if (nsPresContext* pc = doc->GetPresContext()) {
994 pc->RecomputeBrowsingContextDependentData();
996 TransferShowingState(aDisplayDocument, doc);
999 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
1000 for (uint32_t i = 0; i < obs.Length(); ++i) {
1001 obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
1002 nullptr);
1005 return rv;
1008 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
1009 nsIRequestObserver)
1011 NS_IMETHODIMP
1012 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
1013 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
1014 if (map.HaveShutDown()) {
1015 return NS_BINDING_ABORTED;
1018 nsCOMPtr<nsIContentViewer> viewer;
1019 nsCOMPtr<nsILoadGroup> loadGroup;
1020 nsresult rv =
1021 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
1023 // Make sure to do this no matter what
1024 nsresult rv2 =
1025 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
1026 if (NS_FAILED(rv)) {
1027 return rv;
1029 if (NS_FAILED(rv2)) {
1030 mTargetListener = nullptr;
1031 return rv2;
1034 return mTargetListener->OnStartRequest(aRequest);
1037 nsresult ExternalResourceMap::PendingLoad::SetupViewer(
1038 nsIRequest* aRequest, nsIContentViewer** aViewer,
1039 nsILoadGroup** aLoadGroup) {
1040 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
1041 *aViewer = nullptr;
1042 *aLoadGroup = nullptr;
1044 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
1045 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
1047 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
1048 if (httpChannel) {
1049 bool requestSucceeded;
1050 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
1051 !requestSucceeded) {
1052 // Bail out on this load, since it looks like we have an HTTP error page
1053 return NS_BINDING_ABORTED;
1057 nsAutoCString type;
1058 chan->GetContentType(type);
1060 nsCOMPtr<nsILoadGroup> loadGroup;
1061 chan->GetLoadGroup(getter_AddRefs(loadGroup));
1063 // Give this document its own loadgroup
1064 nsCOMPtr<nsILoadGroup> newLoadGroup =
1065 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1066 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
1067 newLoadGroup->SetLoadGroup(loadGroup);
1069 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1070 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1072 nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
1073 new LoadgroupCallbacks(callbacks);
1074 newLoadGroup->SetNotificationCallbacks(newCallbacks);
1076 // This is some serious hackery cribbed from docshell
1077 nsCOMPtr<nsICategoryManager> catMan =
1078 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
1079 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
1080 nsCString contractId;
1081 nsresult rv =
1082 catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
1083 NS_ENSURE_SUCCESS(rv, rv);
1084 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
1085 do_GetService(contractId.get());
1086 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
1088 nsCOMPtr<nsIContentViewer> viewer;
1089 nsCOMPtr<nsIStreamListener> listener;
1090 rv = docLoaderFactory->CreateInstance(
1091 "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
1092 getter_AddRefs(listener), getter_AddRefs(viewer));
1093 NS_ENSURE_SUCCESS(rv, rv);
1094 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
1096 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
1097 if (!parser) {
1098 /// We don't want to deal with the various fake documents yet
1099 return NS_ERROR_NOT_IMPLEMENTED;
1102 // We can't handle HTML and other weird things here yet.
1103 nsIContentSink* sink = parser->GetContentSink();
1104 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
1105 if (!xmlSink) {
1106 return NS_ERROR_NOT_IMPLEMENTED;
1109 listener.swap(mTargetListener);
1110 viewer.forget(aViewer);
1111 newLoadGroup.forget(aLoadGroup);
1112 return NS_OK;
1115 NS_IMETHODIMP
1116 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
1117 nsIInputStream* aStream,
1118 uint64_t aOffset,
1119 uint32_t aCount) {
1120 // mTargetListener might be null if SetupViewer or AddExternalResource failed.
1121 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
1122 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
1123 return NS_BINDING_ABORTED;
1125 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1128 NS_IMETHODIMP
1129 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
1130 nsresult aStatus) {
1131 // mTargetListener might be null if SetupViewer or AddExternalResource failed
1132 if (mTargetListener) {
1133 nsCOMPtr<nsIStreamListener> listener;
1134 mTargetListener.swap(listener);
1135 return listener->OnStopRequest(aRequest, aStatus);
1138 return NS_OK;
1141 nsresult ExternalResourceMap::PendingLoad::StartLoad(
1142 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
1143 MOZ_ASSERT(aURI, "Must have a URI");
1144 MOZ_ASSERT(aRequestingNode, "Must have a node");
1145 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
1147 nsCOMPtr<nsILoadGroup> loadGroup =
1148 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
1150 nsresult rv = NS_OK;
1151 nsCOMPtr<nsIChannel> channel;
1152 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
1153 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1154 nsIContentPolicy::TYPE_OTHER,
1155 nullptr, // aPerformanceStorage
1156 loadGroup);
1157 NS_ENSURE_SUCCESS(rv, rv);
1159 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1160 if (httpChannel) {
1161 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
1162 Unused << NS_WARN_IF(NS_FAILED(rv));
1165 mURI = aURI;
1167 return channel->AsyncOpen(this);
1170 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
1171 nsIInterfaceRequestor)
1173 #define IMPL_SHIM(_i) \
1174 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
1176 IMPL_SHIM(nsILoadContext)
1177 IMPL_SHIM(nsIProgressEventSink)
1178 IMPL_SHIM(nsIChannelEventSink)
1180 #undef IMPL_SHIM
1182 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
1184 #define TRY_SHIM(_i) \
1185 PR_BEGIN_MACRO \
1186 if (IID_IS(_i)) { \
1187 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
1188 if (!real) { \
1189 return NS_NOINTERFACE; \
1191 nsCOMPtr<_i> shim = new _i##Shim(this, real); \
1192 shim.forget(aSink); \
1193 return NS_OK; \
1195 PR_END_MACRO
1197 NS_IMETHODIMP
1198 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
1199 void** aSink) {
1200 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
1201 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
1202 return mCallbacks->GetInterface(aIID, aSink);
1205 *aSink = nullptr;
1207 TRY_SHIM(nsILoadContext);
1208 TRY_SHIM(nsIProgressEventSink);
1209 TRY_SHIM(nsIChannelEventSink);
1211 return NS_NOINTERFACE;
1214 #undef TRY_SHIM
1215 #undef IID_IS
1217 ExternalResourceMap::ExternalResource::~ExternalResource() {
1218 if (mViewer) {
1219 mViewer->Close(nullptr);
1220 mViewer->Destroy();
1224 // ==================================================================
1225 // =
1226 // ==================================================================
1228 // If we ever have an nsIDocumentObserver notification for stylesheet title
1229 // changes we should update the list from that instead of overriding
1230 // EnsureFresh.
1231 class DOMStyleSheetSetList final : public DOMStringList {
1232 public:
1233 explicit DOMStyleSheetSetList(Document* aDocument);
1235 void Disconnect() { mDocument = nullptr; }
1237 virtual void EnsureFresh() override;
1239 protected:
1240 Document* mDocument; // Our document; weak ref. It'll let us know if it
1241 // dies.
1244 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
1245 : mDocument(aDocument) {
1246 NS_ASSERTION(mDocument, "Must have document!");
1249 void DOMStyleSheetSetList::EnsureFresh() {
1250 MOZ_ASSERT(NS_IsMainThread());
1252 mNames.Clear();
1254 if (!mDocument) {
1255 return; // Spec says "no exceptions", and we have no style sets if we have
1256 // no document, for sure
1259 size_t count = mDocument->SheetCount();
1260 nsAutoString title;
1261 for (size_t index = 0; index < count; index++) {
1262 StyleSheet* sheet = mDocument->SheetAt(index);
1263 NS_ASSERTION(sheet, "Null sheet in sheet list!");
1264 sheet->GetTitle(title);
1265 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
1266 return;
1271 // ==================================================================
1272 Document::SelectorCache::SelectorCache(nsIEventTarget* aEventTarget)
1273 : nsExpirationTracker<SelectorCacheKey, 4>(1000, "Document::SelectorCache",
1274 aEventTarget) {}
1276 Document::SelectorCache::~SelectorCache() { AgeAllGenerations(); }
1278 void Document::SelectorCache::NotifyExpired(SelectorCacheKey* aSelector) {
1279 MOZ_ASSERT(NS_IsMainThread());
1280 MOZ_ASSERT(aSelector);
1282 // There is no guarantee that this method won't be re-entered when selector
1283 // matching is ongoing because "memory-pressure" could be notified immediately
1284 // when OOM happens according to the design of nsExpirationTracker.
1285 // The perfect solution is to delete the |aSelector| and its
1286 // RawServoSelectorList in mTable asynchronously.
1287 // We remove these objects synchronously for now because NotifyExpired() will
1288 // never be triggered by "memory-pressure" which is not implemented yet in
1289 // the stage 2 of mozalloc_handle_oom().
1290 // Once these objects are removed asynchronously, we should update the warning
1291 // added in mozalloc_handle_oom() as well.
1292 RemoveObject(aSelector);
1293 mTable.Remove(aSelector->mKey);
1294 delete aSelector;
1297 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1299 // ==================================================================
1300 // =
1301 // ==================================================================
1303 Document::InternalCommandDataHashtable*
1304 Document::sInternalCommandDataHashtable = nullptr;
1306 // static
1307 void Document::Shutdown() {
1308 if (sInternalCommandDataHashtable) {
1309 sInternalCommandDataHashtable->Clear();
1310 delete sInternalCommandDataHashtable;
1311 sInternalCommandDataHashtable = nullptr;
1315 Document::Document(const char* aContentType)
1316 : nsINode(nullptr),
1317 DocumentOrShadowRoot(this),
1318 mCharacterSet(WINDOWS_1252_ENCODING),
1319 mCharacterSetSource(0),
1320 mParentDocument(nullptr),
1321 mCachedRootElement(nullptr),
1322 mNodeInfoManager(nullptr),
1323 #ifdef DEBUG
1324 mStyledLinksCleared(false),
1325 #endif
1326 mCachedStateObjectValid(false),
1327 mBlockAllMixedContent(false),
1328 mBlockAllMixedContentPreloads(false),
1329 mUpgradeInsecureRequests(false),
1330 mUpgradeInsecurePreloads(false),
1331 mDevToolsWatchingDOMMutations(false),
1332 mBidiEnabled(false),
1333 mMayNeedFontPrefsUpdate(true),
1334 mMathMLEnabled(false),
1335 mIsInitialDocumentInWindow(false),
1336 mIgnoreDocGroupMismatches(false),
1337 mLoadedAsData(false),
1338 mAddedToMemoryReportingAsDataDocument(false),
1339 mMayStartLayout(true),
1340 mHaveFiredTitleChange(false),
1341 mIsShowing(false),
1342 mVisible(true),
1343 mRemovedFromDocShell(false),
1344 // mAllowDNSPrefetch starts true, so that we can always reliably && it
1345 // with various values that might disable it. Since we never prefetch
1346 // unless we get a window, and in that case the docshell value will get
1347 // &&-ed in, this is safe.
1348 mAllowDNSPrefetch(true),
1349 mIsStaticDocument(false),
1350 mCreatingStaticClone(false),
1351 mHasPrintCallbacks(false),
1352 mInUnlinkOrDeletion(false),
1353 mHasHadScriptHandlingObject(false),
1354 mIsBeingUsedAsImage(false),
1355 mChromeRulesEnabled(false),
1356 mInChromeDocShell(false),
1357 mIsDevToolsDocument(false),
1358 mIsSyntheticDocument(false),
1359 mHasLinksToUpdateRunnable(false),
1360 mFlushingPendingLinkUpdates(false),
1361 mMayHaveDOMMutationObservers(false),
1362 mMayHaveAnimationObservers(false),
1363 mHasCSP(false),
1364 mHasUnsafeEvalCSP(false),
1365 mHasUnsafeInlineCSP(false),
1366 mHasCSPDeliveredThroughHeader(false),
1367 mBFCacheDisallowed(false),
1368 mHasHadDefaultView(false),
1369 mStyleSheetChangeEventsEnabled(false),
1370 mDevToolsAnonymousAndShadowEventsEnabled(false),
1371 mIsSrcdocDocument(false),
1372 mHasDisplayDocument(false),
1373 mFontFaceSetDirty(true),
1374 mDidFireDOMContentLoaded(true),
1375 mFrameRequestCallbacksScheduled(false),
1376 mIsTopLevelContentDocument(false),
1377 mIsContentDocument(false),
1378 mDidCallBeginLoad(false),
1379 mEncodingMenuDisabled(false),
1380 mLinksEnabled(true),
1381 mIsSVGGlyphsDocument(false),
1382 mInDestructor(false),
1383 mIsGoingAway(false),
1384 mInXBLUpdate(false),
1385 mStyleSetFilled(false),
1386 mQuirkSheetAdded(false),
1387 mContentEditableSheetAdded(false),
1388 mDesignModeSheetAdded(false),
1389 mSSApplicableStateNotificationPending(false),
1390 mMayHaveTitleElement(false),
1391 mDOMLoadingSet(false),
1392 mDOMInteractiveSet(false),
1393 mDOMCompleteSet(false),
1394 mAutoFocusFired(false),
1395 mScrolledToRefAlready(false),
1396 mChangeScrollPosWhenScrollingToRef(false),
1397 mDelayFrameLoaderInitialization(false),
1398 mSynchronousDOMContentLoaded(false),
1399 mMaybeServiceWorkerControlled(false),
1400 mAllowZoom(false),
1401 mValidScaleFloat(false),
1402 mValidMinScale(false),
1403 mValidMaxScale(false),
1404 mWidthStrEmpty(false),
1405 mParserAborted(false),
1406 mReportedDocumentUseCounters(false),
1407 mHasReportedShadowDOMUsage(false),
1408 mHasDelayedRefreshEvent(false),
1409 mLoadEventFiring(false),
1410 mSkipLoadEventAfterClose(false),
1411 mDisableCookieAccess(false),
1412 mDisableDocWrite(false),
1413 mTooDeepWriteRecursion(false),
1414 mPendingMaybeEditingStateChanged(false),
1415 mHasBeenEditable(false),
1416 mHasWarnedAboutZoom(false),
1417 mIsRunningExecCommand(false),
1418 mSetCompleteAfterDOMContentLoaded(false),
1419 mDidHitCompleteSheetCache(false),
1420 mUseCountersInitialized(false),
1421 mShouldReportUseCounters(false),
1422 mShouldSendPageUseCounters(false),
1423 mUserHasInteracted(false),
1424 mHasUserInteractionTimerScheduled(false),
1425 mShouldResistFingerprinting(false),
1426 mXMLDeclarationBits(0),
1427 mOnloadBlockCount(0),
1428 mWriteLevel(0),
1429 mContentEditableCount(0),
1430 mEditingState(EditingState::eOff),
1431 mCompatMode(eCompatibility_FullStandards),
1432 mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
1433 mAncestorIsLoading(false),
1434 mVisibilityState(dom::VisibilityState::Hidden),
1435 mType(eUnknown),
1436 mDefaultElementType(0),
1437 mAllowXULXBL(eTriUnset),
1438 mSkipDTDSecurityChecks(false),
1439 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
1440 mSandboxFlags(0),
1441 mPartID(0),
1442 mMarkedCCGeneration(0),
1443 mPresShell(nullptr),
1444 mSubtreeModifiedDepth(0),
1445 mPreloadPictureDepth(0),
1446 mEventsSuppressed(0),
1447 mIgnoreDestructiveWritesCounter(0),
1448 mStaticCloneCount(0),
1449 mWindow(nullptr),
1450 mBFCacheEntry(nullptr),
1451 mInSyncOperationCount(0),
1452 mBlockDOMContentLoaded(0),
1453 mUpdateNestLevel(0),
1454 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
1455 mViewportType(Unknown),
1456 mViewportFit(ViewportFitType::Auto),
1457 mSubDocuments(nullptr),
1458 mHeaderData(nullptr),
1459 mServoRestyleRootDirtyBits(0),
1460 mThrowOnDynamicMarkupInsertionCounter(0),
1461 mIgnoreOpensDuringUnloadCounter(0),
1462 mSavedResolution(1.0f),
1463 mSavedResolutionBeforeMVM(1.0f),
1464 mGeneration(0),
1465 mCachedTabSizeGeneration(0),
1466 mNextFormNumber(0),
1467 mNextControlNumber(0),
1468 mPreloadService(this),
1469 mShouldNotifyFetchSuccess(false),
1470 mShouldNotifyFormOrPasswordRemoved(false) {
1471 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
1473 SetIsInDocument();
1474 SetIsConnected(true);
1476 // Create these unconditionally, they will be used to warn about the `zoom`
1477 // property, even if use counters are disabled.
1478 mStyleUseCounters.reset(Servo_UseCounters_Create());
1480 SetContentType(nsDependentCString(aContentType));
1482 // Start out mLastStyleSheetSet as null, per spec
1483 SetDOMStringToNull(mLastStyleSheetSet);
1485 // void state used to differentiate an empty source from an unselected source
1486 mPreloadPictureFoundSource.SetIsVoid(true);
1488 RecomputeLanguageFromCharset();
1490 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
1491 mReferrerInfo = new dom::ReferrerInfo(nullptr);
1494 #ifndef ANDROID
1495 // unused by GeckoView
1496 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
1497 if (NS_WARN_IF(!aWin)) {
1498 return false;
1501 nsIURI* uri = aWin->GetDocumentURI();
1502 if (NS_WARN_IF(!uri)) {
1503 return false;
1505 // getSpec is an expensive operation, hence we first check the scheme
1506 // to see if the caller is actually an about: page.
1507 if (!uri->SchemeIs("about")) {
1508 return false;
1511 nsAutoCString aboutSpec;
1512 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
1513 NS_ENSURE_SUCCESS(rv, false);
1515 return aboutSpec.EqualsASCII(aSpec);
1517 #endif
1519 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
1520 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1521 #ifdef ANDROID
1522 // GeckoView uses data URLs for error pages, so for now just check for any
1523 // error page
1524 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1525 #else
1526 return win && IsAboutErrorPage(win, "neterror");
1527 #endif
1530 bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
1531 JSObject* aObject) {
1532 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1533 #ifdef ANDROID
1534 // GeckoView uses data URLs for error pages, so for now just check for any
1535 // error page
1536 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1537 #else
1538 return win && IsAboutErrorPage(win, "httpsonlyerror");
1539 #endif
1542 already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
1543 bool aIsTemporary, ErrorResult& aError) {
1544 RefPtr<Promise> promise = Promise::Create(GetScopeObject(), aError,
1545 Promise::ePropagateUserInteraction);
1546 if (aError.Failed()) {
1547 return nullptr;
1550 nsresult rv = NS_OK;
1551 if (NS_WARN_IF(!mFailedChannel)) {
1552 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1553 return promise.forget();
1556 nsCOMPtr<nsIURI> failedChannelURI;
1557 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
1558 if (!failedChannelURI) {
1559 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1560 return promise.forget();
1563 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI);
1564 if (!innerURI) {
1565 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1566 return promise.forget();
1569 nsAutoCString host;
1570 innerURI->GetAsciiHost(host);
1571 int32_t port;
1572 innerURI->GetPort(&port);
1574 nsCOMPtr<nsITransportSecurityInfo> tsi;
1575 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1576 if (NS_WARN_IF(NS_FAILED(rv))) {
1577 promise->MaybeReject(rv);
1578 return promise.forget();
1580 if (NS_WARN_IF(!tsi)) {
1581 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1582 return promise.forget();
1585 nsCOMPtr<nsIX509Cert> cert;
1586 rv = tsi->GetServerCert(getter_AddRefs(cert));
1587 if (NS_WARN_IF(NS_FAILED(rv))) {
1588 promise->MaybeReject(rv);
1589 return promise.forget();
1591 if (NS_WARN_IF(!cert)) {
1592 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1593 return promise.forget();
1596 if (XRE_IsContentProcess()) {
1597 ContentChild* cc = ContentChild::GetSingleton();
1598 MOZ_ASSERT(cc);
1599 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1600 cc->SendAddCertException(cert, host, port, attrs, aIsTemporary)
1601 ->Then(GetCurrentSerialEventTarget(), __func__,
1602 [promise](const mozilla::MozPromise<
1603 nsresult, mozilla::ipc::ResponseRejectReason,
1604 true>::ResolveOrRejectValue& aValue) {
1605 if (aValue.IsResolve()) {
1606 promise->MaybeResolve(aValue.ResolveValue());
1607 } else {
1608 promise->MaybeRejectWithUndefined();
1611 return promise.forget();
1614 if (XRE_IsParentProcess()) {
1615 nsCOMPtr<nsICertOverrideService> overrideService =
1616 do_GetService(NS_CERTOVERRIDE_CONTRACTID);
1617 if (!overrideService) {
1618 promise->MaybeReject(NS_ERROR_FAILURE);
1619 return promise.forget();
1622 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1623 rv = overrideService->RememberValidityOverride(host, port, attrs, cert,
1624 aIsTemporary);
1625 if (NS_WARN_IF(NS_FAILED(rv))) {
1626 promise->MaybeReject(rv);
1627 return promise.forget();
1630 promise->MaybeResolveWithUndefined();
1631 return promise.forget();
1634 promise->MaybeReject(NS_ERROR_FAILURE);
1635 return promise.forget();
1638 void Document::ReloadWithHttpsOnlyException() {
1639 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
1640 wgc->SendReloadWithHttpsOnlyException();
1644 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
1645 nsresult rv = NS_OK;
1646 if (NS_WARN_IF(!mFailedChannel)) {
1647 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1648 return;
1651 nsCOMPtr<nsITransportSecurityInfo> tsi;
1652 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1653 if (NS_WARN_IF(NS_FAILED(rv))) {
1654 aRv.Throw(rv);
1655 return;
1657 if (NS_WARN_IF(!tsi)) {
1658 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1659 return;
1662 nsAutoString errorCodeString;
1663 rv = tsi->GetErrorCodeString(errorCodeString);
1664 if (NS_WARN_IF(NS_FAILED(rv))) {
1665 aRv.Throw(rv);
1666 return;
1668 aInfo.mErrorCodeString.Assign(errorCodeString);
1671 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
1672 JSObject* aObject) {
1673 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1674 #ifdef ANDROID
1675 // GeckoView uses data URLs for error pages, so for now just check for any
1676 // error page
1677 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1678 #else
1679 return win && IsAboutErrorPage(win, "certerror");
1680 #endif
1683 bool Document::CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject) {
1684 RefPtr<BasePrincipal> principal =
1685 BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx));
1687 if (!principal) {
1688 return false;
1691 // We allow the privilege SSA to be called from system principal.
1692 if (principal->IsSystemPrincipal()) {
1693 return true;
1696 // We only allow calling the privilege SSA from the content script of the
1697 // webcompat extension.
1698 if (auto* policy = principal->ContentScriptAddonPolicy()) {
1699 nsAutoString addonID;
1700 policy->GetId(addonID);
1702 return addonID.EqualsLiteral("webcompat@mozilla.org");
1705 return false;
1708 bool Document::IsErrorPage() const {
1709 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
1710 return loadInfo && loadInfo->GetLoadErrorPage();
1713 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
1714 ErrorResult& aRv) {
1715 nsresult rv = NS_OK;
1716 if (NS_WARN_IF(!mFailedChannel)) {
1717 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1718 return;
1721 nsCOMPtr<nsITransportSecurityInfo> tsi;
1722 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1723 if (NS_WARN_IF(NS_FAILED(rv))) {
1724 aRv.Throw(rv);
1725 return;
1727 if (NS_WARN_IF(!tsi)) {
1728 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1729 return;
1732 nsAutoString errorCodeString;
1733 rv = tsi->GetErrorCodeString(errorCodeString);
1734 if (NS_WARN_IF(NS_FAILED(rv))) {
1735 aRv.Throw(rv);
1736 return;
1738 aInfo.mErrorCodeString.Assign(errorCodeString);
1740 nsITransportSecurityInfo::OverridableErrorCategory errorCategory;
1741 rv = tsi->GetOverridableErrorCategory(&errorCategory);
1742 if (NS_WARN_IF(NS_FAILED(rv))) {
1743 aRv.Throw(rv);
1744 return;
1746 switch (errorCategory) {
1747 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST:
1748 aInfo.mOverridableErrorCategory =
1749 dom::OverridableErrorCategory::Trust_error;
1750 break;
1751 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN:
1752 aInfo.mOverridableErrorCategory =
1753 dom::OverridableErrorCategory::Domain_mismatch;
1754 break;
1755 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME:
1756 aInfo.mOverridableErrorCategory =
1757 dom::OverridableErrorCategory::Expired_or_not_yet_valid;
1758 break;
1759 default:
1760 aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Unset;
1761 break;
1764 nsCOMPtr<nsIX509Cert> cert;
1765 nsCOMPtr<nsIX509CertValidity> validity;
1766 rv = tsi->GetServerCert(getter_AddRefs(cert));
1767 if (NS_WARN_IF(NS_FAILED(rv))) {
1768 aRv.Throw(rv);
1769 return;
1771 if (NS_WARN_IF(!cert)) {
1772 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1773 return;
1776 rv = cert->GetValidity(getter_AddRefs(validity));
1777 if (NS_WARN_IF(NS_FAILED(rv))) {
1778 aRv.Throw(rv);
1779 return;
1781 if (NS_WARN_IF(!validity)) {
1782 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1783 return;
1786 PRTime validityResult;
1787 rv = validity->GetNotBefore(&validityResult);
1788 if (NS_WARN_IF(NS_FAILED(rv))) {
1789 aRv.Throw(rv);
1790 return;
1792 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1794 rv = validity->GetNotAfter(&validityResult);
1795 if (NS_WARN_IF(NS_FAILED(rv))) {
1796 aRv.Throw(rv);
1797 return;
1799 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1801 nsAutoString issuerCommonName;
1802 nsAutoString certChainPEMString;
1803 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
1804 int64_t maxValidity = std::numeric_limits<int64_t>::max();
1805 int64_t minValidity = 0;
1806 PRTime notBefore, notAfter;
1807 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
1808 rv = tsi->GetFailedCertChain(failedCertArray);
1809 if (NS_WARN_IF(NS_FAILED(rv))) {
1810 aRv.Throw(rv);
1811 return;
1814 if (NS_WARN_IF(failedCertArray.IsEmpty())) {
1815 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1816 return;
1819 for (const auto& certificate : failedCertArray) {
1820 rv = certificate->GetIssuerCommonName(issuerCommonName);
1821 if (NS_WARN_IF(NS_FAILED(rv))) {
1822 aRv.Throw(rv);
1823 return;
1826 rv = certificate->GetValidity(getter_AddRefs(validity));
1827 if (NS_WARN_IF(NS_FAILED(rv))) {
1828 aRv.Throw(rv);
1829 return;
1831 if (NS_WARN_IF(!validity)) {
1832 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1833 return;
1836 rv = validity->GetNotBefore(&notBefore);
1837 if (NS_WARN_IF(NS_FAILED(rv))) {
1838 aRv.Throw(rv);
1839 return;
1842 rv = validity->GetNotAfter(&notAfter);
1843 if (NS_WARN_IF(NS_FAILED(rv))) {
1844 aRv.Throw(rv);
1845 return;
1848 notBefore = std::max(minValidity, notBefore);
1849 notAfter = std::min(maxValidity, notAfter);
1850 nsTArray<uint8_t> certArray;
1851 rv = certificate->GetRawDER(certArray);
1852 if (NS_WARN_IF(NS_FAILED(rv))) {
1853 aRv.Throw(rv);
1854 return;
1857 nsAutoString der64;
1858 rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()),
1859 certArray.Length(), der64);
1860 if (NS_WARN_IF(NS_FAILED(rv))) {
1861 aRv.Throw(rv);
1862 return;
1864 if (!certChainStrings.AppendElement(der64, fallible)) {
1865 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1866 return;
1870 aInfo.mIssuerCommonName.Assign(issuerCommonName);
1871 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
1872 aInfo.mCertValidityRangeNotBefore =
1873 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
1875 int32_t errorCode;
1876 rv = tsi->GetErrorCode(&errorCode);
1877 if (NS_WARN_IF(NS_FAILED(rv))) {
1878 aRv.Throw(rv);
1879 return;
1882 nsCOMPtr<nsINSSErrorsService> nsserr =
1883 do_GetService("@mozilla.org/nss_errors_service;1");
1884 if (NS_WARN_IF(!nsserr)) {
1885 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1886 return;
1888 nsresult res;
1889 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
1890 if (NS_WARN_IF(NS_FAILED(rv))) {
1891 aRv.Throw(rv);
1892 return;
1894 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
1895 if (NS_WARN_IF(NS_FAILED(rv))) {
1896 aRv.Throw(rv);
1897 return;
1900 OriginAttributes attrs;
1901 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1902 nsCOMPtr<nsIURI> aURI;
1903 mFailedChannel->GetURI(getter_AddRefs(aURI));
1904 if (XRE_IsContentProcess()) {
1905 ContentChild* cc = ContentChild::GetSingleton();
1906 MOZ_ASSERT(cc);
1907 cc->SendIsSecureURI(aURI, attrs, &aInfo.mHasHSTS);
1908 } else {
1909 nsCOMPtr<nsISiteSecurityService> sss =
1910 do_GetService(NS_SSSERVICE_CONTRACTID);
1911 if (NS_WARN_IF(!sss)) {
1912 return;
1914 Unused << NS_WARN_IF(
1915 NS_FAILED(sss->IsSecureURI(aURI, attrs, &aInfo.mHasHSTS)));
1917 nsCOMPtr<nsIPublicKeyPinningService> pkps =
1918 do_GetService(NS_PKPSERVICE_CONTRACTID);
1919 if (NS_WARN_IF(!pkps)) {
1920 return;
1922 Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP)));
1925 bool Document::IsAboutPage() const {
1926 return NodePrincipal()->SchemeIs("about");
1929 void Document::ConstructUbiNode(void* storage) {
1930 JS::ubi::Concrete<Document>::construct(storage, this);
1933 void Document::LoadEventFired() {
1934 // Object used to collect some telemetry data so we don't need to query for it
1935 // twice.
1936 glean::perf::PageLoadExtra pageLoadEventData;
1938 // Accumulate timing data located in each document's realm and report to
1939 // telemetry.
1940 AccumulateJSTelemetry(pageLoadEventData);
1942 // Collect page load timings
1943 AccumulatePageLoadTelemetry(pageLoadEventData);
1945 // Record page load event
1946 RecordPageLoadEventTelemetry(pageLoadEventData);
1948 // Release the JS bytecode cache from its wait on the load event, and
1949 // potentially dispatch the encoding of the bytecode.
1950 if (ScriptLoader()) {
1951 ScriptLoader()->LoadEventFired();
1955 static uint32_t ConvertToUnsignedFromDouble(double aNumber) {
1956 return aNumber < 0 ? 0 : static_cast<uint32_t>(aNumber);
1959 void Document::RecordPageLoadEventTelemetry(
1960 glean::perf::PageLoadExtra& aEventTelemetryData) {
1961 // If the page load time is empty, then the content wasn't something we want
1962 // to report (i.e. not a top level document).
1963 if (!aEventTelemetryData.loadTime) {
1964 return;
1966 MOZ_ASSERT(IsTopLevelContentDocument());
1968 nsPIDOMWindowOuter* window = GetWindow();
1969 if (!window) {
1970 return;
1973 nsIDocShell* docshell = window->GetDocShell();
1974 if (!docshell) {
1975 return;
1978 nsAutoCString loadTypeStr;
1979 switch (docshell->GetLoadType()) {
1980 case LOAD_NORMAL:
1981 case LOAD_NORMAL_REPLACE:
1982 case LOAD_NORMAL_BYPASS_CACHE:
1983 case LOAD_NORMAL_BYPASS_PROXY:
1984 case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
1985 loadTypeStr.Append("NORMAL");
1986 break;
1987 case LOAD_HISTORY:
1988 loadTypeStr.Append("HISTORY");
1989 break;
1990 case LOAD_RELOAD_NORMAL:
1991 case LOAD_RELOAD_BYPASS_CACHE:
1992 case LOAD_RELOAD_BYPASS_PROXY:
1993 case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
1994 case LOAD_REFRESH:
1995 case LOAD_REFRESH_REPLACE:
1996 case LOAD_RELOAD_CHARSET_CHANGE:
1997 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
1998 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
1999 loadTypeStr.Append("RELOAD");
2000 break;
2001 case LOAD_LINK:
2002 loadTypeStr.Append("LINK");
2003 break;
2004 case LOAD_STOP_CONTENT:
2005 case LOAD_STOP_CONTENT_AND_REPLACE:
2006 loadTypeStr.Append("STOP");
2007 break;
2008 case LOAD_ERROR_PAGE:
2009 loadTypeStr.Append("ERROR");
2010 break;
2011 default:
2012 loadTypeStr.Append("OTHER");
2013 break;
2016 nsCOMPtr<nsIEffectiveTLDService> tldService =
2017 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
2018 if (tldService && mReferrerInfo &&
2019 (docshell->GetLoadType() & nsIDocShell::LOAD_CMD_NORMAL)) {
2020 nsAutoCString currentBaseDomain, referrerBaseDomain;
2021 nsCOMPtr<nsIURI> referrerURI = mReferrerInfo->GetComputedReferrer();
2022 if (referrerURI) {
2023 auto result = NS_SUCCEEDED(
2024 tldService->GetBaseDomain(referrerURI, 0, referrerBaseDomain));
2025 if (result) {
2026 bool sameOrigin = false;
2027 NodePrincipal()->IsSameOrigin(referrerURI, &sameOrigin);
2028 aEventTelemetryData.sameOriginNav = mozilla::Some(sameOrigin);
2033 aEventTelemetryData.loadType = mozilla::Some(loadTypeStr);
2035 // Sending a glean ping must be done on the parent process.
2036 if (ContentChild* cc = ContentChild::GetSingleton()) {
2037 cc->SendRecordPageLoadEvent(aEventTelemetryData);
2041 void Document::AccumulatePageLoadTelemetry(
2042 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2043 // Interested only in top level documents for real websites that are in the
2044 // foreground.
2045 if (!ShouldIncludeInTelemetry(false) || !IsTopLevelContentDocument() ||
2046 !GetNavigationTiming() ||
2047 !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
2048 return;
2051 if (!GetChannel()) {
2052 return;
2055 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
2056 if (!timedChannel) {
2057 return;
2060 // Default duration is 0, use this to check for bogus negative values.
2061 const TimeDuration zeroDuration;
2063 TimeStamp responseStart;
2064 timedChannel->GetResponseStart(&responseStart);
2066 TimeStamp redirectStart, redirectEnd;
2067 timedChannel->GetRedirectStart(&redirectStart);
2068 timedChannel->GetRedirectEnd(&redirectEnd);
2070 uint8_t redirectCount;
2071 timedChannel->GetRedirectCount(&redirectCount);
2072 if (redirectCount) {
2073 aEventTelemetryDataOut.redirectCount =
2074 mozilla::Some(static_cast<uint32_t>(redirectCount));
2077 if (!redirectStart.IsNull() && !redirectEnd.IsNull()) {
2078 TimeDuration redirectTime = redirectEnd - redirectStart;
2079 if (redirectTime > zeroDuration) {
2080 aEventTelemetryDataOut.redirectTime =
2081 mozilla::Some(static_cast<uint32_t>(redirectTime.ToMilliseconds()));
2085 TimeStamp dnsLookupStart, dnsLookupEnd;
2086 timedChannel->GetDomainLookupStart(&dnsLookupStart);
2087 timedChannel->GetDomainLookupEnd(&dnsLookupEnd);
2089 if (!dnsLookupStart.IsNull() && !dnsLookupEnd.IsNull()) {
2090 TimeDuration dnsLookupTime = dnsLookupEnd - dnsLookupStart;
2091 if (dnsLookupTime > zeroDuration) {
2092 aEventTelemetryDataOut.dnsLookupTime =
2093 mozilla::Some(static_cast<uint32_t>(dnsLookupTime.ToMilliseconds()));
2097 TimeStamp navigationStart =
2098 GetNavigationTiming()->GetNavigationStartTimeStamp();
2100 if (!responseStart || !navigationStart) {
2101 return;
2104 nsAutoCString dnsKey("Native");
2105 nsAutoCString http3Key;
2106 nsAutoCString http3WithPriorityKey;
2107 nsAutoCString earlyHintKey;
2108 nsCOMPtr<nsIHttpChannelInternal> httpChannel =
2109 do_QueryInterface(GetChannel());
2110 if (httpChannel) {
2111 bool resolvedByTRR = false;
2112 Unused << httpChannel->GetIsResolvedByTRR(&resolvedByTRR);
2113 if (resolvedByTRR) {
2114 if (nsCOMPtr<nsIDNSService> dns =
2115 do_GetService(NS_DNSSERVICE_CONTRACTID)) {
2116 dns->GetTRRDomainKey(dnsKey);
2117 } else {
2118 // Failed to get the DNS service.
2119 dnsKey = "(fail)"_ns;
2121 aEventTelemetryDataOut.trrDomain = mozilla::Some(dnsKey);
2124 uint32_t major;
2125 uint32_t minor;
2126 if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
2127 if (major == 3) {
2128 http3Key = "http3"_ns;
2129 nsCOMPtr<nsIHttpChannel> httpChannel2 = do_QueryInterface(GetChannel());
2130 nsCString header;
2131 if (httpChannel2 &&
2132 NS_SUCCEEDED(
2133 httpChannel2->GetResponseHeader("priority"_ns, header)) &&
2134 !header.IsEmpty()) {
2135 http3WithPriorityKey = "with_priority"_ns;
2136 } else {
2137 http3WithPriorityKey = "without_priority"_ns;
2139 } else if (major == 2) {
2140 bool supportHttp3 = false;
2141 if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
2142 supportHttp3 = false;
2144 if (supportHttp3) {
2145 http3Key = "supports_http3"_ns;
2149 aEventTelemetryDataOut.httpVer = mozilla::Some(major);
2152 uint32_t earlyHintType = 0;
2153 Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
2154 if (earlyHintType & LinkStyle::ePRECONNECT) {
2155 earlyHintKey.Append("preconnect_"_ns);
2157 if (earlyHintType & LinkStyle::ePRELOAD) {
2158 earlyHintKey.Append("preload_"_ns);
2159 earlyHintKey.Append(mPreloadService.GetEarlyHintUsed() ? "1"_ns : "0"_ns);
2163 TimeStamp asyncOpen;
2164 timedChannel->GetAsyncOpen(&asyncOpen);
2165 if (asyncOpen) {
2166 Telemetry::AccumulateTimeDelta(Telemetry::DNS_PERF_FIRST_BYTE_MS, dnsKey,
2167 asyncOpen, responseStart);
2170 // First Contentful Composite
2171 if (TimeStamp firstContentfulComposite =
2172 GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) {
2173 Telemetry::AccumulateTimeDelta(Telemetry::PERF_FIRST_CONTENTFUL_PAINT_MS,
2174 navigationStart, firstContentfulComposite);
2176 if (!http3Key.IsEmpty()) {
2177 Telemetry::AccumulateTimeDelta(
2178 Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key,
2179 navigationStart, firstContentfulComposite);
2182 if (!http3WithPriorityKey.IsEmpty()) {
2183 Telemetry::AccumulateTimeDelta(
2184 Telemetry::H3P_PERF_FIRST_CONTENTFUL_PAINT_MS, http3WithPriorityKey,
2185 navigationStart, firstContentfulComposite);
2188 if (!earlyHintKey.IsEmpty()) {
2189 Telemetry::AccumulateTimeDelta(
2190 Telemetry::EH_PERF_FIRST_CONTENTFUL_PAINT_MS, earlyHintKey,
2191 navigationStart, firstContentfulComposite);
2194 Telemetry::AccumulateTimeDelta(
2195 Telemetry::DNS_PERF_FIRST_CONTENTFUL_PAINT_MS, dnsKey, navigationStart,
2196 firstContentfulComposite);
2198 Telemetry::AccumulateTimeDelta(
2199 Telemetry::PERF_FIRST_CONTENTFUL_PAINT_FROM_RESPONSESTART_MS,
2200 responseStart, firstContentfulComposite);
2202 TimeDuration fcpTime = firstContentfulComposite - navigationStart;
2203 if (fcpTime > zeroDuration) {
2204 aEventTelemetryDataOut.fcpTime =
2205 mozilla::Some(static_cast<uint32_t>(fcpTime.ToMilliseconds()));
2209 // DOM Content Loaded event
2210 if (TimeStamp dclEventStart =
2211 GetNavigationTiming()->GetDOMContentLoadedEventStartTimeStamp()) {
2212 Telemetry::AccumulateTimeDelta(Telemetry::PERF_DOM_CONTENT_LOADED_TIME_MS,
2213 navigationStart, dclEventStart);
2214 Telemetry::AccumulateTimeDelta(
2215 Telemetry::PERF_DOM_CONTENT_LOADED_TIME_FROM_RESPONSESTART_MS,
2216 responseStart, dclEventStart);
2219 // Load event
2220 if (TimeStamp loadEventStart =
2221 GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
2222 Telemetry::AccumulateTimeDelta(Telemetry::PERF_PAGE_LOAD_TIME_MS,
2223 navigationStart, loadEventStart);
2224 if (!http3Key.IsEmpty()) {
2225 Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
2226 http3Key, navigationStart, loadEventStart);
2229 if (!http3WithPriorityKey.IsEmpty()) {
2230 Telemetry::AccumulateTimeDelta(Telemetry::H3P_PERF_PAGE_LOAD_TIME_MS,
2231 http3WithPriorityKey, navigationStart,
2232 loadEventStart);
2235 if (!earlyHintKey.IsEmpty()) {
2236 Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_PAGE_LOAD_TIME_MS,
2237 earlyHintKey, navigationStart,
2238 loadEventStart);
2241 Telemetry::AccumulateTimeDelta(
2242 Telemetry::PERF_PAGE_LOAD_TIME_FROM_RESPONSESTART_MS, responseStart,
2243 loadEventStart);
2245 TimeDuration responseTime = responseStart - navigationStart;
2246 if (responseTime > zeroDuration) {
2247 aEventTelemetryDataOut.responseTime =
2248 mozilla::Some(static_cast<uint32_t>(responseTime.ToMilliseconds()));
2251 TimeDuration loadTime = loadEventStart - navigationStart;
2252 if (loadTime > zeroDuration) {
2253 aEventTelemetryDataOut.loadTime =
2254 mozilla::Some(static_cast<uint32_t>(loadTime.ToMilliseconds()));
2259 void Document::AccumulateJSTelemetry(
2260 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2261 if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry(false)) {
2262 return;
2265 if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
2266 return;
2269 AutoJSContext cx;
2270 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
2271 JSAutoRealm ar(cx, globalObject);
2272 JS::JSTimers timers = JS::GetJSTimers(cx);
2274 if (!timers.executionTime.IsZero()) {
2275 Telemetry::Accumulate(
2276 Telemetry::JS_PAGELOAD_EXECUTION_MS,
2277 ConvertToUnsignedFromDouble(timers.executionTime.ToMilliseconds()));
2278 aEventTelemetryDataOut.jsExecTime = mozilla::Some(
2279 static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
2282 if (!timers.delazificationTime.IsZero()) {
2283 Telemetry::Accumulate(Telemetry::JS_PAGELOAD_DELAZIFICATION_MS,
2284 ConvertToUnsignedFromDouble(
2285 timers.delazificationTime.ToMilliseconds()));
2288 if (!timers.xdrEncodingTime.IsZero()) {
2289 Telemetry::Accumulate(
2290 Telemetry::JS_PAGELOAD_XDR_ENCODING_MS,
2291 ConvertToUnsignedFromDouble(timers.xdrEncodingTime.ToMilliseconds()));
2294 if (!timers.baselineCompileTime.IsZero()) {
2295 Telemetry::Accumulate(Telemetry::JS_PAGELOAD_BASELINE_COMPILE_MS,
2296 ConvertToUnsignedFromDouble(
2297 timers.baselineCompileTime.ToMilliseconds()));
2300 if (!timers.gcTime.IsZero()) {
2301 Telemetry::Accumulate(
2302 Telemetry::JS_PAGELOAD_GC_MS,
2303 ConvertToUnsignedFromDouble(timers.gcTime.ToMilliseconds()));
2306 if (!timers.protectTime.IsZero()) {
2307 Telemetry::Accumulate(
2308 Telemetry::JS_PAGELOAD_PROTECT_MS,
2309 ConvertToUnsignedFromDouble(timers.protectTime.ToMilliseconds()));
2313 Document::~Document() {
2314 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
2315 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
2316 "Can't be top-level and a resource doc at the same time");
2318 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
2320 if (IsTopLevelContentDocument()) {
2321 RemoveToplevelLoadingDocument(this);
2323 // don't report for about: pages
2324 if (!IsAboutPage()) {
2325 // record CSP telemetry on this document
2326 if (mHasCSP) {
2327 Accumulate(Telemetry::CSP_DOCUMENTS_COUNT, 1);
2329 if (mHasUnsafeInlineCSP) {
2330 Accumulate(Telemetry::CSP_UNSAFE_INLINE_DOCUMENTS_COUNT, 1);
2332 if (mHasUnsafeEvalCSP) {
2333 Accumulate(Telemetry::CSP_UNSAFE_EVAL_DOCUMENTS_COUNT, 1);
2336 if (MOZ_UNLIKELY(mMathMLEnabled)) {
2337 ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
2340 if (IsHTMLDocument()) {
2341 switch (GetCompatibilityMode()) {
2342 case eCompatibility_FullStandards:
2343 Telemetry::AccumulateCategorical(
2344 Telemetry::LABELS_QUIRKS_MODE::FullStandards);
2345 break;
2346 case eCompatibility_AlmostStandards:
2347 Telemetry::AccumulateCategorical(
2348 Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
2349 break;
2350 case eCompatibility_NavQuirks:
2351 Telemetry::AccumulateCategorical(
2352 Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
2353 break;
2354 default:
2355 MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
2356 break;
2362 mInDestructor = true;
2363 mInUnlinkOrDeletion = true;
2365 mozilla::DropJSObjects(this);
2367 // Clear mObservers to keep it in sync with the mutationobserver list
2368 mObservers.Clear();
2370 mIntersectionObservers.Clear();
2372 if (mStyleSheetSetList) {
2373 mStyleSheetSetList->Disconnect();
2376 if (mAnimationController) {
2377 mAnimationController->Disconnect();
2380 MOZ_ASSERT(mTimelines.isEmpty());
2382 mParentDocument = nullptr;
2384 // Kill the subdocument map, doing this will release its strong
2385 // references, if any.
2386 delete mSubDocuments;
2387 mSubDocuments = nullptr;
2389 nsAutoScriptBlocker scriptBlocker;
2391 // Destroy link map now so we don't waste time removing
2392 // links one by one
2393 DestroyElementMaps();
2395 // Invalidate cached array of child nodes
2396 InvalidateChildNodes();
2398 // We should not have child nodes when destructor is called,
2399 // since child nodes keep their owner document alive.
2400 MOZ_ASSERT(!HasChildren());
2402 mCachedRootElement = nullptr;
2404 for (auto& sheets : mAdditionalSheets) {
2405 UnlinkStyleSheets(sheets);
2408 if (mAttrStyleSheet) {
2409 mAttrStyleSheet->SetOwningDocument(nullptr);
2412 if (mListenerManager) {
2413 mListenerManager->Disconnect();
2414 UnsetFlags(NODE_HAS_LISTENERMANAGER);
2417 if (mScriptLoader) {
2418 mScriptLoader->DropDocumentReference();
2421 if (mCSSLoader) {
2422 // Could be null here if Init() failed or if we have been unlinked.
2423 mCSSLoader->DropDocumentReference();
2426 if (mStyleImageLoader) {
2427 mStyleImageLoader->DropDocumentReference();
2430 if (mXULBroadcastManager) {
2431 mXULBroadcastManager->DropDocumentReference();
2434 if (mXULPersist) {
2435 mXULPersist->DropDocumentReference();
2438 if (mPermissionDelegateHandler) {
2439 mPermissionDelegateHandler->DropDocumentReference();
2442 mHeaderData = nullptr;
2444 mPendingTitleChangeEvent.Revoke();
2446 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
2447 "must not have media query lists left");
2449 if (mNodeInfoManager) {
2450 mNodeInfoManager->DropDocumentReference();
2453 if (mDocGroup) {
2454 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
2455 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
2458 UnlinkOriginalDocumentIfStatic();
2460 UnregisterFromMemoryReportingForDataDocument();
2463 NS_INTERFACE_TABLE_HEAD(Document)
2464 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
2465 NS_INTERFACE_TABLE_BEGIN
2466 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
2467 NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
2468 NS_INTERFACE_TABLE_ENTRY(Document, Document)
2469 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
2470 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
2471 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
2472 NS_INTERFACE_TABLE_ENTRY(Document, nsIRadioGroupContainer)
2473 NS_INTERFACE_TABLE_END
2474 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
2475 NS_INTERFACE_MAP_END
2477 NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(Document)
2478 NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(
2479 Document, LastRelease())
2481 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2482 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2483 EventListenerManager* elm = tmp->GetExistingListenerManager();
2484 if (elm) {
2485 elm->MarkForCC();
2487 return true;
2489 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2491 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2492 return Element::CanSkipInCC(tmp);
2493 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2495 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2496 return Element::CanSkipThis(tmp);
2497 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2499 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2500 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2501 char name[512];
2502 nsAutoCString loadedAsData;
2503 if (tmp->IsLoadedAsData()) {
2504 loadedAsData.AssignLiteral("data");
2505 } else {
2506 loadedAsData.AssignLiteral("normal");
2508 uint32_t nsid = tmp->GetDefaultNamespaceID();
2509 nsAutoCString uri;
2510 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2511 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2512 "(xhtml)", "(XLink)", "(XSLT)",
2513 "(MathML)", "(RDF)", "(XUL)"};
2514 if (nsid < ArrayLength(kNSURIs)) {
2515 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2516 kNSURIs[nsid], uri.get());
2517 } else {
2518 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2520 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2521 } else {
2522 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2525 if (!nsINode::Traverse(tmp, cb)) {
2526 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2529 tmp->mExternalResourceMap.Traverse(&cb);
2531 // Traverse all Document pointer members.
2532 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2533 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2534 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2535 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2536 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2537 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry)
2539 // Traverse all Document nsCOMPtrs.
2540 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2541 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2542 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2543 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2544 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2546 DocumentOrShadowRoot::Traverse(tmp, cb);
2548 for (auto& sheets : tmp->mAdditionalSheets) {
2549 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2552 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2553 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserver)
2554 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserverViewport)
2555 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRememberedSizeObserver)
2556 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentVisibilityObserver)
2557 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2558 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2559 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2560 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2561 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2562 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2563 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
2564 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker)
2565 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2566 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2567 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2568 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2569 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2570 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2571 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2572 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2573 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2574 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2575 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2576 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2577 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2578 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2579 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2580 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2581 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2582 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
2584 // Traverse all our nsCOMArrays.
2585 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2587 // Traverse animation components
2588 if (tmp->mAnimationController) {
2589 tmp->mAnimationController->Traverse(&cb);
2592 if (tmp->mSubDocuments) {
2593 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2594 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2596 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2597 cb.NoteXPCOMChild(entry->mKey);
2598 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2599 "mSubDocuments entry->mSubDocument");
2600 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2604 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2606 // We own only the items in mDOMMediaQueryLists that have listeners;
2607 // this reference is managed by their AddListener and RemoveListener
2608 // methods.
2609 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2610 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2611 if (mql->HasListeners() &&
2612 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2613 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2614 cb.NoteXPCOMChild(mql);
2618 // XXX: This should be not needed once bug 1569185 lands.
2619 for (const auto& entry : tmp->mL10nProtoElements) {
2620 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2621 cb.NoteXPCOMChild(entry.GetKey());
2622 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
2625 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2626 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2627 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2628 mPendingFrameStaticClones[i].mStaticCloneOf);
2630 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2632 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2634 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document)
2635 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
2636 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject)
2637 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2639 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2640 tmp->mInUnlinkOrDeletion = true;
2642 tmp->SetStateObject(nullptr);
2644 // Clear out our external resources
2645 tmp->mExternalResourceMap.Shutdown();
2647 nsAutoScriptBlocker scriptBlocker;
2649 nsINode::Unlink(tmp);
2651 while (tmp->HasChildren()) {
2652 // Hold a strong ref to the node when we remove it, because we may be
2653 // the last reference to it.
2654 // If this code changes, change the corresponding code in Document's
2655 // unlink impl and ContentUnbinder::UnbindSubtree.
2656 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2657 tmp->DisconnectChild(child);
2658 child->UnbindFromTree();
2661 tmp->UnlinkOriginalDocumentIfStatic();
2663 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2665 tmp->SetScriptGlobalObject(nullptr);
2667 for (auto& sheets : tmp->mAdditionalSheets) {
2668 tmp->UnlinkStyleSheets(sheets);
2671 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2672 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2673 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserver)
2674 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserverViewport)
2675 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentVisibilityObserver)
2676 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastRememberedSizeObserver)
2677 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2678 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2679 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2680 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry)
2681 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2682 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2683 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2684 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2685 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2686 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2687 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2688 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2689 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
2690 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker)
2691 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2692 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2693 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2694 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2695 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2696 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2697 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2698 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2699 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2700 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2701 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2702 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2703 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2704 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2705 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2706 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2707 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2708 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2710 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
2711 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
2712 tmp->mDocGroup);
2714 tmp->mDocGroup = nullptr;
2716 if (tmp->IsTopLevelContentDocument()) {
2717 RemoveToplevelLoadingDocument(tmp);
2720 tmp->mParentDocument = nullptr;
2722 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2724 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2726 if (tmp->mListenerManager) {
2727 tmp->mListenerManager->Disconnect();
2728 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2729 tmp->mListenerManager = nullptr;
2732 if (tmp->mStyleSheetSetList) {
2733 tmp->mStyleSheetSetList->Disconnect();
2734 tmp->mStyleSheetSetList = nullptr;
2737 delete tmp->mSubDocuments;
2738 tmp->mSubDocuments = nullptr;
2740 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
2741 MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
2742 "How did we get here without our presshell going away "
2743 "first?");
2745 DocumentOrShadowRoot::Unlink(tmp);
2747 // Document has a pretty complex destructor, so we're going to
2748 // assume that *most* cycles you actually want to break somewhere
2749 // else, and not unlink an awful lot here.
2751 tmp->mExpandoAndGeneration.OwnerUnlinked();
2753 if (tmp->mAnimationController) {
2754 tmp->mAnimationController->Unlink();
2757 tmp->mPendingTitleChangeEvent.Revoke();
2759 if (tmp->mCSSLoader) {
2760 tmp->mCSSLoader->DropDocumentReference();
2761 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2764 // We own only the items in mDOMMediaQueryLists that have listeners;
2765 // this reference is managed by their AddListener and RemoveListener
2766 // methods.
2767 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2768 MediaQueryList* next =
2769 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2770 mql->Disconnect();
2771 mql = next;
2774 tmp->mPendingFrameStaticClones.Clear();
2776 tmp->mInUnlinkOrDeletion = false;
2778 tmp->UnregisterFromMemoryReportingForDataDocument();
2780 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2781 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2782 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2783 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2785 nsresult Document::Init() {
2786 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2787 return NS_ERROR_ALREADY_INITIALIZED;
2790 // Force initialization.
2791 mOnloadBlocker = new OnloadBlocker();
2792 mStyleImageLoader = new css::ImageLoader(this);
2794 mNodeInfoManager = new nsNodeInfoManager(this);
2796 // mNodeInfo keeps NodeInfoManager alive!
2797 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2798 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2799 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2800 "Bad NodeType in aNodeInfo");
2802 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2804 mCSSLoader = new css::Loader(this);
2805 // Assume we're not quirky, until we know otherwise
2806 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2808 // If after creation the owner js global is not set for a document
2809 // we use the default compartment for this document, instead of creating
2810 // wrapper in some random compartment when the document is exposed to js
2811 // via some events.
2812 nsCOMPtr<nsIGlobalObject> global =
2813 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2814 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2815 mScopeObject = do_GetWeakReference(global);
2816 MOZ_ASSERT(mScopeObject);
2818 mScriptLoader = new dom::ScriptLoader(this);
2820 // we need to create a policy here so getting the policy within
2821 // ::Policy() can *always* return a non null policy
2822 mFeaturePolicy = new dom::FeaturePolicy(this);
2823 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2825 mStyleSet = MakeUnique<ServoStyleSet>(*this);
2827 RecomputeResistFingerprinting();
2829 return NS_OK;
2832 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2834 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2835 PropertyTable().RemoveAllPropertiesFor(aNode);
2838 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2839 nsCOMPtr<nsIURI> uri;
2840 nsCOMPtr<nsIPrincipal> principal;
2841 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2842 if (aChannel) {
2843 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2844 // nsScriptSecurityManager::GetChannelResultPrincipals.
2845 // Note: this should match the uri used for the OnNewURI call in
2846 // nsDocShell::CreateContentViewer.
2847 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2849 nsIScriptSecurityManager* securityManager =
2850 nsContentUtils::GetSecurityManager();
2851 if (securityManager) {
2852 securityManager->GetChannelResultPrincipals(
2853 aChannel, getter_AddRefs(principal),
2854 getter_AddRefs(partitionedPrincipal));
2858 bool equal = principal->Equals(partitionedPrincipal);
2860 principal = MaybeDowngradePrincipal(principal);
2861 if (equal) {
2862 partitionedPrincipal = principal;
2863 } else {
2864 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2867 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2869 // Note that, since mTiming does not change during a reset, the
2870 // navigationStart time remains unchanged and therefore any future new
2871 // timeline will have the same global clock time as the old one.
2872 mDocumentTimeline = nullptr;
2874 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
2875 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
2876 mDocumentBaseURI = baseURI.forget();
2877 mChromeXHRDocBaseURI = nullptr;
2881 mChannel = aChannel;
2882 RecomputeResistFingerprinting();
2885 void Document::DisconnectNodeTree() {
2886 // Delete references to sub-documents and kill the subdocument map,
2887 // if any. This is not strictly needed, but makes the node tree
2888 // teardown a bit faster.
2889 delete mSubDocuments;
2890 mSubDocuments = nullptr;
2892 bool oldVal = mInUnlinkOrDeletion;
2893 mInUnlinkOrDeletion = true;
2894 { // Scope for update
2895 MOZ_AUTO_DOC_UPDATE(this, true);
2897 // Destroy link map now so we don't waste time removing
2898 // links one by one
2899 DestroyElementMaps();
2901 // Invalidate cached array of child nodes
2902 InvalidateChildNodes();
2904 while (HasChildren()) {
2905 nsMutationGuard::DidMutate();
2906 nsCOMPtr<nsIContent> content = GetLastChild();
2907 nsIContent* previousSibling = content->GetPreviousSibling();
2908 DisconnectChild(content);
2909 if (content == mCachedRootElement) {
2910 // Immediately clear mCachedRootElement, now that it's been removed
2911 // from mChildren, so that GetRootElement() will stop returning this
2912 // now-stale value.
2913 mCachedRootElement = nullptr;
2915 MutationObservers::NotifyContentRemoved(this, content, previousSibling);
2916 content->UnbindFromTree();
2918 MOZ_ASSERT(!mCachedRootElement,
2919 "After removing all children, there should be no root elem");
2921 mInUnlinkOrDeletion = oldVal;
2924 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2925 nsIPrincipal* aPrincipal,
2926 nsIPrincipal* aPartitionedPrincipal) {
2927 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2928 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2930 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2931 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2933 mSecurityInfo = nullptr;
2935 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2936 if (!aLoadGroup || group != aLoadGroup) {
2937 mDocumentLoadGroup = nullptr;
2940 DisconnectNodeTree();
2942 // Reset our stylesheets
2943 ResetStylesheetsToURI(aURI);
2945 // Release the listener manager
2946 if (mListenerManager) {
2947 mListenerManager->Disconnect();
2948 mListenerManager = nullptr;
2951 // Release the stylesheets list.
2952 mDOMStyleSheets = nullptr;
2954 // Release our principal after tearing down the document, rather than before.
2955 // This ensures that, during teardown, the document and the dying window
2956 // (which already nulled out its document pointer and cached the principal)
2957 // have matching principals.
2958 SetPrincipals(nullptr, nullptr);
2960 // Clear the original URI so SetDocumentURI sets it.
2961 mOriginalURI = nullptr;
2963 SetDocumentURI(aURI);
2964 mChromeXHRDocURI = nullptr;
2965 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
2966 // mDocumentURI.
2967 mDocumentBaseURI = nullptr;
2968 mChromeXHRDocBaseURI = nullptr;
2970 // Check if the current document is the top-level DevTools document.
2971 // For inner DevTools frames, mIsDevToolsDocument will be set when
2972 // calling SetDocumentParent.
2973 if (aURI && aURI->SchemeIs("about") &&
2974 aURI->GetSpecOrDefault().EqualsLiteral("about:devtools-toolbox")) {
2975 mIsDevToolsDocument = true;
2978 if (aLoadGroup) {
2979 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
2980 // there was an assertion here that aLoadGroup was not null. This
2981 // is no longer valid: nsDocShell::SetDocument does not create a
2982 // load group, and it works just fine
2984 // XXXbz what does "just fine" mean exactly? And given that there
2985 // is no nsDocShell::SetDocument, what is this talking about?
2987 if (IsContentDocument()) {
2988 // Inform the associated request context about this load start so
2989 // any of its internal load progress flags gets reset.
2990 nsCOMPtr<nsIRequestContextService> rcsvc =
2991 net::RequestContextService::GetOrCreate();
2992 if (rcsvc) {
2993 nsCOMPtr<nsIRequestContext> rc;
2994 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
2995 if (rc) {
2996 rc->BeginLoad();
3002 mLastModified.Truncate();
3003 // XXXbz I guess we're assuming that the caller will either pass in
3004 // a channel with a useful type or call SetContentType?
3005 SetContentType(""_ns);
3006 mContentLanguage.Truncate();
3007 mBaseTarget.Truncate();
3009 mXMLDeclarationBits = 0;
3011 // Now get our new principal
3012 if (aPrincipal) {
3013 SetPrincipals(aPrincipal, aPartitionedPrincipal);
3014 } else {
3015 nsIScriptSecurityManager* securityManager =
3016 nsContentUtils::GetSecurityManager();
3017 if (securityManager) {
3018 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
3020 if (!loadContext && aLoadGroup) {
3021 nsCOMPtr<nsIInterfaceRequestor> cbs;
3022 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
3023 loadContext = do_GetInterface(cbs);
3026 MOZ_ASSERT(loadContext,
3027 "must have a load context or pass in an explicit principal");
3029 nsCOMPtr<nsIPrincipal> principal;
3030 nsresult rv = securityManager->GetLoadContextContentPrincipal(
3031 mDocumentURI, loadContext, getter_AddRefs(principal));
3032 if (NS_SUCCEEDED(rv)) {
3033 SetPrincipals(principal, principal);
3038 if (mFontFaceSet) {
3039 mFontFaceSet->RefreshStandardFontLoadPrincipal();
3042 // Refresh the principal on the realm.
3043 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
3044 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
3048 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
3049 nsIPrincipal* aPrincipal) {
3050 if (!aPrincipal) {
3051 return nullptr;
3054 // We can't load a document with an expanded principal. If we're given one,
3055 // automatically downgrade it to the last principal it subsumes (which is the
3056 // extension principal, in the case of extension content scripts).
3057 auto* basePrin = BasePrincipal::Cast(aPrincipal);
3058 if (basePrin->Is<ExpandedPrincipal>()) {
3059 MOZ_DIAGNOSTIC_ASSERT(false,
3060 "Should never try to create a document with "
3061 "an expanded principal");
3063 auto* expanded = basePrin->As<ExpandedPrincipal>();
3064 return do_AddRef(expanded->AllowList().LastElement());
3067 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
3068 // We basically want the parent document here, but because this is very
3069 // early in the load, GetInProcessParentDocument() returns null, so we use
3070 // the docshell hierarchy to get this information instead.
3071 if (RefPtr<BrowsingContext> parent =
3072 mDocumentContainer->GetBrowsingContext()->GetParent()) {
3073 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
3074 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
3075 nsCOMPtr<nsIPrincipal> nullPrincipal =
3076 NullPrincipal::CreateWithoutOriginAttributes();
3077 return nullPrincipal.forget();
3081 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
3082 return principal.forget();
3085 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
3086 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3088 // lowest index first
3089 int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
3091 size_t count = mStyleSet->SheetCount(StyleOrigin::Author);
3092 size_t index = 0;
3093 for (; index < count; index++) {
3094 auto* sheet = mStyleSet->SheetAt(StyleOrigin::Author, index);
3095 MOZ_ASSERT(sheet);
3096 int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
3097 if (sheetDocIndex > newDocIndex) {
3098 break;
3101 // If the sheet is not owned by the document it can be an author
3102 // sheet registered at nsStyleSheetService or an additional author
3103 // sheet on the document, which means the new
3104 // doc sheet should end up before it.
3105 if (sheetDocIndex < 0) {
3106 if (sheetService) {
3107 auto& authorSheets = *sheetService->AuthorStyleSheets();
3108 if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
3109 break;
3112 if (sheet == GetFirstAdditionalAuthorSheet()) {
3113 break;
3118 return index;
3121 void Document::ResetStylesheetsToURI(nsIURI* aURI) {
3122 MOZ_ASSERT(aURI);
3124 ClearAdoptedStyleSheets();
3126 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
3127 for (auto& sheet : Reversed(aSheetList)) {
3128 sheet->ClearAssociatedDocumentOrShadowRoot();
3129 if (mStyleSetFilled) {
3130 mStyleSet->RemoveStyleSheet(*sheet);
3133 aSheetList.Clear();
3135 ClearSheetList(mStyleSheets);
3136 for (auto& sheets : mAdditionalSheets) {
3137 ClearSheetList(sheets);
3139 if (mStyleSetFilled) {
3140 if (auto* ss = nsStyleSheetService::GetInstance()) {
3141 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
3142 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
3143 if (sheet->IsApplicable()) {
3144 mStyleSet->RemoveStyleSheet(*sheet);
3150 // Now reset our inline style and attribute sheets.
3151 if (mAttrStyleSheet) {
3152 mAttrStyleSheet->Reset();
3153 mAttrStyleSheet->SetOwningDocument(this);
3154 } else {
3155 mAttrStyleSheet = new nsHTMLStyleSheet(this);
3158 if (!mStyleAttrStyleSheet) {
3159 mStyleAttrStyleSheet = new nsHTMLCSSStyleSheet();
3162 if (mStyleSetFilled) {
3163 FillStyleSetDocumentSheets();
3165 if (mStyleSet->StyleSheetsHaveChanged()) {
3166 ApplicableStylesChanged();
3171 static void AppendSheetsToStyleSet(
3172 ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
3173 for (StyleSheet* sheet : Reversed(aSheets)) {
3174 aStyleSet->AppendStyleSheet(*sheet);
3178 void Document::FillStyleSetUserAndUASheets() {
3179 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
3180 // ordering.
3182 // The document will fill in the document sheets when we create the presshell
3183 auto* cache = GlobalStyleSheetCache::Singleton();
3185 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3186 MOZ_ASSERT(sheetService,
3187 "should never be creating a StyleSet after the style sheet "
3188 "service has gone");
3190 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
3191 mStyleSet->AppendStyleSheet(*sheet);
3194 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
3195 : cache->GetUserContentSheet();
3196 if (sheet) {
3197 mStyleSet->AppendStyleSheet(*sheet);
3200 mStyleSet->AppendStyleSheet(*cache->UASheet());
3202 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
3203 mStyleSet->AppendStyleSheet(*cache->MathMLSheet());
3206 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
3207 mStyleSet->AppendStyleSheet(*cache->SVGSheet());
3210 mStyleSet->AppendStyleSheet(*cache->HTMLSheet());
3212 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
3213 mStyleSet->AppendStyleSheet(*cache->NoFramesSheet());
3216 mStyleSet->AppendStyleSheet(*cache->CounterStylesSheet());
3218 // Only load the full XUL sheet if we'll need it.
3219 if (LoadsFullXULStyleSheetUpFront()) {
3220 mStyleSet->AppendStyleSheet(*cache->XULSheet());
3223 mStyleSet->AppendStyleSheet(*cache->FormsSheet());
3224 mStyleSet->AppendStyleSheet(*cache->ScrollbarsSheet());
3226 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
3227 mStyleSet->AppendStyleSheet(*sheet);
3230 MOZ_ASSERT(!mQuirkSheetAdded);
3231 if (NeedsQuirksSheet()) {
3232 mStyleSet->AppendStyleSheet(*cache->QuirkSheet());
3233 mQuirkSheetAdded = true;
3237 void Document::FillStyleSet() {
3238 MOZ_ASSERT(!mStyleSetFilled);
3239 FillStyleSetUserAndUASheets();
3240 FillStyleSetDocumentSheets();
3241 mStyleSetFilled = true;
3244 void Document::RemoveContentEditableStyleSheets() {
3245 MOZ_ASSERT(IsHTMLOrXHTML());
3247 auto* cache = GlobalStyleSheetCache::Singleton();
3248 bool changed = false;
3249 if (mDesignModeSheetAdded) {
3250 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3251 mDesignModeSheetAdded = false;
3252 changed = true;
3254 if (mContentEditableSheetAdded) {
3255 mStyleSet->RemoveStyleSheet(*cache->ContentEditableSheet());
3256 mContentEditableSheetAdded = false;
3257 changed = true;
3259 if (changed) {
3260 MOZ_ASSERT(mStyleSetFilled);
3261 ApplicableStylesChanged();
3265 void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
3266 MOZ_ASSERT(IsHTMLOrXHTML());
3267 MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
3268 "Caller should ensure we're being rendered");
3270 auto* cache = GlobalStyleSheetCache::Singleton();
3271 bool changed = false;
3272 if (!mContentEditableSheetAdded) {
3273 mStyleSet->AppendStyleSheet(*cache->ContentEditableSheet());
3274 mContentEditableSheetAdded = true;
3275 changed = true;
3277 if (mDesignModeSheetAdded != aDesignMode) {
3278 if (mDesignModeSheetAdded) {
3279 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3280 } else {
3281 mStyleSet->AppendStyleSheet(*cache->DesignModeSheet());
3283 mDesignModeSheetAdded = !mDesignModeSheetAdded;
3284 changed = true;
3286 if (changed) {
3287 ApplicableStylesChanged();
3291 void Document::FillStyleSetDocumentSheets() {
3292 MOZ_ASSERT(mStyleSet->SheetCount(StyleOrigin::Author) == 0,
3293 "Style set already has document sheets?");
3295 // Sheets are added in reverse order to avoid worst-case time complexity when
3296 // looking up the index of a sheet.
3298 // Note that usually appending is faster (rebuilds less stuff in the
3299 // styleset), but in this case it doesn't matter since we're filling the
3300 // styleset from scratch anyway.
3301 for (StyleSheet* sheet : Reversed(mStyleSheets)) {
3302 if (sheet->IsApplicable()) {
3303 mStyleSet->AddDocStyleSheet(*sheet);
3307 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
3308 if (aSheet.IsApplicable()) {
3309 mStyleSet->AddDocStyleSheet(aSheet);
3313 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3314 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
3315 mStyleSet->AppendStyleSheet(*sheet);
3318 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAgentSheet]);
3319 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eUserSheet]);
3320 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAuthorSheet]);
3323 void Document::CompatibilityModeChanged() {
3324 MOZ_ASSERT(IsHTMLOrXHTML());
3325 CSSLoader()->SetCompatibilityMode(mCompatMode);
3326 mStyleSet->CompatibilityModeChanged();
3327 if (PresShell* presShell = GetPresShell()) {
3328 // Selectors may have become case-sensitive / case-insensitive, the stylist
3329 // has already performed the relevant invalidation.
3330 presShell->EnsureStyleFlush();
3332 if (!mStyleSetFilled) {
3333 MOZ_ASSERT(!mQuirkSheetAdded);
3334 return;
3336 if (mQuirkSheetAdded == NeedsQuirksSheet()) {
3337 return;
3339 auto* cache = GlobalStyleSheetCache::Singleton();
3340 StyleSheet* sheet = cache->QuirkSheet();
3341 if (mQuirkSheetAdded) {
3342 mStyleSet->RemoveStyleSheet(*sheet);
3343 } else {
3344 mStyleSet->AppendStyleSheet(*sheet);
3346 mQuirkSheetAdded = !mQuirkSheetAdded;
3347 ApplicableStylesChanged();
3350 void Document::SetCompatibilityMode(nsCompatibility aMode) {
3351 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
3352 "Bad compat mode for XHTML document!");
3354 if (mCompatMode == aMode) {
3355 return;
3357 mCompatMode = aMode;
3358 CompatibilityModeChanged();
3359 // Trigger recomputation of the nsViewportInfo the next time it's queried.
3360 mViewportType = Unknown;
3363 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
3364 uint32_t aSandboxFlags,
3365 nsIChannel* aChannel) {
3366 // If the document permits allow-top-navigation and
3367 // allow-top-navigation-by-user-activation this will permit all top
3368 // navigation.
3369 if (aSandboxFlags != SANDBOXED_NONE &&
3370 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
3371 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
3372 nsContentUtils::ReportToConsole(
3373 nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
3374 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
3375 "BothAllowTopNavigationAndUserActivationPresent");
3377 // If the document is sandboxed (via the HTML5 iframe sandbox
3378 // attribute) and both the allow-scripts and allow-same-origin
3379 // keywords are supplied, the sandboxed document can call into its
3380 // parent document and remove its sandboxing entirely - we print a
3381 // warning to the web console in this case.
3382 if (aSandboxFlags & SANDBOXED_NAVIGATION &&
3383 !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
3384 !(aSandboxFlags & SANDBOXED_ORIGIN)) {
3385 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
3386 MOZ_ASSERT(bc->IsInProcess());
3388 RefPtr<BrowsingContext> parentBC = bc->GetParent();
3389 if (!parentBC || !parentBC->IsInProcess()) {
3390 // If parent document is not in process, then by construction it
3391 // cannot be same origin.
3392 return;
3395 // Don't warn if our parent is not the top-level document.
3396 if (!parentBC->IsTopContent()) {
3397 return;
3400 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
3401 MOZ_ASSERT(parentDocShell);
3403 nsCOMPtr<nsIChannel> parentChannel;
3404 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
3405 if (!parentChannel) {
3406 return;
3408 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
3409 if (NS_FAILED(rv)) {
3410 return;
3413 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
3414 nsCOMPtr<nsIURI> iframeUri;
3415 parentChannel->GetURI(getter_AddRefs(iframeUri));
3416 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3417 "Iframe Sandbox"_ns, parentDocument,
3418 nsContentUtils::eSECURITY_PROPERTIES,
3419 "BothAllowScriptsAndSameOriginPresent",
3420 nsTArray<nsString>(), iframeUri);
3424 bool Document::IsSynthesized() {
3425 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
3426 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
3429 // static
3430 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
3431 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
3432 return principal && (principal->IsSystemPrincipal() ||
3433 principal->GetIsAddonOrExpandedAddonPrincipal());
3436 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
3437 nsILoadGroup* aLoadGroup,
3438 nsISupports* aContainer,
3439 nsIStreamListener** aDocListener,
3440 bool aReset) {
3441 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
3442 nsCOMPtr<nsIURI> uri;
3443 aChannel->GetURI(getter_AddRefs(uri));
3444 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
3445 ("DOCUMENT %p StartDocumentLoad %s", this,
3446 uri ? uri->GetSpecOrDefault().get() : ""));
3449 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
3450 "Bad readyState");
3451 SetReadyStateInternal(READYSTATE_LOADING);
3453 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
3454 mLoadedAsData = true;
3455 SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true);
3456 // We need to disable script & style loading in this case.
3457 // We leave them disabled even in EndLoad(), and let anyone
3458 // who puts the document on display to worry about enabling.
3460 // Do not load/process scripts when loading as data
3461 ScriptLoader()->SetEnabled(false);
3463 // styles
3464 CSSLoader()->SetEnabled(
3465 false); // Do not load/process styles when loading as data
3466 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
3467 // Allow CSS, but not scripts
3468 ScriptLoader()->SetEnabled(false);
3471 mMayStartLayout = false;
3472 MOZ_ASSERT(!mReadyForIdle,
3473 "We should never hit DOMContentLoaded before this point");
3475 if (aReset) {
3476 Reset(aChannel, aLoadGroup);
3479 nsAutoCString contentType;
3480 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3481 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3482 contentType))) ||
3483 NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
3484 // XXX this is only necessary for viewsource:
3485 nsACString::const_iterator start, end, semicolon;
3486 contentType.BeginReading(start);
3487 contentType.EndReading(end);
3488 semicolon = start;
3489 FindCharInReadable(';', semicolon, end);
3490 SetContentType(Substring(start, semicolon));
3493 RetrieveRelevantHeaders(aChannel);
3495 mChannel = aChannel;
3496 RecomputeResistFingerprinting();
3497 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
3498 if (inStrmChan) {
3499 bool isSrcdocChannel;
3500 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
3501 if (isSrcdocChannel) {
3502 mIsSrcdocDocument = true;
3506 if (mChannel) {
3507 nsLoadFlags loadFlags;
3508 mChannel->GetLoadFlags(&loadFlags);
3509 bool isDocument = false;
3510 mChannel->GetIsDocument(&isDocument);
3511 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
3512 IsSynthesized() && XRE_IsContentProcess()) {
3513 ContentChild::UpdateCookieStatus(mChannel);
3516 // Store the security info for future use.
3517 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
3520 // If this document is being loaded by a docshell, copy its sandbox flags
3521 // to the document, and store the fullscreen enabled flag. These are
3522 // immutable after being set here.
3523 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
3525 // If this is an error page, don't inherit sandbox flags
3526 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3527 if (docShell && !loadInfo->GetLoadErrorPage()) {
3528 mSandboxFlags = loadInfo->GetSandboxFlags();
3529 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
3532 // Set the opener policy for the top level content document.
3533 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
3534 nsILoadInfo::CrossOriginOpenerPolicy policy =
3535 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
3536 if (IsTopLevelContentDocument() && httpChan &&
3537 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
3538 docShell->GetBrowsingContext()) {
3539 // Setting the opener policy on a discarded context has no effect.
3540 Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
3543 // The CSP directives upgrade-insecure-requests as well as
3544 // block-all-mixed-content not only apply to the toplevel document,
3545 // but also to nested documents. The loadInfo of a subdocument
3546 // load already holds the correct flag, so let's just set it here
3547 // on the document. Please note that we set the appropriate preload
3548 // bits just for the sake of completeness here, because the preloader
3549 // does not reach into subdocuments.
3550 mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
3551 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3552 mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
3553 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3555 // HTTPS-Only Mode flags
3556 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
3557 // sub-resources and sub-documents.
3558 mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
3560 nsresult rv = InitReferrerInfo(aChannel);
3561 NS_ENSURE_SUCCESS(rv, rv);
3563 rv = InitCOEP(aChannel);
3564 NS_ENSURE_SUCCESS(rv, rv);
3566 // Check CSP navigate-to
3567 // We need to enforce the CSP of the document that initiated the load,
3568 // which is the CSP to inherit.
3569 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3570 if (cspToInherit) {
3571 bool allowsNavigateTo = false;
3572 rv = cspToInherit->GetAllowsNavigateTo(
3573 mDocumentURI, loadInfo->GetIsFormSubmission(),
3574 !loadInfo->RedirectChain().IsEmpty(), /* aWasRedirected */
3575 true, /* aEnforceWhitelist */
3576 &allowsNavigateTo);
3577 NS_ENSURE_SUCCESS(rv, rv);
3579 if (!allowsNavigateTo) {
3580 aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
3581 return NS_OK;
3585 rv = InitCSP(aChannel);
3586 NS_ENSURE_SUCCESS(rv, rv);
3588 // Initialize FeaturePolicy
3589 rv = InitFeaturePolicy(aChannel);
3590 NS_ENSURE_SUCCESS(rv, rv);
3592 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3593 NS_ENSURE_SUCCESS(rv, rv);
3595 // Generally XFO and CSP frame-ancestors is handled within
3596 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3597 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3598 nsContentPolicyType internalContentType =
3599 loadInfo->InternalContentPolicyType();
3600 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3601 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3602 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3604 nsresult status;
3605 aChannel->GetStatus(&status);
3606 if (status == NS_ERROR_XFO_VIOLATION) {
3607 // stop! ERROR page!
3608 // But before we have to reset the principal of the document
3609 // because the onload() event fires before the error page
3610 // is displayed and we do not want the enclosing document
3611 // to access the contentDocument.
3612 RefPtr<NullPrincipal> nullPrincipal =
3613 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3614 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3615 // and also GetInnerWindow() is still null at this point, before
3616 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3617 // within Document.cpp
3618 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3619 SetPrincipals(nullPrincipal, nullPrincipal);
3623 return NS_OK;
3626 void Document::SetLoadedAsData(bool aLoadedAsData,
3627 bool aConsiderForMemoryReporting) {
3628 mLoadedAsData = aLoadedAsData;
3629 if (aConsiderForMemoryReporting) {
3630 nsIGlobalObject* global = GetScopeObject();
3631 if (global) {
3632 if (nsPIDOMWindowInner* window = global->AsInnerWindow()) {
3633 nsGlobalWindowInner::Cast(window)
3634 ->RegisterDataDocumentForMemoryReporting(this);
3640 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3642 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
3644 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3645 return mPreloadCSP;
3648 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3649 mPreloadCSP = aPreloadCSP;
3652 void Document::GetCspJSON(nsString& aJSON) {
3653 aJSON.Truncate();
3655 if (!mCSP) {
3656 dom::CSPPolicies jsonPolicies;
3657 jsonPolicies.ToJSON(aJSON);
3658 return;
3660 mCSP->ToJSON(aJSON);
3663 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3664 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3665 nsAutoString messageTag;
3666 aMessages[i]->GetTag(messageTag);
3668 nsAutoString category;
3669 aMessages[i]->GetCategory(category);
3671 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3672 NS_ConvertUTF16toUTF8(category), this,
3673 nsContentUtils::eSECURITY_PROPERTIES,
3674 NS_ConvertUTF16toUTF8(messageTag).get());
3678 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3679 nsresult rv = NS_OK;
3680 if (!aSpeculative) {
3681 // 1) apply settings from regular CSP
3682 if (mCSP) {
3683 // Set up 'block-all-mixed-content' if not already inherited
3684 // from the parent context or set by any other CSP.
3685 if (!mBlockAllMixedContent) {
3686 bool block = false;
3687 rv = mCSP->GetBlockAllMixedContent(&block);
3688 NS_ENSURE_SUCCESS_VOID(rv);
3689 mBlockAllMixedContent = block;
3691 if (!mBlockAllMixedContentPreloads) {
3692 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3695 // Set up 'upgrade-insecure-requests' if not already inherited
3696 // from the parent context or set by any other CSP.
3697 if (!mUpgradeInsecureRequests) {
3698 bool upgrade = false;
3699 rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
3700 NS_ENSURE_SUCCESS_VOID(rv);
3701 mUpgradeInsecureRequests = upgrade;
3703 if (!mUpgradeInsecurePreloads) {
3704 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3706 // Update csp settings in the parent process
3707 if (auto* wgc = GetWindowGlobalChild()) {
3708 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3709 mUpgradeInsecureRequests);
3712 return;
3715 // 2) apply settings from speculative csp
3716 if (mPreloadCSP) {
3717 if (!mBlockAllMixedContentPreloads) {
3718 bool block = false;
3719 rv = mPreloadCSP->GetBlockAllMixedContent(&block);
3720 NS_ENSURE_SUCCESS_VOID(rv);
3721 mBlockAllMixedContent = block;
3723 if (!mUpgradeInsecurePreloads) {
3724 bool upgrade = false;
3725 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
3726 NS_ENSURE_SUCCESS_VOID(rv);
3727 mUpgradeInsecurePreloads = upgrade;
3732 nsresult Document::InitCSP(nsIChannel* aChannel) {
3733 MOZ_ASSERT(!mScriptGlobalObject,
3734 "CSP must be initialized before mScriptGlobalObject is set!");
3736 // If this is a data document - no need to set CSP.
3737 if (mLoadedAsData) {
3738 return NS_OK;
3741 // If this is an image, no need to set a CSP. Otherwise SVG images
3742 // served with a CSP might block internally applied inline styles.
3743 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3744 if (loadInfo->GetExternalContentPolicyType() ==
3745 ExtContentPolicy::TYPE_IMAGE ||
3746 loadInfo->GetExternalContentPolicyType() ==
3747 ExtContentPolicy::TYPE_IMAGESET) {
3748 return NS_OK;
3751 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3753 // If there is a CSP that needs to be inherited from whatever
3754 // global is considered the client of the document fetch then
3755 // we query it here from the loadinfo in case the newly created
3756 // document needs to inherit the CSP. See:
3757 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3758 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
3759 if (inheritedCSP) {
3760 mCSP = loadInfo->GetCspToInherit();
3763 // If there is no CSP to inherit, then we create a new CSP here so
3764 // that history entries always have the right reference in case a
3765 // Meta CSP gets dynamically added after the history entry has
3766 // already been created.
3767 if (!mCSP) {
3768 mCSP = new nsCSPContext();
3771 // Always overwrite the requesting context of the CSP so that any new
3772 // 'self' keyword added to an inherited CSP translates correctly.
3773 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3774 if (NS_WARN_IF(NS_FAILED(rv))) {
3775 return rv;
3778 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3780 nsCOMPtr<nsIHttpChannel> httpChannel;
3781 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3782 if (NS_WARN_IF(NS_FAILED(rv))) {
3783 return rv;
3786 if (httpChannel) {
3787 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3788 tCspHeaderValue);
3790 Unused << httpChannel->GetResponseHeader(
3791 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3793 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3794 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3796 // Check if this is a document from a WebExtension.
3797 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3798 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3800 // If there's no CSP to apply, go ahead and return early
3801 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
3802 cspROHeaderValue.IsEmpty()) {
3803 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3804 nsCOMPtr<nsIURI> chanURI;
3805 aChannel->GetURI(getter_AddRefs(chanURI));
3806 nsAutoCString aspec;
3807 chanURI->GetAsciiSpec(aspec);
3808 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3809 ("no CSP for document, %s", aspec.get()));
3812 return NS_OK;
3815 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3816 ("Document is an add-on or CSP header specified %p", this));
3818 // ----- if the doc is an addon, apply its CSP.
3819 if (addonPolicy) {
3820 mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
3822 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3823 // Bug 1548468: Move CSP off ExpandedPrincipal
3824 // Currently the LoadInfo holds the source of truth for every resource load
3825 // because LoadInfo::GetCsp() queries the CSP from an ExpandedPrincipal
3826 // (and not from the Client) if the load was triggered by an extension.
3827 auto* basePrin = BasePrincipal::Cast(principal);
3828 if (basePrin->Is<ExpandedPrincipal>()) {
3829 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3833 // ----- if there's a full-strength CSP header, apply it.
3834 if (!cspHeaderValue.IsEmpty()) {
3835 mHasCSPDeliveredThroughHeader = true;
3836 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3837 NS_ENSURE_SUCCESS(rv, rv);
3840 // ----- if there's a report-only CSP header, apply it.
3841 if (!cspROHeaderValue.IsEmpty()) {
3842 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3843 NS_ENSURE_SUCCESS(rv, rv);
3846 // ----- Enforce sandbox policy if supplied in CSP header
3847 // The document may already have some sandbox flags set (e.g. if the document
3848 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3849 // directive, intersect the CSP sandbox flags with the existing flags. This
3850 // corresponds to the _least_ permissive policy.
3851 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3852 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3853 NS_ENSURE_SUCCESS(rv, rv);
3855 // Probably the iframe sandbox attribute already caused the creation of a
3856 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3857 // and no one has been created yet.
3858 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3859 !(mSandboxFlags & SANDBOXED_ORIGIN);
3861 mSandboxFlags |= cspSandboxFlags;
3863 if (needNewNullPrincipal) {
3864 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3865 // Skip setting the content blocking allowlist principal to NullPrincipal.
3866 // The principal is only used to enable/disable trackingprotection via
3867 // permission and can be shared with the top level sandboxed site.
3868 // See Bug 1654546.
3869 SetPrincipals(principal, principal);
3872 ApplySettingsFromCSP(false);
3873 return NS_OK;
3876 static Document* GetInProcessParentDocumentFrom(BrowsingContext* aContext) {
3877 BrowsingContext* parentContext = aContext->GetParent();
3878 if (!parentContext) {
3879 return nullptr;
3882 WindowContext* windowContext = parentContext->GetCurrentWindowContext();
3883 if (!windowContext) {
3884 return nullptr;
3887 return windowContext->GetDocument();
3890 already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
3891 BrowsingContext* browsingContext = GetBrowsingContext();
3892 if (!browsingContext) {
3893 return nullptr;
3895 if (!browsingContext->IsContentSubframe()) {
3896 return nullptr;
3899 HTMLIFrameElement* iframe =
3900 HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement());
3901 if (iframe) {
3902 return do_AddRef(iframe->FeaturePolicy());
3905 if (XRE_IsParentProcess()) {
3906 return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy());
3909 if (Document* parentDocument =
3910 GetInProcessParentDocumentFrom(browsingContext)) {
3911 return do_AddRef(parentDocument->FeaturePolicy());
3914 WindowContext* windowContext = browsingContext->GetCurrentWindowContext();
3915 if (!windowContext) {
3916 return nullptr;
3919 WindowGlobalChild* child = windowContext->GetWindowGlobalChild();
3920 if (!child) {
3921 return nullptr;
3924 return do_AddRef(child->GetContainerFeaturePolicy());
3927 void Document::InitFeaturePolicy() {
3928 MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created");
3930 mFeaturePolicy->ResetDeclaredPolicy();
3932 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
3934 RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
3935 if (parentPolicy) {
3936 // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
3937 mFeaturePolicy->InheritPolicy(parentPolicy);
3938 mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
3942 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
3943 InitFeaturePolicy();
3945 // We don't want to parse the http Feature-Policy header if this pref is off.
3946 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
3947 return NS_OK;
3950 nsCOMPtr<nsIHttpChannel> httpChannel;
3951 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3952 if (NS_WARN_IF(NS_FAILED(rv))) {
3953 return rv;
3956 if (!httpChannel) {
3957 return NS_OK;
3960 // query the policy from the header
3961 nsAutoCString value;
3962 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
3963 if (NS_SUCCEEDED(rv)) {
3964 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
3965 NodePrincipal(), nullptr);
3968 return NS_OK;
3971 void Document::EnsureNotEnteringAndExitFullscreen() {
3972 Document::ClearPendingFullscreenRequests(this);
3973 if (GetFullscreenElement()) {
3974 Document::AsyncExitFullscreen(this);
3978 void Document::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
3979 mReferrerInfo = aReferrerInfo;
3980 mCachedReferrerInfoForInternalCSSAndSVGResources = nullptr;
3981 mCachedURLData = nullptr;
3984 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
3985 MOZ_ASSERT(mReferrerInfo);
3986 MOZ_ASSERT(mPreloadReferrerInfo);
3988 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
3989 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
3990 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
3991 // they have an opaque origin.
3992 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
3993 if (BrowsingContext* bc = GetBrowsingContext()) {
3994 // At this point the document is not fully created and mParentDocument has
3995 // not been set yet,
3996 Document* parentDoc = bc->GetEmbedderElement()
3997 ? bc->GetEmbedderElement()->OwnerDoc()
3998 : nullptr;
3999 if (parentDoc) {
4000 SetReferrerInfo(parentDoc->GetReferrerInfo());
4001 mPreloadReferrerInfo = mReferrerInfo;
4002 return NS_OK;
4005 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
4006 "srcdoc without null principal as toplevel!");
4010 nsCOMPtr<nsIHttpChannel> httpChannel;
4011 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4012 if (NS_WARN_IF(NS_FAILED(rv))) {
4013 return rv;
4016 if (!httpChannel) {
4017 return NS_OK;
4020 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo()) {
4021 SetReferrerInfo(referrerInfo);
4024 // Override policy if we get one from Referrerr-Policy header
4025 mozilla::dom::ReferrerPolicy policy =
4026 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
4027 nsCOMPtr<nsIReferrerInfo> clone =
4028 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
4029 ->CloneWithNewPolicy(policy);
4030 SetReferrerInfo(clone);
4031 mPreloadReferrerInfo = mReferrerInfo;
4032 return NS_OK;
4035 nsresult Document::InitCOEP(nsIChannel* aChannel) {
4036 nsCOMPtr<nsIHttpChannel> httpChannel;
4037 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4038 if (NS_FAILED(rv)) {
4039 return NS_OK;
4042 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
4044 if (!intChannel) {
4045 return NS_OK;
4048 nsILoadInfo::CrossOriginEmbedderPolicy policy =
4049 nsILoadInfo::EMBEDDER_POLICY_NULL;
4050 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(
4051 mTrials.IsEnabled(OriginTrial::CoepCredentialless), &policy))) {
4052 mEmbedderPolicy = Some(policy);
4055 return NS_OK;
4058 void Document::StopDocumentLoad() {
4059 if (mParser) {
4060 mParserAborted = true;
4061 mParser->Terminate();
4065 void Document::SetDocumentURI(nsIURI* aURI) {
4066 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
4067 mDocumentURI = aURI;
4068 nsIURI* newBase = GetDocBaseURI();
4070 mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI);
4072 bool equalBases = false;
4073 // Changing just the ref of a URI does not change how relative URIs would
4074 // resolve wrt to it, so we can treat the bases as equal as long as they're
4075 // equal ignoring the ref.
4076 if (oldBase && newBase) {
4077 oldBase->EqualsExceptRef(newBase, &equalBases);
4078 } else {
4079 equalBases = !oldBase && !newBase;
4082 // If this is the first time we're setting the document's URI, set the
4083 // document's original URI.
4084 if (!mOriginalURI) mOriginalURI = mDocumentURI;
4086 // If changing the document's URI changed the base URI of the document, we
4087 // need to refresh the hrefs of all the links on the page.
4088 if (!equalBases) {
4089 mCachedURLData = nullptr;
4090 RefreshLinkHrefs();
4093 // Recalculate our base domain
4094 mBaseDomain.Truncate();
4095 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
4096 if (thirdPartyUtil) {
4097 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
4100 // Tell our WindowGlobalParent that the document's URI has been changed.
4101 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
4102 wgc->SetDocumentURI(mDocumentURI);
4106 static void GetFormattedTimeString(PRTime aTime,
4107 nsAString& aFormattedTimeString) {
4108 PRExplodedTime prtime;
4109 PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
4110 // "MM/DD/YYYY hh:mm:ss"
4111 char formatedTime[24];
4112 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
4113 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
4114 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
4115 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
4116 } else {
4117 // If we for whatever reason failed to find the last modified time
4118 // (or even the current time), fall back to what NS4.x returned.
4119 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
4123 void Document::GetLastModified(nsAString& aLastModified) const {
4124 if (!mLastModified.IsEmpty()) {
4125 aLastModified.Assign(mLastModified);
4126 } else {
4127 GetFormattedTimeString(PR_Now(), aLastModified);
4131 static void IncrementExpandoGeneration(Document& aDoc) {
4132 ++aDoc.mExpandoAndGeneration.generation;
4135 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
4136 MOZ_ASSERT(
4137 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
4138 "Only put elements that need to be exposed as document['name'] in "
4139 "the named table.");
4141 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
4143 // Null for out-of-memory
4144 if (entry) {
4145 if (!entry->HasNameElement() &&
4146 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4147 IncrementExpandoGeneration(*this);
4149 entry->AddNameElement(this, aElement);
4153 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
4154 // Speed up document teardown
4155 if (mIdentifierMap.Count() == 0) return;
4157 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
4158 if (!entry) // Could be false if the element was anonymous, hence never added
4159 return;
4161 entry->RemoveNameElement(aElement);
4162 if (!entry->HasNameElement() &&
4163 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4164 IncrementExpandoGeneration(*this);
4168 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
4169 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
4171 if (entry) { /* True except on OOM */
4172 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4173 !entry->HasNameElement() &&
4174 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4175 IncrementExpandoGeneration(*this);
4177 entry->AddIdElement(aElement);
4181 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
4182 NS_ASSERTION(aId, "huhwhatnow?");
4184 // Speed up document teardown
4185 if (mIdentifierMap.Count() == 0) {
4186 return;
4189 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
4190 if (!entry) // Can be null for XML elements with changing ids.
4191 return;
4193 entry->RemoveIdElement(aElement);
4194 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4195 !entry->HasNameElement() &&
4196 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4197 IncrementExpandoGeneration(*this);
4199 if (entry->IsEmpty()) {
4200 mIdentifierMap.RemoveEntry(entry);
4204 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
4205 bool aPreload) {
4206 ReferrerPolicyEnum policy =
4207 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
4208 // The empty string "" corresponds to no referrer policy, causing a fallback
4209 // to a referrer policy defined elsewhere.
4210 if (policy == ReferrerPolicy::_empty) {
4211 return;
4214 MOZ_ASSERT(mReferrerInfo);
4215 MOZ_ASSERT(mPreloadReferrerInfo);
4217 if (aPreload) {
4218 mPreloadReferrerInfo =
4219 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
4220 ->CloneWithNewPolicy(policy);
4221 } else {
4222 nsCOMPtr<nsIReferrerInfo> clone =
4223 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
4224 ->CloneWithNewPolicy(policy);
4225 SetReferrerInfo(clone);
4229 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
4230 nsIPrincipal* aNewPartitionedPrincipal) {
4231 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
4232 if (aNewPrincipal && mAllowDNSPrefetch &&
4233 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
4234 if (aNewPrincipal->SchemeIs("https")) {
4235 mAllowDNSPrefetch = false;
4239 mCSSLoader->DeregisterFromSheetCache();
4241 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
4242 mPartitionedPrincipal = aNewPartitionedPrincipal;
4244 mCachedURLData = nullptr;
4246 mCSSLoader->RegisterInSheetCache();
4248 #ifdef DEBUG
4249 // Validate that the docgroup is set correctly by calling its getter and
4250 // triggering its sanity check.
4252 // If we're setting the principal to null, we don't want to perform the check,
4253 // as the document is entering an intermediate state where it does not have a
4254 // principal. It will be given another real principal shortly which we will
4255 // check. It's not unsafe to have a document which has a null principal in the
4256 // same docgroup as another document, so this should not be a problem.
4257 if (aNewPrincipal) {
4258 GetDocGroup();
4260 #endif
4263 #ifdef DEBUG
4264 void Document::AssertDocGroupMatchesKey() const {
4265 // Sanity check that we have an up-to-date and accurate docgroup
4266 // We only check if the principal when we can get the browsing context.
4268 // Note that we can be invoked during cycle collection, so we need to handle
4269 // the browsingcontext being partially unlinked - normally you shouldn't
4270 // null-check `Group()` as it shouldn't return nullptr.
4271 if (!GetBrowsingContext() || !GetBrowsingContext()->Group()) {
4272 return;
4275 if (mDocGroup && mDocGroup->GetBrowsingContextGroup()) {
4276 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
4277 GetBrowsingContext()->Group());
4279 // GetKey() can fail, e.g. after the TLD service has shut down.
4280 nsAutoCString docGroupKey;
4281 nsresult rv = mozilla::dom::DocGroup::GetKey(
4282 NodePrincipal(),
4283 GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated(),
4284 docGroupKey);
4285 if (NS_SUCCEEDED(rv)) {
4286 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
4290 #endif
4292 nsresult Document::Dispatch(TaskCategory aCategory,
4293 already_AddRefed<nsIRunnable>&& aRunnable) {
4294 // Note that this method may be called off the main thread.
4295 if (mDocGroup) {
4296 return mDocGroup->Dispatch(aCategory, std::move(aRunnable));
4298 return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
4301 nsISerialEventTarget* Document::EventTargetFor(TaskCategory aCategory) const {
4302 if (mDocGroup) {
4303 return mDocGroup->EventTargetFor(aCategory);
4305 return DispatcherTrait::EventTargetFor(aCategory);
4308 AbstractThread* Document::AbstractMainThreadFor(
4309 mozilla::TaskCategory aCategory) {
4310 MOZ_ASSERT(NS_IsMainThread());
4311 if (mDocGroup) {
4312 return mDocGroup->AbstractMainThreadFor(aCategory);
4314 return DispatcherTrait::AbstractMainThreadFor(aCategory);
4317 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
4318 bool aIsTracking) {
4319 if (aIsTracking) {
4320 mTrackingScripts.Insert(aURL);
4321 } else {
4322 MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
4326 bool Document::IsScriptTracking(JSContext* aCx) const {
4327 JS::AutoFilename filename;
4328 uint32_t line = 0;
4329 uint32_t column = 0;
4330 if (!JS::DescribeScriptedCaller(aCx, &filename, &line, &column)) {
4331 return false;
4333 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
4336 void Document::GetContentType(nsAString& aContentType) {
4337 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
4340 void Document::SetContentType(const nsACString& aContentType) {
4341 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
4342 aContentType.EqualsLiteral("application/xhtml+xml")) {
4343 mDefaultElementType = kNameSpaceID_XHTML;
4346 mCachedEncoder = nullptr;
4347 mContentType = aContentType;
4350 bool Document::GetAllowPlugins() {
4351 // First, we ask our docshell if it allows plugins.
4352 auto* browsingContext = GetBrowsingContext();
4354 if (browsingContext) {
4355 if (!browsingContext->GetAllowPlugins()) {
4356 return false;
4359 // If the docshell allows plugins, we check whether
4360 // we are sandboxed and plugins should not be allowed.
4361 if (mSandboxFlags & SANDBOXED_PLUGINS) {
4362 return false;
4366 return true;
4369 bool Document::HasPendingInitialTranslation() {
4370 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
4373 bool Document::HasPendingL10nMutations() const {
4374 return mDocumentL10n && mDocumentL10n->HasPendingMutations();
4377 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
4378 JS::Rooted<JSObject*> object(aCx, aObject);
4379 nsCOMPtr<nsIPrincipal> callerPrincipal =
4380 nsContentUtils::SubjectPrincipal(aCx);
4381 nsGlobalWindowInner* win = xpc::WindowOrNull(object);
4382 bool allowed = false;
4383 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
4384 &allowed);
4385 return allowed;
4388 void Document::LocalizationLinkAdded(Element* aLinkElement) {
4389 if (!AllowsL10n()) {
4390 return;
4393 nsAutoString href;
4394 aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
4396 if (!mDocumentL10n) {
4397 Element* elem = GetDocumentElement();
4398 MOZ_DIAGNOSTIC_ASSERT(elem);
4400 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
4401 mDocumentL10n = DocumentL10n::Create(this, isSync);
4402 if (NS_WARN_IF(!mDocumentL10n)) {
4403 return;
4407 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
4409 if (mReadyState >= READYSTATE_INTERACTIVE) {
4410 nsContentUtils::AddScriptRunner(NewRunnableMethod(
4411 "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
4412 &DocumentL10n::TriggerInitialTranslation));
4413 } else {
4414 if (!mDocumentL10n->mBlockingLayout) {
4415 // Our initial translation is going to block layout start. Make sure
4416 // we don't fire the load event until after that stops happening and
4417 // layout has a chance to start.
4418 BlockOnload();
4419 mDocumentL10n->mBlockingLayout = true;
4424 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
4425 if (!AllowsL10n()) {
4426 return;
4429 if (mDocumentL10n) {
4430 nsAutoString href;
4431 aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
4432 uint32_t remaining =
4433 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
4434 if (remaining == 0) {
4435 if (mDocumentL10n->mBlockingLayout) {
4436 mDocumentL10n->mBlockingLayout = false;
4437 UnblockOnload(/* aFireSync = */ false);
4439 mDocumentL10n = nullptr;
4445 * This method should be called once the end of the l10n
4446 * resource container has been parsed.
4448 * In XUL this is the end of the first </linkset>,
4449 * In XHTML/HTML this is the end of </head>.
4451 * This milestone is used to allow for batch
4452 * localization context I/O and building done
4453 * once when all resources in the document have been
4454 * collected.
4456 void Document::OnL10nResourceContainerParsed() {
4457 // XXX: This is a scaffolding for where we might inject prefetch
4458 // in bug 1717241.
4461 void Document::OnParsingCompleted() {
4462 // Let's call it again, in case the resource
4463 // container has not been closed, and only
4464 // now we're closing the document.
4465 OnL10nResourceContainerParsed();
4467 if (mDocumentL10n) {
4468 RefPtr<DocumentL10n> l10n = mDocumentL10n;
4469 l10n->TriggerInitialTranslation();
4473 void Document::InitialTranslationCompleted(bool aL10nCached) {
4474 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4475 // This means we blocked the load event in LocalizationLinkAdded. It's
4476 // important that the load blocker removal here be async, because our caller
4477 // will notify the content sink after us, and we want the content sync's
4478 // work to happen before the load event fires.
4479 mDocumentL10n->mBlockingLayout = false;
4480 UnblockOnload(/* aFireSync = */ false);
4483 mL10nProtoElements.Clear();
4485 nsXULPrototypeDocument* proto = GetPrototype();
4486 if (proto) {
4487 proto->SetIsL10nCached(aL10nCached);
4491 bool Document::AllowsL10n() const {
4492 if (IsStaticDocument()) {
4493 // We don't allow l10n on static documents, because the nodes are already
4494 // cloned translated, and static docs don't get parsed so we never
4495 // TriggerInitialTranslation, etc, so a load blocker would keep hanging
4496 // forever.
4497 return false;
4499 bool allowed = false;
4500 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4501 return allowed;
4504 bool Document::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) {
4505 MOZ_ASSERT(NS_IsMainThread());
4507 return nsContentUtils::IsSystemCaller(aCx) ||
4508 StaticPrefs::dom_animations_api_core_enabled();
4511 bool Document::IsWebAnimationsEnabled(CallerType aCallerType) {
4512 MOZ_ASSERT(NS_IsMainThread());
4514 return aCallerType == dom::CallerType::System ||
4515 StaticPrefs::dom_animations_api_core_enabled();
4518 bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
4519 JSObject* /*unused*/
4521 MOZ_ASSERT(NS_IsMainThread());
4523 return nsContentUtils::IsSystemCaller(aCx) ||
4524 StaticPrefs::dom_animations_api_getAnimations_enabled();
4527 bool Document::AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
4528 JSObject* /*unused*/
4530 MOZ_ASSERT(NS_IsMainThread());
4532 return nsContentUtils::IsSystemCaller(aCx) ||
4533 StaticPrefs::dom_animations_api_implicit_keyframes_enabled();
4536 bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
4537 JSObject* /*unused*/
4539 MOZ_ASSERT(NS_IsMainThread());
4541 return nsContentUtils::IsSystemCaller(aCx) ||
4542 StaticPrefs::dom_animations_api_timelines_enabled();
4545 DocumentTimeline* Document::Timeline() {
4546 if (!mDocumentTimeline) {
4547 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4550 return mDocumentTimeline;
4553 SVGSVGElement* Document::GetSVGRootElement() const {
4554 Element* root = GetRootElement();
4555 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4556 return nullptr;
4558 return static_cast<SVGSVGElement*>(root);
4561 /* Return true if the document is in the focused top-level window, and is an
4562 * ancestor of the focused DOMWindow. */
4563 bool Document::HasFocus(ErrorResult& rv) const {
4564 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4565 if (!fm) {
4566 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4567 return false;
4570 BrowsingContext* bc = GetBrowsingContext();
4571 if (!bc) {
4572 return false;
4575 if (!fm->IsInActiveWindow(bc)) {
4576 return false;
4579 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
4582 bool Document::ThisDocumentHasFocus() const {
4583 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4584 return fm && fm->GetFocusedWindow() &&
4585 fm->GetFocusedWindow()->GetExtantDoc() == this;
4588 void Document::GetDesignMode(nsAString& aDesignMode) {
4589 if (IsInDesignMode()) {
4590 aDesignMode.AssignLiteral("on");
4591 } else {
4592 aDesignMode.AssignLiteral("off");
4596 void Document::SetDesignMode(const nsAString& aDesignMode,
4597 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4598 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4601 static void NotifyEditableStateChange(Document& aDoc) {
4602 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4603 nsMutationGuard g;
4604 #endif
4605 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4606 node = node->GetNextNode(&aDoc)) {
4607 if (auto* element = Element::FromNode(node)) {
4608 element->UpdateState(true);
4611 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4614 void Document::SetDesignMode(const nsAString& aDesignMode,
4615 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4616 ErrorResult& rv) {
4617 if (aSubjectPrincipal.isSome() &&
4618 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4619 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4620 return;
4622 const bool editableMode = IsInDesignMode();
4623 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4624 SetEditableFlag(!editableMode);
4625 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4626 // state of all descendant elements of it. Update that now.
4627 NotifyEditableStateChange(*this);
4628 rv = EditingStateChanged();
4632 nsCommandManager* Document::GetMidasCommandManager() {
4633 // check if we have it cached
4634 if (mMidasCommandManager) {
4635 return mMidasCommandManager;
4638 nsPIDOMWindowOuter* window = GetWindow();
4639 if (!window) {
4640 return nullptr;
4643 nsIDocShell* docshell = window->GetDocShell();
4644 if (!docshell) {
4645 return nullptr;
4648 mMidasCommandManager = docshell->GetCommandManager();
4649 return mMidasCommandManager;
4652 // static
4653 void Document::EnsureInitializeInternalCommandDataHashtable() {
4654 if (sInternalCommandDataHashtable) {
4655 return;
4657 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
4658 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4659 // clang-format off
4660 sInternalCommandDataHashtable->InsertOrUpdate(
4661 u"bold"_ns,
4662 InternalCommandData(
4663 "cmd_bold",
4664 Command::FormatBold,
4665 ExecCommandParam::Ignore,
4666 StyleUpdatingCommand::GetInstance,
4667 CommandOnTextEditor::Disabled));
4668 sInternalCommandDataHashtable->InsertOrUpdate(
4669 u"italic"_ns,
4670 InternalCommandData(
4671 "cmd_italic",
4672 Command::FormatItalic,
4673 ExecCommandParam::Ignore,
4674 StyleUpdatingCommand::GetInstance,
4675 CommandOnTextEditor::Disabled));
4676 sInternalCommandDataHashtable->InsertOrUpdate(
4677 u"underline"_ns,
4678 InternalCommandData(
4679 "cmd_underline",
4680 Command::FormatUnderline,
4681 ExecCommandParam::Ignore,
4682 StyleUpdatingCommand::GetInstance,
4683 CommandOnTextEditor::Disabled));
4684 sInternalCommandDataHashtable->InsertOrUpdate(
4685 u"strikethrough"_ns,
4686 InternalCommandData(
4687 "cmd_strikethrough",
4688 Command::FormatStrikeThrough,
4689 ExecCommandParam::Ignore,
4690 StyleUpdatingCommand::GetInstance,
4691 CommandOnTextEditor::Disabled));
4692 sInternalCommandDataHashtable->InsertOrUpdate(
4693 u"subscript"_ns,
4694 InternalCommandData(
4695 "cmd_subscript",
4696 Command::FormatSubscript,
4697 ExecCommandParam::Ignore,
4698 StyleUpdatingCommand::GetInstance,
4699 CommandOnTextEditor::Disabled));
4700 sInternalCommandDataHashtable->InsertOrUpdate(
4701 u"superscript"_ns,
4702 InternalCommandData(
4703 "cmd_superscript",
4704 Command::FormatSuperscript,
4705 ExecCommandParam::Ignore,
4706 StyleUpdatingCommand::GetInstance,
4707 CommandOnTextEditor::Disabled));
4708 sInternalCommandDataHashtable->InsertOrUpdate(
4709 u"cut"_ns,
4710 InternalCommandData(
4711 "cmd_cut",
4712 Command::Cut,
4713 ExecCommandParam::Ignore,
4714 CutCommand::GetInstance,
4715 CommandOnTextEditor::Enabled));
4716 sInternalCommandDataHashtable->InsertOrUpdate(
4717 u"copy"_ns,
4718 InternalCommandData(
4719 "cmd_copy",
4720 Command::Copy,
4721 ExecCommandParam::Ignore,
4722 CopyCommand::GetInstance,
4723 CommandOnTextEditor::Enabled));
4724 sInternalCommandDataHashtable->InsertOrUpdate(
4725 u"paste"_ns,
4726 InternalCommandData(
4727 "cmd_paste",
4728 Command::Paste,
4729 ExecCommandParam::Ignore,
4730 PasteCommand::GetInstance,
4731 CommandOnTextEditor::Enabled));
4732 sInternalCommandDataHashtable->InsertOrUpdate(
4733 u"delete"_ns,
4734 InternalCommandData(
4735 "cmd_deleteCharBackward",
4736 Command::DeleteCharBackward,
4737 ExecCommandParam::Ignore,
4738 DeleteCommand::GetInstance,
4739 CommandOnTextEditor::Enabled));
4740 sInternalCommandDataHashtable->InsertOrUpdate(
4741 u"forwarddelete"_ns,
4742 InternalCommandData(
4743 "cmd_deleteCharForward",
4744 Command::DeleteCharForward,
4745 ExecCommandParam::Ignore,
4746 DeleteCommand::GetInstance,
4747 CommandOnTextEditor::Enabled));
4748 sInternalCommandDataHashtable->InsertOrUpdate(
4749 u"selectall"_ns,
4750 InternalCommandData(
4751 "cmd_selectAll",
4752 Command::SelectAll,
4753 ExecCommandParam::Ignore,
4754 SelectAllCommand::GetInstance,
4755 CommandOnTextEditor::Enabled));
4756 sInternalCommandDataHashtable->InsertOrUpdate(
4757 u"undo"_ns,
4758 InternalCommandData(
4759 "cmd_undo",
4760 Command::HistoryUndo,
4761 ExecCommandParam::Ignore,
4762 UndoCommand::GetInstance,
4763 CommandOnTextEditor::Enabled));
4764 sInternalCommandDataHashtable->InsertOrUpdate(
4765 u"redo"_ns,
4766 InternalCommandData(
4767 "cmd_redo",
4768 Command::HistoryRedo,
4769 ExecCommandParam::Ignore,
4770 RedoCommand::GetInstance,
4771 CommandOnTextEditor::Enabled));
4772 sInternalCommandDataHashtable->InsertOrUpdate(
4773 u"indent"_ns,
4774 InternalCommandData("cmd_indent",
4775 Command::FormatIndent,
4776 ExecCommandParam::Ignore,
4777 IndentCommand::GetInstance,
4778 CommandOnTextEditor::Disabled));
4779 sInternalCommandDataHashtable->InsertOrUpdate(
4780 u"outdent"_ns,
4781 InternalCommandData(
4782 "cmd_outdent",
4783 Command::FormatOutdent,
4784 ExecCommandParam::Ignore,
4785 OutdentCommand::GetInstance,
4786 CommandOnTextEditor::Disabled));
4787 sInternalCommandDataHashtable->InsertOrUpdate(
4788 u"backcolor"_ns,
4789 InternalCommandData(
4790 "cmd_highlight",
4791 Command::FormatBackColor,
4792 ExecCommandParam::String,
4793 HighlightColorStateCommand::GetInstance,
4794 CommandOnTextEditor::Disabled));
4795 sInternalCommandDataHashtable->InsertOrUpdate(
4796 u"hilitecolor"_ns,
4797 InternalCommandData(
4798 "cmd_highlight",
4799 Command::FormatBackColor,
4800 ExecCommandParam::String,
4801 HighlightColorStateCommand::GetInstance,
4802 CommandOnTextEditor::Disabled));
4803 sInternalCommandDataHashtable->InsertOrUpdate(
4804 u"forecolor"_ns,
4805 InternalCommandData(
4806 "cmd_fontColor",
4807 Command::FormatFontColor,
4808 ExecCommandParam::String,
4809 FontColorStateCommand::GetInstance,
4810 CommandOnTextEditor::Disabled));
4811 sInternalCommandDataHashtable->InsertOrUpdate(
4812 u"fontname"_ns,
4813 InternalCommandData(
4814 "cmd_fontFace",
4815 Command::FormatFontName,
4816 ExecCommandParam::String,
4817 FontFaceStateCommand::GetInstance,
4818 CommandOnTextEditor::Disabled));
4819 sInternalCommandDataHashtable->InsertOrUpdate(
4820 u"fontsize"_ns,
4821 InternalCommandData(
4822 "cmd_fontSize",
4823 Command::FormatFontSize,
4824 ExecCommandParam::String,
4825 FontSizeStateCommand::GetInstance,
4826 CommandOnTextEditor::Disabled));
4827 sInternalCommandDataHashtable->InsertOrUpdate(
4828 u"inserthorizontalrule"_ns,
4829 InternalCommandData(
4830 "cmd_insertHR",
4831 Command::InsertHorizontalRule,
4832 ExecCommandParam::Ignore,
4833 InsertTagCommand::GetInstance,
4834 CommandOnTextEditor::Disabled));
4835 sInternalCommandDataHashtable->InsertOrUpdate(
4836 u"createlink"_ns,
4837 InternalCommandData(
4838 "cmd_insertLinkNoUI",
4839 Command::InsertLink,
4840 ExecCommandParam::String,
4841 InsertTagCommand::GetInstance,
4842 CommandOnTextEditor::Disabled));
4843 sInternalCommandDataHashtable->InsertOrUpdate(
4844 u"insertimage"_ns,
4845 InternalCommandData(
4846 "cmd_insertImageNoUI",
4847 Command::InsertImage,
4848 ExecCommandParam::String,
4849 InsertTagCommand::GetInstance,
4850 CommandOnTextEditor::Disabled));
4851 sInternalCommandDataHashtable->InsertOrUpdate(
4852 u"inserthtml"_ns,
4853 InternalCommandData(
4854 "cmd_insertHTML",
4855 Command::InsertHTML,
4856 ExecCommandParam::String,
4857 InsertHTMLCommand::GetInstance,
4858 // TODO: Chromium inserts text content of the document fragment
4859 // created from the param.
4860 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
4861 CommandOnTextEditor::Disabled));
4862 sInternalCommandDataHashtable->InsertOrUpdate(
4863 u"inserttext"_ns,
4864 InternalCommandData(
4865 "cmd_insertText",
4866 Command::InsertText,
4867 ExecCommandParam::String,
4868 InsertPlaintextCommand::GetInstance,
4869 CommandOnTextEditor::Enabled));
4870 sInternalCommandDataHashtable->InsertOrUpdate(
4871 u"justifyleft"_ns,
4872 InternalCommandData(
4873 "cmd_align",
4874 Command::FormatJustifyLeft,
4875 ExecCommandParam::Ignore, // Will be set to "left"
4876 AlignCommand::GetInstance,
4877 CommandOnTextEditor::Disabled));
4878 sInternalCommandDataHashtable->InsertOrUpdate(
4879 u"justifyright"_ns,
4880 InternalCommandData(
4881 "cmd_align",
4882 Command::FormatJustifyRight,
4883 ExecCommandParam::Ignore, // Will be set to "right"
4884 AlignCommand::GetInstance,
4885 CommandOnTextEditor::Disabled));
4886 sInternalCommandDataHashtable->InsertOrUpdate(
4887 u"justifycenter"_ns,
4888 InternalCommandData(
4889 "cmd_align",
4890 Command::FormatJustifyCenter,
4891 ExecCommandParam::Ignore, // Will be set to "center"
4892 AlignCommand::GetInstance,
4893 CommandOnTextEditor::Disabled));
4894 sInternalCommandDataHashtable->InsertOrUpdate(
4895 u"justifyfull"_ns,
4896 InternalCommandData(
4897 "cmd_align",
4898 Command::FormatJustifyFull,
4899 ExecCommandParam::Ignore, // Will be set to "justify"
4900 AlignCommand::GetInstance,
4901 CommandOnTextEditor::Disabled));
4902 sInternalCommandDataHashtable->InsertOrUpdate(
4903 u"removeformat"_ns,
4904 InternalCommandData(
4905 "cmd_removeStyles",
4906 Command::FormatRemove,
4907 ExecCommandParam::Ignore,
4908 RemoveStylesCommand::GetInstance,
4909 CommandOnTextEditor::Disabled));
4910 sInternalCommandDataHashtable->InsertOrUpdate(
4911 u"unlink"_ns,
4912 InternalCommandData(
4913 "cmd_removeLinks",
4914 Command::FormatRemoveLink,
4915 ExecCommandParam::Ignore,
4916 StyleUpdatingCommand::GetInstance,
4917 CommandOnTextEditor::Disabled));
4918 sInternalCommandDataHashtable->InsertOrUpdate(
4919 u"insertorderedlist"_ns,
4920 InternalCommandData(
4921 "cmd_ol",
4922 Command::InsertOrderedList,
4923 ExecCommandParam::Ignore,
4924 ListCommand::GetInstance,
4925 CommandOnTextEditor::Disabled));
4926 sInternalCommandDataHashtable->InsertOrUpdate(
4927 u"insertunorderedlist"_ns,
4928 InternalCommandData(
4929 "cmd_ul",
4930 Command::InsertUnorderedList,
4931 ExecCommandParam::Ignore,
4932 ListCommand::GetInstance,
4933 CommandOnTextEditor::Disabled));
4934 sInternalCommandDataHashtable->InsertOrUpdate(
4935 u"insertparagraph"_ns,
4936 InternalCommandData(
4937 "cmd_insertParagraph",
4938 Command::InsertParagraph,
4939 ExecCommandParam::Ignore,
4940 InsertParagraphCommand::GetInstance,
4941 CommandOnTextEditor::Enabled));
4942 sInternalCommandDataHashtable->InsertOrUpdate(
4943 u"insertlinebreak"_ns,
4944 InternalCommandData(
4945 "cmd_insertLineBreak",
4946 Command::InsertLineBreak,
4947 ExecCommandParam::Ignore,
4948 InsertLineBreakCommand::GetInstance,
4949 CommandOnTextEditor::Enabled));
4950 sInternalCommandDataHashtable->InsertOrUpdate(
4951 u"formatblock"_ns,
4952 InternalCommandData(
4953 "cmd_paragraphState",
4954 Command::FormatBlock,
4955 ExecCommandParam::String,
4956 ParagraphStateCommand::GetInstance,
4957 CommandOnTextEditor::Disabled));
4958 sInternalCommandDataHashtable->InsertOrUpdate(
4959 u"styleWithCSS"_ns,
4960 InternalCommandData(
4961 "cmd_setDocumentUseCSS",
4962 Command::SetDocumentUseCSS,
4963 ExecCommandParam::Boolean,
4964 SetDocumentStateCommand::GetInstance,
4965 CommandOnTextEditor::FallThrough));
4966 sInternalCommandDataHashtable->InsertOrUpdate(
4967 u"usecss"_ns, // Legacy command
4968 InternalCommandData(
4969 "cmd_setDocumentUseCSS",
4970 Command::SetDocumentUseCSS,
4971 ExecCommandParam::InvertedBoolean,
4972 SetDocumentStateCommand::GetInstance,
4973 CommandOnTextEditor::FallThrough));
4974 sInternalCommandDataHashtable->InsertOrUpdate(
4975 u"contentReadOnly"_ns,
4976 InternalCommandData(
4977 "cmd_setDocumentReadOnly",
4978 Command::SetDocumentReadOnly,
4979 ExecCommandParam::Boolean,
4980 SetDocumentStateCommand::GetInstance,
4981 CommandOnTextEditor::Enabled));
4982 sInternalCommandDataHashtable->InsertOrUpdate(
4983 u"insertBrOnReturn"_ns,
4984 InternalCommandData(
4985 "cmd_insertBrOnReturn",
4986 Command::SetDocumentInsertBROnEnterKeyPress,
4987 ExecCommandParam::Boolean,
4988 SetDocumentStateCommand::GetInstance,
4989 CommandOnTextEditor::FallThrough));
4990 sInternalCommandDataHashtable->InsertOrUpdate(
4991 u"defaultParagraphSeparator"_ns,
4992 InternalCommandData(
4993 "cmd_defaultParagraphSeparator",
4994 Command::SetDocumentDefaultParagraphSeparator,
4995 ExecCommandParam::String,
4996 SetDocumentStateCommand::GetInstance,
4997 CommandOnTextEditor::FallThrough));
4998 sInternalCommandDataHashtable->InsertOrUpdate(
4999 u"enableObjectResizing"_ns,
5000 InternalCommandData(
5001 "cmd_enableObjectResizing",
5002 Command::ToggleObjectResizers,
5003 ExecCommandParam::Boolean,
5004 SetDocumentStateCommand::GetInstance,
5005 CommandOnTextEditor::FallThrough));
5006 sInternalCommandDataHashtable->InsertOrUpdate(
5007 u"enableInlineTableEditing"_ns,
5008 InternalCommandData(
5009 "cmd_enableInlineTableEditing",
5010 Command::ToggleInlineTableEditor,
5011 ExecCommandParam::Boolean,
5012 SetDocumentStateCommand::GetInstance,
5013 CommandOnTextEditor::FallThrough));
5014 sInternalCommandDataHashtable->InsertOrUpdate(
5015 u"enableAbsolutePositionEditing"_ns,
5016 InternalCommandData(
5017 "cmd_enableAbsolutePositionEditing",
5018 Command::ToggleAbsolutePositionEditor,
5019 ExecCommandParam::Boolean,
5020 SetDocumentStateCommand::GetInstance,
5021 CommandOnTextEditor::FallThrough));
5022 sInternalCommandDataHashtable->InsertOrUpdate(
5023 u"enableCompatibleJoinSplitDirection"_ns,
5024 InternalCommandData("cmd_enableCompatibleJoinSplitNodeDirection",
5025 Command::EnableCompatibleJoinSplitNodeDirection,
5026 ExecCommandParam::Boolean,
5027 SetDocumentStateCommand::GetInstance,
5028 CommandOnTextEditor::FallThrough));
5029 #if 0
5030 // with empty string
5031 sInternalCommandDataHashtable->InsertOrUpdate(
5032 u"justifynone"_ns,
5033 InternalCommandData(
5034 "cmd_align",
5035 Command::Undefined,
5036 ExecCommandParam::Ignore,
5037 nullptr,
5038 CommandOnTextEditor::Disabled)); // Not implemented yet.
5039 // REQUIRED SPECIAL REVIEW special review
5040 sInternalCommandDataHashtable->InsertOrUpdate(
5041 u"saveas"_ns,
5042 InternalCommandData(
5043 "cmd_saveAs",
5044 Command::Undefined,
5045 ExecCommandParam::Boolean,
5046 nullptr,
5047 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5048 // REQUIRED SPECIAL REVIEW special review
5049 sInternalCommandDataHashtable->InsertOrUpdate(
5050 u"print"_ns,
5051 InternalCommandData(
5052 "cmd_print",
5053 Command::Undefined,
5054 ExecCommandParam::Boolean,
5055 nullptr,
5056 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5057 #endif // #if 0
5058 // clang-format on
5061 Document::InternalCommandData Document::ConvertToInternalCommand(
5062 const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */,
5063 nsAString* aAdjustedValue /* = nullptr */) {
5064 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
5065 EnsureInitializeInternalCommandDataHashtable();
5066 InternalCommandData commandData;
5067 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
5068 return InternalCommandData();
5070 // Ignore if the command is disabled by a corresponding pref due to Gecko
5071 // specific.
5072 switch (commandData.mCommand) {
5073 case Command::SetDocumentReadOnly:
5074 if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() &&
5075 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) {
5076 return InternalCommandData();
5078 break;
5079 case Command::SetDocumentInsertBROnEnterKeyPress:
5080 MOZ_DIAGNOSTIC_ASSERT(
5081 aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn"));
5082 if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) {
5083 return InternalCommandData();
5085 break;
5086 default:
5087 break;
5089 if (!aAdjustedValue) {
5090 // No further work to do
5091 return commandData;
5093 switch (commandData.mExecCommandParam) {
5094 case ExecCommandParam::Ignore:
5095 // Just have to copy it, no checking
5096 switch (commandData.mCommand) {
5097 case Command::FormatJustifyLeft:
5098 aAdjustedValue->AssignLiteral("left");
5099 break;
5100 case Command::FormatJustifyRight:
5101 aAdjustedValue->AssignLiteral("right");
5102 break;
5103 case Command::FormatJustifyCenter:
5104 aAdjustedValue->AssignLiteral("center");
5105 break;
5106 case Command::FormatJustifyFull:
5107 aAdjustedValue->AssignLiteral("justify");
5108 break;
5109 default:
5110 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
5111 EditorCommandParamType::None);
5112 break;
5114 return commandData;
5116 case ExecCommandParam::Boolean:
5117 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5118 EditorCommandParamType::Bool));
5119 // If this is a boolean value and it's not explicitly false (e.g. no
5120 // value). We default to "true" (see bug 301490).
5121 if (!aValue.LowerCaseEqualsLiteral("false")) {
5122 aAdjustedValue->AssignLiteral("true");
5123 } else {
5124 aAdjustedValue->AssignLiteral("false");
5126 return commandData;
5128 case ExecCommandParam::InvertedBoolean:
5129 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5130 EditorCommandParamType::Bool));
5131 // For old backwards commands we invert the check.
5132 if (aValue.LowerCaseEqualsLiteral("false")) {
5133 aAdjustedValue->AssignLiteral("true");
5134 } else {
5135 aAdjustedValue->AssignLiteral("false");
5137 return commandData;
5139 case ExecCommandParam::String:
5140 MOZ_ASSERT(!!(
5141 EditorCommand::GetParamType(commandData.mCommand) &
5142 (EditorCommandParamType::String | EditorCommandParamType::CString)));
5143 switch (commandData.mCommand) {
5144 case Command::FormatBlock: {
5145 const char16_t* start = aValue.BeginReading();
5146 const char16_t* end = aValue.EndReading();
5147 if (start != end && *start == '<' && *(end - 1) == '>') {
5148 ++start;
5149 --end;
5151 // XXX Should we reorder this array with actual usage?
5152 static const nsStaticAtom* kFormattableBlockTags[] = {
5153 // clang-format off
5154 nsGkAtoms::address,
5155 nsGkAtoms::blockquote,
5156 nsGkAtoms::dd,
5157 nsGkAtoms::div,
5158 nsGkAtoms::dl,
5159 nsGkAtoms::dt,
5160 nsGkAtoms::h1,
5161 nsGkAtoms::h2,
5162 nsGkAtoms::h3,
5163 nsGkAtoms::h4,
5164 nsGkAtoms::h5,
5165 nsGkAtoms::h6,
5166 nsGkAtoms::p,
5167 nsGkAtoms::pre,
5168 // clang-format on
5170 nsAutoString value(nsDependentSubstring(start, end));
5171 ToLowerCase(value);
5172 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
5173 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
5174 if (valueAtom == kTag) {
5175 kTag->ToString(*aAdjustedValue);
5176 return commandData;
5179 return InternalCommandData();
5181 case Command::FormatFontSize: {
5182 // Per editing spec as of April 23, 2012, we need to reject the value
5183 // if it's not a valid floating-point number surrounded by optional
5184 // whitespace. Otherwise, we parse it as a legacy font size. For
5185 // now, we just parse as a legacy font size regardless (matching
5186 // WebKit) -- bug 747879.
5187 int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
5188 if (!size) {
5189 return InternalCommandData();
5191 MOZ_ASSERT(aAdjustedValue->IsEmpty());
5192 aAdjustedValue->AppendInt(size);
5193 return commandData;
5195 case Command::InsertImage:
5196 case Command::InsertLink:
5197 if (aValue.IsEmpty()) {
5198 // Invalid value, return false
5199 return InternalCommandData();
5201 aAdjustedValue->Assign(aValue);
5202 return commandData;
5203 case Command::SetDocumentDefaultParagraphSeparator:
5204 if (!aValue.LowerCaseEqualsLiteral("div") &&
5205 !aValue.LowerCaseEqualsLiteral("p") &&
5206 !aValue.LowerCaseEqualsLiteral("br")) {
5207 // Invalid value
5208 return InternalCommandData();
5210 aAdjustedValue->Assign(aValue);
5211 return commandData;
5212 default:
5213 aAdjustedValue->Assign(aValue);
5214 return commandData;
5217 default:
5218 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
5219 return InternalCommandData();
5223 Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
5224 Document& aDocument, const InternalCommandData& aCommandData)
5225 : mCommandData(aCommandData) {
5226 // We'll retrieve an editor with current DOM tree and layout information.
5227 // However, JS may have already hidden or remove exposed root content of
5228 // the editor. Therefore, we need the latest layout information here.
5229 aDocument.FlushPendingNotifications(FlushType::Layout);
5230 if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) {
5231 mDoNothing = true;
5232 return;
5235 if (nsPresContext* presContext = aDocument.GetPresContext()) {
5236 // Consider context of command handling which is automatically resolved
5237 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5238 // The order is:
5239 // 1. TextEditor if there is an active element and it has TextEditor like
5240 // <input type="text"> or <textarea>.
5241 // 2. HTMLEditor for the document, if there is.
5242 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
5243 if (aCommandData.IsCutOrCopyCommand()) {
5244 // Note that we used to use DocShell to handle `cut` and `copy` command
5245 // for dispatching corresponding events for making possible web apps to
5246 // implement their own editor without editable elements but supports
5247 // standard shortcut keys, etc. In this case, we prefer to use active
5248 // element's editor to keep same behavior.
5249 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5250 } else {
5251 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5252 mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
5253 if (!mActiveEditor) {
5254 mActiveEditor = mHTMLEditor;
5259 // Then, retrieve editor command class instance which should handle it
5260 // and can handle it now.
5261 if (!mActiveEditor) {
5262 // If the command is available without editor, we should redirect the
5263 // command to focused descendant with DocShell.
5264 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5265 mDoNothing = true;
5266 return;
5268 return;
5271 // Otherwise, we should use EditorCommand instance (which is singleton
5272 // instance) when it's enabled.
5273 mEditorCommand = aCommandData.mGetEditorCommandFunc
5274 ? aCommandData.mGetEditorCommandFunc()
5275 : nullptr;
5276 if (!mEditorCommand) {
5277 mDoNothing = true;
5278 mActiveEditor = nullptr;
5279 mHTMLEditor = nullptr;
5280 return;
5283 if (IsCommandEnabled()) {
5284 return;
5287 // If the EditorCommand instance is disabled, we should do nothing if
5288 // the command requires an editor.
5289 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5290 // Do nothing if editor specific commands is disabled (bug 760052).
5291 mDoNothing = true;
5292 return;
5295 // Otherwise, we should redirect it to focused descendant with DocShell.
5296 mEditorCommand = nullptr;
5297 mActiveEditor = nullptr;
5298 mHTMLEditor = nullptr;
5301 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
5302 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
5303 switch (mCommandData.mCommandOnTextEditor) {
5304 case CommandOnTextEditor::Enabled:
5305 return mActiveEditor;
5306 case CommandOnTextEditor::Disabled:
5307 return mActiveEditor && mActiveEditor->IsTextEditor()
5308 ? nullptr
5309 : mActiveEditor.get();
5310 case CommandOnTextEditor::FallThrough:
5311 return mHTMLEditor;
5313 return nullptr;
5316 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
5317 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
5318 // Make sure frames are up to date, since that can affect whether
5319 // we're editable.
5320 doc->FlushPendingNotifications(FlushType::Frames);
5322 EditorBase* targetEditor = GetTargetEditor();
5323 if (targetEditor && targetEditor->IsTextEditor()) {
5324 // FYI: When `disabled` attribute is set, `TextEditor` treats it as
5325 // "readonly" too.
5326 return !targetEditor->IsReadonly();
5328 return aDocument->IsEditingOn();
5331 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
5332 EditorBase* targetEditor = GetTargetEditor();
5333 if (!targetEditor) {
5334 return false;
5336 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5337 return MOZ_KnownLive(mEditorCommand)
5338 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
5341 nsresult Document::AutoEditorCommandTarget::DoCommand(
5342 nsIPrincipal* aPrincipal) const {
5343 MOZ_ASSERT(!DoNothing());
5344 MOZ_ASSERT(mEditorCommand);
5345 EditorBase* targetEditor = GetTargetEditor();
5346 if (!targetEditor) {
5347 return NS_SUCCESS_DOM_NO_OPERATION;
5349 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5350 return MOZ_KnownLive(mEditorCommand)
5351 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
5352 aPrincipal);
5355 template <typename ParamType>
5356 nsresult Document::AutoEditorCommandTarget::DoCommandParam(
5357 const ParamType& aParam, nsIPrincipal* aPrincipal) const {
5358 MOZ_ASSERT(!DoNothing());
5359 MOZ_ASSERT(mEditorCommand);
5360 EditorBase* targetEditor = GetTargetEditor();
5361 if (!targetEditor) {
5362 return NS_SUCCESS_DOM_NO_OPERATION;
5364 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5365 return MOZ_KnownLive(mEditorCommand)
5366 ->DoCommandParam(mCommandData.mCommand, aParam,
5367 MOZ_KnownLive(*targetEditor), aPrincipal);
5370 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
5371 nsCommandParams& aParams) const {
5372 MOZ_ASSERT(mEditorCommand);
5373 EditorBase* targetEditor = GetTargetEditor();
5374 if (!targetEditor) {
5375 return NS_OK;
5377 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5378 return MOZ_KnownLive(mEditorCommand)
5379 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
5380 MOZ_KnownLive(targetEditor), nullptr);
5383 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
5384 const nsAString& aValue,
5385 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
5386 // Only allow on HTML documents.
5387 if (!IsHTMLOrXHTML()) {
5388 aRv.ThrowInvalidStateError(
5389 "execCommand is only supported on HTML documents");
5390 return false;
5392 // Otherwise, don't throw exception for compatibility with Chrome.
5394 // if they are requesting UI from us, let's fail since we have no UI
5395 if (aShowUI) {
5396 return false;
5399 // If we're running an execCommand, we should just return false.
5400 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
5401 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
5402 mIsRunningExecCommand) {
5403 return false;
5406 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
5407 // this might add some ugly JS dependencies?
5409 nsAutoString adjustedValue;
5410 InternalCommandData commandData =
5411 ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
5412 switch (commandData.mCommand) {
5413 case Command::DoNothing:
5414 return false;
5415 case Command::SetDocumentReadOnly:
5416 SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly);
5417 break;
5418 case Command::EnableCompatibleJoinSplitNodeDirection:
5419 // We don't allow to take the legacy behavior back if the new one is
5420 // enabled by default.
5421 if (StaticPrefs::
5422 editor_join_split_direction_compatible_with_the_other_browsers() &&
5423 !adjustedValue.EqualsLiteral("true") &&
5424 !aSubjectPrincipal.IsSystemPrincipal()) {
5425 return false;
5427 break;
5428 default:
5429 break;
5432 // Do security check first.
5433 if (commandData.IsCutOrCopyCommand()) {
5434 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
5435 // We have rejected the event due to it not being performed in an
5436 // input-driven context therefore, we report the error to the console.
5437 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
5438 this, nsContentUtils::eDOM_PROPERTIES,
5439 "ExecCommandCutCopyDeniedNotInputDriven");
5440 return false;
5442 } else if (commandData.IsPasteCommand()) {
5443 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
5444 nsGkAtoms::clipboardRead)) {
5445 return false;
5449 AutoRunningExecCommandMarker markRunningExecCommand(*this);
5451 // Next, consider context of command handling which is automatically resolved
5452 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5453 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5454 if (commandData.IsAvailableOnlyWhenEditable() &&
5455 !editCommandTarget.IsEditable(this)) {
5456 return false;
5459 if (editCommandTarget.DoNothing()) {
5460 return false;
5463 // If we cannot use EditorCommand instance directly, we need to handle the
5464 // command with traditional path (i.e., with DocShell or nsCommandManager).
5465 if (!editCommandTarget.IsEditor()) {
5466 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
5468 // Special case clipboard write commands like Command::Cut and
5469 // Command::Copy. For such commands, we need the behaviour from
5470 // nsWindowRoot::GetControllers() which is to look at the focused element,
5471 // and defer to a focused textbox's controller. The code past taken by
5472 // other commands in ExecCommand() always uses the window directly, rather
5473 // than deferring to the textbox, which is desireable for most editor
5474 // commands, but not these commands (as those should allow copying out of
5475 // embedded editors). This behaviour is invoked if we call DoCommand()
5476 // directly on the docShell.
5477 // XXX This means that we allow web app to pick up selected content in
5478 // descendant document and write it into the clipboard when a
5479 // descendant document has focus. However, Chromium does not allow
5480 // this and this seems that it's not good behavior from point of view
5481 // of security. We should treat this issue in another bug.
5482 if (commandData.IsCutOrCopyCommand()) {
5483 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
5484 if (!docShell) {
5485 return false;
5487 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
5488 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
5489 return false;
5491 return NS_SUCCEEDED(rv);
5494 // Otherwise (currently, only clipboard read commands like Command::Paste),
5495 // we don't need to redirect the command to focused subdocument.
5496 // Therefore, we should handle it with nsCommandManager as used to be.
5497 // It may dispatch only preceding event of editing on non-editable element
5498 // to make web apps possible to handle standard shortcut key, etc in
5499 // their own editor.
5500 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5501 if (!commandManager) {
5502 return false;
5505 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5506 if (!window) {
5507 return false;
5510 // Return false for disabled commands (bug 760052)
5511 if (!commandManager->IsCommandEnabled(
5512 nsDependentCString(commandData.mXULCommandName), window)) {
5513 return false;
5516 MOZ_ASSERT(commandData.IsPasteCommand() ||
5517 commandData.mCommand == Command::SelectAll);
5518 nsresult rv =
5519 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
5520 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5523 // Now, our target is fixed to the editor. So, we can use EditorCommand
5524 // in EditorCommandTarget directly.
5526 EditorCommandParamType paramType =
5527 EditorCommand::GetParamType(commandData.mCommand);
5529 // If we don't have meaningful parameter or the EditorCommand does not
5530 // require additional parameter, we can use `DoCommand()`.
5531 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
5532 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
5533 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
5534 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5537 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
5538 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
5539 // a `bool` value.
5540 if (!!(paramType & EditorCommandParamType::Bool)) {
5541 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
5542 adjustedValue.EqualsLiteral("false"));
5543 nsresult rv = editCommandTarget.DoCommandParam(
5544 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
5545 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5548 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
5549 // in this case. However, `paramType` may contain both `String` and
5550 // `CString` but in such case, we should use `DoCommandParam()` which
5551 // takes `nsAString`. So, we should check whether `paramType` contains
5552 // `String` or not first.
5553 if (!!(paramType & EditorCommandParamType::String)) {
5554 MOZ_ASSERT(!adjustedValue.IsVoid());
5555 nsresult rv =
5556 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
5557 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5560 // Finally, `paramType` should have `CString`. We should use
5561 // `DoCommandParam()` which takes `nsACString`.
5562 if (!!(paramType & EditorCommandParamType::CString)) {
5563 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
5564 MOZ_ASSERT(!utf8Value.IsVoid());
5565 nsresult rv =
5566 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
5567 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5570 MOZ_ASSERT_UNREACHABLE(
5571 "Not yet implemented to handle new EditorCommandParamType");
5572 return false;
5575 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
5576 nsIPrincipal& aSubjectPrincipal,
5577 ErrorResult& aRv) {
5578 // Only allow on HTML documents.
5579 if (!IsHTMLOrXHTML()) {
5580 aRv.ThrowInvalidStateError(
5581 "queryCommandEnabled is only supported on HTML documents");
5582 return false;
5584 // Otherwise, don't throw exception for compatibility with Chrome.
5586 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5587 switch (commandData.mCommand) {
5588 case Command::DoNothing:
5589 return false;
5590 case Command::SetDocumentReadOnly:
5591 SetUseCounter(
5592 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5593 break;
5594 case Command::SetDocumentInsertBROnEnterKeyPress:
5595 SetUseCounter(
5596 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5597 break;
5598 default:
5599 break;
5602 // cut & copy are always allowed
5603 if (commandData.IsCutOrCopyCommand()) {
5604 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
5607 // Report false for restricted commands
5608 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
5609 return false;
5612 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5613 if (commandData.IsAvailableOnlyWhenEditable() &&
5614 !editCommandTarget.IsEditable(this)) {
5615 return false;
5618 if (editCommandTarget.IsEditor()) {
5619 return editCommandTarget.IsCommandEnabled();
5622 // get command manager and dispatch command to our window if it's acceptable
5623 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5624 if (!commandManager) {
5625 return false;
5628 nsPIDOMWindowOuter* window = GetWindow();
5629 if (!window) {
5630 return false;
5633 return commandManager->IsCommandEnabled(
5634 nsDependentCString(commandData.mXULCommandName), window);
5637 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5638 ErrorResult& aRv) {
5639 // Only allow on HTML documents.
5640 if (!IsHTMLOrXHTML()) {
5641 aRv.ThrowInvalidStateError(
5642 "queryCommandIndeterm is only supported on HTML documents");
5643 return false;
5645 // Otherwise, don't throw exception for compatibility with Chrome.
5647 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5648 if (commandData.mCommand == Command::DoNothing) {
5649 return false;
5652 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5653 if (commandData.IsAvailableOnlyWhenEditable() &&
5654 !editCommandTarget.IsEditable(this)) {
5655 return false;
5657 RefPtr<nsCommandParams> params = new nsCommandParams();
5658 if (editCommandTarget.IsEditor()) {
5659 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5660 return false;
5662 } else {
5663 // get command manager and dispatch command to our window if it's acceptable
5664 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5665 if (!commandManager) {
5666 return false;
5669 nsPIDOMWindowOuter* window = GetWindow();
5670 if (!window) {
5671 return false;
5674 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5675 window, params))) {
5676 return false;
5680 // If command does not have a state_mixed value, this call fails and sets
5681 // retval to false. This is fine -- we want to return false in that case
5682 // anyway (bug 738385), so we just don't throw regardless.
5683 return params->GetBool("state_mixed");
5686 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5687 ErrorResult& aRv) {
5688 // Only allow on HTML documents.
5689 if (!IsHTMLOrXHTML()) {
5690 aRv.ThrowInvalidStateError(
5691 "queryCommandState is only supported on HTML documents");
5692 return false;
5694 // Otherwise, don't throw exception for compatibility with Chrome.
5696 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5697 switch (commandData.mCommand) {
5698 case Command::DoNothing:
5699 return false;
5700 case Command::SetDocumentReadOnly:
5701 SetUseCounter(
5702 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5703 break;
5704 case Command::SetDocumentInsertBROnEnterKeyPress:
5705 SetUseCounter(
5706 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5707 break;
5708 default:
5709 break;
5712 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5713 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5714 // return false always.
5715 return false;
5718 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5719 if (commandData.IsAvailableOnlyWhenEditable() &&
5720 !editCommandTarget.IsEditable(this)) {
5721 return false;
5723 RefPtr<nsCommandParams> params = new nsCommandParams();
5724 if (editCommandTarget.IsEditor()) {
5725 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5726 return false;
5728 } else {
5729 // get command manager and dispatch command to our window if it's acceptable
5730 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5731 if (!commandManager) {
5732 return false;
5735 nsPIDOMWindowOuter* window = GetWindow();
5736 if (!window) {
5737 return false;
5740 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5741 window, params))) {
5742 return false;
5746 // handle alignment as a special case (possibly other commands too?)
5747 // Alignment is special because the external api is individual
5748 // commands but internally we use cmd_align with different
5749 // parameters. When getting the state of this command, we need to
5750 // return the boolean for this particular alignment rather than the
5751 // string of 'which alignment is this?'
5752 switch (commandData.mCommand) {
5753 case Command::FormatJustifyLeft: {
5754 nsAutoCString currentValue;
5755 nsresult rv = params->GetCString("state_attribute", currentValue);
5756 if (NS_FAILED(rv)) {
5757 return false;
5759 return currentValue.EqualsLiteral("left");
5761 case Command::FormatJustifyRight: {
5762 nsAutoCString currentValue;
5763 nsresult rv = params->GetCString("state_attribute", currentValue);
5764 if (NS_FAILED(rv)) {
5765 return false;
5767 return currentValue.EqualsLiteral("right");
5769 case Command::FormatJustifyCenter: {
5770 nsAutoCString currentValue;
5771 nsresult rv = params->GetCString("state_attribute", currentValue);
5772 if (NS_FAILED(rv)) {
5773 return false;
5775 return currentValue.EqualsLiteral("center");
5777 case Command::FormatJustifyFull: {
5778 nsAutoCString currentValue;
5779 nsresult rv = params->GetCString("state_attribute", currentValue);
5780 if (NS_FAILED(rv)) {
5781 return false;
5783 return currentValue.EqualsLiteral("justify");
5785 default:
5786 break;
5789 // If command does not have a state_all value, this call fails and sets
5790 // retval to false. This is fine -- we want to return false in that case
5791 // anyway (bug 738385), so we just succeed and return false regardless.
5792 return params->GetBool("state_all");
5795 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5796 CallerType aCallerType, ErrorResult& aRv) {
5797 // Only allow on HTML documents.
5798 if (!IsHTMLOrXHTML()) {
5799 aRv.ThrowInvalidStateError(
5800 "queryCommandSupported is only supported on HTML documents");
5801 return false;
5803 // Otherwise, don't throw exception for compatibility with Chrome.
5805 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5806 switch (commandData.mCommand) {
5807 case Command::DoNothing:
5808 return false;
5809 case Command::SetDocumentReadOnly:
5810 SetUseCounter(
5811 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5812 break;
5813 case Command::SetDocumentInsertBROnEnterKeyPress:
5814 SetUseCounter(
5815 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5816 break;
5817 default:
5818 break;
5821 // Gecko technically supports all the clipboard commands including
5822 // cut/copy/paste, but non-privileged content will be unable to call
5823 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5824 // may also be disallowed to be called from non-privileged content.
5825 // For that reason, we report the support status of corresponding
5826 // command accordingly.
5827 if (aCallerType != CallerType::System) {
5828 if (commandData.IsPasteCommand()) {
5829 return false;
5831 if (commandData.IsCutOrCopyCommand() &&
5832 !StaticPrefs::dom_allow_cut_copy()) {
5833 // XXXbz should we worry about correctly reporting "true" in the
5834 // "restricted, but we're an addon with clipboardWrite permissions" case?
5835 // See also nsContentUtils::IsCutCopyAllowed.
5836 return false;
5840 // aHTMLCommandName is supported if it can be converted to a Midas command
5841 return true;
5844 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5845 nsAString& aValue, ErrorResult& aRv) {
5846 aValue.Truncate();
5848 // Only allow on HTML documents.
5849 if (!IsHTMLOrXHTML()) {
5850 aRv.ThrowInvalidStateError(
5851 "queryCommandValue is only supported on HTML documents");
5852 return;
5854 // Otherwise, don't throw exception for compatibility with Chrome.
5856 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5857 switch (commandData.mCommand) {
5858 case Command::DoNothing:
5859 // Return empty string
5860 return;
5861 case Command::SetDocumentReadOnly:
5862 SetUseCounter(
5863 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5864 break;
5865 case Command::SetDocumentInsertBROnEnterKeyPress:
5866 SetUseCounter(
5867 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5868 break;
5869 default:
5870 break;
5873 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5874 if (commandData.IsAvailableOnlyWhenEditable() &&
5875 !editCommandTarget.IsEditable(this)) {
5876 return;
5878 RefPtr<nsCommandParams> params = new nsCommandParams();
5879 if (editCommandTarget.IsEditor()) {
5880 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5881 return;
5884 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5885 return;
5887 } else {
5888 // get command manager and dispatch command to our window if it's acceptable
5889 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5890 if (!commandManager) {
5891 return;
5894 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5895 if (!window) {
5896 return;
5899 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5900 return;
5903 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5904 window, params))) {
5905 return;
5909 // If command does not have a state_attribute value, this call fails, and
5910 // aValue will wind up being the empty string. This is fine -- we want to
5911 // return "" in that case anyway (bug 738385), so we just return NS_OK
5912 // regardless.
5913 nsAutoCString result;
5914 params->GetCString("state_attribute", result);
5915 CopyUTF8toUTF16(result, aValue);
5918 void Document::MaybeEditingStateChanged() {
5919 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
5920 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
5921 if (nsContentUtils::IsSafeToRunScript()) {
5922 EditingStateChanged();
5923 } else if (!mInDestructor) {
5924 nsContentUtils::AddScriptRunner(
5925 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
5926 &Document::MaybeEditingStateChanged));
5931 void Document::NotifyFetchOrXHRSuccess() {
5932 if (mShouldNotifyFetchSuccess) {
5933 nsContentUtils::DispatchEventOnlyToChrome(
5934 this, ToSupports(this), u"DOMDocFetchSuccess"_ns, CanBubble::eNo,
5935 Cancelable::eNo, /* DefaultAction */ nullptr);
5939 void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
5940 mShouldNotifyFetchSuccess = aShouldNotify;
5943 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
5944 mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
5947 void Document::TearingDownEditor() {
5948 if (IsEditingOn()) {
5949 mEditingState = EditingState::eTearingDown;
5950 if (IsHTMLOrXHTML()) {
5951 RemoveContentEditableStyleSheets();
5956 nsresult Document::TurnEditingOff() {
5957 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
5959 nsPIDOMWindowOuter* window = GetWindow();
5960 if (!window) {
5961 return NS_ERROR_FAILURE;
5964 nsIDocShell* docshell = window->GetDocShell();
5965 if (!docshell) {
5966 return NS_ERROR_FAILURE;
5969 bool isBeingDestroyed = false;
5970 docshell->IsBeingDestroyed(&isBeingDestroyed);
5971 if (isBeingDestroyed) {
5972 return NS_ERROR_FAILURE;
5975 nsCOMPtr<nsIEditingSession> editSession;
5976 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5977 NS_ENSURE_SUCCESS(rv, rv);
5979 // turn editing off
5980 rv = editSession->TearDownEditorOnWindow(window);
5981 NS_ENSURE_SUCCESS(rv, rv);
5983 mEditingState = EditingState::eOff;
5985 // Editor resets selection since it is being destroyed. But if focus is
5986 // still into editable control, we have to initialize selection again.
5987 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
5988 if (RefPtr<TextControlElement> textControlElement =
5989 TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
5990 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
5991 textEditor->ReinitializeSelection(*textControlElement);
5996 return NS_OK;
5999 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
6000 nsIDocShell* docShell = aWindow->GetDocShell();
6001 if (!docShell) {
6002 return false;
6004 return docShell->GetPresShell() != nullptr;
6007 HTMLEditor* Document::GetHTMLEditor() const {
6008 nsPIDOMWindowOuter* window = GetWindow();
6009 if (!window) {
6010 return nullptr;
6013 nsIDocShell* docshell = window->GetDocShell();
6014 if (!docshell) {
6015 return nullptr;
6018 return docshell->GetHTMLEditor();
6021 nsresult Document::EditingStateChanged() {
6022 if (mRemovedFromDocShell) {
6023 return NS_OK;
6026 if (mEditingState == EditingState::eSettingUp ||
6027 mEditingState == EditingState::eTearingDown) {
6028 // XXX We shouldn't recurse
6029 return NS_OK;
6032 const bool designMode = IsInDesignMode();
6033 EditingState newState =
6034 designMode ? EditingState::eDesignMode
6035 : (mContentEditableCount > 0 ? EditingState::eContentEditable
6036 : EditingState::eOff);
6037 if (mEditingState == newState) {
6038 // No changes in editing mode.
6039 return NS_OK;
6042 const bool thisDocumentHasFocus = ThisDocumentHasFocus();
6043 if (newState == EditingState::eOff) {
6044 // Editing is being turned off.
6045 nsAutoScriptBlocker scriptBlocker;
6046 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6047 NotifyEditableStateChange(*this);
6048 nsresult rv = TurnEditingOff();
6049 // If this document has focus and the editing state of this document
6050 // becomes "off", it means that HTMLEditor won't handle any inputs nor
6051 // modify the DOM tree. However, HTMLEditor may not receive `blur`
6052 // event for this state change since this may occur without focus change.
6053 // Therefore, let's notify HTMLEditor of this editing state change.
6054 // Note that even if focusedElement is an editable text control element,
6055 // it becomes not editable from HTMLEditor point of view since text
6056 // control elements are manged by TextEditor.
6057 RefPtr<Element> focusedElement =
6058 nsFocusManager::GetFocusManager()
6059 ? nsFocusManager::GetFocusManager()->GetFocusedElement()
6060 : nullptr;
6061 DebugOnly<nsresult> rvIgnored =
6062 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6063 htmlEditor, *this, focusedElement);
6064 NS_WARNING_ASSERTION(
6065 NS_SUCCEEDED(rvIgnored),
6066 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but "
6067 "ignored");
6068 return rv;
6071 // Flush out style changes on our _parent_ document, if any, so that
6072 // our check for a presshell won't get stale information.
6073 if (mParentDocument) {
6074 mParentDocument->FlushPendingNotifications(FlushType::Style);
6077 // get editing session, make sure this is a strong reference so the
6078 // window can't get deleted during the rest of this call.
6079 const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6080 if (!window) {
6081 return NS_ERROR_FAILURE;
6084 nsIDocShell* docshell = window->GetDocShell();
6085 if (!docshell) {
6086 return NS_ERROR_FAILURE;
6089 // FlushPendingNotifications might destroy our docshell.
6090 bool isBeingDestroyed = false;
6091 docshell->IsBeingDestroyed(&isBeingDestroyed);
6092 if (isBeingDestroyed) {
6093 return NS_ERROR_FAILURE;
6096 nsCOMPtr<nsIEditingSession> editSession;
6097 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6098 NS_ENSURE_SUCCESS(rv, rv);
6100 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
6101 if (htmlEditor) {
6102 // We might already have an editor if it was set up for mail, let's see
6103 // if this is actually the case.
6104 uint32_t flags = 0;
6105 htmlEditor->GetFlags(&flags);
6106 if (flags & nsIEditor::eEditorMailMask) {
6107 // We already have a mail editor, then we should not attempt to create
6108 // another one.
6109 return NS_OK;
6113 if (!HasPresShell(window)) {
6114 // We should not make the window editable or setup its editor.
6115 // It's probably style=display:none.
6116 return NS_OK;
6119 bool makeWindowEditable = mEditingState == EditingState::eOff;
6120 bool spellRecheckAll = false;
6121 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
6122 htmlEditor = nullptr;
6125 EditingState oldState = mEditingState;
6126 nsAutoEditingState push(this, EditingState::eSettingUp);
6128 RefPtr<PresShell> presShell = GetPresShell();
6129 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
6131 // If we're entering the design mode from non-editable state, put the
6132 // selection at the beginning of the document for compatibility reasons.
6133 bool collapseSelectionAtBeginningOfDocument =
6134 designMode && oldState == EditingState::eOff;
6135 // However, mEditingState may be eOff even if there is some
6136 // `contenteditable` area and selection has been initialized for it because
6137 // mEditingState for `contenteditable` may have been scheduled to modify
6138 // when safe. In such case, we should not reinitialize selection.
6139 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
6140 Selection* selection =
6141 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
6142 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
6143 if (selection && selection->RangeCount()) {
6144 // Perhaps, we don't need to check whether the selection is in
6145 // an editing host or not because all contents will be editable
6146 // in designMode. (And we don't want to make this code so complicated
6147 // because of legacy API.)
6148 collapseSelectionAtBeginningOfDocument = false;
6152 MOZ_ASSERT(mStyleSetFilled);
6154 // Before making this window editable, we need to modify UA style sheet
6155 // because new style may change whether focused element will be focusable
6156 // or not.
6157 if (IsHTMLOrXHTML()) {
6158 AddContentEditableStyleSheetsToStyleSet(designMode);
6161 if (designMode) {
6162 // designMode is being turned on (overrides contentEditable).
6163 spellRecheckAll = oldState == EditingState::eContentEditable;
6166 // Adjust focused element with new style but blur event shouldn't be fired
6167 // until mEditingState is modified with newState.
6168 nsAutoScriptBlocker scriptBlocker;
6169 if (designMode) {
6170 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6171 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6172 window, nsFocusManager::eOnlyCurrentWindow,
6173 getter_AddRefs(focusedWindow));
6174 if (focusedContent) {
6175 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
6176 bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable()
6177 : !focusedContent->IsFocusable();
6178 if (clearFocus) {
6179 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
6180 fm->ClearFocus(window);
6181 // If we need to dispatch blur event, we should put off after
6182 // modifying mEditingState since blur event handler may change
6183 // designMode state again.
6184 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
6190 if (makeWindowEditable) {
6191 // Editing is being turned on (through designMode or contentEditable)
6192 // Turn on editor.
6193 // XXX This can cause flushing which can change the editing state, so make
6194 // sure to avoid recursing.
6195 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
6196 NS_ENSURE_SUCCESS(rv, rv);
6199 // XXX Need to call TearDownEditorOnWindow for all failures.
6200 htmlEditor = docshell->GetHTMLEditor();
6201 if (!htmlEditor) {
6202 // Return NS_OK even though we've failed to create an editor here. This
6203 // is so that the setter of designMode on non-HTML documents does not
6204 // fail.
6205 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
6206 // would detect that we can't support the mimetype if appropriate and
6207 // would fall onto the eEditorErrorCantEditMimeType path.
6208 return NS_OK;
6211 if (collapseSelectionAtBeginningOfDocument) {
6212 htmlEditor->BeginningOfDocument();
6215 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6216 nsContentUtils::AddScriptBlocker();
6220 mEditingState = newState;
6221 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6222 nsContentUtils::RemoveScriptBlocker();
6223 // If mEditingState is overwritten by another call and already disabled
6224 // the editing, we shouldn't keep making window editable.
6225 if (mEditingState == EditingState::eOff) {
6226 return NS_OK;
6230 if (makeWindowEditable) {
6231 // TODO: We should do this earlier in this method.
6232 // Previously, we called `ExecCommand` with `insertBrOnReturn` command
6233 // whose argument is false here. Then, if it returns error, we
6234 // stopped making it editable. However, after bug 1697078 fixed,
6235 // `ExecCommand` returns error only when the document is not XHTML's
6236 // nor HTML's. Therefore, we use same error handling for now.
6237 if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
6238 // Editor setup failed. Editing is not on after all.
6239 // XXX Should we reset the editable flag on nodes?
6240 editSession->TearDownEditorOnWindow(window);
6241 mEditingState = EditingState::eOff;
6242 return NS_ERROR_DOM_INVALID_STATE_ERR;
6244 // Set the editor to not insert <br> elements on return when in <p> elements
6245 // by default.
6246 htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
6249 // Resync the editor's spellcheck state.
6250 if (spellRecheckAll) {
6251 nsCOMPtr<nsISelectionController> selectionController =
6252 htmlEditor->GetSelectionController();
6253 if (NS_WARN_IF(!selectionController)) {
6254 return NS_ERROR_FAILURE;
6257 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
6258 nsISelectionController::SELECTION_SPELLCHECK);
6259 if (spellCheckSelection) {
6260 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
6263 htmlEditor->SyncRealTimeSpell();
6265 MaybeDispatchCheckKeyPressEventModelEvent();
6267 // If this document keeps having focus and the HTMLEditor is in the design
6268 // mode, it may not receive `focus` event for this editing state change since
6269 // this may occur without a focus change. Therefore, let's notify HTMLEditor
6270 // of this editing state change.
6271 if (thisDocumentHasFocus && htmlEditor->IsInDesignMode() &&
6272 ThisDocumentHasFocus()) {
6273 DebugOnly<nsresult> rvIgnored =
6274 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, nullptr);
6275 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6276 "HTMLEditor::FocusedElementOrDocumentBecomesEditable()"
6277 " failed, but ignored");
6280 return NS_OK;
6283 // Helper class, used below in ChangeContentEditableCount().
6284 class DeferredContentEditableCountChangeEvent : public Runnable {
6285 public:
6286 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
6287 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
6288 mDoc(aDoc),
6289 mElement(aElement) {}
6291 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
6292 if (mElement && mElement->OwnerDoc() == mDoc) {
6293 RefPtr<Document> doc = std::move(mDoc);
6294 RefPtr<Element> element = std::move(mElement);
6295 doc->DeferredContentEditableCountChange(element);
6297 return NS_OK;
6300 private:
6301 RefPtr<Document> mDoc;
6302 RefPtr<Element> mElement;
6305 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
6306 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
6307 "Trying to decrement too much.");
6309 mContentEditableCount += aChange;
6311 nsContentUtils::AddScriptRunner(
6312 new DeferredContentEditableCountChangeEvent(this, aElement));
6315 void Document::DeferredContentEditableCountChange(Element* aElement) {
6316 const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
6317 const bool elementHasFocus =
6318 aElement && fm && fm->GetFocusedElement() == aElement;
6319 if (elementHasFocus) {
6320 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
6321 // When contenteditable of aElement is changed and HTMLEditor works with it
6322 // or needs to start working with it, HTMLEditor may not receive `focus`
6323 // event nor `blur` event because this may occur without a focus change.
6324 // Therefore, we need to notify HTMLEditor of this contenteditable attribute
6325 // change.
6326 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6327 if (aElement->HasFlag(NODE_IS_EDITABLE)) {
6328 if (htmlEditor) {
6329 DebugOnly<nsresult> rvIgnored =
6330 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this,
6331 aElement);
6332 NS_WARNING_ASSERTION(
6333 NS_SUCCEEDED(rvIgnored),
6334 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6335 "ignored");
6337 } else {
6338 DebugOnly<nsresult> rvIgnored =
6339 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6340 htmlEditor, *this, aElement);
6341 NS_WARNING_ASSERTION(
6342 NS_SUCCEEDED(rvIgnored),
6343 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, "
6344 "but ignored");
6348 if (mParser ||
6349 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
6350 return;
6353 EditingState oldState = mEditingState;
6355 nsresult rv = EditingStateChanged();
6356 NS_ENSURE_SUCCESS_VOID(rv);
6358 if (oldState == mEditingState &&
6359 mEditingState == EditingState::eContentEditable) {
6360 // We just changed the contentEditable state of a node, we need to reset
6361 // the spellchecking state of that node.
6362 if (aElement) {
6363 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6364 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
6365 rv = htmlEditor->GetInlineSpellChecker(false,
6366 getter_AddRefs(spellChecker));
6367 NS_ENSURE_SUCCESS_VOID(rv);
6369 if (spellChecker &&
6370 aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) {
6371 RefPtr<nsRange> range = nsRange::Create(aElement);
6372 IgnoredErrorResult res;
6373 range->SelectNode(*aElement, res);
6374 if (res.Failed()) {
6375 // The node might be detached from the document at this point,
6376 // which would cause this call to fail. In this case, we can
6377 // safely ignore the contenteditable count change.
6378 return;
6381 rv = spellChecker->SpellCheckRange(range);
6382 NS_ENSURE_SUCCESS_VOID(rv);
6388 // aElement causes creating new HTMLEditor and the element had and keep
6389 // having focus, the HTMLEditor won't receive `focus` event. Therefore, we
6390 // need to notify HTMLEditor of it becomes editable.
6391 if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) &&
6392 fm->GetFocusedElement() == aElement) {
6393 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6394 DebugOnly<nsresult> rvIgnored =
6395 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement);
6396 NS_WARNING_ASSERTION(
6397 NS_SUCCEEDED(rvIgnored),
6398 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6399 "ignored");
6404 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
6405 // Currently, we need to check only when we're becoming editable for
6406 // contenteditable.
6407 if (mEditingState != EditingState::eContentEditable) {
6408 return;
6411 if (mHasBeenEditable) {
6412 return;
6414 mHasBeenEditable = true;
6416 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
6417 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
6418 // with proper keypress event for the active web app.
6419 WidgetEvent checkEvent(true, eUnidentifiedEvent);
6420 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
6421 checkEvent.mFlags.mCancelable = false;
6422 checkEvent.mFlags.mBubbles = false;
6423 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
6424 // Post the event rather than dispatching it synchronously because we need
6425 // a call of SetKeyPressEventModel() before first key input. Therefore, we
6426 // can avoid paying unnecessary runtime cost for most web apps.
6427 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
6430 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
6431 PresShell* presShell = GetPresShell();
6432 if (!presShell) {
6433 return;
6435 presShell->SetKeyPressEventModel(aKeyPressEventModel);
6438 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
6440 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
6441 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
6442 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
6443 aFocusTime >= mLastFocusTime);
6444 mLastFocusTime = aFocusTime;
6447 void Document::GetReferrer(nsAString& aReferrer) const {
6448 aReferrer.Truncate();
6449 if (!mReferrerInfo) {
6450 return;
6453 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
6454 if (!referrer) {
6455 return;
6458 nsAutoCString uri;
6459 nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
6460 if (NS_WARN_IF(NS_FAILED(rv))) {
6461 return;
6464 CopyUTF8toUTF16(uri, aReferrer);
6467 void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
6468 aCookie.Truncate(); // clear current cookie in case service fails;
6469 // no cookie isn't an error condition.
6471 if (mDisableCookieAccess) {
6472 return;
6475 // If the document's sandboxed origin flag is set, then reading cookies
6476 // is prohibited.
6477 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6478 aRv.ThrowSecurityError(
6479 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6480 "flag.");
6481 return;
6484 StorageAccess storageAccess = CookieAllowedForDocument(this);
6485 if (storageAccess == StorageAccess::eDeny) {
6486 return;
6489 if (ShouldPartitionStorage(storageAccess) &&
6490 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6491 return;
6494 // If the document is a cookie-averse Document... return the empty string.
6495 if (IsCookieAverse()) {
6496 return;
6499 // not having a cookie service isn't an error
6500 nsCOMPtr<nsICookieService> service =
6501 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6502 if (service) {
6503 nsAutoCString cookie;
6504 service->GetCookieStringFromDocument(this, cookie);
6505 // CopyUTF8toUTF16 doesn't handle error
6506 // because it assumes that the input is valid.
6507 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
6511 void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
6512 if (mDisableCookieAccess) {
6513 return;
6516 // If the document's sandboxed origin flag is set, then setting cookies
6517 // is prohibited.
6518 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6519 aRv.ThrowSecurityError(
6520 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6521 "flag.");
6522 return;
6525 StorageAccess storageAccess = CookieAllowedForDocument(this);
6526 if (storageAccess == StorageAccess::eDeny) {
6527 return;
6530 if (ShouldPartitionStorage(storageAccess) &&
6531 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6532 return;
6535 // If the document is a cookie-averse Document... do nothing.
6536 if (IsCookieAverse()) {
6537 return;
6540 if (!mDocumentURI) {
6541 return;
6544 // not having a cookie service isn't an error
6545 nsCOMPtr<nsICookieService> service =
6546 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6547 if (!service) {
6548 return;
6551 NS_ConvertUTF16toUTF8 cookie(aCookie);
6552 nsresult rv = service->SetCookieStringFromDocument(this, cookie);
6554 // No warning messages here.
6555 if (NS_FAILED(rv)) {
6556 return;
6559 nsCOMPtr<nsIObserverService> observerService =
6560 mozilla::services::GetObserverService();
6561 if (observerService) {
6562 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
6563 nsString(aCookie).get());
6567 ReferrerPolicy Document::GetReferrerPolicy() const {
6568 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
6569 : ReferrerPolicy::_empty;
6572 void Document::GetAlinkColor(nsAString& aAlinkColor) {
6573 aAlinkColor.Truncate();
6575 HTMLBodyElement* body = GetBodyElement();
6576 if (body) {
6577 body->GetALink(aAlinkColor);
6581 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
6582 HTMLBodyElement* body = GetBodyElement();
6583 if (body) {
6584 body->SetALink(aAlinkColor);
6588 void Document::GetLinkColor(nsAString& aLinkColor) {
6589 aLinkColor.Truncate();
6591 HTMLBodyElement* body = GetBodyElement();
6592 if (body) {
6593 body->GetLink(aLinkColor);
6597 void Document::SetLinkColor(const nsAString& aLinkColor) {
6598 HTMLBodyElement* body = GetBodyElement();
6599 if (body) {
6600 body->SetLink(aLinkColor);
6604 void Document::GetVlinkColor(nsAString& aVlinkColor) {
6605 aVlinkColor.Truncate();
6607 HTMLBodyElement* body = GetBodyElement();
6608 if (body) {
6609 body->GetVLink(aVlinkColor);
6613 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
6614 HTMLBodyElement* body = GetBodyElement();
6615 if (body) {
6616 body->SetVLink(aVlinkColor);
6620 void Document::GetBgColor(nsAString& aBgColor) {
6621 aBgColor.Truncate();
6623 HTMLBodyElement* body = GetBodyElement();
6624 if (body) {
6625 body->GetBgColor(aBgColor);
6629 void Document::SetBgColor(const nsAString& aBgColor) {
6630 HTMLBodyElement* body = GetBodyElement();
6631 if (body) {
6632 body->SetBgColor(aBgColor);
6636 void Document::GetFgColor(nsAString& aFgColor) {
6637 aFgColor.Truncate();
6639 HTMLBodyElement* body = GetBodyElement();
6640 if (body) {
6641 body->GetText(aFgColor);
6645 void Document::SetFgColor(const nsAString& aFgColor) {
6646 HTMLBodyElement* body = GetBodyElement();
6647 if (body) {
6648 body->SetText(aFgColor);
6652 void Document::CaptureEvents() {
6653 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
6656 void Document::ReleaseEvents() {
6657 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
6660 HTMLAllCollection* Document::All() {
6661 if (!mAll) {
6662 mAll = new HTMLAllCollection(this);
6664 return mAll;
6667 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
6668 if (mIsSrcdocDocument) {
6669 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
6670 if (inStrmChan) {
6671 return inStrmChan->GetSrcdocData(aSrcdocData);
6674 aSrcdocData = VoidString();
6675 return NS_OK;
6678 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
6679 nsPIDOMWindowOuter* win = GetWindow();
6680 if (!win) {
6681 return nullptr;
6683 return WindowProxyHolder(win->GetBrowsingContext());
6686 nsIContent* Document::GetUnretargetedFocusedContent(
6687 IncludeChromeOnly aIncludeChromeOnly) const {
6688 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6689 if (!window) {
6690 return nullptr;
6692 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6693 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6694 window, nsFocusManager::eOnlyCurrentWindow,
6695 getter_AddRefs(focusedWindow));
6696 if (!focusedContent) {
6697 return nullptr;
6699 // be safe and make sure the element is from this document
6700 if (focusedContent->OwnerDoc() != this) {
6701 return nullptr;
6703 if (focusedContent->ChromeOnlyAccess() &&
6704 aIncludeChromeOnly == IncludeChromeOnly::No) {
6705 return focusedContent->FindFirstNonChromeOnlyAccessContent();
6707 return focusedContent;
6710 Element* Document::GetActiveElement() {
6711 // Get the focused element.
6712 Element* focusedElement = GetRetargetedFocusedElement();
6713 if (focusedElement) {
6714 return focusedElement;
6717 // No focused element anywhere in this document. Try to get the BODY.
6718 if (IsHTMLOrXHTML()) {
6719 Element* bodyElement = AsHTMLDocument()->GetBody();
6720 if (bodyElement) {
6721 return bodyElement;
6723 // Special case to handle the transition to XHTML from XUL documents
6724 // where there currently isn't a body element, but we need to match the
6725 // XUL behavior. This should be removed when bug 1540278 is resolved.
6726 if (nsContentUtils::IsChromeDoc(this)) {
6727 Element* docElement = GetDocumentElement();
6728 if (docElement && docElement->IsXULElement()) {
6729 return docElement;
6732 // Because of IE compatibility, return null when html document doesn't have
6733 // a body.
6734 return nullptr;
6737 // If we couldn't get a BODY, return the root element.
6738 return GetDocumentElement();
6741 Element* Document::GetCurrentScript() {
6742 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
6743 return el;
6746 void Document::ReleaseCapture() const {
6747 // only release the capture if the caller can access it. This prevents a
6748 // page from stopping a scrollbar grab for example.
6749 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
6750 if (node && nsContentUtils::CanCallerAccess(node)) {
6751 PresShell::ReleaseCapturingContent();
6755 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
6756 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
6757 return mChromeXHRDocBaseURI;
6760 return GetDocBaseURI();
6763 void Document::SetBaseURI(nsIURI* aURI) {
6764 if (!aURI && !mDocumentBaseURI) {
6765 return;
6768 // Don't do anything if the URI wasn't actually changed.
6769 if (aURI && mDocumentBaseURI) {
6770 bool equalBases = false;
6771 mDocumentBaseURI->Equals(aURI, &equalBases);
6772 if (equalBases) {
6773 return;
6777 mDocumentBaseURI = aURI;
6778 mCachedURLData = nullptr;
6779 RefreshLinkHrefs();
6782 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
6783 const nsAString& aURI) {
6784 RefPtr<nsIURI> resolvedURI;
6785 MOZ_TRY(
6786 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
6787 return OwningNonNull<nsIURI>(std::move(resolvedURI));
6790 nsIReferrerInfo* Document::ReferrerInfoForInternalCSSAndSVGResources() {
6791 if (!mCachedReferrerInfoForInternalCSSAndSVGResources) {
6792 mCachedReferrerInfoForInternalCSSAndSVGResources =
6793 ReferrerInfo::CreateForInternalCSSAndSVGResources(this);
6795 return mCachedReferrerInfoForInternalCSSAndSVGResources;
6798 URLExtraData* Document::DefaultStyleAttrURLData() {
6799 MOZ_ASSERT(NS_IsMainThread());
6800 if (!mCachedURLData) {
6801 mCachedURLData = new URLExtraData(
6802 GetDocBaseURI(), ReferrerInfoForInternalCSSAndSVGResources(),
6803 NodePrincipal());
6805 return mCachedURLData;
6808 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
6809 if (mCharacterSet != aEncoding) {
6810 mCharacterSet = aEncoding;
6811 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
6812 RecomputeLanguageFromCharset();
6814 if (nsPresContext* context = GetPresContext()) {
6815 context->DocumentCharSetChanged(aEncoding);
6820 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
6821 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
6824 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
6825 aData.Truncate();
6826 const HeaderData* data = mHeaderData.get();
6827 while (data) {
6828 if (data->mField == aHeaderField) {
6829 aData = data->mData;
6830 break;
6832 data = data->mNext.get();
6836 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
6837 if (!aHeaderField) {
6838 NS_ERROR("null headerField");
6839 return;
6842 if (!mHeaderData) {
6843 if (!aData.IsEmpty()) { // don't bother storing empty string
6844 mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
6846 } else {
6847 HeaderData* data = mHeaderData.get();
6848 UniquePtr<HeaderData>* lastPtr = &mHeaderData;
6849 bool found = false;
6850 do { // look for existing and replace
6851 if (data->mField == aHeaderField) {
6852 if (!aData.IsEmpty()) {
6853 data->mData.Assign(aData);
6854 } else { // don't store empty string
6855 // Note that data->mNext is moved to a temporary before the old value
6856 // of *lastPtr is deleted.
6857 *lastPtr = std::move(data->mNext);
6859 found = true;
6861 break;
6863 lastPtr = &data->mNext;
6864 data = lastPtr->get();
6865 } while (data);
6867 if (!aData.IsEmpty() && !found) {
6868 // didn't find, append
6869 *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
6873 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
6874 CopyUTF16toUTF8(aData, mContentLanguage);
6875 mMayNeedFontPrefsUpdate = true;
6876 if (auto* presContext = GetPresContext()) {
6877 presContext->ContentLanguageChanged();
6881 if (aHeaderField == nsGkAtoms::origin_trial) {
6882 mTrials.UpdateFromToken(aData, NodePrincipal());
6883 if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) {
6884 InitCOEP(mChannel);
6886 // If we still don't have a WindowContext, WindowContext::OnNewDocument
6887 // will take care of this.
6888 if (WindowContext* ctx = GetWindowContext()) {
6889 if (mEmbedderPolicy) {
6890 Unused << ctx->SetEmbedderPolicy(mEmbedderPolicy.value());
6896 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
6897 SetPreferredStyleSheetSet(aData);
6900 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
6901 // We get into this code before we have a script global yet, so get to our
6902 // container via mDocumentContainer.
6903 if (mDocumentContainer) {
6904 // Note: using mDocumentURI instead of mBaseURI here, for consistency
6905 // (used to just use the current URI of our webnavigation, but that
6906 // should really be the same thing). Note that this code can run
6907 // before the current URI of the webnavigation has been updated, so we
6908 // can't assert equality here.
6909 mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
6913 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
6914 mAllowDNSPrefetch) {
6915 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
6916 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
6919 if (aHeaderField == nsGkAtoms::handheldFriendly) {
6920 mViewportType = Unknown;
6924 void Document::SetEarlyHints(
6925 nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) {
6926 mEarlyHints = std::move(aEarlyHints);
6929 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
6930 NotNull<const Encoding*>& aEncoding,
6931 nsHtml5TreeOpExecutor* aExecutor) {
6932 if (aChannel) {
6933 nsAutoCString charsetVal;
6934 nsresult rv = aChannel->GetContentCharset(charsetVal);
6935 if (NS_SUCCEEDED(rv)) {
6936 const Encoding* preferred = Encoding::ForLabel(charsetVal);
6937 if (preferred) {
6938 if (aExecutor && preferred == REPLACEMENT_ENCODING) {
6939 aExecutor->ComplainAboutBogusProtocolCharset(this, false);
6941 aEncoding = WrapNotNull(preferred);
6942 aCharsetSource = kCharsetFromChannel;
6943 return;
6944 } else if (aExecutor && !charsetVal.IsEmpty()) {
6945 aExecutor->ComplainAboutBogusProtocolCharset(this, true);
6951 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
6952 #ifdef DEBUG
6953 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
6954 const Element* element = Element::FromNode(node);
6955 if (!element) {
6956 continue;
6958 MOZ_ASSERT(!element->HasServoData());
6960 #endif
6963 already_AddRefed<PresShell> Document::CreatePresShell(
6964 nsPresContext* aContext, nsViewManager* aViewManager) {
6965 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
6967 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
6969 AssertNoStaleServoDataIn(*this);
6971 RefPtr<PresShell> presShell = new PresShell(this);
6972 // Note: we don't hold a ref to the shell (it holds a ref to us)
6973 mPresShell = presShell;
6975 if (!mStyleSetFilled) {
6976 FillStyleSet();
6979 presShell->Init(aContext, aViewManager);
6980 if (RefPtr<class HighlightRegistry> highlightRegistry = mHighlightRegistry) {
6981 highlightRegistry->AddHighlightSelectionsToFrameSelection();
6983 // Gaining a shell causes changes in how media queries are evaluated, so
6984 // invalidate that.
6985 aContext->MediaFeatureValuesChanged(
6986 {MediaFeatureChange::kAllChanges},
6987 MediaFeatureChangePropagation::JustThisDocument);
6989 // Make sure to never paint if we belong to an invisible DocShell.
6990 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
6991 if (docShell && docShell->IsInvisible()) {
6992 presShell->SetNeverPainting(true);
6995 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
6996 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
6997 presShell.get(), docShell.get()));
6999 mExternalResourceMap.ShowViewers();
7001 UpdateFrameRequestCallbackSchedulingState();
7003 if (mDocumentL10n) {
7004 // In case we already accumulated mutations,
7005 // we'll trigger the refresh driver now.
7006 mDocumentL10n->OnCreatePresShell();
7009 if (HasAutoFocusCandidates()) {
7010 ScheduleFlushAutoFocusCandidates();
7012 // Now that we have a shell, we might have @font-face rules (the presence of a
7013 // shell may change which rules apply to us). We don't need to do anything
7014 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
7015 // is ready to update we'll flush the font set.
7016 MarkUserFontSetDirty();
7018 // Take the author style disabled state from the top browsing cvontext.
7019 // (PageStyleChild.sys.mjs ensures this is up to date.)
7020 if (BrowsingContext* bc = GetBrowsingContext()) {
7021 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
7024 return presShell.forget();
7027 void Document::UpdateFrameRequestCallbackSchedulingState(
7028 PresShell* aOldPresShell) {
7029 // If this condition changes to depend on some other variable, make sure to
7030 // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
7031 // that variable can change. Also consider if you should change
7032 // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
7033 // condition.
7034 bool shouldBeScheduled =
7035 WouldScheduleFrameRequestCallbacks() && !mFrameRequestManager.IsEmpty();
7036 if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
7037 // nothing to do
7038 return;
7041 PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
7042 MOZ_RELEASE_ASSERT(presShell);
7044 nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
7045 if (shouldBeScheduled) {
7046 rd->ScheduleFrameRequestCallbacks(this);
7047 } else {
7048 rd->RevokeFrameRequestCallbacks(this);
7051 mFrameRequestCallbacksScheduled = shouldBeScheduled;
7054 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
7055 MOZ_ASSERT(aCallbacks.IsEmpty());
7056 mFrameRequestManager.Take(aCallbacks);
7057 // No need to manually remove ourselves from the refresh driver; it will
7058 // handle that part. But we do have to update our state.
7059 mFrameRequestCallbacksScheduled = false;
7062 bool Document::ShouldThrottleFrameRequests() const {
7063 if (mStaticCloneCount > 0) {
7064 // Even if we're not visible, a static clone may be, so run at full speed.
7065 return false;
7068 if (Hidden()) {
7069 // We're not visible (probably in a background tab or the bf cache).
7070 return true;
7073 if (!mPresShell) {
7074 // Can't do anything smarter. We don't run frame requests in documents
7075 // without a pres shell anyways.
7076 return false;
7079 if (!mPresShell->IsActive()) {
7080 // The pres shell is not active (we're an invisible OOP iframe or such), so
7081 // throttle.
7082 return true;
7085 if (mPresShell->IsPaintingSuppressed()) {
7086 // Historically we have throttled frame requests until we've painted at
7087 // least once, so keep doing that.
7088 return true;
7091 Element* el = GetEmbedderElement();
7092 if (!el) {
7093 // If we're not in-process, our refresh driver is throttled separately (via
7094 // PresShell::SetIsActive, so not much more we can do here.
7095 return false;
7098 if (!StaticPrefs::layout_throttle_in_process_iframes()) {
7099 return false;
7102 // Note that because we have to scroll this document into view at least once
7103 // to unthrottle it, we will drop one requestAnimationFrame frame when a
7104 // document that previously wasn't visible scrolls into view. This is
7105 // acceptable / unlikely to be human-perceivable, though we could improve on
7106 // it if needed by adding an intersection margin or something of that sort.
7107 const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
7108 *el->OwnerDoc(), /* aRoot = */ nullptr, /* aMargin = */ nullptr);
7109 const IntersectionOutput output =
7110 DOMIntersectionObserver::Intersect(input, *el);
7111 return !output.Intersects();
7114 void Document::DeletePresShell() {
7115 mExternalResourceMap.HideViewers();
7116 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
7117 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
7118 presContext->RefreshDriver()->CancelFlushAutoFocus(this);
7121 // When our shell goes away, request that all our images be immediately
7122 // discarded, so we don't carry around decoded image data for a document we
7123 // no longer intend to paint.
7124 ImageTracker()->RequestDiscardAll();
7126 // Now that we no longer have a shell, we need to forget about any FontFace
7127 // objects for @font-face rules that came from the style set. There's no need
7128 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
7129 // no point on it.
7130 MarkUserFontSetDirty();
7132 if (mResizeObserverController) {
7133 mResizeObserverController->ShellDetachedFromDocument();
7136 if (IsEditingOn()) {
7137 TurnEditingOff();
7140 PresShell* oldPresShell = mPresShell;
7141 mPresShell = nullptr;
7142 UpdateFrameRequestCallbackSchedulingState(oldPresShell);
7144 ClearStaleServoData();
7145 AssertNoStaleServoDataIn(*this);
7147 mStyleSet->ShellDetachedFromDocument();
7148 mStyleSetFilled = false;
7149 mQuirkSheetAdded = false;
7150 mContentEditableSheetAdded = false;
7151 mDesignModeSheetAdded = false;
7154 void Document::DisallowBFCaching(uint32_t aStatus) {
7155 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
7156 if (!mBFCacheDisallowed) {
7157 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
7158 wgc->SendUpdateBFCacheStatus(aStatus, 0);
7161 mBFCacheDisallowed = true;
7164 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
7165 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
7167 if (mPresShell) {
7168 if (aEntry) {
7169 mPresShell->StopObservingRefreshDriver();
7170 } else if (mBFCacheEntry) {
7171 mPresShell->StartObservingRefreshDriver();
7174 mBFCacheEntry = aEntry;
7177 bool Document::RemoveFromBFCacheSync() {
7178 bool removed = false;
7179 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
7180 entry->RemoveFromBFCacheSync();
7181 removed = true;
7182 } else if (!IsCurrentActiveDocument()) {
7183 // In the old bfcache implementation while the new page is loading, but
7184 // before nsIContentViewer.show() has been called, the previous page doesn't
7185 // yet have nsIBFCacheEntry. However, the previous page isn't the current
7186 // active document anymore.
7187 DisallowBFCaching();
7188 removed = true;
7191 if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
7192 if (BrowsingContext* bc = GetBrowsingContext()) {
7193 if (bc->IsInBFCache()) {
7194 ContentChild* cc = ContentChild::GetSingleton();
7195 // IPC is asynchronous but the caller is supposed to check the return
7196 // value. The reason for 'Sync' in the method name is that the old
7197 // implementation may run scripts. There is Async variant in
7198 // the old session history implementation for the cases where
7199 // synchronous operation isn't safe.
7200 cc->SendRemoveFromBFCache(bc->Top());
7201 removed = true;
7205 return removed;
7208 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
7209 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
7211 NS_RELEASE(e->mKey);
7212 if (e->mSubDocument) {
7213 e->mSubDocument->SetParentDocument(nullptr);
7214 NS_RELEASE(e->mSubDocument);
7218 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
7219 SubDocMapEntry* e =
7220 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
7222 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
7223 NS_ADDREF(e->mKey);
7225 e->mSubDocument = nullptr;
7228 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
7229 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
7231 if (!aSubDoc) {
7232 // aSubDoc is nullptr, remove the mapping
7234 if (mSubDocuments) {
7235 mSubDocuments->Remove(aElement);
7237 } else {
7238 if (!mSubDocuments) {
7239 // Create a new hashtable
7241 static const PLDHashTableOps hash_table_ops = {
7242 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
7243 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
7245 mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
7248 // Add a mapping to the hash table
7249 auto entry =
7250 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
7252 if (!entry) {
7253 return NS_ERROR_OUT_OF_MEMORY;
7256 if (entry->mSubDocument) {
7257 entry->mSubDocument->SetParentDocument(nullptr);
7259 // Release the old sub document
7260 NS_RELEASE(entry->mSubDocument);
7263 entry->mSubDocument = aSubDoc;
7264 NS_ADDREF(entry->mSubDocument);
7266 aSubDoc->SetParentDocument(this);
7269 return NS_OK;
7272 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
7273 if (mSubDocuments && aContent->IsElement()) {
7274 auto entry = static_cast<SubDocMapEntry*>(
7275 mSubDocuments->Search(aContent->AsElement()));
7277 if (entry) {
7278 return entry->mSubDocument;
7282 return nullptr;
7285 Element* Document::GetEmbedderElement() const {
7286 // We check if we're the active document in our BrowsingContext
7287 // by comparing against its document, rather than checking if the
7288 // WindowContext is cached, since mWindow may be null when we're
7289 // called (such as in nsPresContext::Init).
7290 if (BrowsingContext* bc = GetBrowsingContext()) {
7291 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
7294 return nullptr;
7297 Element* Document::GetRootElement() const {
7298 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
7299 ? mCachedRootElement
7300 : GetRootElementInternal();
7303 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
7305 Element* Document::GetRootElementInternal() const {
7306 // We invoke GetRootElement() immediately before the servo traversal, so we
7307 // should always have a cache hit from Servo.
7308 MOZ_ASSERT(NS_IsMainThread());
7310 // Loop backwards because any non-elements, such as doctypes and PIs
7311 // are likely to appear before the root element.
7312 for (nsIContent* child = GetLastChild(); child;
7313 child = child->GetPreviousSibling()) {
7314 if (Element* element = Element::FromNode(child)) {
7315 const_cast<Document*>(this)->mCachedRootElement = element;
7316 return element;
7320 const_cast<Document*>(this)->mCachedRootElement = nullptr;
7321 return nullptr;
7324 void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
7325 bool aNotify, ErrorResult& aRv) {
7326 if (aKid->IsElement() && GetRootElement()) {
7327 NS_WARNING("Inserting root element when we already have one");
7328 aRv.ThrowHierarchyRequestError("There is already a root element.");
7329 return;
7332 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
7335 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
7336 Maybe<mozAutoDocUpdate> updateBatch;
7337 if (aKid->IsElement()) {
7338 updateBatch.emplace(this, aNotify);
7339 // Destroy the link map up front before we mess with the child list.
7340 DestroyElementMaps();
7343 // Preemptively clear mCachedRootElement, since we may be about to remove it
7344 // from our child list, and we don't want to return this maybe-obsolete value
7345 // from any GetRootElement() calls that happen inside of RemoveChildNode().
7346 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
7347 // GetRootElement() calls until after it's removed the child from mChildren.
7348 // Any call before that point would restore this soon-to-be-obsolete cached
7349 // answer, and our clearing here would be fruitless.)
7350 mCachedRootElement = nullptr;
7351 nsINode::RemoveChildNode(aKid, aNotify);
7352 MOZ_ASSERT(mCachedRootElement != aKid,
7353 "Stale pointer in mCachedRootElement, after we tried to clear it "
7354 "(maybe somebody called GetRootElement() too early?)");
7357 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
7358 if (mStyleSetFilled) {
7359 mStyleSet->AddDocStyleSheet(aSheet);
7360 ApplicableStylesChanged();
7364 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
7365 mStyleSet->RecordShadowStyleChange(aShadowRoot);
7366 ApplicableStylesChanged();
7369 void Document::ApplicableStylesChanged() {
7370 // TODO(emilio): if we decide to resolve style in display: none iframes, then
7371 // we need to always track style changes and remove the mStyleSetFilled.
7372 if (!mStyleSetFilled) {
7373 return;
7376 MarkUserFontSetDirty();
7377 PresShell* ps = GetPresShell();
7378 if (!ps) {
7379 return;
7382 ps->EnsureStyleFlush();
7383 nsPresContext* pc = ps->GetPresContext();
7384 if (!pc) {
7385 return;
7388 pc->MarkCounterStylesDirty();
7389 pc->MarkFontFeatureValuesDirty();
7390 pc->MarkFontPaletteValuesDirty();
7391 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
7394 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
7395 if (mStyleSetFilled) {
7396 mStyleSet->RemoveStyleSheet(aSheet);
7397 ApplicableStylesChanged();
7401 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
7402 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
7404 if (aSheet.IsApplicable()) {
7405 AddStyleSheetToStyleSets(aSheet);
7409 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
7410 const bool applicable = aSheet.IsApplicable();
7411 // If we're actually in the document style sheet list
7412 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
7413 if (applicable) {
7414 AddStyleSheetToStyleSets(aSheet);
7415 } else {
7416 RemoveStyleSheetFromStyleSets(aSheet);
7420 PostStyleSheetApplicableStateChangeEvent(aSheet);
7422 if (!mSSApplicableStateNotificationPending) {
7423 MOZ_RELEASE_ASSERT(NS_IsMainThread());
7424 nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(
7425 "Document::NotifyStyleSheetApplicableStateChanged", this,
7426 &Document::NotifyStyleSheetApplicableStateChanged);
7427 mSSApplicableStateNotificationPending =
7428 NS_SUCCEEDED(Dispatch(TaskCategory::Other, notification.forget()));
7432 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
7433 if (!StyleSheetChangeEventsEnabled()) {
7434 return;
7437 StyleSheetApplicableStateChangeEventInit init;
7438 init.mBubbles = true;
7439 init.mCancelable = true;
7440 init.mStylesheet = &aSheet;
7441 init.mApplicable = aSheet.IsApplicable();
7443 RefPtr<StyleSheetApplicableStateChangeEvent> event =
7444 StyleSheetApplicableStateChangeEvent::Constructor(
7445 this, u"StyleSheetApplicableStateChanged"_ns, init);
7446 event->SetTrusted(true);
7447 event->SetTarget(this);
7448 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7449 new AsyncEventDispatcher(this, event);
7450 asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
7451 asyncDispatcher->PostDOMEvent();
7454 void Document::NotifyStyleSheetApplicableStateChanged() {
7455 mSSApplicableStateNotificationPending = false;
7456 nsCOMPtr<nsIObserverService> observerService =
7457 mozilla::services::GetObserverService();
7458 if (observerService) {
7459 observerService->NotifyObservers(
7460 ToSupports(this), "style-sheet-applicable-state-changed", nullptr);
7464 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
7465 nsIURI* aSheetURI) {
7466 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
7467 bool bEqual;
7468 nsIURI* uri = aSheets[i]->GetSheetURI();
7470 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
7471 return i;
7474 return -1;
7477 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
7478 nsIURI* aSheetURI) {
7479 MOZ_ASSERT(aSheetURI, "null arg");
7481 // Checking if we have loaded this one already.
7482 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
7483 return NS_ERROR_INVALID_ARG;
7485 // Loading the sheet sync.
7486 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
7488 css::SheetParsingMode parsingMode;
7489 switch (aType) {
7490 case Document::eAgentSheet:
7491 parsingMode = css::eAgentSheetFeatures;
7492 break;
7494 case Document::eUserSheet:
7495 parsingMode = css::eUserSheetFeatures;
7496 break;
7498 case Document::eAuthorSheet:
7499 parsingMode = css::eAuthorSheetFeatures;
7500 break;
7502 default:
7503 MOZ_CRASH("impossible value for aType");
7506 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
7507 css::Loader::UseSystemPrincipal::Yes);
7508 if (result.isErr()) {
7509 return result.unwrapErr();
7512 RefPtr<StyleSheet> sheet = result.unwrap();
7514 sheet->SetAssociatedDocumentOrShadowRoot(this);
7515 MOZ_ASSERT(sheet->IsApplicable());
7517 return AddAdditionalStyleSheet(aType, sheet);
7520 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
7521 StyleSheet* aSheet) {
7522 if (mAdditionalSheets[aType].Contains(aSheet)) {
7523 return NS_ERROR_INVALID_ARG;
7526 if (!aSheet->IsApplicable()) {
7527 return NS_ERROR_INVALID_ARG;
7530 mAdditionalSheets[aType].AppendElement(aSheet);
7532 if (mStyleSetFilled) {
7533 mStyleSet->AppendStyleSheet(*aSheet);
7534 ApplicableStylesChanged();
7536 return NS_OK;
7539 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
7540 nsIURI* aSheetURI) {
7541 MOZ_ASSERT(aSheetURI);
7543 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
7545 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
7546 if (i >= 0) {
7547 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
7548 sheets.RemoveElementAt(i);
7550 if (!mIsGoingAway) {
7551 MOZ_ASSERT(sheetRef->IsApplicable());
7552 if (mStyleSetFilled) {
7553 mStyleSet->RemoveStyleSheet(*sheetRef);
7554 ApplicableStylesChanged();
7557 sheetRef->ClearAssociatedDocumentOrShadowRoot();
7561 nsIGlobalObject* Document::GetScopeObject() const {
7562 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
7563 return scope;
7566 DocGroup* Document::GetDocGroupOrCreate() {
7567 if (!mDocGroup && GetBrowsingContext()) {
7568 BrowsingContextGroup* group = GetBrowsingContext()->Group();
7569 MOZ_ASSERT(group);
7571 nsAutoCString docGroupKey;
7572 nsresult rv = mozilla::dom::DocGroup::GetKey(
7573 NodePrincipal(), group->IsPotentiallyCrossOriginIsolated(),
7574 docGroupKey);
7575 if (NS_SUCCEEDED(rv)) {
7576 mDocGroup = group->AddDocument(docGroupKey, this);
7579 return mDocGroup;
7582 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
7583 mScopeObject = do_GetWeakReference(aGlobal);
7584 if (aGlobal) {
7585 mHasHadScriptHandlingObject = true;
7587 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
7588 if (!window) {
7589 return;
7591 BrowsingContextGroup* browsingContextGroup =
7592 window->GetBrowsingContextGroup();
7594 // We should already have the principal, and now that we have been added
7595 // to a window, we should be able to join a DocGroup!
7596 nsAutoCString docGroupKey;
7597 nsresult rv = mozilla::dom::DocGroup::GetKey(
7598 NodePrincipal(),
7599 browsingContextGroup->IsPotentiallyCrossOriginIsolated(), docGroupKey);
7600 if (mDocGroup) {
7601 if (NS_SUCCEEDED(rv)) {
7602 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
7604 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
7605 browsingContextGroup);
7606 } else {
7607 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
7609 MOZ_ASSERT(mDocGroup);
7612 MOZ_ASSERT_IF(
7613 mNodeInfoManager->GetArenaAllocator(),
7614 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
7618 bool Document::ContainsEMEContent() {
7619 nsPIDOMWindowInner* win = GetInnerWindow();
7620 // Note this case is different from checking just media elements in that
7621 // it covers when we've created MediaKeys but not associated them with a
7622 // media element.
7623 return win && win->HasActiveMediaKeysInstance();
7626 bool Document::ContainsMSEContent() {
7627 bool containsMSE = false;
7629 auto check = [&containsMSE](nsISupports* aSupports) {
7630 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7631 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7632 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
7633 if (ms) {
7634 containsMSE = true;
7639 EnumerateActivityObservers(check);
7640 return containsMSE;
7643 static void NotifyActivityChangedCallback(nsISupports* aSupports) {
7644 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7645 if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7646 mediaElem->NotifyOwnerDocumentActivityChanged();
7648 nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
7649 do_QueryInterface(aSupports));
7650 if (objectLoadingContent) {
7651 nsObjectLoadingContent* olc =
7652 static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
7653 olc->NotifyOwnerDocumentActivityChanged();
7655 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
7656 do_QueryInterface(aSupports));
7657 if (objectDocumentActivity) {
7658 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
7659 } else {
7660 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
7661 do_QueryInterface(aSupports));
7662 if (imageLoadingContent) {
7663 auto ilc = static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
7664 ilc->NotifyOwnerDocumentActivityChanged();
7669 void Document::NotifyActivityChanged() {
7670 EnumerateActivityObservers(NotifyActivityChangedCallback);
7673 bool Document::IsTopLevelWindowInactive() const {
7674 if (BrowsingContext* bc = GetBrowsingContext()) {
7675 return !bc->GetIsActiveBrowserWindow();
7678 return false;
7681 void Document::SetContainer(nsDocShell* aContainer) {
7682 if (aContainer) {
7683 mDocumentContainer = aContainer;
7684 } else {
7685 mDocumentContainer = WeakPtr<nsDocShell>();
7688 mInChromeDocShell =
7689 aContainer && aContainer->GetBrowsingContext()->IsChrome();
7691 NotifyActivityChanged();
7693 // IsTopLevelWindowInactive depends on the docshell, so
7694 // update the cached value now that it's available.
7695 UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false);
7696 if (!aContainer) {
7697 return;
7700 BrowsingContext* context = aContainer->GetBrowsingContext();
7701 MOZ_ASSERT_IF(context && mDocGroup,
7702 context->Group() == mDocGroup->GetBrowsingContextGroup());
7703 if (context && context->IsContent()) {
7704 SetIsTopLevelContentDocument(context->IsTopContent());
7705 SetIsContentDocument(true);
7706 } else {
7707 SetIsTopLevelContentDocument(false);
7708 SetIsContentDocument(false);
7712 nsISupports* Document::GetContainer() const {
7713 return static_cast<nsIDocShell*>(mDocumentContainer);
7716 void Document::SetScriptGlobalObject(
7717 nsIScriptGlobalObject* aScriptGlobalObject) {
7718 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
7719 mAnimationController->IsPausedByType(
7720 SMILTimeContainer::PAUSE_PAGEHIDE |
7721 SMILTimeContainer::PAUSE_BEGIN),
7722 "Clearing window pointer while animations are unpaused");
7724 if (mScriptGlobalObject && !aScriptGlobalObject) {
7725 // We're detaching from the window. We need to grab a pointer to
7726 // our layout history state now.
7727 mLayoutHistoryState = GetLayoutHistoryState();
7729 // Also make sure to remove our onload blocker now if we haven't done it yet
7730 if (mOnloadBlockCount != 0) {
7731 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
7732 if (loadGroup) {
7733 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
7737 if (GetController().isSome()) {
7738 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
7739 loader->ClearCacheForControlledDocument(this);
7742 // We may become controlled again if this document comes back out
7743 // of bfcache. Clear our state to allow that to happen. Only
7744 // clear this flag if we are actually controlled, though, so pages
7745 // that were force reloaded don't become controlled when they
7746 // come out of bfcache.
7747 mMaybeServiceWorkerControlled = false;
7750 if (GetWindowContext()) {
7751 // The document is about to lose its window, so this is a good time to
7752 // send our page use counters, while we still have access to our
7753 // WindowContext.
7755 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
7756 // catches some cases of documents losing their window that don't
7757 // get in here.)
7758 SendPageUseCounters();
7762 // BlockOnload() might be called before mScriptGlobalObject is set.
7763 // We may need to add the blocker once mScriptGlobalObject is set.
7764 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
7766 mScriptGlobalObject = aScriptGlobalObject;
7768 if (needOnloadBlocker) {
7769 EnsureOnloadBlocker();
7772 UpdateFrameRequestCallbackSchedulingState();
7774 if (aScriptGlobalObject) {
7775 // Go back to using the docshell for the layout history state
7776 mLayoutHistoryState = nullptr;
7777 SetScopeObject(aScriptGlobalObject);
7778 mHasHadDefaultView = true;
7780 if (mAllowDNSPrefetch) {
7781 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7782 if (docShell) {
7783 #ifdef DEBUG
7784 nsCOMPtr<nsIWebNavigation> webNav =
7785 do_GetInterface(aScriptGlobalObject);
7786 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
7787 "Unexpected container or script global?");
7788 #endif
7789 bool allowDNSPrefetch;
7790 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
7791 mAllowDNSPrefetch = allowDNSPrefetch;
7795 // If we are set in a window that is already focused we should remember this
7796 // as the time the document gained focus.
7797 if (HasFocus(IgnoreErrors())) {
7798 SetLastFocusTime(TimeStamp::Now());
7802 // Remember the pointer to our window (or lack there of), to avoid
7803 // having to QI every time it's asked for.
7804 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
7805 mWindow = window;
7807 // Now that we know what our window is, we can flush the CSP errors to the
7808 // Web Console. We are flushing all messages that occurred and were stored in
7809 // the queue prior to this point.
7810 if (mCSP) {
7811 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
7814 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
7815 do_QueryInterface(GetChannel());
7816 if (internalChannel) {
7817 nsCOMArray<nsISecurityConsoleMessage> messages;
7818 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
7819 MOZ_ASSERT(NS_SUCCEEDED(rv));
7820 SendToConsole(messages);
7823 // Set our visibility state, but do not fire the event. This is correct
7824 // because either we're coming out of bfcache (in which case IsVisible() will
7825 // still test false at this point and no state change will happen) or we're
7826 // doing the initial document load and don't want to fire the event for this
7827 // change.
7829 // When the visibility is changed, notify it to observers.
7830 // Some observers need the notification, for example HTMLMediaElement uses
7831 // it to update internal media resource allocation.
7832 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
7833 // creation are already done before Document::SetScriptGlobalObject() call.
7834 // MediaDecoder decides whether starting decoding is decided based on
7835 // document's visibility. When the MediaDecoder is created,
7836 // Document::SetScriptGlobalObject() is not yet called and document is
7837 // hidden state. Therefore the MediaDecoder decides that decoding is
7838 // not yet necessary. But soon after Document::SetScriptGlobalObject()
7839 // call, the document becomes not hidden. At the time, MediaDecoder needs
7840 // to know it and needs to start updating decoding.
7841 UpdateVisibilityState(DispatchVisibilityChange::No);
7843 // The global in the template contents owner document should be the same.
7844 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
7845 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
7848 // Tell the script loader about the new global object.
7849 if (mScriptLoader && !IsTemplateContentsOwner()) {
7850 mScriptLoader->SetGlobalObject(mScriptGlobalObject);
7853 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
7854 mScriptGlobalObject && GetChannel()) {
7855 // If we are shift-reloaded, don't associate with a ServiceWorker.
7856 if (mDocumentContainer->IsForceReloading()) {
7857 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
7858 return;
7861 mMaybeServiceWorkerControlled = true;
7865 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
7866 MOZ_ASSERT(!mScriptGlobalObject,
7867 "Do not call this when mScriptGlobalObject is set!");
7868 if (mHasHadDefaultView) {
7869 return nullptr;
7872 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
7873 do_QueryReferent(mScopeObject);
7874 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
7875 if (win) {
7876 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
7877 if (!outer || outer->GetCurrentInnerWindow() != win) {
7878 NS_WARNING("Wrong inner/outer window combination!");
7879 return nullptr;
7882 return scriptHandlingObject;
7884 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
7885 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
7886 "Wrong script object!");
7887 if (aScriptObject) {
7888 SetScopeObject(aScriptObject);
7889 mHasHadDefaultView = false;
7893 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
7894 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
7895 // Let's use mScriptGlobalObject. Even if the document is already removed from
7896 // the docshell, the outer window might be still obtainable from the it.
7897 nsCOMPtr<nsPIDOMWindowOuter> win;
7898 if (mRemovedFromDocShell) {
7899 // The docshell returns the outer window we are done.
7900 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
7901 if (kungFuDeathGrip) {
7902 win = kungFuDeathGrip->GetWindow();
7904 } else {
7905 if (nsCOMPtr<nsPIDOMWindowInner> inner =
7906 do_QueryInterface(mScriptGlobalObject)) {
7907 // mScriptGlobalObject is always the inner window, let's get the outer.
7908 win = inner->GetOuterWindow();
7912 return win;
7915 bool Document::InternalAllowXULXBL() {
7916 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
7917 mAllowXULXBL = eTriTrue;
7918 return true;
7921 mAllowXULXBL = eTriFalse;
7922 return false;
7925 // Note: We don't hold a reference to the document observer; we assume
7926 // that it has a live reference to the document.
7927 void Document::AddObserver(nsIDocumentObserver* aObserver) {
7928 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
7929 "Observer already in the list");
7930 mObservers.AppendElement(aObserver);
7931 AddMutationObserver(aObserver);
7934 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
7935 // If we're in the process of destroying the document (and we're
7936 // informing the observers of the destruction), don't remove the
7937 // observers from the list. This is not a big deal, since we
7938 // don't hold a live reference to the observers.
7939 if (!mInDestructor) {
7940 RemoveMutationObserver(aObserver);
7941 return mObservers.RemoveElement(aObserver);
7944 return mObservers.Contains(aObserver);
7947 void Document::BeginUpdate() {
7948 ++mUpdateNestLevel;
7949 nsContentUtils::AddScriptBlocker();
7950 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
7953 void Document::EndUpdate() {
7954 const bool reset = !mPendingMaybeEditingStateChanged;
7955 mPendingMaybeEditingStateChanged = true;
7957 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
7959 --mUpdateNestLevel;
7961 nsContentUtils::RemoveScriptBlocker();
7963 if (mXULBroadcastManager) {
7964 mXULBroadcastManager->MaybeBroadcast();
7967 if (reset) {
7968 mPendingMaybeEditingStateChanged = false;
7970 MaybeEditingStateChanged();
7973 void Document::BeginLoad() {
7974 if (IsEditingOn()) {
7975 // Reset() blows away all event listeners in the document, and our
7976 // editor relies heavily on those. Midas is turned on, to make it
7977 // work, re-initialize it to give it a chance to add its event
7978 // listeners again.
7980 TurnEditingOff();
7981 EditingStateChanged();
7984 MOZ_ASSERT(!mDidCallBeginLoad);
7985 mDidCallBeginLoad = true;
7987 // Block onload here to prevent having to deal with blocking and
7988 // unblocking it while we know the document is loading.
7989 BlockOnload();
7990 mDidFireDOMContentLoaded = false;
7991 BlockDOMContentLoaded();
7993 if (mScriptLoader) {
7994 mScriptLoader->BeginDeferringScripts();
7997 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
8000 void Document::MozSetImageElement(const nsAString& aImageElementId,
8001 Element* aElement) {
8002 if (aImageElementId.IsEmpty()) return;
8004 // Hold a script blocker while calling SetImageElement since that can call
8005 // out to id-observers
8006 nsAutoScriptBlocker scriptBlocker;
8008 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
8009 if (entry) {
8010 entry->SetImageElement(aElement);
8011 if (entry->IsEmpty()) {
8012 mIdentifierMap.RemoveEntry(entry);
8017 void Document::DispatchContentLoadedEvents() {
8018 // If you add early returns from this method, make sure you're
8019 // calling UnblockOnload properly.
8021 // Unpin references to preloaded images
8022 mPreloadingImages.Clear();
8024 // DOM manipulation after content loaded should not care if the element
8025 // came from the preloader.
8026 mPreloadedPreconnects.Clear();
8028 if (mTiming) {
8029 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
8032 // Dispatch observer notification to notify observers document is interactive.
8033 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
8034 if (os) {
8035 nsIPrincipal* principal = NodePrincipal();
8036 os->NotifyObservers(ToSupports(this),
8037 principal->IsSystemPrincipal()
8038 ? "chrome-document-interactive"
8039 : "content-document-interactive",
8040 nullptr);
8043 // Fire a DOM event notifying listeners that this document has been
8044 // loaded (excluding images and other loads initiated by this
8045 // document).
8046 nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
8047 u"DOMContentLoaded"_ns, CanBubble::eYes,
8048 Cancelable::eNo);
8050 if (auto* const window = GetInnerWindow()) {
8051 const RefPtr<ServiceWorkerContainer> serviceWorker =
8052 window->Navigator()->ServiceWorker();
8054 // This could cause queued messages from a service worker to get
8055 // dispatched on serviceWorker.
8056 serviceWorker->StartMessages();
8059 if (MayStartLayout()) {
8060 MaybeResolveReadyForIdle();
8063 nsIDocShell* docShell = GetDocShell();
8065 if (TimelineConsumers::HasConsumer(docShell)) {
8066 TimelineConsumers::AddMarkerForDocShell(
8067 docShell,
8068 MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
8071 if (mTiming) {
8072 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
8075 // If this document is a [i]frame, fire a DOMFrameContentLoaded
8076 // event on all parent documents notifying that the HTML (excluding
8077 // other external files such as images and stylesheets) in a frame
8078 // has finished loading.
8080 // target_frame is the [i]frame element that will be used as the
8081 // target for the event. It's the [i]frame whose content is done
8082 // loading.
8083 nsCOMPtr<Element> target_frame = GetEmbedderElement();
8085 if (target_frame && target_frame->IsInComposedDoc()) {
8086 nsCOMPtr<Document> parent = target_frame->OwnerDoc();
8087 while (parent) {
8088 RefPtr<Event> event;
8089 if (parent) {
8090 IgnoredErrorResult ignored;
8091 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
8094 if (event) {
8095 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
8097 event->SetTarget(target_frame);
8098 event->SetTrusted(true);
8100 // To dispatch this event we must manually call
8101 // EventDispatcher::Dispatch() on the ancestor document since the
8102 // target is not in the same document, so the event would never reach
8103 // the ancestor document if we used the normal event
8104 // dispatching code.
8106 WidgetEvent* innerEvent = event->WidgetEventPtr();
8107 if (innerEvent) {
8108 nsEventStatus status = nsEventStatus_eIgnore;
8110 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
8111 // TODO: Bug 1506441
8112 EventDispatcher::Dispatch(MOZ_KnownLive(ToSupports(parent)),
8113 context, innerEvent, event, &status);
8118 parent = parent->GetInProcessParentDocument();
8122 nsPIDOMWindowInner* inner = GetInnerWindow();
8123 if (inner) {
8124 inner->NoteDOMContentLoaded();
8127 // TODO
8128 if (mMaybeServiceWorkerControlled) {
8129 using mozilla::dom::ServiceWorkerManager;
8130 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
8131 if (swm) {
8132 Maybe<ClientInfo> clientInfo = GetClientInfo();
8133 if (clientInfo.isSome()) {
8134 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
8139 if (mSetCompleteAfterDOMContentLoaded) {
8140 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
8141 mSetCompleteAfterDOMContentLoaded = false;
8144 UnblockOnload(true);
8147 void Document::EndLoad() {
8148 bool turnOnEditing =
8149 mParser && (IsInDesignMode() || mContentEditableCount > 0);
8151 #if defined(DEBUG)
8152 // only assert if nothing stopped the load on purpose
8153 if (!mParserAborted) {
8154 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
8156 #endif
8158 // EndLoad may have been called without a matching call to BeginLoad, in the
8159 // case of a failed parse (for example, due to timeout). In such a case, we
8160 // still want to execute part of this code to do appropriate cleanup, but we
8161 // gate part of it because it is intended to match 1-for-1 with calls to
8162 // BeginLoad. We have an explicit flag bit for this purpose, since it's
8163 // complicated and error prone to derive this condition from other related
8164 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
8166 // Part 1: Code that always executes to cleanup end of parsing, whether
8167 // that parsing was successful or not.
8169 // Drop the ref to our parser, if any, but keep hold of the sink so that we
8170 // can flush it from FlushPendingNotifications as needed. We might have to
8171 // do that to get a StartLayout() to happen.
8172 if (mParser) {
8173 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
8174 mParser = nullptr;
8177 // Update the attributes on the PerformanceNavigationTiming before notifying
8178 // the onload observers.
8179 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
8180 if (RefPtr<Performance> performance = window->GetPerformance()) {
8181 performance->UpdateNavigationTimingEntry();
8185 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
8187 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
8189 if (!mDidCallBeginLoad) {
8190 return;
8192 mDidCallBeginLoad = false;
8194 UnblockDOMContentLoaded();
8196 if (turnOnEditing) {
8197 EditingStateChanged();
8200 if (!GetWindow()) {
8201 // This is a document that's not in a window. For example, this could be an
8202 // XMLHttpRequest responseXML document, or a document created via DOMParser
8203 // or DOMImplementation. We don't reach this code normally for such
8204 // documents (which is not obviously correct), but can reach it via
8205 // document.open()/document.close().
8207 // Such documents don't fire load events, but per spec should set their
8208 // readyState to "complete" when parsing and all loading of subresources is
8209 // done. Parsing is done now, and documents not in a window don't load
8210 // subresources, so just go ahead and mark ourselves as complete.
8211 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
8212 /* updateTimingInformation = */ false);
8214 // Reset mSkipLoadEventAfterClose just in case.
8215 mSkipLoadEventAfterClose = false;
8219 void Document::UnblockDOMContentLoaded() {
8220 MOZ_ASSERT(mBlockDOMContentLoaded);
8221 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
8222 return;
8225 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
8226 ("DOCUMENT %p UnblockDOMContentLoaded", this));
8228 mDidFireDOMContentLoaded = true;
8229 if (PresShell* presShell = GetPresShell()) {
8230 presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
8233 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
8234 if (!mSynchronousDOMContentLoaded) {
8235 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8236 nsCOMPtr<nsIRunnable> ev =
8237 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
8238 &Document::DispatchContentLoadedEvents);
8239 Dispatch(TaskCategory::Other, ev.forget());
8240 } else {
8241 DispatchContentLoadedEvents();
8245 void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) {
8246 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
8247 "Someone forgot a scriptblocker");
8248 NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged,
8249 (this, aElement, aStateMask));
8252 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
8253 StyleRuleChangeKind) {
8254 if (aSheet.IsApplicable()) {
8255 ApplicableStylesChanged();
8259 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
8260 if (aRule.IsIncompleteImportRule()) {
8261 return;
8264 if (aSheet.IsApplicable()) {
8265 ApplicableStylesChanged();
8269 void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
8270 if (aSheet.IsApplicable()) {
8271 ApplicableStylesChanged();
8275 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
8276 if (aSheet.IsApplicable()) {
8277 ApplicableStylesChanged();
8281 static Element* GetCustomContentContainer(PresShell* aPresShell) {
8282 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
8283 return nullptr;
8286 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
8289 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
8290 Element& aElement, bool aForce, ErrorResult& aRv) {
8291 // Clone the node to avoid returning a direct reference.
8292 nsCOMPtr<nsINode> clone = aElement.CloneNode(true, aRv);
8293 if (aRv.Failed()) {
8294 return nullptr;
8297 PresShell* shell = GetPresShell();
8298 if (aForce && !GetCustomContentContainer(shell)) {
8299 FlushPendingNotifications(FlushType::Layout);
8300 shell = GetPresShell();
8303 nsAutoScriptBlocker scriptBlocker;
8305 auto anonContent =
8306 MakeRefPtr<AnonymousContent>(clone.forget().downcast<Element>());
8308 mAnonymousContents.AppendElement(anonContent);
8310 if (Element* container = GetCustomContentContainer(shell)) {
8311 container->AppendChildTo(&anonContent->ContentNode(), true, IgnoreErrors());
8312 shell->GetCanvasFrame()->ShowCustomContentContainer();
8315 return anonContent.forget();
8318 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
8319 PresShell* aPresShell) {
8320 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
8321 if (!container) {
8322 return;
8324 container->RemoveChild(aAnonContent.ContentNode(), IgnoreErrors());
8327 void Document::RemoveAnonymousContent(AnonymousContent& aContent) {
8328 nsAutoScriptBlocker scriptBlocker;
8330 auto index = mAnonymousContents.IndexOf(&aContent);
8331 if (index == mAnonymousContents.NoIndex) {
8332 return;
8335 mAnonymousContents.RemoveElementAt(index);
8336 RemoveAnonContentFromCanvas(aContent, GetPresShell());
8338 if (mAnonymousContents.IsEmpty() &&
8339 GetCustomContentContainer(GetPresShell())) {
8340 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
8344 Element* Document::GetAnonRootIfInAnonymousContentContainer(
8345 nsINode* aNode) const {
8346 if (!aNode->IsInNativeAnonymousSubtree()) {
8347 return nullptr;
8350 PresShell* presShell = GetPresShell();
8351 if (!presShell || !presShell->GetCanvasFrame()) {
8352 return nullptr;
8355 nsAutoScriptBlocker scriptBlocker;
8356 nsCOMPtr<Element> customContainer =
8357 presShell->GetCanvasFrame()->GetCustomContentContainer();
8358 if (!customContainer) {
8359 return nullptr;
8362 // An arbitrary number of elements can be inserted as children of the custom
8363 // container frame. We want the one that was added that contains aNode, so
8364 // we need to keep track of the last child separately using |child| here.
8365 nsINode* child = aNode;
8366 nsINode* parent = aNode->GetParentNode();
8367 while (parent && parent->IsInNativeAnonymousSubtree()) {
8368 if (parent == customContainer) {
8369 return Element::FromNode(child);
8371 child = parent;
8372 parent = child->GetParentNode();
8374 return nullptr;
8377 Maybe<ClientInfo> Document::GetClientInfo() const {
8378 if (const Document* orig = GetOriginalDocument()) {
8379 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
8380 return info;
8384 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8385 return inner->GetClientInfo();
8388 return Maybe<ClientInfo>();
8391 Maybe<ClientState> Document::GetClientState() const {
8392 if (const Document* orig = GetOriginalDocument()) {
8393 if (Maybe<ClientState> state = orig->GetClientState()) {
8394 return state;
8398 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8399 return inner->GetClientState();
8402 return Maybe<ClientState>();
8405 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
8406 if (const Document* orig = GetOriginalDocument()) {
8407 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
8408 return controller;
8412 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8413 return inner->GetController();
8416 return Maybe<ServiceWorkerDescriptor>();
8420 // Document interface
8422 DocumentType* Document::GetDoctype() const {
8423 for (nsIContent* child = GetFirstChild(); child;
8424 child = child->GetNextSibling()) {
8425 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
8426 return static_cast<DocumentType*>(child);
8429 return nullptr;
8432 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
8433 if (!mDOMImplementation) {
8434 nsCOMPtr<nsIURI> uri;
8435 NS_NewURI(getter_AddRefs(uri), "about:blank");
8436 if (!uri) {
8437 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
8438 return nullptr;
8440 bool hasHadScriptObject = true;
8441 nsIScriptGlobalObject* scriptObject =
8442 GetScriptHandlingObject(hasHadScriptObject);
8443 if (!scriptObject && hasHadScriptObject) {
8444 rv.Throw(NS_ERROR_UNEXPECTED);
8445 return nullptr;
8447 mDOMImplementation = new DOMImplementation(
8448 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
8451 return mDOMImplementation;
8454 bool IsLowercaseASCII(const nsAString& aValue) {
8455 int32_t len = aValue.Length();
8456 for (int32_t i = 0; i < len; ++i) {
8457 char16_t c = aValue[i];
8458 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
8459 return false;
8462 return true;
8465 already_AddRefed<Element> Document::CreateElement(
8466 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8467 ErrorResult& rv) {
8468 rv = nsContentUtils::CheckQName(aTagName, false);
8469 if (rv.Failed()) {
8470 return nullptr;
8473 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
8474 nsAutoString lcTagName;
8475 if (needsLowercase) {
8476 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
8479 const nsString* is = nullptr;
8480 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
8481 if (aOptions.IsElementCreationOptions()) {
8482 const ElementCreationOptions& options =
8483 aOptions.GetAsElementCreationOptions();
8485 if (options.mIs.WasPassed()) {
8486 is = &options.mIs.Value();
8489 // Check 'pseudo' and throw an exception if it's not one allowed
8490 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
8491 if (options.mPseudo.WasPassed()) {
8492 Maybe<PseudoStyleType> type =
8493 nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
8494 if (!type || *type == PseudoStyleType::NotPseudo ||
8495 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
8496 rv.ThrowNotSupportedError("Invalid pseudo-element");
8497 return nullptr;
8499 pseudoType = *type;
8503 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
8504 nullptr, mDefaultElementType, is);
8506 if (pseudoType != PseudoStyleType::NotPseudo) {
8507 elem->SetPseudoElementType(pseudoType);
8510 return elem.forget();
8513 already_AddRefed<Element> Document::CreateElementNS(
8514 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8515 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
8516 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8517 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8518 mNodeInfoManager, ELEMENT_NODE,
8519 getter_AddRefs(nodeInfo));
8520 if (rv.Failed()) {
8521 return nullptr;
8524 const nsString* is = nullptr;
8525 if (aOptions.IsElementCreationOptions()) {
8526 const ElementCreationOptions& options =
8527 aOptions.GetAsElementCreationOptions();
8528 if (options.mIs.WasPassed()) {
8529 is = &options.mIs.Value();
8533 nsCOMPtr<Element> element;
8534 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
8535 NOT_FROM_PARSER, is);
8536 if (rv.Failed()) {
8537 return nullptr;
8540 return element.forget();
8543 already_AddRefed<Element> Document::CreateXULElement(
8544 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8545 ErrorResult& aRv) {
8546 aRv = nsContentUtils::CheckQName(aTagName, false);
8547 if (aRv.Failed()) {
8548 return nullptr;
8551 const nsString* is = nullptr;
8552 if (aOptions.IsElementCreationOptions()) {
8553 const ElementCreationOptions& options =
8554 aOptions.GetAsElementCreationOptions();
8555 if (options.mIs.WasPassed()) {
8556 is = &options.mIs.Value();
8560 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
8561 if (!elem) {
8562 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
8563 return nullptr;
8565 return elem.forget();
8568 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
8569 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8570 return text.forget();
8573 already_AddRefed<nsTextNode> Document::CreateTextNode(
8574 const nsAString& aData) const {
8575 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8576 // Don't notify; this node is still being created.
8577 text->SetText(aData, false);
8578 return text.forget();
8581 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
8582 RefPtr<DocumentFragment> frag =
8583 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
8584 return frag.forget();
8587 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
8588 already_AddRefed<dom::Comment> Document::CreateComment(
8589 const nsAString& aData) const {
8590 RefPtr<dom::Comment> comment =
8591 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
8593 // Don't notify; this node is still being created.
8594 comment->SetText(aData, false);
8595 return comment.forget();
8598 already_AddRefed<CDATASection> Document::CreateCDATASection(
8599 const nsAString& aData, ErrorResult& rv) {
8600 if (IsHTMLDocument()) {
8601 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8602 return nullptr;
8605 if (FindInReadable(u"]]>"_ns, aData)) {
8606 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8607 return nullptr;
8610 RefPtr<CDATASection> cdata =
8611 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
8613 // Don't notify; this node is still being created.
8614 cdata->SetText(aData, false);
8616 return cdata.forget();
8619 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
8620 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
8621 nsresult res = nsContentUtils::CheckQName(aTarget, false);
8622 if (NS_FAILED(res)) {
8623 rv.Throw(res);
8624 return nullptr;
8627 if (FindInReadable(u"?>"_ns, aData)) {
8628 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8629 return nullptr;
8632 RefPtr<ProcessingInstruction> pi =
8633 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
8635 return pi.forget();
8638 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
8639 ErrorResult& rv) {
8640 if (!mNodeInfoManager) {
8641 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8642 return nullptr;
8645 nsresult res = nsContentUtils::CheckQName(aName, false);
8646 if (NS_FAILED(res)) {
8647 rv.Throw(res);
8648 return nullptr;
8651 nsAutoString name;
8652 if (IsHTMLDocument()) {
8653 nsContentUtils::ASCIIToLower(aName, name);
8654 } else {
8655 name = aName;
8658 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8659 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
8660 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
8661 if (NS_FAILED(res)) {
8662 rv.Throw(res);
8663 return nullptr;
8666 RefPtr<Attr> attribute =
8667 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8668 return attribute.forget();
8671 already_AddRefed<Attr> Document::CreateAttributeNS(
8672 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8673 ErrorResult& rv) {
8674 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8675 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8676 mNodeInfoManager, ATTRIBUTE_NODE,
8677 getter_AddRefs(nodeInfo));
8678 if (rv.Failed()) {
8679 return nullptr;
8682 RefPtr<Attr> attribute =
8683 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8684 return attribute.forget();
8687 void Document::ResolveScheduledSVGPresAttrs() {
8688 for (SVGElement* svg : mLazySVGPresElements) {
8689 svg->UpdateContentDeclarationBlock();
8691 mLazySVGPresElements.Clear();
8694 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
8695 const {
8696 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
8698 const nsTArray<nsWeakPtr> blockedNodes = mBlockedNodesByClassifier.Clone();
8700 for (unsigned long i = 0; i < blockedNodes.Length(); i++) {
8701 nsWeakPtr weakNode = blockedNodes[i];
8702 nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode);
8703 // Consider only nodes to which we have managed to get strong references.
8704 // Coping with nullptrs since it's expected for nodes to disappear when
8705 // nobody else is referring to them.
8706 if (node) {
8707 list->AppendElement(node);
8711 return list.forget();
8714 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
8715 aSheetSet.Truncate();
8717 // Look through our sheets, find the selected set title
8718 size_t count = SheetCount();
8719 nsAutoString title;
8720 for (size_t index = 0; index < count; index++) {
8721 StyleSheet* sheet = SheetAt(index);
8722 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8724 if (sheet->Disabled()) {
8725 // Disabled sheets don't affect the currently selected set
8726 continue;
8729 sheet->GetTitle(title);
8731 if (aSheetSet.IsEmpty()) {
8732 aSheetSet = title;
8733 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
8734 // Sheets from multiple sets enabled; return null string, per spec.
8735 SetDOMStringToNull(aSheetSet);
8736 return;
8741 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
8742 if (DOMStringIsNull(aSheetSet)) {
8743 return;
8746 // Must update mLastStyleSheetSet before doing anything else with stylesheets
8747 // or CSSLoaders.
8748 mLastStyleSheetSet = aSheetSet;
8749 EnableStyleSheetsForSetInternal(aSheetSet, true);
8752 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
8753 mPreferredStyleSheetSet = aSheetSet;
8754 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
8755 // spec.
8756 if (DOMStringIsNull(mLastStyleSheetSet)) {
8757 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
8758 // per spec. The idea here is that we're changing our preferred set and
8759 // that shouldn't change the value of lastStyleSheetSet. Also, we're
8760 // using the Internal version so we can update the CSSLoader and not have
8761 // to worry about null strings.
8762 EnableStyleSheetsForSetInternal(aSheetSet, true);
8766 DOMStringList* Document::StyleSheetSets() {
8767 if (!mStyleSheetSetList) {
8768 mStyleSheetSetList = new DOMStyleSheetSetList(this);
8770 return mStyleSheetSetList;
8773 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
8774 // Per spec, passing in null is a no-op.
8775 if (!DOMStringIsNull(aSheetSet)) {
8776 // Note: must make sure to not change the CSSLoader's preferred sheet --
8777 // that value should be equal to either our lastStyleSheetSet (if that's
8778 // non-null) or to our preferredStyleSheetSet. And this method doesn't
8779 // change either of those.
8780 EnableStyleSheetsForSetInternal(aSheetSet, false);
8784 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
8785 bool aUpdateCSSLoader) {
8786 size_t count = SheetCount();
8787 nsAutoString title;
8788 for (size_t index = 0; index < count; index++) {
8789 StyleSheet* sheet = SheetAt(index);
8790 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8792 sheet->GetTitle(title);
8793 if (!title.IsEmpty()) {
8794 sheet->SetEnabled(title.Equals(aSheetSet));
8797 if (aUpdateCSSLoader) {
8798 CSSLoader()->DocumentStyleSheetSetChanged();
8800 if (mStyleSet->StyleSheetsHaveChanged()) {
8801 ApplicableStylesChanged();
8805 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
8806 nsAutoCString charset;
8807 GetDocumentCharacterSet()->Name(charset);
8808 CopyASCIItoUTF16(charset, aCharacterSet);
8811 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
8812 ErrorResult& rv) const {
8813 nsINode* imported = &aNode;
8815 switch (imported->NodeType()) {
8816 case DOCUMENT_NODE: {
8817 break;
8819 case DOCUMENT_FRAGMENT_NODE:
8820 case ATTRIBUTE_NODE:
8821 case ELEMENT_NODE:
8822 case PROCESSING_INSTRUCTION_NODE:
8823 case TEXT_NODE:
8824 case CDATA_SECTION_NODE:
8825 case COMMENT_NODE:
8826 case DOCUMENT_TYPE_NODE: {
8827 return imported->Clone(aDeep, mNodeInfoManager, rv);
8829 default: {
8830 NS_WARNING("Don't know how to clone this nodetype for importNode.");
8834 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8835 return nullptr;
8838 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
8839 return nsRange::Create(this, 0, this, 0, rv);
8842 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
8843 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
8844 ErrorResult& rv) const {
8845 RefPtr<NodeIterator> iterator =
8846 new NodeIterator(&aRoot, aWhatToShow, aFilter);
8847 return iterator.forget();
8850 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
8851 uint32_t aWhatToShow,
8852 NodeFilter* aFilter,
8853 ErrorResult& rv) const {
8854 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
8855 return walker.forget();
8858 already_AddRefed<Location> Document::GetLocation() const {
8859 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
8861 if (!w) {
8862 return nullptr;
8865 return do_AddRef(w->Location());
8868 already_AddRefed<nsIURI> Document::GetDomainURI() {
8869 nsIPrincipal* principal = NodePrincipal();
8871 nsCOMPtr<nsIURI> uri;
8872 principal->GetDomain(getter_AddRefs(uri));
8873 if (uri) {
8874 return uri.forget();
8876 auto* basePrin = BasePrincipal::Cast(principal);
8877 basePrin->GetURI(getter_AddRefs(uri));
8878 return uri.forget();
8881 void Document::GetDomain(nsAString& aDomain) {
8882 nsCOMPtr<nsIURI> uri = GetDomainURI();
8884 if (!uri) {
8885 aDomain.Truncate();
8886 return;
8889 nsAutoCString hostName;
8890 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
8891 if (NS_SUCCEEDED(rv)) {
8892 CopyUTF8toUTF16(hostName, aDomain);
8893 } else {
8894 // If we can't get the host from the URI (e.g. about:, javascript:,
8895 // etc), just return an empty string.
8896 aDomain.Truncate();
8900 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
8901 if (!GetBrowsingContext()) {
8902 // If our browsing context is null; disallow setting domain
8903 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8904 return;
8907 if (mSandboxFlags & SANDBOXED_DOMAIN) {
8908 // We're sandboxed; disallow setting domain
8909 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8910 return;
8913 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
8914 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8915 return;
8918 if (aDomain.IsEmpty()) {
8919 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8920 return;
8923 nsCOMPtr<nsIURI> uri = GetDomainURI();
8924 if (!uri) {
8925 rv.Throw(NS_ERROR_FAILURE);
8926 return;
8929 // Check new domain - must be a superdomain of the current host
8930 // For example, a page from foo.bar.com may set domain to bar.com,
8931 // but not to ar.com, baz.com, or fi.foo.bar.com.
8933 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
8934 if (!newURI) {
8935 // Error: illegal domain
8936 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8937 return;
8940 if (GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated()) {
8941 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
8942 return;
8945 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
8946 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
8947 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
8948 wgc->SendSetDocumentDomain(newURI);
8952 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
8953 const nsACString& aHostString) {
8954 if (aHostString.IsEmpty()) {
8955 return nullptr;
8958 // Create new URI
8959 nsCOMPtr<nsIURI> uri = GetDomainURI();
8960 if (!uri) {
8961 return nullptr;
8964 nsresult rv;
8965 rv = NS_MutateURI(uri)
8966 .SetUserPass(""_ns)
8967 .SetPort(-1) // we want to reset the port number if needed.
8968 .SetHostPort(aHostString)
8969 .Finalize(uri);
8970 if (NS_FAILED(rv)) {
8971 return nullptr;
8974 return uri.forget();
8977 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
8978 const nsAString& aNewDomain, nsIURI* aOrigHost) {
8979 if (NS_WARN_IF(!aOrigHost)) {
8980 return nullptr;
8983 nsCOMPtr<nsIURI> newURI =
8984 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
8985 if (!newURI) {
8986 // Error: failed to parse input domain
8987 return nullptr;
8990 if (!IsValidDomain(aOrigHost, newURI)) {
8991 // Error: illegal domain
8992 return nullptr;
8995 nsAutoCString domain;
8996 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
8997 return nullptr;
9000 return CreateInheritingURIForHost(domain);
9003 /* static */
9004 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
9005 // Check new domain - must be a superdomain of the current host
9006 // For example, a page from foo.bar.com may set domain to bar.com,
9007 // but not to ar.com, baz.com, or fi.foo.bar.com.
9008 nsAutoCString current;
9009 nsAutoCString domain;
9010 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
9011 current.Truncate();
9013 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
9014 domain.Truncate();
9017 bool ok = current.Equals(domain);
9018 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
9019 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
9020 // We're golden if the new domain is the current page's base domain or a
9021 // subdomain of it.
9022 nsCOMPtr<nsIEffectiveTLDService> tldService =
9023 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
9024 if (!tldService) {
9025 return false;
9028 nsAutoCString currentBaseDomain;
9029 ok = NS_SUCCEEDED(
9030 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
9031 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
9032 (domain.Length() >= currentBaseDomain.Length()),
9033 "uh-oh! slight optimization wasn't valid somehow!");
9034 ok = ok && domain.Length() >= currentBaseDomain.Length();
9037 return ok;
9040 Element* Document::GetHtmlElement() const {
9041 Element* rootElement = GetRootElement();
9042 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
9043 return rootElement;
9044 return nullptr;
9047 Element* Document::GetHtmlChildElement(nsAtom* aTag) {
9048 Element* html = GetHtmlElement();
9049 if (!html) return nullptr;
9051 // Look for the element with aTag inside html. This needs to run
9052 // forwards to find the first such element.
9053 for (nsIContent* child = html->GetFirstChild(); child;
9054 child = child->GetNextSibling()) {
9055 if (child->IsHTMLElement(aTag)) return child->AsElement();
9057 return nullptr;
9060 nsGenericHTMLElement* Document::GetBody() {
9061 Element* html = GetHtmlElement();
9062 if (!html) {
9063 return nullptr;
9066 for (nsIContent* child = html->GetFirstChild(); child;
9067 child = child->GetNextSibling()) {
9068 if (child->IsHTMLElement(nsGkAtoms::body) ||
9069 child->IsHTMLElement(nsGkAtoms::frameset)) {
9070 return static_cast<nsGenericHTMLElement*>(child);
9074 return nullptr;
9077 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
9078 nsCOMPtr<Element> root = GetRootElement();
9080 // The body element must be either a body tag or a frameset tag. And we must
9081 // have a root element to be able to add kids to it.
9082 if (!newBody ||
9083 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
9084 rv.ThrowHierarchyRequestError(
9085 "The new body must be either a body tag or frameset tag.");
9086 return;
9089 if (!root) {
9090 rv.ThrowHierarchyRequestError("No root element.");
9091 return;
9094 // Use DOM methods so that we pass through the appropriate security checks.
9095 nsCOMPtr<Element> currentBody = GetBody();
9096 if (currentBody) {
9097 root->ReplaceChild(*newBody, *currentBody, rv);
9098 } else {
9099 root->AppendChild(*newBody, rv);
9103 HTMLSharedElement* Document::GetHead() {
9104 return static_cast<HTMLSharedElement*>(GetHeadElement());
9107 Element* Document::GetTitleElement() {
9108 // mMayHaveTitleElement will have been set to true if any HTML or SVG
9109 // <title> element has been bound to this document. So if it's false,
9110 // we know there is nothing to do here. This avoids us having to search
9111 // the whole DOM if someone calls document.title on a large document
9112 // without a title.
9113 if (!mMayHaveTitleElement) {
9114 return nullptr;
9117 Element* root = GetRootElement();
9118 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
9119 // In SVG, the document's title must be a child
9120 for (nsIContent* child = root->GetFirstChild(); child;
9121 child = child->GetNextSibling()) {
9122 if (child->IsSVGElement(nsGkAtoms::title)) {
9123 return child->AsElement();
9126 return nullptr;
9129 // We check the HTML namespace even for non-HTML documents, except SVG. This
9130 // matches the spec and the behavior of all tested browsers.
9131 for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
9132 if (node->IsHTMLElement(nsGkAtoms::title)) {
9133 return node->AsElement();
9136 return nullptr;
9139 void Document::GetTitle(nsAString& aTitle) {
9140 aTitle.Truncate();
9142 Element* rootElement = GetRootElement();
9143 if (!rootElement) {
9144 return;
9147 if (rootElement->IsXULElement()) {
9148 rootElement->GetAttr(nsGkAtoms::title, aTitle);
9149 } else if (Element* title = GetTitleElement()) {
9150 nsContentUtils::GetNodeTextContent(title, false, aTitle);
9151 } else {
9152 return;
9155 aTitle.CompressWhitespace();
9158 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
9159 Element* rootElement = GetRootElement();
9160 if (!rootElement) {
9161 return;
9164 if (rootElement->IsXULElement()) {
9165 aRv =
9166 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
9167 return;
9170 Maybe<mozAutoDocUpdate> updateBatch;
9171 nsCOMPtr<Element> title = GetTitleElement();
9172 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
9173 if (!title) {
9174 // Batch updates so that mutation events don't change "the title
9175 // element" under us
9176 updateBatch.emplace(this, true);
9177 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
9178 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
9179 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
9180 NOT_FROM_PARSER);
9181 if (!title) {
9182 return;
9184 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
9185 IgnoreErrors());
9187 } else if (rootElement->IsHTMLElement()) {
9188 if (!title) {
9189 // Batch updates so that mutation events don't change "the title
9190 // element" under us
9191 updateBatch.emplace(this, true);
9192 Element* head = GetHeadElement();
9193 if (!head) {
9194 return;
9197 RefPtr<mozilla::dom::NodeInfo> titleInfo;
9198 titleInfo = mNodeInfoManager->GetNodeInfo(
9199 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
9200 title = NS_NewHTMLTitleElement(titleInfo.forget());
9201 if (!title) {
9202 return;
9205 head->AppendChildTo(title, true, IgnoreErrors());
9207 } else {
9208 return;
9211 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
9214 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
9215 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
9216 "Setting a title while unlinking or destroying the element?");
9217 if (mInUnlinkOrDeletion) {
9218 return;
9221 if (aBoundTitleElement) {
9222 mMayHaveTitleElement = true;
9224 if (mPendingTitleChangeEvent.IsPending()) {
9225 return;
9228 MOZ_RELEASE_ASSERT(NS_IsMainThread());
9229 RefPtr<nsRunnableMethod<Document, void, false>> event =
9230 NewNonOwningRunnableMethod("Document::DoNotifyPossibleTitleChange", this,
9231 &Document::DoNotifyPossibleTitleChange);
9232 if (NS_WARN_IF(NS_FAILED(Dispatch(TaskCategory::Other, do_AddRef(event))))) {
9233 return;
9235 mPendingTitleChangeEvent = std::move(event);
9238 void Document::DoNotifyPossibleTitleChange() {
9239 if (!mPendingTitleChangeEvent.IsPending()) {
9240 return;
9242 // Make sure the pending runnable method is cleared.
9243 mPendingTitleChangeEvent.Revoke();
9244 mHaveFiredTitleChange = true;
9246 nsAutoString title;
9247 GetTitle(title);
9249 if (RefPtr<PresShell> presShell = GetPresShell()) {
9250 nsCOMPtr<nsISupports> container =
9251 presShell->GetPresContext()->GetContainerWeak();
9252 if (container) {
9253 if (nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container)) {
9254 docShellWin->SetTitle(title);
9259 if (WindowGlobalChild* child = GetWindowGlobalChild()) {
9260 child->SendUpdateDocumentTitle(title);
9263 // Fire a DOM event for the title change.
9264 nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
9265 u"DOMTitleChanged"_ns, CanBubble::eYes,
9266 Cancelable::eYes);
9268 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
9269 if (obs) {
9270 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
9274 already_AddRefed<MediaQueryList> Document::MatchMedia(
9275 const nsACString& aMediaQueryList, CallerType aCallerType) {
9276 RefPtr<MediaQueryList> result =
9277 new MediaQueryList(this, aMediaQueryList, aCallerType);
9279 mDOMMediaQueryLists.insertBack(result);
9281 return result.forget();
9284 void Document::SetMayStartLayout(bool aMayStartLayout) {
9285 mMayStartLayout = aMayStartLayout;
9286 if (MayStartLayout()) {
9287 // Before starting layout, check whether we're a toplevel chrome
9288 // window. If we are, setup some state so that we don't have to restyle
9289 // the whole tree after StartLayout.
9290 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
9291 // We're the chrome document!
9292 win->BeforeStartLayout();
9294 ReadyState state = GetReadyStateEnum();
9295 if (state >= READYSTATE_INTERACTIVE) {
9296 // DOMContentLoaded has fired already.
9297 MaybeResolveReadyForIdle();
9301 MaybeEditingStateChanged();
9304 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
9305 mInitializableFrameLoaders.RemoveElement(aLoader);
9306 // Don't even try to initialize.
9307 if (mInDestructor) {
9308 NS_WARNING(
9309 "Trying to initialize a frame loader while"
9310 "document is being deleted");
9311 return NS_ERROR_FAILURE;
9314 mInitializableFrameLoaders.AppendElement(aLoader);
9315 if (!mFrameLoaderRunner) {
9316 mFrameLoaderRunner =
9317 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9318 &Document::MaybeInitializeFinalizeFrameLoaders);
9319 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9320 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9322 return NS_OK;
9325 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
9326 nsIRunnable* aFinalizer) {
9327 mInitializableFrameLoaders.RemoveElement(aLoader);
9328 if (mInDestructor) {
9329 return NS_ERROR_FAILURE;
9332 LogRunnable::LogDispatch(aFinalizer);
9333 mFrameLoaderFinalizers.AppendElement(aFinalizer);
9334 if (!mFrameLoaderRunner) {
9335 mFrameLoaderRunner =
9336 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9337 &Document::MaybeInitializeFinalizeFrameLoaders);
9338 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9339 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9341 return NS_OK;
9344 void Document::MaybeInitializeFinalizeFrameLoaders() {
9345 if (mDelayFrameLoaderInitialization) {
9346 // This method will be recalled when !mDelayFrameLoaderInitialization.
9347 mFrameLoaderRunner = nullptr;
9348 return;
9351 // We're not in an update, but it is not safe to run scripts, so
9352 // postpone frameloader initialization and finalization.
9353 if (!nsContentUtils::IsSafeToRunScript()) {
9354 if (!mInDestructor && !mFrameLoaderRunner &&
9355 (mInitializableFrameLoaders.Length() ||
9356 mFrameLoaderFinalizers.Length())) {
9357 mFrameLoaderRunner = NewRunnableMethod(
9358 "Document::MaybeInitializeFinalizeFrameLoaders", this,
9359 &Document::MaybeInitializeFinalizeFrameLoaders);
9360 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9362 return;
9364 mFrameLoaderRunner = nullptr;
9366 // Don't use a temporary array for mInitializableFrameLoaders, because
9367 // loading a frame may cause some other frameloader to be removed from the
9368 // array. But be careful to keep the loader alive when starting the load!
9369 while (mInitializableFrameLoaders.Length()) {
9370 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
9371 mInitializableFrameLoaders.RemoveElementAt(0);
9372 NS_ASSERTION(loader, "null frameloader in the array?");
9373 loader->ReallyStartLoading();
9376 uint32_t length = mFrameLoaderFinalizers.Length();
9377 if (length > 0) {
9378 nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
9379 std::move(mFrameLoaderFinalizers);
9380 for (uint32_t i = 0; i < length; ++i) {
9381 LogRunnable::Run run(finalizers[i]);
9382 finalizers[i]->Run();
9387 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
9388 uint32_t length = mInitializableFrameLoaders.Length();
9389 for (uint32_t i = 0; i < length; ++i) {
9390 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
9391 mInitializableFrameLoaders.RemoveElementAt(i);
9392 return;
9397 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
9398 mPrototypeDocument = aPrototype;
9399 mSynchronousDOMContentLoaded = true;
9402 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
9403 return GetPermissionDelegateHandler();
9406 Document* Document::RequestExternalResource(
9407 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
9408 ExternalResourceLoad** aPendingLoad) {
9409 MOZ_ASSERT(aURI, "Must have a URI");
9410 MOZ_ASSERT(aRequestingNode, "Must have a node");
9411 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
9412 if (mDisplayDocument) {
9413 return mDisplayDocument->RequestExternalResource(
9414 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
9417 return mExternalResourceMap.RequestResource(
9418 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
9421 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
9422 mExternalResourceMap.EnumerateResources(aCallback);
9425 SMILAnimationController* Document::GetAnimationController() {
9426 // We create the animation controller lazily because most documents won't want
9427 // one and only SVG documents and the like will call this
9428 if (mAnimationController) return mAnimationController;
9429 // Refuse to create an Animation Controller for data documents.
9430 if (mLoadedAsData) return nullptr;
9432 mAnimationController = new SMILAnimationController(this);
9434 // If there's a presContext then check the animation mode and pause if
9435 // necessary.
9436 nsPresContext* context = GetPresContext();
9437 if (mAnimationController && context &&
9438 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
9439 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
9442 // If we're hidden (or being hidden), notify the newly-created animation
9443 // controller. (Skip this check for SVG-as-an-image documents, though,
9444 // because they don't get OnPageShow / OnPageHide calls).
9445 if (!mIsShowing && !mIsBeingUsedAsImage) {
9446 mAnimationController->OnPageHide();
9449 return mAnimationController;
9452 PendingAnimationTracker* Document::GetOrCreatePendingAnimationTracker() {
9453 if (!mPendingAnimationTracker) {
9454 mPendingAnimationTracker = new PendingAnimationTracker(this);
9457 return mPendingAnimationTracker;
9460 ScrollTimelineAnimationTracker*
9461 Document::GetOrCreateScrollTimelineAnimationTracker() {
9462 if (!mScrollTimelineAnimationTracker) {
9463 mScrollTimelineAnimationTracker = new ScrollTimelineAnimationTracker(this);
9466 return mScrollTimelineAnimationTracker;
9470 * Retrieve the "direction" property of the document.
9472 * @lina 01/09/2001
9474 void Document::GetDir(nsAString& aDirection) const {
9475 aDirection.Truncate();
9476 Element* rootElement = GetHtmlElement();
9477 if (rootElement) {
9478 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
9483 * Set the "direction" property of the document.
9485 * @lina 01/09/2001
9487 void Document::SetDir(const nsAString& aDirection) {
9488 Element* rootElement = GetHtmlElement();
9489 if (rootElement) {
9490 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
9494 nsIHTMLCollection* Document::Images() {
9495 if (!mImages) {
9496 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
9497 nsGkAtoms::img);
9499 return mImages;
9502 nsIHTMLCollection* Document::Embeds() {
9503 if (!mEmbeds) {
9504 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
9505 nsGkAtoms::embed);
9507 return mEmbeds;
9510 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9511 void* aData) {
9512 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
9513 aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href);
9516 nsIHTMLCollection* Document::Links() {
9517 if (!mLinks) {
9518 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
9520 return mLinks;
9523 nsIHTMLCollection* Document::Forms() {
9524 if (!mForms) {
9525 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
9526 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
9527 nsGkAtoms::form);
9530 return mForms;
9533 nsIHTMLCollection* Document::Scripts() {
9534 if (!mScripts) {
9535 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
9536 nsGkAtoms::script);
9538 return mScripts;
9541 nsIHTMLCollection* Document::Applets() {
9542 if (!mApplets) {
9543 mApplets = new nsEmptyContentList(this);
9545 return mApplets;
9548 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9549 void* aData) {
9550 return aElement->IsHTMLElement(nsGkAtoms::a) &&
9551 aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
9554 nsIHTMLCollection* Document::Anchors() {
9555 if (!mAnchors) {
9556 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
9558 return mAnchors;
9561 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
9562 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
9563 ErrorResult& rv) {
9564 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9565 "XOW should have caught this!");
9567 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
9568 if (!window) {
9569 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
9570 return nullptr;
9572 nsCOMPtr<nsPIDOMWindowOuter> outer =
9573 nsPIDOMWindowOuter::GetFromCurrentInner(window);
9574 if (!outer) {
9575 rv.Throw(NS_ERROR_NOT_INITIALIZED);
9576 return nullptr;
9578 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
9579 RefPtr<BrowsingContext> newBC;
9580 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
9581 if (!newBC) {
9582 return nullptr;
9584 return WindowProxyHolder(std::move(newBC));
9587 Document* Document::Open(const Optional<nsAString>& /* unused */,
9588 const Optional<nsAString>& /* unused */,
9589 ErrorResult& aError) {
9590 // Implements
9591 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
9593 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9594 "XOW should have caught this!");
9596 // Step 1 -- throw if we're an XML document.
9597 if (!IsHTMLDocument() || mDisableDocWrite) {
9598 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9599 return nullptr;
9602 // Step 2 -- throw if dynamic markup insertion should throw.
9603 if (ShouldThrowOnDynamicMarkupInsertion()) {
9604 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9605 return nullptr;
9608 // Step 3 -- get the entry document, so we can use it for security checks.
9609 nsCOMPtr<Document> callerDoc = GetEntryDocument();
9610 if (!callerDoc) {
9611 // If we're called from C++ or in some other way without an originating
9612 // document we can't do a document.open w/o changing the principal of the
9613 // document to something like about:blank (as that's the only sane thing to
9614 // do when we don't know the origin of this call), and since we can't
9615 // change the principals of a document for security reasons we'll have to
9616 // refuse to go ahead with this call.
9618 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9619 return nullptr;
9622 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
9623 // the entry document.
9624 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
9625 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9626 return nullptr;
9629 // Step 5 -- if we have an active parser with a nonzero script nesting level,
9630 // just no-op.
9631 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
9632 return this;
9635 // Step 6 -- check for open() during unload. Per spec, this is just a check
9636 // of the ignore-opens-during-unload counter, but our unload event code
9637 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
9638 // do), so we check for unload directly.
9639 if (ShouldIgnoreOpens()) {
9640 return this;
9643 RefPtr<nsDocShell> shell(mDocumentContainer);
9644 if (shell) {
9645 bool inUnload;
9646 shell->GetIsInUnload(&inUnload);
9647 if (inUnload) {
9648 return this;
9652 // document.open() inherits the CSP from the opening document.
9653 // Please create an actual copy of the CSP (do not share the same
9654 // reference) otherwise appending a new policy within the opened
9655 // document will be incorrectly propagated to the opening doc.
9656 nsCOMPtr<nsIContentSecurityPolicy> csp = callerDoc->GetCsp();
9657 if (csp) {
9658 RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
9659 cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
9660 mCSP = cspToInherit;
9663 // At this point we know this is a valid-enough document.open() call
9664 // and not a no-op. Increment our use counter.
9665 SetUseCounter(eUseCounter_custom_DocumentOpen);
9667 // Step 7 -- stop existing navigation of our browsing context (and all other
9668 // loads it's doing) if we're the active document of our browsing context.
9669 // Note that we do not want to stop anything if there is no existing
9670 // navigation.
9671 if (shell && IsCurrentActiveDocument() &&
9672 shell->GetIsAttemptingToNavigate()) {
9673 shell->Stop(nsIWebNavigation::STOP_NETWORK);
9675 // The Stop call may have cancelled the onload blocker request or
9676 // prevented it from getting added, so we need to make sure it gets added
9677 // to the document again otherwise the document could have a non-zero
9678 // onload block count without the onload blocker request being in the
9679 // loadgroup.
9680 EnsureOnloadBlocker();
9683 // Step 8 -- clear event listeners out of our DOM tree
9684 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
9685 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
9686 elm->RemoveAllListeners();
9690 // Step 9 -- clear event listeners from our window, if we have one.
9692 // Note that we explicitly want the inner window, and only if we're its
9693 // document. We want to do this (per spec) even when we're not the "active
9694 // document", so we can't go through GetWindow(), because it might forward to
9695 // the wrong inner.
9696 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
9697 if (win->GetExtantDoc() == this) {
9698 if (EventListenerManager* elm =
9699 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
9700 elm->RemoveAllListeners();
9705 // If we have a parser that has a zero script nesting level, we need to
9706 // properly terminate it. We do that after we've removed all the event
9707 // listeners (so termination won't trigger event listeners if it does
9708 // something to the DOM), but before we remove all elements from the document
9709 // (so if termination does modify the DOM in some way we will just blow it
9710 // away immediately. See the similar code in WriteCommon that handles the
9711 // !IsInsertionPointDefined() case and should stay in sync with this code.
9712 if (mParser) {
9713 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
9714 "Why didn't we take the early return?");
9715 // Make sure we don't re-enter.
9716 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9717 mParser->Terminate();
9718 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9721 // Step 10 -- remove all our DOM kids without firing any mutation events.
9723 // We want to ignore any recursive calls to Open() that happen while
9724 // disconnecting the node tree. The spec doesn't say to do this, but the
9725 // spec also doesn't envision unload events on subframes firing while we do
9726 // this, while all browsers fire them in practice. See
9727 // <https://github.com/whatwg/html/issues/4611>.
9728 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9729 DisconnectNodeTree();
9732 // Step 11 -- if we're the current document in our docshell, do the
9733 // equivalent of pushState() with the new URL we should have.
9734 if (shell && IsCurrentActiveDocument()) {
9735 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
9736 if (callerDoc != this) {
9737 nsCOMPtr<nsIURI> noFragmentURI;
9738 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
9739 if (NS_WARN_IF(NS_FAILED(rv))) {
9740 aError.Throw(rv);
9741 return nullptr;
9743 newURI = std::move(noFragmentURI);
9746 // UpdateURLAndHistory might do various member-setting, so make sure we're
9747 // holding strong refs to all the refcounted args on the stack. We can
9748 // assume that our caller is holding on to "this" already.
9749 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
9750 bool equalURIs;
9751 nsresult rv = currentURI->Equals(newURI, &equalURIs);
9752 if (NS_WARN_IF(NS_FAILED(rv))) {
9753 aError.Throw(rv);
9754 return nullptr;
9756 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
9757 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
9758 /* aReplace = */ true, currentURI,
9759 equalURIs);
9760 if (NS_WARN_IF(NS_FAILED(rv))) {
9761 aError.Throw(rv);
9762 return nullptr;
9765 // And use the security info of the caller document as well, since
9766 // it's the thing providing our data.
9767 mSecurityInfo = callerDoc->GetSecurityInfo();
9769 // This is not mentioned in the spec, but I think that's a spec bug. See
9770 // <https://github.com/whatwg/html/issues/4299>. In any case, since our
9771 // URL may be changing away from about:blank here, we really want to unset
9772 // this flag no matter what, since only about:blank can be an initial
9773 // document.
9774 SetIsInitialDocument(false);
9776 // And let our docloader know that it will need to track our load event.
9777 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
9780 // Per spec nothing happens with our URI in other cases, though note
9781 // <https://github.com/whatwg/html/issues/4286>.
9783 // Note that we don't need to do anything here with base URIs per spec.
9784 // That said, this might be assuming that we implement
9785 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
9786 // correctly, which we don't right now for the about:blank case.
9788 // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
9789 mSkipLoadEventAfterClose = mLoadEventFiring;
9791 // Preliminary to steps 13-16. Set our ready state to uninitialized before
9792 // we do anything else, so we can then proceed to later ready state levels.
9793 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
9794 /* updateTimingInformation = */ false);
9795 // Reset a flag that affects readyState behavior.
9796 mSetCompleteAfterDOMContentLoaded = false;
9798 // Step 13 -- set our compat mode to standards.
9799 SetCompatibilityMode(eCompatibility_FullStandards);
9801 // Step 14 -- create a new parser associated with document. This also does
9802 // step 16 implicitly.
9803 mParserAborted = false;
9804 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
9805 mParser = parser;
9806 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
9807 nsresult rv = parser->StartExecutor();
9808 if (NS_WARN_IF(NS_FAILED(rv))) {
9809 aError.Throw(rv);
9810 return nullptr;
9813 // Clear out our form control state, because the state of controls
9814 // in the pre-open() document should not affect the state of
9815 // controls that are now going to be written.
9816 mLayoutHistoryState = nullptr;
9818 if (shell) {
9819 // Prepare the docshell and the document viewer for the impending
9820 // out-of-band document.write()
9821 shell->PrepareForNewContentModel();
9823 nsCOMPtr<nsIContentViewer> cv;
9824 shell->GetContentViewer(getter_AddRefs(cv));
9825 if (cv) {
9826 cv->LoadStart(this);
9830 // Step 15.
9831 SetReadyStateInternal(Document::READYSTATE_LOADING,
9832 /* updateTimingInformation = */ false);
9834 // Step 16 happened with step 14 above.
9836 // Step 17.
9837 return this;
9840 void Document::Close(ErrorResult& rv) {
9841 if (!IsHTMLDocument()) {
9842 // No calling document.close() on XHTML!
9844 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9845 return;
9848 if (ShouldThrowOnDynamicMarkupInsertion()) {
9849 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9850 return;
9853 if (!mParser || !mParser->IsScriptCreated()) {
9854 return;
9857 ++mWriteLevel;
9858 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
9859 ->Parse(u""_ns, nullptr, true);
9860 --mWriteLevel;
9863 void Document::WriteCommon(const Sequence<nsString>& aText,
9864 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
9865 // Fast path the common case
9866 if (aText.Length() == 1) {
9867 WriteCommon(aText[0], aNewlineTerminate, rv);
9868 } else {
9869 // XXXbz it would be nice if we could pass all the strings to the parser
9870 // without having to do all this copying and then ask it to start
9871 // parsing....
9872 nsString text;
9873 for (size_t i = 0; i < aText.Length(); ++i) {
9874 text.Append(aText[i]);
9876 WriteCommon(text, aNewlineTerminate, rv);
9880 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
9881 ErrorResult& aRv) {
9882 #ifdef DEBUG
9884 // Assert that we do not use or accidentally introduce doc.write()
9885 // in system privileged context or in any of our about: pages.
9886 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
9887 bool isAboutOrPrivContext = principal->IsSystemPrincipal();
9888 if (!isAboutOrPrivContext) {
9889 if (principal->SchemeIs("about")) {
9890 // about:blank inherits the security contetext and this assertion
9891 // is only meant for actual about: pages.
9892 nsAutoCString host;
9893 principal->GetHost(host);
9894 isAboutOrPrivContext = !host.EqualsLiteral("blank");
9897 // Some automated tests use an empty string to kick off some parsing
9898 // mechansims, but they do not do any harm since they use an empty string.
9899 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
9900 "do not use doc.write in privileged context!");
9902 #endif
9904 mTooDeepWriteRecursion =
9905 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
9906 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
9907 aRv.Throw(NS_ERROR_UNEXPECTED);
9908 return;
9911 if (!IsHTMLDocument() || mDisableDocWrite) {
9912 // No calling document.write*() on XHTML!
9914 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9915 return;
9918 if (ShouldThrowOnDynamicMarkupInsertion()) {
9919 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9920 return;
9923 if (mParserAborted) {
9924 // Hixie says aborting the parser doesn't undefine the insertion point.
9925 // However, since we null out mParser in that case, we track the
9926 // theoretically defined insertion point using mParserAborted.
9927 return;
9930 // Implement Step 4.1 of:
9931 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
9932 if (ShouldIgnoreOpens()) {
9933 return;
9936 void* key = GenerateParserKey();
9937 if (mParser && !mParser->IsInsertionPointDefined()) {
9938 if (mIgnoreDestructiveWritesCounter) {
9939 // Instead of implying a call to document.open(), ignore the call.
9940 nsContentUtils::ReportToConsole(
9941 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9942 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9943 return;
9945 // The spec doesn't tell us to ignore opens from here, but we need to
9946 // ensure opens are ignored here. See similar code in Open() that handles
9947 // the case of an existing parser which is not currently running script and
9948 // should stay in sync with this code.
9949 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9950 mParser->Terminate();
9951 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9954 if (!mParser) {
9955 if (mIgnoreDestructiveWritesCounter) {
9956 // Instead of implying a call to document.open(), ignore the call.
9957 nsContentUtils::ReportToConsole(
9958 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9959 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9960 return;
9963 Open({}, {}, aRv);
9965 // If Open() fails, or if it didn't create a parser (as it won't
9966 // if the user chose to not discard the current document through
9967 // onbeforeunload), don't write anything.
9968 if (aRv.Failed() || !mParser) {
9969 return;
9973 static constexpr auto new_line = u"\n"_ns;
9975 ++mWriteLevel;
9977 // This could be done with less code, but for performance reasons it
9978 // makes sense to have the code for two separate Parse() calls here
9979 // since the concatenation of strings costs more than we like. And
9980 // why pay that price when we don't need to?
9981 if (aNewlineTerminate) {
9982 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
9983 ->Parse(aText + new_line, key, false);
9984 } else {
9985 aRv =
9986 (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
9989 --mWriteLevel;
9991 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
9994 void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
9995 WriteCommon(aText, false, rv);
9998 void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
9999 WriteCommon(aText, true, rv);
10002 void* Document::GenerateParserKey(void) {
10003 if (!mScriptLoader) {
10004 // If we don't have a script loader, then the parser probably isn't parsing
10005 // anything anyway, so just return null.
10006 return nullptr;
10009 // The script loader provides us with the currently executing script element,
10010 // which is guaranteed to be unique per script.
10011 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
10012 if (script && mParser && mParser->IsScriptCreated()) {
10013 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
10014 if (creatorParser != mParser) {
10015 // Make scripts that aren't inserted by the active parser of this document
10016 // participate in the context of the script that document.open()ed
10017 // this document.
10018 return nullptr;
10021 return script;
10024 /* static */
10025 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
10026 nsAtom* aAtom, void* aData) {
10027 MOZ_ASSERT(aElement, "Must have element to work with!");
10029 if (!aElement->HasName()) {
10030 return false;
10033 nsString* elementName = static_cast<nsString*>(aData);
10034 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
10035 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
10036 eCaseMatters);
10039 /* static */
10040 void* Document::UseExistingNameString(nsINode* aRootNode,
10041 const nsString* aName) {
10042 return const_cast<nsString*>(aName);
10045 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
10046 if (mDocumentURI) {
10047 nsAutoCString uri;
10048 nsresult rv = mDocumentURI->GetSpec(uri);
10049 NS_ENSURE_SUCCESS(rv, rv);
10051 CopyUTF8toUTF16(uri, aDocumentURI);
10052 } else {
10053 aDocumentURI.Truncate();
10056 return NS_OK;
10059 // Alias of above
10060 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
10062 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
10063 CallerType aCallerType,
10064 ErrorResult& aRv) const {
10065 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
10066 aRv = GetDocumentURI(aDocumentURI);
10067 return;
10070 nsAutoCString uri;
10071 nsresult res = mChromeXHRDocURI->GetSpec(uri);
10072 if (NS_FAILED(res)) {
10073 aRv.Throw(res);
10074 return;
10076 CopyUTF8toUTF16(uri, aDocumentURI);
10079 nsIURI* Document::GetDocumentURIObject() const {
10080 if (!mChromeXHRDocURI) {
10081 return GetDocumentURI();
10084 return mChromeXHRDocURI;
10087 void Document::GetCompatMode(nsString& aCompatMode) const {
10088 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
10089 mCompatMode == eCompatibility_AlmostStandards ||
10090 mCompatMode == eCompatibility_FullStandards,
10091 "mCompatMode is neither quirks nor strict for this document");
10093 if (mCompatMode == eCompatibility_NavQuirks) {
10094 aCompatMode.AssignLiteral("BackCompat");
10095 } else {
10096 aCompatMode.AssignLiteral("CSS1Compat");
10100 } // namespace dom
10101 } // namespace mozilla
10103 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
10104 if (Element* element = Element::FromNode(aNode)) {
10105 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
10106 while (true) {
10107 RefPtr<Attr> attr;
10109 // Use an iterator to get an arbitrary attribute from the
10110 // cache. The iterator must be destroyed before any other
10111 // operations on mAttributeCache, to avoid hash table
10112 // assertions.
10113 auto iter = map->mAttributeCache.ConstIter();
10114 if (iter.Done()) {
10115 break;
10117 attr = iter.UserData();
10120 BlastSubtreeToPieces(attr);
10122 mozilla::DebugOnly<nsresult> rv =
10123 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
10124 attr->NodeInfo()->NameAtom(), false);
10126 // XXX Should we abort here?
10127 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
10131 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
10132 BlastSubtreeToPieces(shadow);
10133 element->UnattachShadow();
10137 while (aNode->HasChildren()) {
10138 nsIContent* node = aNode->GetFirstChild();
10139 BlastSubtreeToPieces(node);
10140 aNode->RemoveChildNode(node, false);
10144 namespace mozilla::dom {
10146 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) {
10147 OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
10149 // Scope firing mutation events so that we don't carry any state that
10150 // might be stale
10152 if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
10153 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
10157 nsAutoScriptBlocker scriptBlocker;
10159 switch (adoptedNode->NodeType()) {
10160 case ATTRIBUTE_NODE: {
10161 // Remove from ownerElement.
10162 OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
10164 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement();
10165 if (rv.Failed()) {
10166 return nullptr;
10169 if (ownerElement) {
10170 OwningNonNull<Attr> newAttr =
10171 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
10172 if (rv.Failed()) {
10173 return nullptr;
10177 break;
10179 case DOCUMENT_FRAGMENT_NODE: {
10180 if (adoptedNode->IsShadowRoot()) {
10181 rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
10182 return nullptr;
10184 [[fallthrough]];
10186 case ELEMENT_NODE:
10187 case PROCESSING_INSTRUCTION_NODE:
10188 case TEXT_NODE:
10189 case CDATA_SECTION_NODE:
10190 case COMMENT_NODE:
10191 case DOCUMENT_TYPE_NODE: {
10192 // Don't allow adopting a node's anonymous subtree out from under it.
10193 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
10194 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10195 return nullptr;
10198 // We don't want to adopt an element into its own contentDocument or into
10199 // a descendant contentDocument, so we check if the frameElement of this
10200 // document or any of its parents is the adopted node or one of its
10201 // descendants.
10202 RefPtr<BrowsingContext> bc = GetBrowsingContext();
10203 while (bc) {
10204 nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
10205 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
10206 rv.ThrowHierarchyRequestError(
10207 "Trying to adopt a node into its own contentDocument or a "
10208 "descendant contentDocument.");
10209 return nullptr;
10212 if (XRE_IsParentProcess()) {
10213 bc = bc->Canonical()->GetParentCrossChromeBoundary();
10214 } else {
10215 bc = bc->GetParent();
10219 // Remove from parent.
10220 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
10221 if (parent) {
10222 parent->RemoveChildNode(adoptedNode->AsContent(), true);
10223 } else {
10224 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
10227 break;
10229 case DOCUMENT_NODE: {
10230 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10231 return nullptr;
10233 default: {
10234 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
10236 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10237 return nullptr;
10241 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
10242 bool sameDocument = oldDocument == this;
10244 AutoJSContext cx;
10245 JS::Rooted<JSObject*> newScope(cx, nullptr);
10246 if (!sameDocument) {
10247 newScope = GetWrapper();
10248 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
10249 // Make sure cx is in a semi-sane compartment before we call WrapNative.
10250 // It's kind of irrelevant, given that we're passing aAllowWrapping =
10251 // false, and documents should always insist on being wrapped in an
10252 // canonical scope. But we try to pass something sane anyway.
10253 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
10254 JSAutoRealm ar(cx, globalObject);
10255 JS::Rooted<JS::Value> v(cx);
10256 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
10257 /* aAllowWrapping = */ false);
10258 if (rv.Failed()) return nullptr;
10259 newScope = &v.toObject();
10263 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
10264 if (rv.Failed()) {
10265 // Disconnect all nodes from their parents, since some have the old document
10266 // as their ownerDocument and some have this as their ownerDocument.
10267 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
10268 return nullptr;
10271 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
10272 "Should still be in the document we just got adopted into");
10274 return adoptedNode;
10277 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
10279 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
10280 const nsString& aScaleString) {
10281 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
10282 if (aScaleString.EqualsLiteral("device-width") ||
10283 aScaleString.EqualsLiteral("device-height")) {
10284 return Some(LayoutDeviceToScreenScale(10.0f));
10285 } else if (aScaleString.EqualsLiteral("yes")) {
10286 return Some(LayoutDeviceToScreenScale(1.0f));
10287 } else if (aScaleString.EqualsLiteral("no")) {
10288 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10289 } else if (aScaleString.IsEmpty()) {
10290 return Nothing();
10293 nsresult scaleErrorCode;
10294 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
10295 if (NS_FAILED(scaleErrorCode)) {
10296 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10299 if (scale < 0) {
10300 return Nothing();
10302 return Some(clamped(LayoutDeviceToScreenScale(scale), ViewportMinScale(),
10303 ViewportMaxScale()));
10306 void Document::ParseScalesInViewportMetaData(
10307 const ViewportMetaData& aViewportMetaData) {
10308 Maybe<LayoutDeviceToScreenScale> scale;
10310 scale = ParseScaleString(aViewportMetaData.mInitialScale);
10311 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
10312 mValidScaleFloat = scale.isSome();
10314 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
10315 // Chrome uses '5' for the fallback value of maximum-scale, we might
10316 // consider matching it in future.
10317 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
10318 mScaleMaxFloat = scale.valueOr(ViewportMaxScale());
10319 mValidMaxScale = scale.isSome();
10321 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
10322 mScaleMinFloat = scale.valueOr(ViewportMinScale());
10323 mValidMinScale = scale.isSome();
10325 // Resolve min-zoom and max-zoom values.
10326 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
10327 if (mValidMaxScale && mValidMinScale) {
10328 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
10332 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
10333 const nsAString& aHeightString,
10334 bool aHasValidScale) {
10335 // The width and height properties
10336 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
10338 // The width and height viewport <META> properties are translated into width
10339 // and height descriptors, setting the min-width/min-height value to
10340 // extend-to-zoom and the max-width/max-height value to the length from the
10341 // viewport <META> property as follows:
10343 // 1. Non-negative number values are translated to pixel lengths, clamped to
10344 // the range: [1px, 10000px]
10345 // 2. Negative number values are dropped
10346 // 3. device-width and device-height translate to 100vw and 100vh respectively
10347 // 4. Other keywords and unknown values are also dropped
10348 mMinWidth = nsViewportInfo::kAuto;
10349 mMaxWidth = nsViewportInfo::kAuto;
10350 if (!aWidthString.IsEmpty()) {
10351 mMinWidth = nsViewportInfo::kExtendToZoom;
10352 if (aWidthString.EqualsLiteral("device-width")) {
10353 mMaxWidth = nsViewportInfo::kDeviceSize;
10354 } else {
10355 nsresult widthErrorCode;
10356 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
10357 if (NS_FAILED(widthErrorCode)) {
10358 mMaxWidth = nsViewportInfo::kAuto;
10359 } else if (mMaxWidth >= 0.0f) {
10360 mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
10361 } else {
10362 mMaxWidth = nsViewportInfo::kAuto;
10365 } else if (aHasValidScale) {
10366 if (aHeightString.IsEmpty()) {
10367 mMinWidth = nsViewportInfo::kExtendToZoom;
10368 mMaxWidth = nsViewportInfo::kExtendToZoom;
10370 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
10371 mMinWidth = nsViewportInfo::kExtendToZoom;
10372 mMaxWidth = nsViewportInfo::kDeviceSize;
10375 mMinHeight = nsViewportInfo::kAuto;
10376 mMaxHeight = nsViewportInfo::kAuto;
10377 if (!aHeightString.IsEmpty()) {
10378 mMinHeight = nsViewportInfo::kExtendToZoom;
10379 if (aHeightString.EqualsLiteral("device-height")) {
10380 mMaxHeight = nsViewportInfo::kDeviceSize;
10381 } else {
10382 nsresult heightErrorCode;
10383 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
10384 if (NS_FAILED(heightErrorCode)) {
10385 mMaxHeight = nsViewportInfo::kAuto;
10386 } else if (mMaxHeight >= 0.0f) {
10387 mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
10388 } else {
10389 mMaxHeight = nsViewportInfo::kAuto;
10395 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
10396 MOZ_ASSERT(mPresShell);
10398 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
10399 // widget scale and the full zoom.
10400 nsPresContext* context = mPresShell->GetPresContext();
10401 // When querying the full zoom, get it from the device context rather than
10402 // directly from the pres context, because the device context's value can
10403 // include an adjustment necessary to keep the number of app units per device
10404 // pixel an integer, and we want the adjusted value.
10405 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
10406 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
10407 CSSToLayoutDeviceScale layoutDeviceScale =
10408 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
10410 CSSToScreenScale defaultScale =
10411 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
10413 // Special behaviour for desktop mode, provided we are not on an about: page,
10414 // or fullscreen.
10415 const bool fullscreen = Fullscreen();
10416 auto* bc = GetBrowsingContext();
10417 if (bc && bc->ForceDesktopViewport() && !IsAboutPage() && !fullscreen) {
10418 CSSCoord viewportWidth =
10419 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
10420 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
10421 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
10422 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
10423 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
10424 return nsViewportInfo(fakeDesktopSize, scaleToFit,
10425 nsViewportInfo::ZoomFlag::AllowZoom,
10426 nsViewportInfo::ZoomBehaviour::Mobile,
10427 nsViewportInfo::AutoScaleFlag::AutoScale);
10430 // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
10431 if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
10432 return nsViewportInfo(aDisplaySize, defaultScale,
10433 nsLayoutUtils::AllowZoomingForDocument(this)
10434 ? nsViewportInfo::ZoomFlag::AllowZoom
10435 : nsViewportInfo::ZoomFlag::DisallowZoom,
10436 StaticPrefs::apz_allow_zooming_out()
10437 ? nsViewportInfo::ZoomBehaviour::Mobile
10438 : nsViewportInfo::ZoomBehaviour::Desktop);
10441 // In cases where the width of the CSS viewport is less than or equal to the
10442 // width of the display (i.e. width <= device-width) then we disable
10443 // double-tap-to-zoom behaviour. See bug 941995 for details.
10445 switch (mViewportType) {
10446 case DisplayWidthHeight:
10447 return nsViewportInfo(aDisplaySize, defaultScale,
10448 nsViewportInfo::ZoomFlag::AllowZoom,
10449 nsViewportInfo::ZoomBehaviour::Mobile);
10450 case Unknown: {
10451 // We might early exit if the viewport is empty. Even if we don't,
10452 // at the end of this case we'll note that it was empty. Later, when
10453 // we're using the cached values, this will trigger alternate code paths.
10454 if (!mLastModifiedViewportMetaData) {
10455 // If the docType specifies that we are on a site optimized for mobile,
10456 // then we want to return specially crafted defaults for the viewport
10457 // info.
10458 if (RefPtr<DocumentType> docType = GetDoctype()) {
10459 nsAutoString docId;
10460 docType->GetPublicId(docId);
10461 if ((docId.Find(u"WAP") != -1) || (docId.Find(u"Mobile") != -1) ||
10462 (docId.Find(u"WML") != -1)) {
10463 // We're making an assumption that the docType can't change here
10464 mViewportType = DisplayWidthHeight;
10465 return nsViewportInfo(aDisplaySize, defaultScale,
10466 nsViewportInfo::ZoomFlag::AllowZoom,
10467 nsViewportInfo::ZoomBehaviour::Mobile);
10471 nsAutoString handheldFriendly;
10472 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
10473 if (handheldFriendly.EqualsLiteral("true")) {
10474 mViewportType = DisplayWidthHeight;
10475 return nsViewportInfo(aDisplaySize, defaultScale,
10476 nsViewportInfo::ZoomFlag::AllowZoom,
10477 nsViewportInfo::ZoomBehaviour::Mobile);
10481 ViewportMetaData metaData = GetViewportMetaData();
10483 // Parse initial-scale, minimum-scale and maximum-scale.
10484 ParseScalesInViewportMetaData(metaData);
10486 // Parse width and height properties
10487 // This function sets m{Min,Max}{Width,Height}.
10488 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
10489 mValidScaleFloat);
10491 mAllowZoom = true;
10492 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
10493 (metaData.mUserScalable.EqualsLiteral("no")) ||
10494 (metaData.mUserScalable.EqualsLiteral("false"))) {
10495 mAllowZoom = false;
10498 // Resolve viewport-fit value.
10499 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
10500 mViewportFit = ViewportFitType::Auto;
10501 if (!metaData.mViewportFit.IsEmpty()) {
10502 if (metaData.mViewportFit.EqualsLiteral("contain")) {
10503 mViewportFit = ViewportFitType::Contain;
10504 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
10505 mViewportFit = ViewportFitType::Cover;
10509 mWidthStrEmpty = metaData.mWidth.IsEmpty();
10511 mViewportType = Specified;
10512 [[fallthrough]];
10514 case Specified:
10515 default:
10516 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
10517 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
10518 bool effectiveValidMaxScale = mValidMaxScale;
10520 nsViewportInfo::ZoomFlag effectiveZoomFlag =
10521 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
10522 : nsViewportInfo::ZoomFlag::DisallowZoom;
10523 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
10524 // If the pref to force user-scalable is enabled, we ignore the values
10525 // from the meta-viewport tag for these properties and just assume they
10526 // allow the page to be scalable. Note in particular that this code is
10527 // in the "Specified" branch of the enclosing switch statement, so that
10528 // calls to GetViewportInfo always use the latest value of the
10529 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
10530 // return nsViewportInfo instances are all consistent with
10531 // browser_ui_zoom_force_user_scalable() already.
10532 effectiveMinScale = ViewportMinScale();
10533 effectiveMaxScale = ViewportMaxScale();
10534 effectiveValidMaxScale = true;
10535 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
10538 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
10539 auto ComputeExtendZoom = [&]() -> float {
10540 if (mValidScaleFloat && effectiveValidMaxScale) {
10541 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
10543 if (mValidScaleFloat) {
10544 return mScaleFloat.scale;
10546 if (effectiveValidMaxScale) {
10547 return effectiveMaxScale.scale;
10549 return nsViewportInfo::kAuto;
10552 // Resolving 'extend-to-zoom'
10553 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
10554 float extendZoom = ComputeExtendZoom();
10556 CSSCoord minWidth = mMinWidth;
10557 CSSCoord maxWidth = mMaxWidth;
10558 CSSCoord minHeight = mMinHeight;
10559 CSSCoord maxHeight = mMaxHeight;
10561 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
10562 // viewport size. We need to use this scaled size for any clamping of
10563 // width or height.
10564 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
10566 // Our min and max width and height values are mostly as specified by
10567 // the viewport declaration, but we make an exception for max width.
10568 // Max width, if auto, and if there's no initial-scale, will be set
10569 // to a default size. This is to support legacy site design with no
10570 // viewport declaration, and to do that using the same scheme as
10571 // Chrome does, in order to maintain web compatibility. Since the
10572 // default size has a complicated calculation, we fixup the maxWidth
10573 // value after setting it, above.
10574 if (maxWidth == nsViewportInfo::kAuto && !mValidScaleFloat) {
10575 if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
10576 bc->InRDMPane()) {
10577 // If RDM and touch simulation are active, then use the simulated
10578 // screen width to accommodate for cases where the screen width is
10579 // larger than the desktop viewport default.
10580 maxWidth = nsViewportInfo::Max(
10581 displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
10582 } else {
10583 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
10585 // Divide by fullZoom to stretch CSS pixel size of viewport in order
10586 // to keep device pixel size unchanged after full zoom applied.
10587 // See bug 974242.
10588 maxWidth /= fullZoom;
10590 // We set minWidth to ExtendToZoom, which will cause our later width
10591 // calculation to expand to maxWidth, if scale restrictions allow it.
10592 minWidth = nsViewportInfo::kExtendToZoom;
10595 // Resolve device-width and device-height first.
10596 if (maxWidth == nsViewportInfo::kDeviceSize) {
10597 maxWidth = displaySize.width;
10599 if (maxHeight == nsViewportInfo::kDeviceSize) {
10600 maxHeight = displaySize.height;
10602 if (extendZoom == nsViewportInfo::kAuto) {
10603 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10604 maxWidth = nsViewportInfo::kAuto;
10606 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10607 maxHeight = nsViewportInfo::kAuto;
10609 if (minWidth == nsViewportInfo::kExtendToZoom) {
10610 minWidth = maxWidth;
10612 if (minHeight == nsViewportInfo::kExtendToZoom) {
10613 minHeight = maxHeight;
10615 } else {
10616 CSSSize extendSize = displaySize / extendZoom;
10617 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10618 maxWidth = extendSize.width;
10620 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10621 maxHeight = extendSize.height;
10623 if (minWidth == nsViewportInfo::kExtendToZoom) {
10624 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
10626 if (minHeight == nsViewportInfo::kExtendToZoom) {
10627 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
10631 // Resolve initial width and height from min/max descriptors
10632 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
10633 CSSCoord width = nsViewportInfo::kAuto;
10634 if (minWidth != nsViewportInfo::kAuto ||
10635 maxWidth != nsViewportInfo::kAuto) {
10636 width = nsViewportInfo::Max(
10637 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
10639 CSSCoord height = nsViewportInfo::kAuto;
10640 if (minHeight != nsViewportInfo::kAuto ||
10641 maxHeight != nsViewportInfo::kAuto) {
10642 height = nsViewportInfo::Max(
10643 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
10646 // Resolve width value
10647 // https://drafts.csswg.org/css-device-adapt/#resolve-width
10648 if (width == nsViewportInfo::kAuto) {
10649 if (height == nsViewportInfo::kAuto || aDisplaySize.height == 0) {
10650 width = displaySize.width;
10651 } else {
10652 width = height * aDisplaySize.width / aDisplaySize.height;
10656 // Resolve height value
10657 // https://drafts.csswg.org/css-device-adapt/#resolve-height
10658 if (height == nsViewportInfo::kAuto) {
10659 if (aDisplaySize.width == 0) {
10660 height = displaySize.height;
10661 } else {
10662 height = width * aDisplaySize.height / aDisplaySize.width;
10665 MOZ_ASSERT(width != nsViewportInfo::kAuto &&
10666 height != nsViewportInfo::kAuto);
10668 CSSSize size(width, height);
10670 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
10671 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
10672 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
10674 nsViewportInfo::AutoSizeFlag sizeFlag =
10675 nsViewportInfo::AutoSizeFlag::FixedSize;
10676 if (mMaxWidth == nsViewportInfo::kDeviceSize ||
10677 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::kDeviceSize ||
10678 mScaleFloat.scale == 1.0f)) ||
10679 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::kAuto &&
10680 mMaxHeight < 0)) {
10681 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
10684 // FIXME: Resolving width and height should be done above 'Resolve width
10685 // value' and 'Resolve height value'.
10686 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
10687 size = displaySize;
10690 // The purpose of clamping the viewport width to a minimum size is to
10691 // prevent page authors from setting it to a ridiculously small value.
10692 // If the page is actually being rendered in a very small area (as might
10693 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
10694 // prevent the viewport from taking on that size.
10695 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
10697 size.width = clamped(size.width, effectiveMinSize.width,
10698 float(kViewportMaxSize.width));
10700 // Also recalculate the default zoom, if it wasn't specified in the
10701 // metadata, and the width is specified.
10702 if (!mValidScaleFloat && !mWidthStrEmpty) {
10703 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
10704 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
10707 size.height = clamped(size.height, effectiveMinSize.height,
10708 float(kViewportMaxSize.height));
10710 // In cases of user-scalable=no, if we have a positive scale, clamp it to
10711 // min and max, and then use the clamped value for the scale, the min, and
10712 // the max. If we don't have a positive scale, assert that we are setting
10713 // the auto scale flag.
10714 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
10715 scaleFloat > CSSToScreenScale(0.0f)) {
10716 scaleFloat = scaleMinFloat = scaleMaxFloat =
10717 clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
10719 MOZ_ASSERT(
10720 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
10721 "If we don't have a positive scale, we should be using auto scale.");
10723 // We need to perform a conversion, but only if the initial or maximum
10724 // scale were set explicitly by the user.
10725 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
10726 scaleFloat <= scaleMaxFloat) {
10727 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
10728 size.width = std::max(size.width, displaySize.width);
10729 size.height = std::max(size.height, displaySize.height);
10730 } else if (effectiveValidMaxScale) {
10731 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
10732 size.width = std::max(size.width, displaySize.width);
10733 size.height = std::max(size.height, displaySize.height);
10736 return nsViewportInfo(
10737 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
10738 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
10739 : nsViewportInfo::AutoScaleFlag::AutoScale,
10740 effectiveZoomFlag, mViewportFit);
10744 ViewportMetaData Document::GetViewportMetaData() const {
10745 return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
10746 : ViewportMetaData();
10749 void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
10750 mLastModifiedViewportMetaData = std::move(aData);
10751 // Trigger recomputation of the nsViewportInfo the next time it's queried.
10752 mViewportType = Unknown;
10754 AsyncEventDispatcher::RunDOMEventWhenSafe(
10755 *this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes,
10756 ChromeOnlyDispatch::eYes);
10759 EventListenerManager* Document::GetOrCreateListenerManager() {
10760 if (!mListenerManager) {
10761 mListenerManager =
10762 new EventListenerManager(static_cast<EventTarget*>(this));
10763 SetFlags(NODE_HAS_LISTENERMANAGER);
10766 return mListenerManager;
10769 EventListenerManager* Document::GetExistingListenerManager() const {
10770 return mListenerManager;
10773 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
10774 aVisitor.mCanHandle = true;
10775 // FIXME! This is a hack to make middle mouse paste working also in Editor.
10776 // Bug 329119
10777 aVisitor.mForceContentDispatch = true;
10779 // Load events must not propagate to |window| object, see bug 335251.
10780 if (aVisitor.mEvent->mMessage != eLoad) {
10781 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
10782 aVisitor.SetParentTarget(
10783 window ? window->GetTargetForEventTargetChain() : nullptr, false);
10787 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
10788 CallerType aCallerType,
10789 ErrorResult& rv) const {
10790 nsPresContext* presContext = GetPresContext();
10792 // Create event even without presContext.
10793 RefPtr<Event> ev =
10794 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
10795 nullptr, aEventType, aCallerType);
10796 if (!ev) {
10797 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10798 return nullptr;
10800 WidgetEvent* e = ev->WidgetEventPtr();
10801 e->mFlags.mBubbles = false;
10802 e->mFlags.mCancelable = false;
10803 return ev.forget();
10806 void Document::FlushPendingNotifications(FlushType aType) {
10807 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
10808 FlushPendingNotifications(flush);
10811 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
10812 FlushType flushType = aFlush.mFlushType;
10814 RefPtr<Document> documentOnStack = this;
10816 // We need to flush the sink for non-HTML documents (because the XML
10817 // parser still does insertion with deferred notifications). We
10818 // also need to flush the sink if this is a layout-related flush, to
10819 // make sure that layout is started as needed. But we can skip that
10820 // part if we have no presshell or if it's already done an initial
10821 // reflow.
10822 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
10823 mPresShell && !mPresShell->DidInitialize())) &&
10824 (mParser || mWeakSink)) {
10825 nsCOMPtr<nsIContentSink> sink;
10826 if (mParser) {
10827 sink = mParser->GetContentSink();
10828 } else {
10829 sink = do_QueryReferent(mWeakSink);
10830 if (!sink) {
10831 mWeakSink = nullptr;
10834 // Determine if it is safe to flush the sink notifications
10835 // by determining if it safe to flush all the presshells.
10836 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
10837 sink->FlushPendingNotifications(flushType);
10841 // Should we be flushing pending binding constructors in here?
10843 if (flushType <= FlushType::ContentAndNotify) {
10844 // Nothing to do here
10845 return;
10848 // If we have a parent we must flush the parent too to ensure that our
10849 // container is reflowed if its size was changed.
10851 // We do it only if the subdocument and the parent can observe each other
10852 // synchronously (that is, if we're not cross-origin), to avoid work that is
10853 // not observable, and if the parent document has finished loading all its
10854 // render-blocking stylesheets and may start laying out the document, to avoid
10855 // unnecessary flashes of unstyled content on the parent document. Note that
10856 // this last bit means that size-dependent media queries in this document may
10857 // produce incorrect results temporarily.
10859 // But if it's not safe to flush ourselves, then don't flush the parent, since
10860 // that can cause things like resizes of our frame's widget, which we can't
10861 // handle while flushing is unsafe.
10862 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
10863 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
10864 ChangesToFlush parentFlush = aFlush;
10865 if (flushType >= FlushType::Style) {
10866 // Since media queries mean that a size change of our container can affect
10867 // style, we need to promote a style flush on ourself to a layout flush on
10868 // our parent, since we need our container to be the correct size to
10869 // determine the correct style.
10870 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
10872 mParentDocument->FlushPendingNotifications(parentFlush);
10875 if (RefPtr<PresShell> presShell = GetPresShell()) {
10876 presShell->FlushPendingNotifications(aFlush);
10880 void Document::FlushExternalResources(FlushType aType) {
10881 NS_ASSERTION(
10882 aType >= FlushType::Style,
10883 "should only need to flush for style or higher in external resources");
10884 if (GetDisplayDocument()) {
10885 return;
10888 auto flush = [aType](Document& aDoc) {
10889 aDoc.FlushPendingNotifications(aType);
10890 return CallState::Continue;
10893 EnumerateExternalResources(flush);
10896 void Document::SetXMLDeclaration(const char16_t* aVersion,
10897 const char16_t* aEncoding,
10898 const int32_t aStandalone) {
10899 if (!aVersion || *aVersion == '\0') {
10900 mXMLDeclarationBits = 0;
10901 return;
10904 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
10906 if (aEncoding && *aEncoding != '\0') {
10907 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
10910 if (aStandalone == 1) {
10911 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
10912 XML_DECLARATION_BITS_STANDALONE_YES;
10913 } else if (aStandalone == 0) {
10914 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
10918 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
10919 nsAString& aStandalone) {
10920 aVersion.Truncate();
10921 aEncoding.Truncate();
10922 aStandalone.Truncate();
10924 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
10925 return;
10928 // always until we start supporting 1.1 etc.
10929 aVersion.AssignLiteral("1.0");
10931 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
10932 // This is what we have stored, not necessarily what was written
10933 // in the original
10934 GetCharacterSet(aEncoding);
10937 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
10938 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
10939 aStandalone.AssignLiteral("yes");
10940 } else {
10941 aStandalone.AssignLiteral("no");
10946 void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
10947 mColorSchemeMetaTags.Insert(aMeta);
10948 RecomputeColorScheme();
10951 void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
10952 mColorSchemeMetaTags.RemoveElement(aMeta);
10953 RecomputeColorScheme();
10956 void Document::RecomputeColorScheme() {
10957 if (!StaticPrefs::layout_css_color_scheme_enabled()) {
10958 return;
10960 auto oldColorScheme = mColorSchemeBits;
10961 mColorSchemeBits = 0;
10962 const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
10963 for (const HTMLMetaElement* el : elements) {
10964 nsAutoString content;
10965 if (!el->GetAttr(nsGkAtoms::content, content)) {
10966 continue;
10969 NS_ConvertUTF16toUTF8 contentU8(content);
10970 if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
10971 break;
10975 if (mColorSchemeBits == oldColorScheme) {
10976 return;
10979 if (nsPresContext* pc = GetPresContext()) {
10980 // This affects system colors, which are inherited, so we need to recascade.
10981 pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
10985 bool Document::IsScriptEnabled() const {
10986 // If this document is sandboxed without 'allow-scripts'
10987 // script is not enabled
10988 if (HasScriptsBlockedBySandbox()) {
10989 return false;
10992 nsCOMPtr<nsIScriptGlobalObject> globalObject =
10993 do_QueryInterface(GetInnerWindow());
10994 if (!globalObject || !globalObject->HasJSGlobal()) {
10995 return false;
10998 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
10999 .Allowed();
11002 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
11003 PRTime modDate = 0;
11004 nsresult rv;
11006 nsCOMPtr<nsIHttpChannel> httpChannel;
11007 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
11008 if (NS_WARN_IF(NS_FAILED(rv))) {
11009 return;
11012 if (httpChannel) {
11013 nsAutoCString tmp;
11014 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
11016 if (NS_SUCCEEDED(rv)) {
11017 PRTime time;
11018 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
11019 if (st == PR_SUCCESS) {
11020 modDate = time;
11024 static const char* const headers[] = {
11025 "default-style", "content-style-type", "content-language",
11026 "content-disposition", "refresh", "x-dns-prefetch-control",
11027 "x-frame-options", "origin-trial",
11028 // add more http headers if you need
11029 // XXXbz don't add content-location support without reading bug
11030 // 238654 and its dependencies/dups first.
11033 nsAutoCString headerVal;
11034 const char* const* name = headers;
11035 while (*name) {
11036 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
11037 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
11038 RefPtr<nsAtom> key = NS_Atomize(*name);
11039 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
11041 ++name;
11043 } else {
11044 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
11045 if (fileChannel) {
11046 nsCOMPtr<nsIFile> file;
11047 fileChannel->GetFile(getter_AddRefs(file));
11048 if (file) {
11049 PRTime msecs;
11050 rv = file->GetLastModifiedTime(&msecs);
11052 if (NS_SUCCEEDED(rv)) {
11053 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
11056 } else {
11057 nsAutoCString contentDisp;
11058 rv = aChannel->GetContentDispositionHeader(contentDisp);
11059 if (NS_SUCCEEDED(rv)) {
11060 SetHeaderData(nsGkAtoms::headerContentDisposition,
11061 NS_ConvertASCIItoUTF16(contentDisp));
11066 mLastModified.Truncate();
11067 if (modDate != 0) {
11068 GetFormattedTimeString(modDate, mLastModified);
11072 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
11073 // set any HTTP-EQUIV data into document's header data as well as url
11074 nsAutoString header;
11075 aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
11076 if (!header.IsEmpty()) {
11077 // Ignore META REFRESH when document is sandboxed from automatic features.
11078 nsContentUtils::ASCIIToLower(header);
11079 if (nsGkAtoms::refresh->Equals(header) &&
11080 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
11081 return;
11084 nsAutoString result;
11085 aMetaElement->GetAttr(nsGkAtoms::content, result);
11086 if (!result.IsEmpty()) {
11087 RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
11088 SetHeaderData(fieldAtom, result);
11092 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
11093 nsGkAtoms::handheldFriendly, eIgnoreCase)) {
11094 nsAutoString result;
11095 aMetaElement->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result);
11096 if (!result.IsEmpty()) {
11097 nsContentUtils::ASCIIToLower(result);
11098 SetHeaderData(nsGkAtoms::handheldFriendly, result);
11103 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
11104 nsAtom* aPrefix,
11105 int32_t aNamespaceID,
11106 const nsAString* aIs) {
11107 #ifdef DEBUG
11108 nsAutoString qName;
11109 if (aPrefix) {
11110 aPrefix->ToString(qName);
11111 qName.Append(':');
11113 qName.Append(aName);
11115 // Note: "a:b:c" is a valid name in non-namespaces XML, and
11116 // Document::CreateElement can call us with such a name and no prefix,
11117 // which would cause an error if we just used true here.
11118 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
11119 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
11120 "Don't pass invalid prefixes to Document::CreateElem, "
11121 "check caller.");
11122 #endif
11124 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
11125 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
11126 getter_AddRefs(nodeInfo));
11127 NS_ENSURE_TRUE(nodeInfo, nullptr);
11129 nsCOMPtr<Element> element;
11130 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
11131 NOT_FROM_PARSER, aIs);
11132 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
11135 bool Document::IsSafeToFlush() const {
11136 PresShell* presShell = GetPresShell();
11137 if (!presShell) {
11138 return true;
11140 return presShell->IsSafeToFlush();
11143 void Document::Sanitize() {
11144 // Sanitize the document by resetting all (current and former) password fields
11145 // and any form fields with autocomplete=off to their default values. We do
11146 // this now, instead of when the presentation is restored, to offer some
11147 // protection in case there is ever an exploit that allows a cached document
11148 // to be accessed from a different document.
11150 // First locate all input elements, regardless of whether they are
11151 // in a form, and reset the password and autocomplete=off elements.
11153 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
11155 nsAutoString value;
11157 uint32_t length = nodes->Length(true);
11158 for (uint32_t i = 0; i < length; ++i) {
11159 NS_ASSERTION(nodes->Item(i), "null item in node list!");
11161 RefPtr<HTMLInputElement> input =
11162 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
11163 if (!input) continue;
11165 input->GetAttr(nsGkAtoms::autocomplete, value);
11166 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
11167 input->Reset();
11171 // Now locate all _form_ elements that have autocomplete=off and reset them
11172 nodes = GetElementsByTagName(u"form"_ns);
11174 length = nodes->Length(true);
11175 for (uint32_t i = 0; i < length; ++i) {
11176 // Reset() may change the list dynamically.
11177 RefPtr<HTMLFormElement> form =
11178 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
11179 if (!form) continue;
11181 form->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, value);
11182 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
11186 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
11187 if (!mSubDocuments) {
11188 return;
11191 // PLDHashTable::Iterator can't handle modifications while iterating so we
11192 // copy all entries to an array first before calling any callbacks.
11193 AutoTArray<RefPtr<Document>, 8> subdocs;
11194 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11195 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11196 if (Document* subdoc = entry->mSubDocument) {
11197 subdocs.AppendElement(subdoc);
11200 for (auto& subdoc : subdocs) {
11201 if (aCallback(*subdoc) == CallState::Stop) {
11202 break;
11207 void Document::CollectDescendantDocuments(
11208 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
11209 if (!mSubDocuments) {
11210 return;
11213 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11214 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11215 const Document* subdoc = entry->mSubDocument;
11216 if (subdoc) {
11217 if (aCallback(subdoc)) {
11218 aDescendants.AppendElement(entry->mSubDocument);
11220 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
11225 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
11226 uint32_t& aBFCacheCombo,
11227 bool aIncludeSubdocuments,
11228 bool aAllowUnloadListeners) {
11229 bool ret = true;
11231 if (!IsBFCachingAllowed()) {
11232 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
11233 ret = false;
11236 nsAutoCString uri;
11237 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11238 if (mDocumentURI) {
11239 mDocumentURI->GetSpec(uri);
11243 if (EventHandlingSuppressed()) {
11244 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11245 ("Save of %s blocked on event handling suppression", uri.get()));
11246 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
11247 ret = false;
11250 // Do not allow suspended windows to be placed in the
11251 // bfcache. This method is also used to verify a document
11252 // coming out of the bfcache is ok to restore, though. So
11253 // we only want to block suspend windows that aren't also
11254 // frozen.
11255 nsPIDOMWindowInner* win = GetInnerWindow();
11256 if (win && win->IsSuspended() && !win->IsFrozen()) {
11257 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11258 ("Save of %s blocked on suspended Window", uri.get()));
11259 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
11260 ret = false;
11263 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
11264 bool thirdParty = false;
11265 // Currently some other mobile browsers seem to bfcache only cross-domain
11266 // pages, but bfcache those also when there are unload event listeners, so
11267 // this is trying to match that behavior as much as possible.
11268 bool allowUnloadListeners =
11269 aAllowUnloadListeners &&
11270 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
11271 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
11272 channel, &thirdParty)) &&
11273 thirdParty));
11275 // Check our event listener manager for unload/beforeunload listeners.
11276 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
11277 if (!allowUnloadListeners && piTarget) {
11278 EventListenerManager* manager = piTarget->GetExistingListenerManager();
11279 if (manager) {
11280 if (manager->HasUnloadListeners()) {
11281 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11282 ("Save of %s blocked due to unload handlers", uri.get()));
11283 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
11284 ret = false;
11286 if (manager->HasBeforeUnloadListeners()) {
11287 if (!mozilla::SessionHistoryInParent() ||
11288 !StaticPrefs::
11289 docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
11290 MOZ_LOG(
11291 gPageCacheLog, mozilla::LogLevel::Verbose,
11292 ("Save of %s blocked due to beforeUnload handlers", uri.get()));
11293 aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
11294 ret = false;
11300 // Check if we have pending network requests
11301 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11302 if (loadGroup) {
11303 nsCOMPtr<nsISimpleEnumerator> requests;
11304 loadGroup->GetRequests(getter_AddRefs(requests));
11306 bool hasMore = false;
11308 // We want to bail out if we have any requests other than aNewRequest (or
11309 // in the case when aNewRequest is a part of a multipart response the base
11310 // channel the multipart response is coming in on).
11311 nsCOMPtr<nsIChannel> baseChannel;
11312 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
11313 if (part) {
11314 part->GetBaseChannel(getter_AddRefs(baseChannel));
11317 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11318 nsCOMPtr<nsISupports> elem;
11319 requests->GetNext(getter_AddRefs(elem));
11321 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11322 if (request && request != aNewRequest && request != baseChannel) {
11323 // Favicon loads don't need to block caching.
11324 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
11325 if (channel) {
11326 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
11327 if (li->InternalContentPolicyType() ==
11328 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
11329 continue;
11333 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11334 nsAutoCString requestName;
11335 request->GetName(requestName);
11336 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
11337 ("Save of %s blocked because document has request %s",
11338 uri.get(), requestName.get()));
11340 aBFCacheCombo |= BFCacheStatus::REQUEST;
11341 ret = false;
11346 // Check if we have active GetUserMedia use
11347 if (MediaManager::Exists() && win &&
11348 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
11349 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11350 ("Save of %s blocked due to GetUserMedia", uri.get()));
11351 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
11352 ret = false;
11355 #ifdef MOZ_WEBRTC
11356 // Check if we have active PeerConnections
11357 if (win && win->HasActivePeerConnections()) {
11358 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11359 ("Save of %s blocked due to PeerConnection", uri.get()));
11360 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
11361 ret = false;
11363 #endif // MOZ_WEBRTC
11365 // Don't save presentations for documents containing EME content, so that
11366 // CDMs reliably shutdown upon user navigation.
11367 if (ContainsEMEContent()) {
11368 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
11369 ret = false;
11372 // Don't save presentations for documents containing MSE content, to
11373 // reduce memory usage.
11374 if (ContainsMSEContent()) {
11375 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11376 ("Save of %s blocked due to MSE use", uri.get()));
11377 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
11378 ret = false;
11381 if (aIncludeSubdocuments && mSubDocuments) {
11382 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11383 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11384 Document* subdoc = entry->mSubDocument;
11386 uint32_t subDocBFCacheCombo = 0;
11387 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
11388 bool canCache =
11389 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
11390 true, allowUnloadListeners)
11391 : false;
11392 if (!canCache) {
11393 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11394 ("Save of %s blocked due to subdocument blocked", uri.get()));
11395 aBFCacheCombo |= subDocBFCacheCombo;
11396 ret = false;
11401 if (!mozilla::BFCacheInParent()) {
11402 // BFCache is currently not compatible with remote subframes (bug 1609324)
11403 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
11404 for (auto& child : browsingContext->Children()) {
11405 if (!child->IsInProcess()) {
11406 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
11407 ret = false;
11408 break;
11414 if (win) {
11415 auto* globalWindow = nsGlobalWindowInner::Cast(win);
11416 #ifdef MOZ_WEBSPEECH
11417 if (globalWindow->HasActiveSpeechSynthesis()) {
11418 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11419 ("Save of %s blocked due to Speech use", uri.get()));
11420 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
11421 ret = false;
11423 #endif
11424 if (globalWindow->HasUsedVR()) {
11425 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11426 ("Save of %s blocked due to having used VR", uri.get()));
11427 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
11428 ret = false;
11431 if (win->HasActiveLocks()) {
11432 MOZ_LOG(
11433 gPageCacheLog, mozilla::LogLevel::Verbose,
11434 ("Save of %s blocked due to having active lock requests", uri.get()));
11435 aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
11436 ret = false;
11439 if (win->HasActiveWebTransports()) {
11440 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11441 ("Save of %s blocked due to WebTransport", uri.get()));
11442 aBFCacheCombo |= BFCacheStatus::ACTIVE_WEBTRANSPORT;
11443 ret = false;
11447 return ret;
11450 void Document::Destroy() {
11451 // The ContentViewer wants to release the document now. So, tell our content
11452 // to drop any references to the document so that it can be destroyed.
11453 if (mIsGoingAway) {
11454 return;
11457 ReportDocumentUseCounters();
11458 SetDevToolsWatchingDOMMutations(false);
11460 mIsGoingAway = true;
11462 ScriptLoader()->Destroy();
11463 SetScriptGlobalObject(nullptr);
11464 RemovedFromDocShell();
11466 bool oldVal = mInUnlinkOrDeletion;
11467 mInUnlinkOrDeletion = true;
11469 #ifdef DEBUG
11470 uint32_t oldChildCount = GetChildCount();
11471 #endif
11473 for (nsIContent* child = GetFirstChild(); child;
11474 child = child->GetNextSibling()) {
11475 child->DestroyContent();
11476 MOZ_ASSERT(child->GetParentNode() == this);
11478 MOZ_ASSERT(oldChildCount == GetChildCount());
11479 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
11481 mInUnlinkOrDeletion = oldVal;
11483 mLayoutHistoryState = nullptr;
11485 if (mOriginalDocument) {
11486 mOriginalDocument->mLatestStaticClone = nullptr;
11489 if (IsStaticDocument()) {
11490 RemoveProperty(nsGkAtoms::printisfocuseddoc);
11491 RemoveProperty(nsGkAtoms::printselectionranges);
11494 // Shut down our external resource map. We might not need this for
11495 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
11496 // tearing down all those frame trees right now is the right thing to do.
11497 mExternalResourceMap.Shutdown();
11499 // Manually break cycles via promise's global object pointer.
11500 mReadyForIdle = nullptr;
11501 mOrientationPendingPromise = nullptr;
11503 // To break cycles.
11504 mPreloadService.ClearAllPreloads();
11506 if (mDocumentL10n) {
11507 mDocumentL10n->Destroy();
11511 void Document::RemovedFromDocShell() {
11512 mEditingState = EditingState::eOff;
11514 if (mRemovedFromDocShell) return;
11516 mRemovedFromDocShell = true;
11517 NotifyActivityChanged();
11519 for (nsIContent* child = GetFirstChild(); child;
11520 child = child->GetNextSibling()) {
11521 child->SaveSubtreeState();
11524 nsIDocShell* docShell = GetDocShell();
11525 if (docShell) {
11526 docShell->SynchronizeLayoutHistoryState();
11530 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
11531 const {
11532 nsCOMPtr<nsILayoutHistoryState> state;
11533 if (!mScriptGlobalObject) {
11534 state = mLayoutHistoryState;
11535 } else {
11536 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
11537 if (docShell) {
11538 docShell->GetLayoutHistoryState(getter_AddRefs(state));
11542 return state.forget();
11545 void Document::EnsureOnloadBlocker() {
11546 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11547 // -- it's not ours.
11548 if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
11549 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11550 if (loadGroup) {
11551 // Check first to see if mOnloadBlocker is in the loadgroup.
11552 nsCOMPtr<nsISimpleEnumerator> requests;
11553 loadGroup->GetRequests(getter_AddRefs(requests));
11555 bool hasMore = false;
11556 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11557 nsCOMPtr<nsISupports> elem;
11558 requests->GetNext(getter_AddRefs(elem));
11559 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11560 if (request && request == mOnloadBlocker) {
11561 return;
11565 // Not in the loadgroup, so add it.
11566 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11571 void Document::BlockOnload() {
11572 if (mDisplayDocument) {
11573 mDisplayDocument->BlockOnload();
11574 return;
11577 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11578 // -- it's not ours.
11579 if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
11580 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11581 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11584 ++mOnloadBlockCount;
11587 void Document::UnblockOnload(bool aFireSync) {
11588 if (mDisplayDocument) {
11589 mDisplayDocument->UnblockOnload(aFireSync);
11590 return;
11593 --mOnloadBlockCount;
11595 if (mOnloadBlockCount == 0) {
11596 if (mScriptGlobalObject) {
11597 // Only manipulate the loadgroup in this case, because if
11598 // mScriptGlobalObject is null, it's not ours.
11599 if (aFireSync) {
11600 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
11601 ++mOnloadBlockCount;
11602 DoUnblockOnload();
11603 } else {
11604 PostUnblockOnloadEvent();
11606 } else if (mIsBeingUsedAsImage) {
11607 // To correctly unblock onload for a document that contains an SVG
11608 // image, we need to know when all of the SVG document's resources are
11609 // done loading, in a way comparable to |window.onload|. We fire this
11610 // event to indicate that the SVG should be considered fully loaded.
11611 // Because scripting is disabled on SVG-as-image documents, this event
11612 // is not accessible to content authors. (See bug 837315.)
11613 RefPtr<AsyncEventDispatcher> asyncDispatcher =
11614 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
11615 CanBubble::eNo, ChromeOnlyDispatch::eNo);
11616 asyncDispatcher->PostDOMEvent();
11621 class nsUnblockOnloadEvent : public Runnable {
11622 public:
11623 explicit nsUnblockOnloadEvent(Document* aDoc)
11624 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
11625 NS_IMETHOD Run() override {
11626 mDoc->DoUnblockOnload();
11627 return NS_OK;
11630 private:
11631 RefPtr<Document> mDoc;
11634 void Document::PostUnblockOnloadEvent() {
11635 MOZ_RELEASE_ASSERT(NS_IsMainThread());
11636 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
11637 nsresult rv = Dispatch(TaskCategory::Other, evt.forget());
11638 if (NS_SUCCEEDED(rv)) {
11639 // Stabilize block count so we don't post more events while this one is up
11640 ++mOnloadBlockCount;
11641 } else {
11642 NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
11646 void Document::DoUnblockOnload() {
11647 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
11648 MOZ_ASSERT(mOnloadBlockCount != 0,
11649 "Shouldn't have a count of zero here, since we stabilized in "
11650 "PostUnblockOnloadEvent");
11652 --mOnloadBlockCount;
11654 if (mOnloadBlockCount != 0) {
11655 // We blocked again after the last unblock. Nothing to do here. We'll
11656 // post a new event when we unblock again.
11657 return;
11660 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11661 // -- it's not ours.
11662 if (mScriptGlobalObject) {
11663 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11664 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
11669 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
11670 for (nsIFrame* f = aFrame; f;
11671 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
11672 nsIContent* content = f->GetContent();
11673 if (!content) {
11674 continue;
11677 if (content->OwnerDoc() == this) {
11678 return content;
11680 // We must be in a subdocument so jump directly to the root frame.
11681 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
11682 // the containing document.
11683 f = f->PresContext()->GetPresShell()->GetRootFrame();
11686 return nullptr;
11689 void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
11690 const nsAString& aType, bool aInFrameSwap,
11691 bool aPersisted, bool aOnlySystemGroup) {
11692 if (!aDispatchTarget) {
11693 return;
11696 PageTransitionEventInit init;
11697 init.mBubbles = true;
11698 init.mCancelable = true;
11699 init.mPersisted = aPersisted;
11700 init.mInFrameSwap = aInFrameSwap;
11702 RefPtr<PageTransitionEvent> event =
11703 PageTransitionEvent::Constructor(this, aType, init);
11705 event->SetTrusted(true);
11706 event->SetTarget(this);
11707 if (aOnlySystemGroup) {
11708 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
11710 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
11711 nullptr);
11714 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
11715 bool aOnlySystemGroup) {
11716 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11717 nsCString uri;
11718 if (GetDocumentURI()) {
11719 uri = GetDocumentURI()->GetSpecOrDefault();
11721 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11722 ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
11725 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11726 MOZ_DIAGNOSTIC_ASSERT(
11727 inFrameLoaderSwap ==
11728 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11730 Element* root = GetRootElement();
11731 if (aPersisted && root) {
11732 // Send out notifications that our <link> elements are attached.
11733 RefPtr<nsContentList> links =
11734 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11736 uint32_t linkCount = links->Length(true);
11737 for (uint32_t i = 0; i < linkCount; ++i) {
11738 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
11742 // See Document
11743 if (!inFrameLoaderSwap) {
11744 if (aPersisted) {
11745 ImageTracker()->SetAnimatingState(true);
11748 // Set mIsShowing before firing events, in case those event handlers
11749 // move us around.
11750 mIsShowing = true;
11751 mVisible = true;
11753 UpdateVisibilityState();
11756 NotifyActivityChanged();
11758 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11759 aExternalResource.OnPageShow(aPersisted, nullptr);
11760 return CallState::Continue;
11762 EnumerateExternalResources(notifyExternal);
11764 if (mAnimationController) {
11765 mAnimationController->OnPageShow();
11768 if (!mIsBeingUsedAsImage) {
11769 // Dispatch observer notification to notify observers page is shown.
11770 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11771 if (os) {
11772 nsIPrincipal* principal = NodePrincipal();
11773 os->NotifyObservers(ToSupports(this),
11774 principal->IsSystemPrincipal() ? "chrome-page-shown"
11775 : "content-page-shown",
11776 nullptr);
11779 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11780 if (!target) {
11781 target = do_QueryInterface(GetWindow());
11783 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
11784 aPersisted, aOnlySystemGroup);
11788 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
11789 if (nsPresContext* presContext = aDocument.GetPresContext()) {
11790 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
11791 FullscreenEventType::Change, &aDocument, aTarget);
11792 presContext->RefreshDriver()->ScheduleFullscreenEvent(
11793 std::move(pendingEvent));
11797 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
11798 bool aOnlySystemGroup) {
11799 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11800 nsCString uri;
11801 if (GetDocumentURI()) {
11802 uri = GetDocumentURI()->GetSpecOrDefault();
11804 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11805 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
11808 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11809 MOZ_DIAGNOSTIC_ASSERT(
11810 inFrameLoaderSwap ==
11811 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11813 // Send out notifications that our <link> elements are detached,
11814 // but only if this is not a full unload.
11815 Element* root = GetRootElement();
11816 if (aPersisted && root) {
11817 RefPtr<nsContentList> links =
11818 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11820 uint32_t linkCount = links->Length(true);
11821 for (uint32_t i = 0; i < linkCount; ++i) {
11822 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved();
11826 if (mAnimationController) {
11827 mAnimationController->OnPageHide();
11830 if (!inFrameLoaderSwap) {
11831 if (aPersisted) {
11832 // We do not stop the animations (bug 1024343) when the page is refreshing
11833 // while being dragged out.
11834 ImageTracker()->SetAnimatingState(false);
11837 // Set mIsShowing before firing events, in case those event handlers
11838 // move us around.
11839 mIsShowing = false;
11840 mVisible = false;
11843 ExitPointerLock();
11845 if (!mIsBeingUsedAsImage) {
11846 // Dispatch observer notification to notify observers page is hidden.
11847 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11848 if (os) {
11849 nsIPrincipal* principal = NodePrincipal();
11850 os->NotifyObservers(ToSupports(this),
11851 principal->IsSystemPrincipal()
11852 ? "chrome-page-hidden"
11853 : "content-page-hidden",
11854 nullptr);
11857 // Now send out a PageHide event.
11858 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11859 if (!target) {
11860 target = do_QueryInterface(GetWindow());
11863 PageUnloadingEventTimeStamp timeStamp(this);
11864 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
11865 aPersisted, aOnlySystemGroup);
11869 if (!inFrameLoaderSwap) {
11870 UpdateVisibilityState();
11873 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11874 aExternalResource.OnPageHide(aPersisted, nullptr);
11875 return CallState::Continue;
11877 EnumerateExternalResources(notifyExternal);
11878 NotifyActivityChanged();
11880 ClearPendingFullscreenRequests(this);
11881 if (Fullscreen()) {
11882 // If this document was fullscreen, we should exit fullscreen in this
11883 // doctree branch. This ensures that if the user navigates while in
11884 // fullscreen mode we don't leave its still visible ancestor documents
11885 // in fullscreen mode. So exit fullscreen in the document's fullscreen
11886 // root document, as this will exit fullscreen in all the root's
11887 // descendant documents. Note that documents are removed from the
11888 // doctree by the time OnPageHide() is called, so we must store a
11889 // reference to the root (in Document::mFullscreenRoot) since we can't
11890 // just traverse the doctree to get the root.
11891 Document::ExitFullscreenInDocTree(this);
11893 // Since the document is removed from the doctree before OnPageHide() is
11894 // called, ExitFullscreen() can't traverse from the root down to *this*
11895 // document, so we must manually call CleanupFullscreenState() below too.
11896 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
11897 // so we *must* call it after ExitFullscreen(), not before.
11898 // OnPageHide() is called in every hidden (i.e. descendant) document,
11899 // so calling CleanupFullscreenState() here will ensure all hidden
11900 // documents have their fullscreen state reset.
11901 CleanupFullscreenState();
11903 // The fullscreenchange event is to be queued in the refresh driver,
11904 // however a hidden page wouldn't trigger that again, so it makes no
11905 // sense to dispatch such event here.
11909 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
11910 NS_ASSERTION(
11911 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
11912 "mSubtreeModifiedTargets not cleared after dispatching?");
11913 ++mSubtreeModifiedDepth;
11914 if (aTarget) {
11915 // MayDispatchMutationEvent is often called just before this method,
11916 // so it has already appended the node to mSubtreeModifiedTargets.
11917 int32_t count = mSubtreeModifiedTargets.Count();
11918 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
11919 mSubtreeModifiedTargets.AppendObject(aTarget);
11924 void Document::MutationEventDispatched(nsINode* aTarget) {
11925 if (--mSubtreeModifiedDepth) {
11926 return;
11929 int32_t count = mSubtreeModifiedTargets.Count();
11930 if (!count) {
11931 return;
11934 nsPIDOMWindowInner* window = GetInnerWindow();
11935 if (window &&
11936 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
11937 mSubtreeModifiedTargets.Clear();
11938 return;
11941 nsCOMArray<nsINode> realTargets;
11942 for (nsINode* possibleTarget : mSubtreeModifiedTargets) {
11943 if (possibleTarget->ChromeOnlyAccess()) {
11944 continue;
11947 nsINode* commonAncestor = nullptr;
11948 int32_t realTargetCount = realTargets.Count();
11949 for (int32_t j = 0; j < realTargetCount; ++j) {
11950 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
11951 possibleTarget, realTargets[j]);
11952 if (commonAncestor) {
11953 realTargets.ReplaceObjectAt(commonAncestor, j);
11954 break;
11957 if (!commonAncestor) {
11958 realTargets.AppendObject(possibleTarget);
11962 mSubtreeModifiedTargets.Clear();
11964 for (const nsCOMPtr<nsINode>& target : realTargets) {
11965 InternalMutationEvent mutation(true, eLegacySubtreeModified);
11966 // MOZ_KnownLive due to bug 1620312
11967 AsyncEventDispatcher::RunDOMEventWhenSafe(MOZ_KnownLive(*target), mutation);
11971 void Document::DestroyElementMaps() {
11972 #ifdef DEBUG
11973 mStyledLinksCleared = true;
11974 #endif
11975 mStyledLinks.Clear();
11976 // Notify ID change listeners before clearing the identifier map.
11977 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
11978 iter.Get()->ClearAndNotify();
11980 mIdentifierMap.Clear();
11981 mComposedShadowRoots.Clear();
11982 mResponsiveContent.Clear();
11983 IncrementExpandoGeneration(*this);
11986 void Document::RefreshLinkHrefs() {
11987 // Get a list of all links we know about. We will reset them, which will
11988 // remove them from the document, so we need a copy of what is in the
11989 // hashtable.
11990 const LinkArray linksToNotify = ToArray(mStyledLinks);
11992 // Reset all of our styled links.
11993 nsAutoScriptBlocker scriptBlocker;
11994 for (LinkArray::size_type i = 0; i < linksToNotify.Length(); i++) {
11995 linksToNotify[i]->ResetLinkState(true, linksToNotify[i]->ElementHasHref());
11999 nsresult Document::CloneDocHelper(Document* clone) const {
12000 clone->mIsStaticDocument = mCreatingStaticClone;
12002 // Init document
12003 nsresult rv = clone->Init();
12004 NS_ENSURE_SUCCESS(rv, rv);
12006 if (mCreatingStaticClone) {
12007 if (mOriginalDocument) {
12008 clone->mOriginalDocument = mOriginalDocument;
12009 } else {
12010 clone->mOriginalDocument = const_cast<Document*>(this);
12012 clone->mOriginalDocument->mLatestStaticClone = clone;
12013 clone->mOriginalDocument->mStaticCloneCount++;
12015 nsCOMPtr<nsILoadGroup> loadGroup;
12017 // |mDocumentContainer| is the container of the document that is being
12018 // created and not the original container. See CreateStaticClone function().
12019 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
12020 if (docLoader) {
12021 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
12023 nsCOMPtr<nsIChannel> channel = GetChannel();
12024 nsCOMPtr<nsIURI> uri;
12025 if (channel) {
12026 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
12027 } else {
12028 uri = Document::GetDocumentURI();
12030 clone->mChannel = channel;
12031 clone->mShouldResistFingerprinting = mShouldResistFingerprinting;
12032 if (uri) {
12033 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
12036 clone->mIsSrcdocDocument = mIsSrcdocDocument;
12037 clone->SetContainer(mDocumentContainer);
12039 // Setup the navigation time. This will be needed by any animations in the
12040 // document, even if they are only paused.
12041 MOZ_ASSERT(!clone->GetNavigationTiming(),
12042 "Navigation time was already set?");
12043 if (mTiming) {
12044 RefPtr<nsDOMNavigationTiming> timing =
12045 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
12046 clone->SetNavigationTiming(timing);
12048 clone->SetCsp(mCSP);
12051 // Now ensure that our clone has the same URI, base URI, and principal as us.
12052 // We do this after the mCreatingStaticClone block above, because that block
12053 // can set the base URI to an incorrect value in cases when base URI
12054 // information came from the channel. So we override explicitly, and do it
12055 // for all these properties, in case ResetToURI messes with any of the rest of
12056 // them.
12057 clone->SetDocumentURI(Document::GetDocumentURI());
12058 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
12059 clone->SetPrincipals(NodePrincipal(), mPartitionedPrincipal);
12060 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
12061 clone->mActiveCookiePrincipal = mActiveCookiePrincipal;
12062 // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
12063 // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
12064 // when printed standalone via window.print() (where there won't be a parent
12065 // document to grab the URI from).
12066 clone->mDocumentBaseURI = GetDocBaseURI();
12067 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
12068 clone->mReferrerInfo =
12069 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
12070 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
12072 bool hasHadScriptObject = true;
12073 nsIScriptGlobalObject* scriptObject =
12074 GetScriptHandlingObject(hasHadScriptObject);
12075 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
12076 if (mCreatingStaticClone) {
12077 // If we're doing a static clone (print, print preview), then we're going to
12078 // be setting a scope object after the clone. It's better to set it only
12079 // once, so we don't do that here. However, we do want to act as if there is
12080 // a script handling object. So we set mHasHadScriptHandlingObject.
12081 clone->mHasHadScriptHandlingObject = true;
12082 } else if (scriptObject) {
12083 clone->SetScriptHandlingObject(scriptObject);
12084 } else {
12085 clone->SetScopeObject(GetScopeObject());
12087 // Make the clone a data document
12088 clone->SetLoadedAsData(
12089 true,
12090 /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
12092 // Misc state
12094 // State from Document
12095 clone->mCharacterSet = mCharacterSet;
12096 clone->mCharacterSetSource = mCharacterSetSource;
12097 clone->SetCompatibilityMode(mCompatMode);
12098 clone->mBidiOptions = mBidiOptions;
12099 clone->mContentLanguage = mContentLanguage;
12100 clone->SetContentType(GetContentTypeInternal());
12101 clone->mSecurityInfo = mSecurityInfo;
12103 // State from Document
12104 clone->mType = mType;
12105 clone->mXMLDeclarationBits = mXMLDeclarationBits;
12106 clone->mBaseTarget = mBaseTarget;
12108 return NS_OK;
12111 void Document::NotifyLoading(bool aNewParentIsLoading,
12112 const ReadyState& aCurrentState,
12113 ReadyState aNewState) {
12114 // Mirror the top-level loading state down to all subdocuments
12115 bool was_loading = mAncestorIsLoading ||
12116 aCurrentState == READYSTATE_LOADING ||
12117 aCurrentState == READYSTATE_INTERACTIVE;
12118 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
12119 aNewState == READYSTATE_INTERACTIVE; // new value for state
12120 bool set_load_state = was_loading != is_loading;
12122 MOZ_LOG(
12123 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12124 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
12125 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
12126 "set_load_state: %d",
12127 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
12128 (int)aNewState, was_loading, is_loading, set_load_state));
12130 mAncestorIsLoading = aNewParentIsLoading;
12131 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
12132 // Tell our innerwindow (and thus TimeoutManager)
12133 nsPIDOMWindowInner* inner = GetInnerWindow();
12134 if (inner) {
12135 inner->SetActiveLoadingState(is_loading);
12137 BrowsingContext* context = GetBrowsingContext();
12138 if (context) {
12139 // Don't use PreOrderWalk to mirror this down; go down one level as a
12140 // time so we can set mAncestorIsLoading and take into account the
12141 // readystates of the subdocument. In the child process it will call
12142 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
12143 // iterate it's children
12144 for (auto& child : context->Children()) {
12145 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12146 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
12147 // Setting ancestor loading on a discarded browsing context has no
12148 // effect.
12149 Unused << child->SetAncestorLoading(is_loading);
12155 void Document::SetReadyStateInternal(ReadyState aReadyState,
12156 bool aUpdateTimingInformation) {
12157 if (aReadyState == READYSTATE_UNINITIALIZED) {
12158 // Transition back to uninitialized happens only to keep assertions happy
12159 // right before readyState transitions to something else. Make this
12160 // transition undetectable by Web content.
12161 mReadyState = aReadyState;
12162 return;
12165 if (IsTopLevelContentDocument()) {
12166 if (aReadyState == READYSTATE_LOADING) {
12167 AddToplevelLoadingDocument(this);
12168 } else if (aReadyState == READYSTATE_COMPLETE) {
12169 RemoveToplevelLoadingDocument(this);
12173 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
12174 mLoadingTimeStamp = TimeStamp::Now();
12176 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
12177 mReadyState = aReadyState;
12178 if (aUpdateTimingInformation && mTiming) {
12179 switch (aReadyState) {
12180 case READYSTATE_LOADING:
12181 mTiming->NotifyDOMLoading(GetDocumentURI());
12182 break;
12183 case READYSTATE_INTERACTIVE:
12184 mTiming->NotifyDOMInteractive(GetDocumentURI());
12185 break;
12186 case READYSTATE_COMPLETE:
12187 mTiming->NotifyDOMComplete(GetDocumentURI());
12188 break;
12189 default:
12190 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
12191 break;
12194 // At the time of loading start, we don't have timing object, record time.
12196 if (READYSTATE_INTERACTIVE == aReadyState &&
12197 NodePrincipal()->IsSystemPrincipal()) {
12198 if (!mXULPersist) {
12199 mXULPersist = new XULPersist(this);
12200 mXULPersist->Init();
12202 if (!mChromeObserver) {
12203 mChromeObserver = new ChromeObserver(this);
12204 mChromeObserver->Init();
12208 if (aUpdateTimingInformation) {
12209 RecordNavigationTiming(aReadyState);
12212 AsyncEventDispatcher::RunDOMEventWhenSafe(
12213 *this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
12216 void Document::GetReadyState(nsAString& aReadyState) const {
12217 switch (mReadyState) {
12218 case READYSTATE_LOADING:
12219 aReadyState.AssignLiteral(u"loading");
12220 break;
12221 case READYSTATE_INTERACTIVE:
12222 aReadyState.AssignLiteral(u"interactive");
12223 break;
12224 case READYSTATE_COMPLETE:
12225 aReadyState.AssignLiteral(u"complete");
12226 break;
12227 default:
12228 aReadyState.AssignLiteral(u"uninitialized");
12232 void Document::SuppressEventHandling(uint32_t aIncrease) {
12233 mEventsSuppressed += aIncrease;
12234 if (mEventsSuppressed == aIncrease) {
12235 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
12236 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12239 UpdateFrameRequestCallbackSchedulingState();
12240 for (uint32_t i = 0; i < aIncrease; ++i) {
12241 ScriptLoader()->AddExecuteBlocker();
12244 auto suppressInSubDoc = [aIncrease](Document& aSubDoc) {
12245 aSubDoc.SuppressEventHandling(aIncrease);
12246 return CallState::Continue;
12249 EnumerateSubDocuments(suppressInSubDoc);
12252 void Document::NotifyAbortedLoad() {
12253 // If we still have outstanding work blocking DOMContentLoaded,
12254 // then don't try to change the readystate now, but wait until
12255 // they finish and then do so.
12256 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
12257 mSetCompleteAfterDOMContentLoaded = true;
12258 return;
12261 // Otherwise we're fully done at this point, so set the
12262 // readystate to complete.
12263 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
12264 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
12268 MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents(
12269 nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) {
12270 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
12271 if (MOZ_UNLIKELY(!fm)) {
12272 return;
12275 nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments);
12276 for (uint32_t i = 0; i < documents.Length(); ++i) {
12277 nsCOMPtr<Document> document = std::move(documents[i]);
12278 // NB: Don't bother trying to fire delayed events on documents that were
12279 // closed before this event ran.
12280 if (!document->EventHandlingSuppressed()) {
12281 fm->FireDelayedEvents(document);
12282 RefPtr<PresShell> presShell = document->GetPresShell();
12283 if (presShell) {
12284 // Only fire events for active documents.
12285 bool fire = aFireEvents && document->GetInnerWindow() &&
12286 document->GetInnerWindow()->IsCurrentInnerWindow();
12287 presShell->FireOrClearDelayedEvents(fire);
12289 document->FireOrClearPostMessageEvents(aFireEvents);
12294 void Document::PreloadPictureClosed() {
12295 MOZ_ASSERT(mPreloadPictureDepth > 0);
12296 mPreloadPictureDepth--;
12297 if (mPreloadPictureDepth == 0) {
12298 mPreloadPictureFoundSource.SetIsVoid(true);
12302 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
12303 const nsAString& aSizesAttr,
12304 const nsAString& aTypeAttr,
12305 const nsAString& aMediaAttr) {
12306 // Nested pictures are not valid syntax, so while we'll eventually load them,
12307 // it's not worth tracking sources mixed between nesting levels to preload
12308 // them effectively.
12309 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
12310 // <picture> selects the first matching source, so if this returns a URI we
12311 // needn't consider new sources until a new <picture> is encountered.
12312 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
12313 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
12314 aMediaAttr, mPreloadPictureFoundSource);
12315 if (found && mPreloadPictureFoundSource.IsVoid()) {
12316 // Found an empty source, which counts
12317 mPreloadPictureFoundSource.SetIsVoid(false);
12322 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
12323 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
12324 const nsAString& aSizesAttr, bool* aIsImgSet) {
12325 nsString sourceURL;
12326 bool isImgSet;
12327 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
12328 // We're in a <picture> element and found a URI from a source previous to
12329 // this image, use it.
12330 sourceURL = mPreloadPictureFoundSource;
12331 isImgSet = true;
12332 } else {
12333 // Otherwise try to use this <img> as a source
12334 HTMLImageElement::SelectSourceForTagWithAttrs(
12335 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
12336 VoidString(), sourceURL);
12337 isImgSet = !aSrcsetAttr.IsEmpty();
12340 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
12341 if (sourceURL.IsEmpty()) {
12342 return nullptr;
12345 // Construct into URI using passed baseURI (the parser may know of base URI
12346 // changes that have not reached us)
12347 nsresult rv;
12348 nsCOMPtr<nsIURI> uri;
12349 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
12350 this, aBaseURI);
12351 if (NS_FAILED(rv)) {
12352 return nullptr;
12355 *aIsImgSet = isImgSet;
12357 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
12358 // this this <picture> share the same <sources> (though this is not valid per
12359 // spec)
12360 return uri.forget();
12363 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
12364 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
12365 bool aLinkPreload, uint64_t aEarlyHintPreloaderId) {
12366 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
12367 nsIRequest::LOAD_RECORD_START_REQUEST_DELAY |
12368 nsContentUtils::CORSModeToLoadImageFlags(
12369 Element::StringToCORSMode(aCrossOriginAttr));
12371 nsContentPolicyType policyType =
12372 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
12373 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
12375 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12376 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12378 RefPtr<imgRequestProxy> request;
12380 nsLiteralString initiator = aEarlyHintPreloaderId
12381 ? u"early-hints"_ns
12382 : (aLinkPreload ? u"link"_ns : u"img"_ns);
12384 nsresult rv = nsContentUtils::LoadImage(
12385 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
12386 nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request),
12387 policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId);
12389 // Pin image-reference to avoid evicting it from the img-cache before
12390 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
12391 // unlink
12392 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
12393 mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
12397 void Document::MaybePreLoadImage(nsIURI* aUri,
12398 const nsAString& aCrossOriginAttr,
12399 ReferrerPolicyEnum aReferrerPolicy,
12400 bool aIsImgSet, bool aLinkPreload,
12401 const TimeStamp& aInitTimestamp) {
12402 const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr);
12403 if (aLinkPreload) {
12404 // Check if the image was already preloaded in this document to avoid
12405 // duplicate preloading.
12406 PreloadHashKey key =
12407 PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode);
12408 if (!mPreloadService.PreloadExists(key)) {
12409 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12410 aLinkPreload, 0);
12412 return;
12415 // Early exit if the img is already present in the img-cache
12416 // which indicates that the "real" load has already started and
12417 // that we shouldn't preload it.
12418 if (nsContentUtils::IsImageAvailable(aUri, NodePrincipal(), corsMode, this)) {
12419 return;
12422 #ifdef NIGHTLY_BUILD
12423 Telemetry::Accumulate(
12424 Telemetry::DOCUMENT_PRELOAD_IMAGE_ASYNCOPEN_DELAY,
12425 static_cast<uint32_t>(
12426 (TimeStamp::Now() - aInitTimestamp).ToMilliseconds()));
12427 #endif
12429 // Image not in cache - trigger preload
12430 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload,
12434 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
12435 NS_MutateURI mutator(aOrigURI);
12436 if (NS_FAILED(mutator.GetStatus())) {
12437 return;
12440 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
12441 // which ignores the path and uses only the origin. The other is for the
12442 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
12443 // non-Anonymous preconnects create different connections on the wire and
12444 // therefore should not be considred duplicates of each other and we
12445 // normalize the path before putting it in the hash to accomplish that.
12447 if (aCORSMode == CORS_ANONYMOUS) {
12448 mutator.SetPathQueryRef("/anonymous"_ns);
12449 } else {
12450 mutator.SetPathQueryRef("/"_ns);
12453 nsCOMPtr<nsIURI> uri;
12454 nsresult rv = mutator.Finalize(uri);
12455 if (NS_FAILED(rv)) {
12456 return;
12459 const bool existingEntryFound =
12460 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
12461 if (entry) {
12462 return true;
12464 entry.Insert(true);
12465 return false;
12467 if (existingEntryFound) {
12468 return;
12471 nsCOMPtr<nsISpeculativeConnect> speculator(
12472 do_QueryInterface(nsContentUtils::GetIOService()));
12473 if (!speculator) {
12474 return;
12477 OriginAttributes oa;
12478 StoragePrincipalHelper::GetOriginAttributesForNetworkState(this, oa);
12479 speculator->SpeculativeConnectWithOriginAttributesNative(
12480 uri, std::move(oa), nullptr, aCORSMode == CORS_ANONYMOUS);
12483 void Document::ForgetImagePreload(nsIURI* aURI) {
12484 // Checking count is faster than hashing the URI in the common
12485 // case of empty table.
12486 if (mPreloadingImages.Count() != 0) {
12487 nsCOMPtr<imgIRequest> req;
12488 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
12489 if (req) {
12490 // Make sure to cancel the request so imagelib knows it's gone.
12491 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
12496 void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates,
12497 bool aNotify) {
12498 const DocumentState oldStates = mDocumentState;
12499 if (aMaybeChangedStates.HasAtLeastOneOfStates(
12500 DocumentState::ALL_LOCALEDIR_BITS)) {
12501 mDocumentState &= ~DocumentState::ALL_LOCALEDIR_BITS;
12502 if (IsDocumentRightToLeft()) {
12503 mDocumentState |= DocumentState::RTL_LOCALE;
12504 } else {
12505 mDocumentState |= DocumentState::LTR_LOCALE;
12509 if (aMaybeChangedStates.HasAtLeastOneOfStates(DocumentState::LWTHEME)) {
12510 if (ComputeDocumentLWTheme()) {
12511 mDocumentState |= DocumentState::LWTHEME;
12512 } else {
12513 mDocumentState &= ~DocumentState::LWTHEME;
12517 if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) {
12518 if (IsTopLevelWindowInactive()) {
12519 mDocumentState |= DocumentState::WINDOW_INACTIVE;
12520 } else {
12521 mDocumentState &= ~DocumentState::WINDOW_INACTIVE;
12525 const DocumentState changedStates = oldStates ^ mDocumentState;
12526 if (aNotify && !changedStates.IsEmpty()) {
12527 if (PresShell* ps = GetObservingPresShell()) {
12528 ps->DocumentStatesChanged(changedStates);
12533 namespace {
12536 * Stub for LoadSheet(), since all we want is to get the sheet into
12537 * the CSSLoader's style cache
12539 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
12540 ~StubCSSLoaderObserver() = default;
12542 public:
12543 NS_IMETHOD
12544 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
12545 NS_DECL_ISUPPORTS
12547 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
12549 } // namespace
12551 SheetPreloadStatus Document::PreloadStyle(
12552 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
12553 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aIntegrity,
12554 css::StylePreloadKind aKind, uint64_t aEarlyHintPreloaderId) {
12555 MOZ_ASSERT(aKind != css::StylePreloadKind::None);
12557 // The CSSLoader will retain this object after we return.
12558 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
12560 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12561 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12563 // Charset names are always ASCII.
12564 auto result = CSSLoader()->LoadSheet(
12565 uri, aKind, aEncoding, referrerInfo, obs, aEarlyHintPreloaderId,
12566 Element::StringToCORSMode(aCrossOriginAttr), aIntegrity);
12567 if (result.isErr()) {
12568 return SheetPreloadStatus::Errored;
12570 RefPtr<StyleSheet> sheet = result.unwrap();
12571 if (sheet->IsComplete()) {
12572 return SheetPreloadStatus::AlreadyComplete;
12574 return SheetPreloadStatus::InProgress;
12577 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
12578 return CSSLoader()
12579 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
12580 .unwrapOr(nullptr);
12583 void Document::ResetDocumentDirection() {
12584 if (!nsContentUtils::IsChromeDoc(this)) {
12585 return;
12587 UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true);
12590 bool Document::IsDocumentRightToLeft() {
12591 if (!nsContentUtils::IsChromeDoc(this)) {
12592 return false;
12594 // setting the localedir attribute on the root element forces a
12595 // specific direction for the document.
12596 Element* element = GetRootElement();
12597 if (element) {
12598 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
12599 nullptr};
12600 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
12601 strings, eCaseMatters)) {
12602 case 0:
12603 return false;
12604 case 1:
12605 return true;
12606 default:
12607 break; // otherwise, not a valid value, so fall through
12611 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
12612 !mDocumentURI->SchemeIs("resource")) {
12613 return false;
12616 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
12619 class nsDelayedEventDispatcher : public Runnable {
12620 public:
12621 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
12622 : mozilla::Runnable("nsDelayedEventDispatcher"),
12623 mDocuments(std::move(aDocuments)) {}
12624 virtual ~nsDelayedEventDispatcher() = default;
12626 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
12627 // bug 1535398.
12628 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
12629 FireOrClearDelayedEvents(std::move(mDocuments), true);
12630 return NS_OK;
12633 private:
12634 nsTArray<nsCOMPtr<Document>> mDocuments;
12637 static void GetAndUnsuppressSubDocuments(
12638 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
12639 if (aDocument.EventHandlingSuppressed() > 0) {
12640 aDocument.DecreaseEventSuppression();
12641 aDocument.ScriptLoader()->RemoveExecuteBlocker();
12643 aDocuments.AppendElement(&aDocument);
12644 auto recurse = [&aDocuments](Document& aSubDoc) {
12645 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
12646 return CallState::Continue;
12648 aDocument.EnumerateSubDocuments(recurse);
12651 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
12652 nsTArray<nsCOMPtr<Document>> documents;
12653 GetAndUnsuppressSubDocuments(*this, documents);
12655 for (nsCOMPtr<Document>& doc : documents) {
12656 if (!doc->EventHandlingSuppressed()) {
12657 if (WindowGlobalChild* wgc = doc->GetWindowGlobalChild()) {
12658 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12661 MOZ_ASSERT(NS_IsMainThread());
12662 nsTArray<RefPtr<net::ChannelEventQueue>> queues =
12663 std::move(doc->mSuspendedQueues);
12664 for (net::ChannelEventQueue* queue : queues) {
12665 queue->Resume();
12668 // If there have been any events driven by the refresh driver which were
12669 // delayed due to events being suppressed in this document, make sure
12670 // there is a refresh scheduled soon so the events will run.
12671 if (doc->mHasDelayedRefreshEvent) {
12672 doc->mHasDelayedRefreshEvent = false;
12674 if (doc->mPresShell) {
12675 nsRefreshDriver* rd =
12676 doc->mPresShell->GetPresContext()->RefreshDriver();
12677 rd->RunDelayedEventsSoon();
12683 if (aFireEvents) {
12684 MOZ_RELEASE_ASSERT(NS_IsMainThread());
12685 nsCOMPtr<nsIRunnable> ded =
12686 new nsDelayedEventDispatcher(std::move(documents));
12687 Dispatch(TaskCategory::Other, ded.forget());
12688 } else {
12689 FireOrClearDelayedEvents(std::move(documents), false);
12693 bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
12694 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
12697 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
12698 MOZ_ASSERT(NS_IsMainThread());
12699 MOZ_ASSERT(EventHandlingSuppressed());
12700 mSuspendedQueues.AppendElement(aQueue);
12703 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
12704 MOZ_ASSERT(NS_IsMainThread());
12706 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
12707 mSuspendedPostMessageEvents.AppendElement(aEvent);
12708 return true;
12710 return false;
12713 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
12714 nsTArray<RefPtr<PostMessageEvent>> events =
12715 std::move(mSuspendedPostMessageEvents);
12717 if (aFireEvents) {
12718 for (PostMessageEvent* event : events) {
12719 event->Run();
12724 void Document::SetSuppressedEventListener(EventListener* aListener) {
12725 mSuppressedEventListener = aListener;
12726 auto setOnSubDocs = [&](Document& aDocument) {
12727 aDocument.SetSuppressedEventListener(aListener);
12728 return CallState::Continue;
12730 EnumerateSubDocuments(setOnSubDocs);
12733 bool Document::IsActive() const {
12734 return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
12735 !GetBrowsingContext()->IsInBFCache();
12738 nsISupports* Document::GetCurrentContentSink() {
12739 return mParser ? mParser->GetContentSink() : nullptr;
12742 Document* Document::GetTemplateContentsOwner() {
12743 if (!mTemplateContentsOwner) {
12744 bool hasHadScriptObject = true;
12745 nsIScriptGlobalObject* scriptObject =
12746 GetScriptHandlingObject(hasHadScriptObject);
12748 nsCOMPtr<Document> document;
12749 nsresult rv = NS_NewDOMDocument(
12750 getter_AddRefs(document),
12751 u""_ns, // aNamespaceURI
12752 u""_ns, // aQualifiedName
12753 nullptr, // aDoctype
12754 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
12755 true, // aLoadedAsData
12756 scriptObject, // aEventObject
12757 IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
12758 NS_ENSURE_SUCCESS(rv, nullptr);
12760 mTemplateContentsOwner = document;
12761 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
12763 if (!scriptObject) {
12764 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
12767 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
12769 // Set |mTemplateContentsOwner| as the template contents owner of itself so
12770 // that it is the template contents owner of nested template elements.
12771 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
12774 MOZ_ASSERT(mTemplateContentsOwner->IsTemplateContentsOwner());
12775 return mTemplateContentsOwner;
12778 // https://html.spec.whatwg.org/#the-autofocus-attribute
12779 void Document::ElementWithAutoFocusInserted(Element* aAutoFocusCandidate) {
12780 BrowsingContext* bc = GetBrowsingContext();
12781 if (!bc) {
12782 return;
12785 // If target is not fully active, then return.
12786 if (!IsCurrentActiveDocument()) {
12787 return;
12790 // If target's active sandboxing flag set has the sandboxed automatic features
12791 // browsing context flag, then return.
12792 if (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES) {
12793 return;
12796 // For each ancestorBC of target's browsing context's ancestor browsing
12797 // contexts: if ancestorBC's active document's origin is not same origin with
12798 // target's origin, then return.
12799 while (bc) {
12800 BrowsingContext* parent = bc->GetParent();
12801 if (!parent) {
12802 break;
12804 // AncestorBC is not the same site
12805 if (!parent->IsInProcess()) {
12806 return;
12809 Document* currentDocument = bc->GetDocument();
12810 if (!currentDocument) {
12811 return;
12814 Document* parentDocument = parent->GetDocument();
12815 if (!parentDocument) {
12816 return;
12819 // Not same origin
12820 if (!currentDocument->NodePrincipal()->Equals(
12821 parentDocument->NodePrincipal())) {
12822 return;
12825 bc = parent;
12827 MOZ_ASSERT(bc->IsTop());
12829 Document* topDocument = bc->GetDocument();
12830 MOZ_ASSERT(topDocument);
12831 topDocument->AppendAutoFocusCandidateToTopDocument(aAutoFocusCandidate);
12834 void Document::ScheduleFlushAutoFocusCandidates() {
12835 MOZ_ASSERT(mPresShell && mPresShell->DidInitialize());
12836 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12837 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
12838 rd->ScheduleAutoFocusFlush(this);
12842 void Document::AppendAutoFocusCandidateToTopDocument(
12843 Element* aAutoFocusCandidate) {
12844 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12845 if (mAutoFocusFired) {
12846 return;
12849 if (!HasAutoFocusCandidates()) {
12850 // PresShell may be initialized later
12851 if (mPresShell && mPresShell->DidInitialize()) {
12852 ScheduleFlushAutoFocusCandidates();
12856 nsWeakPtr element = do_GetWeakReference(aAutoFocusCandidate);
12857 mAutoFocusCandidates.RemoveElement(element);
12858 mAutoFocusCandidates.AppendElement(element);
12861 void Document::SetAutoFocusFired() {
12862 mAutoFocusCandidates.Clear();
12863 mAutoFocusFired = true;
12866 // https://html.spec.whatwg.org/#flush-autofocus-candidates
12867 void Document::FlushAutoFocusCandidates() {
12868 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12869 if (mAutoFocusFired) {
12870 return;
12873 if (!mPresShell) {
12874 return;
12877 MOZ_ASSERT(HasAutoFocusCandidates());
12878 MOZ_ASSERT(mPresShell->DidInitialize());
12880 nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetWindow();
12881 // We should be the top document
12882 if (!topWindow) {
12883 return;
12886 #ifdef DEBUG
12888 // Trying to find the top window (equivalent to window.top).
12889 nsCOMPtr<nsPIDOMWindowOuter> top = topWindow->GetInProcessTop();
12890 MOZ_ASSERT(topWindow == top);
12892 #endif
12894 // Don't steal the focus from the user
12895 if (topWindow->GetFocusedElement()) {
12896 SetAutoFocusFired();
12897 return;
12900 MOZ_ASSERT(mDocumentURI);
12901 nsAutoCString ref;
12902 // GetRef never fails
12903 nsresult rv = mDocumentURI->GetRef(ref);
12904 if (NS_SUCCEEDED(rv) &&
12905 nsContentUtils::GetTargetElement(this, NS_ConvertUTF8toUTF16(ref))) {
12906 SetAutoFocusFired();
12907 return;
12910 nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mAutoFocusCandidates);
12911 while (iter.HasMore()) {
12912 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(iter.GetNext());
12913 if (!autoFocusElement) {
12914 continue;
12916 RefPtr<Document> autoFocusElementDoc = autoFocusElement->OwnerDoc();
12917 // Get the latest info about the frame and allow scripts
12918 // to run which might affect the focusability of this element.
12919 autoFocusElementDoc->FlushPendingNotifications(FlushType::Frames);
12921 // Above layout flush may cause the PresShell to disappear.
12922 if (!mPresShell) {
12923 return;
12926 // Re-get the element because the ownerDoc() might have changed
12927 autoFocusElementDoc = autoFocusElement->OwnerDoc();
12928 BrowsingContext* bc = autoFocusElementDoc->GetBrowsingContext();
12929 if (!bc) {
12930 continue;
12933 // If doc is not fully active, then remove element from candidates, and
12934 // continue.
12935 if (!autoFocusElementDoc->IsCurrentActiveDocument()) {
12936 iter.Remove();
12937 continue;
12940 nsCOMPtr<nsIContentSink> sink =
12941 do_QueryInterface(autoFocusElementDoc->GetCurrentContentSink());
12942 if (sink) {
12943 nsHtml5TreeOpExecutor* executor =
12944 static_cast<nsHtml5TreeOpExecutor*>(sink->AsExecutor());
12945 if (executor) {
12946 // This is a HTML5 document
12947 MOZ_ASSERT(autoFocusElementDoc->IsHTMLDocument());
12948 // If doc's script-blocking style sheet counter is greater than 0, th
12949 // return.
12950 if (executor->WaitForPendingSheets()) {
12951 // In this case, element is the currently-best candidate, but doc is
12952 // not ready for autofocusing. We'll try again next time flush
12953 // autofocus candidates is called.
12954 ScheduleFlushAutoFocusCandidates();
12955 return;
12960 // The autofocus element could be moved to a different
12961 // top level BC.
12962 if (bc->Top()->GetDocument() != this) {
12963 continue;
12966 iter.Remove();
12968 // Let inclusiveAncestorDocuments be a list consisting of doc, plus the
12969 // active documents of each of doc's browsing context's ancestor browsing
12970 // contexts.
12971 // If any Document in inclusiveAncestorDocuments has non-null target
12972 // element, then continue.
12973 bool shouldFocus = true;
12974 while (bc) {
12975 Document* doc = bc->GetDocument();
12976 if (!doc) {
12977 shouldFocus = false;
12978 break;
12981 nsIURI* uri = doc->GetDocumentURI();
12982 if (!uri) {
12983 shouldFocus = false;
12984 break;
12987 nsAutoCString ref;
12988 nsresult rv = uri->GetRef(ref);
12989 // If there is an element in the document tree that has an ID equal to
12990 // fragment
12991 if (NS_SUCCEEDED(rv) &&
12992 nsContentUtils::GetTargetElement(doc, NS_ConvertUTF8toUTF16(ref))) {
12993 shouldFocus = false;
12994 break;
12996 bc = bc->GetParent();
12999 if (!shouldFocus) {
13000 continue;
13003 MOZ_ASSERT(topWindow);
13004 if (TryAutoFocusCandidate(*autoFocusElement)) {
13005 // We've successfully autofocused an element, don't
13006 // need to try to focus the rest.
13007 SetAutoFocusFired();
13008 break;
13012 if (HasAutoFocusCandidates()) {
13013 ScheduleFlushAutoFocusCandidates();
13017 bool Document::TryAutoFocusCandidate(Element& aElement) {
13018 const FocusOptions options;
13019 if (RefPtr<Element> target = nsFocusManager::GetTheFocusableArea(
13020 &aElement, nsFocusManager::ProgrammaticFocusFlags(options))) {
13021 target->Focus(options, CallerType::NonSystem, IgnoreErrors());
13022 return true;
13025 return false;
13028 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
13029 if (!aDocumentURI) {
13030 return;
13033 nsAutoCString ref;
13035 // Since all URI's that pass through here aren't URL's we can't
13036 // rely on the nsIURI implementation for providing a way for
13037 // finding the 'ref' part of the URI, we'll haveto revert to
13038 // string routines for finding the data past '#'
13040 nsresult rv = aDocumentURI->GetSpec(ref);
13041 if (NS_FAILED(rv)) {
13042 Unused << aDocumentURI->GetRef(mScrollToRef);
13043 return;
13046 nsReadingIterator<char> start, end;
13048 ref.BeginReading(start);
13049 ref.EndReading(end);
13051 if (FindCharInReadable('#', start, end)) {
13052 ++start; // Skip over the '#'
13054 mScrollToRef = Substring(start, end);
13058 void Document::ScrollToRef() {
13059 if (mScrolledToRefAlready) {
13060 RefPtr<PresShell> presShell = GetPresShell();
13061 if (presShell) {
13062 presShell->ScrollToAnchor();
13064 return;
13067 if (mScrollToRef.IsEmpty()) {
13068 return;
13071 RefPtr<PresShell> presShell = GetPresShell();
13072 if (presShell) {
13073 nsresult rv = NS_ERROR_FAILURE;
13074 // We assume that the bytes are in UTF-8, as it says in the spec:
13075 // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
13076 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
13077 // Check an empty string which might be caused by the UTF-8 conversion
13078 if (!ref.IsEmpty()) {
13079 // Note that GoToAnchor will handle flushing layout as needed.
13080 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
13081 } else {
13082 rv = NS_ERROR_FAILURE;
13085 if (NS_FAILED(rv)) {
13086 nsAutoCString buff;
13087 const bool unescaped =
13088 NS_UnescapeURL(mScrollToRef.BeginReading(), mScrollToRef.Length(),
13089 /*aFlags =*/0, buff);
13091 // This attempt is only necessary if characters were unescaped.
13092 if (unescaped) {
13093 NS_ConvertUTF8toUTF16 utf16Str(buff);
13094 if (!utf16Str.IsEmpty()) {
13095 rv = presShell->GoToAnchor(utf16Str,
13096 mChangeScrollPosWhenScrollingToRef);
13100 // If UTF-8 URI failed then try to assume the string as a
13101 // document's charset.
13102 if (NS_FAILED(rv)) {
13103 const Encoding* encoding = GetDocumentCharacterSet();
13104 rv = encoding->DecodeWithoutBOMHandling(unescaped ? buff : mScrollToRef,
13105 ref);
13106 if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
13107 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
13111 if (NS_SUCCEEDED(rv)) {
13112 mScrolledToRefAlready = true;
13117 void Document::RegisterActivityObserver(nsISupports* aSupports) {
13118 if (!mActivityObservers) {
13119 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
13121 mActivityObservers->Insert(aSupports);
13124 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
13125 if (!mActivityObservers) {
13126 return false;
13128 return mActivityObservers->EnsureRemoved(aSupports);
13131 void Document::EnumerateActivityObservers(
13132 ActivityObserverEnumerator aEnumerator) {
13133 if (!mActivityObservers) {
13134 return;
13137 const auto keyArray =
13138 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
13139 for (auto& observer : keyArray) {
13140 aEnumerator(observer.get());
13144 void Document::RegisterPendingLinkUpdate(Link* aLink) {
13145 if (aLink->HasPendingLinkUpdate()) {
13146 return;
13149 aLink->SetHasPendingLinkUpdate();
13151 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
13152 nsCOMPtr<nsIRunnable> event =
13153 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
13154 &Document::FlushPendingLinkUpdates);
13155 // Do this work in a second in the worst case.
13156 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
13157 EventQueuePriority::Idle);
13158 if (NS_FAILED(rv)) {
13159 // If during shutdown posting a runnable doesn't succeed, we probably
13160 // don't need to update link states.
13161 return;
13163 mHasLinksToUpdateRunnable = true;
13166 mLinksToUpdate.InfallibleAppend(aLink);
13169 void Document::FlushPendingLinkUpdates() {
13170 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
13171 MOZ_ASSERT(mHasLinksToUpdateRunnable);
13172 mHasLinksToUpdateRunnable = false;
13174 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
13175 mFlushingPendingLinkUpdates = true;
13177 while (!mLinksToUpdate.IsEmpty()) {
13178 LinksToUpdateList links(std::move(mLinksToUpdate));
13179 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
13180 Link* link = iter.Get();
13181 Element* element = link->GetElement();
13182 if (element->OwnerDoc() == this) {
13183 link->ClearHasPendingLinkUpdate();
13184 if (element->IsInComposedDoc()) {
13185 element->UpdateLinkState(link->LinkState());
13193 * Retrieves the node in a static-clone document that corresponds to aOrigNode,
13194 * which is a node in the original document from which aStaticClone was cloned.
13196 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
13197 Document& aStaticClone) {
13198 MOZ_ASSERT(aOrigNode);
13200 // Selections in anonymous subtrees aren't supported.
13201 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
13202 return nullptr;
13205 // If the node is disconnected, this is a bug in the selection code, but it
13206 // can happen with shadow DOM so handle it.
13207 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
13208 return nullptr;
13211 AutoTArray<Maybe<uint32_t>, 32> indexArray;
13212 const nsINode* current = aOrigNode;
13213 while (const nsINode* parent = current->GetParentNode()) {
13214 Maybe<uint32_t> index = parent->ComputeIndexOf(current);
13215 NS_ENSURE_TRUE(index.isSome(), nullptr);
13216 indexArray.AppendElement(std::move(index));
13217 current = parent;
13219 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
13220 nsINode* correspondingNode = [&]() -> nsINode* {
13221 if (current->IsDocument()) {
13222 return &aStaticClone;
13224 const auto* shadow = ShadowRoot::FromNode(*current);
13225 if (!shadow) {
13226 return nullptr;
13228 nsINode* correspondingHost =
13229 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
13230 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
13231 return nullptr;
13233 return correspondingHost->AsElement()->GetShadowRoot();
13234 }();
13236 if (NS_WARN_IF(!correspondingNode)) {
13237 return nullptr;
13239 for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
13240 correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
13241 NS_ENSURE_TRUE(correspondingNode, nullptr);
13243 return correspondingNode;
13247 * Caches the selection ranges from the source document onto the static clone in
13248 * case the "Print Selection Only" functionality is invoked.
13250 * Note that we cannot use the selection obtained from GetOriginalDocument()
13251 * since that selection may have mutated after the print was invoked.
13253 * Note also that because nsRange objects point into a specific document's
13254 * nodes, we cannot reuse an array of nsRange objects across multiple static
13255 * clone documents. For that reason we cache a new array of ranges on each
13256 * static clone that we create.
13258 * TODO(emilio): This can be simplified once we don't re-clone from static
13259 * documents.
13261 * @param aSourceDoc the document from which we are caching selection ranges
13262 * @param aStaticClone the document that will hold the cache
13263 * @return true if a selection range was cached
13265 static void CachePrintSelectionRanges(const Document& aSourceDoc,
13266 Document& aStaticClone) {
13267 MOZ_ASSERT(aStaticClone.IsStaticDocument());
13268 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc));
13269 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
13271 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
13273 // When the user opts to "Print Selection Only", the print code prefers any
13274 // selection in the static clone corresponding to the focused frame. If this
13275 // is that static clone, flag it for the printing code:
13276 const bool isFocusedDoc = [&] {
13277 if (sourceDocIsStatic) {
13278 return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc));
13280 nsPIDOMWindowOuter* window = aSourceDoc.GetWindow();
13281 if (!window) {
13282 return false;
13284 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
13285 if (!rootWindow) {
13286 return false;
13288 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
13289 nsFocusManager::GetFocusedDescendant(rootWindow,
13290 nsFocusManager::eIncludeAllDescendants,
13291 getter_AddRefs(focusedWindow));
13292 return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc;
13293 }();
13294 if (isFocusedDoc) {
13295 aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc,
13296 reinterpret_cast<void*>(true));
13299 const Selection* origSelection = nullptr;
13300 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
13302 if (sourceDocIsStatic) {
13303 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
13304 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
13305 } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
13306 origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
13309 if (!origSelection && !origRanges) {
13310 return;
13313 const uint32_t rangeCount =
13314 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
13315 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
13317 for (const uint32_t i : IntegerRange(rangeCount)) {
13318 MOZ_ASSERT_IF(!sourceDocIsStatic,
13319 origSelection->RangeCount() == rangeCount);
13320 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
13321 : origSelection->GetRangeAt(i);
13322 MOZ_ASSERT(range);
13323 nsINode* startContainer = range->GetStartContainer();
13324 nsINode* endContainer = range->GetEndContainer();
13326 if (!startContainer || !endContainer) {
13327 continue;
13330 nsINode* startNode =
13331 GetCorrespondingNodeInDocument(startContainer, aStaticClone);
13332 nsINode* endNode =
13333 GetCorrespondingNodeInDocument(endContainer, aStaticClone);
13335 if (NS_WARN_IF(!startNode || !endNode)) {
13336 continue;
13339 RefPtr<nsRange> clonedRange =
13340 nsRange::Create(startNode, range->StartOffset(), endNode,
13341 range->EndOffset(), IgnoreErrors());
13342 if (clonedRange && !clonedRange->Collapsed()) {
13343 printRanges->AppendElement(std::move(clonedRange));
13347 if (printRanges->IsEmpty()) {
13348 return;
13351 aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
13352 printRanges.release(),
13353 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
13356 already_AddRefed<Document> Document::CreateStaticClone(
13357 nsIDocShell* aCloneContainer, nsIContentViewer* aViewer,
13358 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
13359 MOZ_ASSERT(!mCreatingStaticClone);
13360 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
13361 MOZ_DIAGNOSTIC_ASSERT(aViewer);
13363 mCreatingStaticClone = true;
13364 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
13365 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
13367 auto raii = MakeScopeExit([&] {
13368 RemoveProperty(nsGkAtoms::adoptedsheetclones);
13369 mCreatingStaticClone = false;
13372 // Make document use different container during cloning.
13374 // FIXME(emilio): Why is this needed?
13375 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
13376 SetContainer(nsDocShell::Cast(aCloneContainer));
13377 IgnoredErrorResult rv;
13378 nsCOMPtr<nsINode> clonedNode = CloneNode(true, rv);
13379 SetContainer(originalShell);
13380 if (rv.Failed()) {
13381 return nullptr;
13384 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
13385 if (!clonedDoc) {
13386 return nullptr;
13389 size_t sheetsCount = SheetCount();
13390 for (size_t i = 0; i < sheetsCount; ++i) {
13391 RefPtr<StyleSheet> sheet = SheetAt(i);
13392 if (sheet) {
13393 if (sheet->IsApplicable()) {
13394 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13395 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13396 if (clonedSheet) {
13397 clonedDoc->AddStyleSheet(clonedSheet);
13402 clonedDoc->CloneAdoptedSheetsFrom(*this);
13404 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
13405 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
13406 for (StyleSheet* sheet : sheets) {
13407 if (sheet->IsApplicable()) {
13408 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13409 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13410 if (clonedSheet) {
13411 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
13412 clonedSheet);
13418 // Font faces created with the JS API will not be reflected in the
13419 // stylesheets and need to be copied over to the cloned document.
13420 if (const FontFaceSet* set = GetFonts()) {
13421 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
13424 clonedDoc->mReferrerInfo =
13425 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
13426 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
13427 CachePrintSelectionRanges(*this, *clonedDoc);
13429 // We're done with the clone, embed ourselves into the document viewer and
13430 // clone our children. The order here is pretty important, because our
13431 // document our document needs to have an owner global before we can create
13432 // the frame loaders for subdocuments.
13433 aViewer->SetDocument(clonedDoc);
13435 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
13437 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
13438 for (const auto& clone : pendingClones) {
13439 RefPtr<Element> element = do_QueryObject(clone.mElement);
13440 RefPtr<nsFrameLoader> frameLoader =
13441 nsFrameLoader::Create(element, /* aNetworkCreated */ false);
13443 if (NS_WARN_IF(!frameLoader)) {
13444 continue;
13447 clone.mElement->SetFrameLoader(frameLoader);
13449 nsresult rv = frameLoader->FinishStaticClone(
13450 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
13451 Unused << NS_WARN_IF(NS_FAILED(rv));
13454 return clonedDoc.forget();
13457 void Document::UnlinkOriginalDocumentIfStatic() {
13458 if (IsStaticDocument() && mOriginalDocument) {
13459 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
13460 mOriginalDocument->mStaticCloneCount--;
13461 mOriginalDocument = nullptr;
13463 MOZ_ASSERT(!mOriginalDocument);
13466 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
13467 int32_t* aHandle) {
13468 nsresult rv = mFrameRequestManager.Schedule(aCallback, aHandle);
13469 if (NS_FAILED(rv)) {
13470 return rv;
13473 UpdateFrameRequestCallbackSchedulingState();
13474 return NS_OK;
13477 void Document::CancelFrameRequestCallback(int32_t aHandle) {
13478 if (mFrameRequestManager.Cancel(aHandle)) {
13479 UpdateFrameRequestCallbackSchedulingState();
13483 bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
13484 return mFrameRequestManager.IsCanceled(aHandle);
13487 nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
13488 // Get the document's current state object. This is the object backing both
13489 // history.state and popStateEvent.state.
13491 // mStateObjectContainer may be null; this just means that there's no
13492 // current state object.
13494 if (!mCachedStateObjectValid) {
13495 if (mStateObjectContainer) {
13496 AutoJSAPI jsapi;
13497 // Init with null is "OK" in the sense that it will just fail.
13498 if (!jsapi.Init(GetScopeObject())) {
13499 return NS_ERROR_UNEXPECTED;
13501 JS::Rooted<JS::Value> value(jsapi.cx());
13502 nsresult rv =
13503 mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value);
13504 NS_ENSURE_SUCCESS(rv, rv);
13506 mCachedStateObject = value;
13507 if (!value.isNullOrUndefined()) {
13508 mozilla::HoldJSObjects(this);
13510 } else {
13511 mCachedStateObject = JS::NullValue();
13513 mCachedStateObjectValid = true;
13516 aState.set(mCachedStateObject);
13517 return NS_OK;
13520 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
13521 mTiming = aTiming;
13522 if (!mLoadingTimeStamp.IsNull() && mTiming) {
13523 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(), mLoadingTimeStamp);
13526 // If there's already the DocumentTimeline instance, tell it since the
13527 // DocumentTimeline is based on both the navigation start time stamp and the
13528 // refresh driver timestamp.
13529 if (mDocumentTimeline) {
13530 mDocumentTimeline->UpdateLastRefreshDriverTime();
13534 nsContentList* Document::ImageMapList() {
13535 if (!mImageMaps) {
13536 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
13537 nsGkAtoms::map);
13540 return mImageMaps;
13543 #define DEPRECATED_OPERATION(_op) #_op "Warning",
13544 static const char* kDeprecationWarnings[] = {
13545 #include "nsDeprecatedOperationList.h"
13546 nullptr};
13547 #undef DEPRECATED_OPERATION
13549 #define DOCUMENT_WARNING(_op) #_op "Warning",
13550 static const char* kDocumentWarnings[] = {
13551 #include "nsDocumentWarningList.h"
13552 nullptr};
13553 #undef DOCUMENT_WARNING
13555 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
13556 switch (aOperation) {
13557 #define DEPRECATED_OPERATION(_op) \
13558 case DeprecatedOperations::e##_op: \
13559 return eUseCounter_##_op;
13560 #include "nsDeprecatedOperationList.h"
13561 #undef DEPRECATED_OPERATION
13562 default:
13563 MOZ_CRASH();
13567 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
13568 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
13571 void Document::WarnOnceAbout(
13572 DeprecatedOperations aOperation, bool asError /* = false */,
13573 const nsTArray<nsString>& aParams /* = empty array */) const {
13574 MOZ_ASSERT(NS_IsMainThread());
13575 if (HasWarnedAbout(aOperation)) {
13576 return;
13578 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
13579 // Don't count deprecated operations for about pages since those pages
13580 // are almost in our control, and we always need to remove uses there
13581 // before we remove the operation itself anyway.
13582 if (!IsAboutPage()) {
13583 const_cast<Document*>(this)->SetUseCounter(
13584 OperationToUseCounter(aOperation));
13586 uint32_t flags =
13587 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13588 nsContentUtils::ReportToConsole(
13589 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
13590 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
13593 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
13594 return mDocWarningWarnedAbout[aWarning];
13597 void Document::WarnOnceAbout(
13598 DocumentWarnings aWarning, bool asError /* = false */,
13599 const nsTArray<nsString>& aParams /* = empty array */) const {
13600 MOZ_ASSERT(NS_IsMainThread());
13601 if (HasWarnedAbout(aWarning)) {
13602 return;
13604 mDocWarningWarnedAbout[aWarning] = true;
13605 uint32_t flags =
13606 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13607 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
13608 nsContentUtils::eDOM_PROPERTIES,
13609 kDocumentWarnings[aWarning], aParams);
13612 mozilla::dom::ImageTracker* Document::ImageTracker() {
13613 if (!mImageTracker) {
13614 mImageTracker = new mozilla::dom::ImageTracker;
13616 return mImageTracker;
13619 void Document::ScheduleSVGUseElementShadowTreeUpdate(
13620 SVGUseElement& aUseElement) {
13621 MOZ_ASSERT(aUseElement.IsInComposedDoc());
13623 if (MOZ_UNLIKELY(mIsStaticDocument)) {
13624 // Printing doesn't deal well with dynamic DOM mutations.
13625 return;
13628 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
13630 if (PresShell* presShell = GetPresShell()) {
13631 presShell->EnsureStyleFlush();
13635 void Document::DoUpdateSVGUseElementShadowTrees() {
13636 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13638 do {
13639 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
13640 mSVGUseElementsNeedingShadowTreeUpdate);
13641 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
13643 for (const auto& useElement : useElementsToUpdate) {
13644 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
13645 // The element was in another <use> shadow tree which we processed
13646 // already and also needed an update, and is removed from the document
13647 // now, so nothing to do here.
13648 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
13649 continue;
13651 useElement->UpdateShadowTree();
13653 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13656 void Document::NotifyMediaFeatureValuesChanged() {
13657 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
13658 imageElement->MediaFeatureValuesChanged();
13662 already_AddRefed<Touch> Document::CreateTouch(
13663 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
13664 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
13665 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
13666 float aRotationAngle, float aForce) {
13667 RefPtr<Touch> touch =
13668 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
13669 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
13670 return touch.forget();
13673 already_AddRefed<TouchList> Document::CreateTouchList() {
13674 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13675 return retval.forget();
13678 already_AddRefed<TouchList> Document::CreateTouchList(
13679 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
13680 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13681 retval->Append(&aTouch);
13682 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13683 retval->Append(aTouches[i].get());
13685 return retval.forget();
13688 already_AddRefed<TouchList> Document::CreateTouchList(
13689 const Sequence<OwningNonNull<Touch>>& aTouches) {
13690 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13691 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13692 retval->Append(aTouches[i].get());
13694 return retval.forget();
13697 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
13698 float aX, float aY) {
13699 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
13701 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
13702 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
13703 nsPoint pt(x, y);
13705 FlushPendingNotifications(FlushType::Layout);
13707 PresShell* presShell = GetPresShell();
13708 if (!presShell) {
13709 return nullptr;
13712 nsIFrame* rootFrame = presShell->GetRootFrame();
13714 // XUL docs, unlike HTML, have no frame tree until everything's done loading
13715 if (!rootFrame) {
13716 return nullptr;
13719 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
13720 RelativeTo{rootFrame}, pt,
13721 {{FrameForPointOption::IgnorePaintSuppression,
13722 FrameForPointOption::IgnoreCrossDoc}});
13723 if (!ptFrame) {
13724 return nullptr;
13727 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
13728 nsPoint adjustedPoint = pt;
13729 if (nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
13730 adjustedPoint) !=
13731 nsLayoutUtils::TRANSFORM_SUCCEEDED) {
13732 return nullptr;
13735 nsIFrame::ContentOffsets offsets =
13736 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
13738 nsCOMPtr<nsIContent> node = offsets.content;
13739 uint32_t offset = offsets.offset;
13740 nsCOMPtr<nsIContent> anonNode = node;
13741 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
13742 if (nodeIsAnonymous) {
13743 node = ptFrame->GetContent();
13744 nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
13745 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
13746 nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
13747 if (textFrame) {
13748 // If the anonymous content node has a child, then we need to make sure
13749 // that we get the appropriate child, as otherwise the offset may not be
13750 // correct when we construct a range for it.
13751 nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
13752 if (firstChild) {
13753 anonNode = firstChild;
13756 if (textArea) {
13757 offset =
13758 nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
13761 node = nonanon;
13762 } else {
13763 node = nullptr;
13764 offset = 0;
13768 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
13769 if (nodeIsAnonymous) {
13770 aCaretPos->SetAnonymousContentNode(anonNode);
13772 return aCaretPos.forget();
13775 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
13776 // We rely on correct frame information here, so need to flush frames.
13777 FlushPendingNotifications(FlushType::Frames);
13779 // An element that is the HTML body element is potentially scrollable if all
13780 // of the following conditions are true:
13782 // The element has an associated CSS layout box.
13783 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
13784 if (!bodyFrame) {
13785 return false;
13788 // The element's parent element's computed value of the overflow-x and
13789 // overflow-y properties are visible.
13790 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
13791 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
13792 if (parentFrame &&
13793 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
13794 return false;
13797 // The element's computed value of the overflow-x or overflow-y properties is
13798 // not visible.
13799 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
13802 Element* Document::GetScrollingElement() {
13803 // Keep this in sync with IsScrollingElement.
13804 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
13805 RefPtr<HTMLBodyElement> body = GetBodyElement();
13806 if (body && !IsPotentiallyScrollable(body)) {
13807 return body;
13810 return nullptr;
13813 return GetRootElement();
13816 bool Document::IsScrollingElement(Element* aElement) {
13817 // Keep this in sync with GetScrollingElement.
13818 MOZ_ASSERT(aElement);
13820 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
13821 return aElement == GetRootElement();
13824 // In the common case when aElement != body, avoid refcounting.
13825 HTMLBodyElement* body = GetBodyElement();
13826 if (aElement != body) {
13827 return false;
13830 // Now we know body is non-null, since aElement is not null. It's the
13831 // scrolling element for the document if it itself is not potentially
13832 // scrollable.
13833 RefPtr<HTMLBodyElement> strongBody(body);
13834 return !IsPotentiallyScrollable(strongBody);
13837 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
13838 public:
13839 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
13840 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
13842 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
13843 const BlockParsingOptions& aOptions)
13844 : mPromise(aPromise) {
13845 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
13846 if (parser &&
13847 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
13848 parser->BlockParser();
13849 mParser = do_GetWeakReference(parser);
13850 mDocument = aDocument;
13851 mDocument->BlockOnload();
13852 mDocument->BlockDOMContentLoaded();
13856 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13857 ErrorResult& aRv) override {
13858 MaybeUnblockParser();
13860 mPromise->MaybeResolve(aValue);
13863 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13864 ErrorResult& aRv) override {
13865 MaybeUnblockParser();
13867 mPromise->MaybeReject(aValue);
13870 protected:
13871 virtual ~UnblockParsingPromiseHandler() {
13872 // If we're being cleaned up by the cycle collector, our mDocument reference
13873 // may have been unlinked while our mParser weak reference is still alive.
13874 if (mDocument) {
13875 MaybeUnblockParser();
13879 private:
13880 void MaybeUnblockParser() {
13881 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
13882 if (parser) {
13883 MOZ_DIAGNOSTIC_ASSERT(mDocument);
13884 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
13885 if (parser == docParser) {
13886 parser->UnblockParser();
13887 parser->ContinueInterruptedParsingAsync();
13890 if (mDocument) {
13891 // We blocked DOMContentLoaded and load events on this document. Unblock
13892 // them. Note that we want to do that no matter what's going on with the
13893 // parser state for this document. Maybe someone caused it to stop being
13894 // parsed, so CreatorParserOrNull() is returning null, but we still want
13895 // to unblock these.
13896 mDocument->UnblockDOMContentLoaded();
13897 mDocument->UnblockOnload(false);
13899 mParser = nullptr;
13900 mDocument = nullptr;
13903 nsWeakPtr mParser;
13904 RefPtr<Promise> mPromise;
13905 RefPtr<Document> mDocument;
13908 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
13910 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
13911 NS_INTERFACE_MAP_ENTRY(nsISupports)
13912 NS_INTERFACE_MAP_END
13914 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
13915 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
13917 already_AddRefed<Promise> Document::BlockParsing(
13918 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
13919 RefPtr<Promise> resultPromise =
13920 Promise::Create(aPromise.GetParentObject(), aRv);
13921 if (aRv.Failed()) {
13922 return nullptr;
13925 RefPtr<PromiseNativeHandler> promiseHandler =
13926 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
13927 aPromise.AppendNativeHandler(promiseHandler);
13929 return resultPromise.forget();
13932 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
13933 if (mFailedChannel) {
13934 nsCOMPtr<nsIURI> failedURI;
13935 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
13936 return failedURI.forget();
13940 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
13941 if (!uri) {
13942 return nullptr;
13945 return uri.forget();
13948 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
13949 if (mIsGoingAway) {
13950 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13951 return nullptr;
13954 if (!mReadyForIdle) {
13955 nsIGlobalObject* global = GetScopeObject();
13956 if (!global) {
13957 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13958 return nullptr;
13961 mReadyForIdle = Promise::Create(global, aRv);
13962 if (aRv.Failed()) {
13963 return nullptr;
13967 return mReadyForIdle;
13970 void Document::MaybeResolveReadyForIdle() {
13971 IgnoredErrorResult rv;
13972 Promise* readyPromise = GetDocumentReadyForIdle(rv);
13973 if (readyPromise) {
13974 readyPromise->MaybeResolveWithUndefined();
13978 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
13979 // The policy is created when the document is initialized. We _must_ have a
13980 // policy here even if the featurePolicy pref is off. If this assertion fails,
13981 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
13982 MOZ_ASSERT(mFeaturePolicy);
13983 return mFeaturePolicy;
13986 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
13987 // Only chrome documents are allowed to use command dispatcher.
13988 if (!nsContentUtils::IsChromeDoc(this)) {
13989 return nullptr;
13991 if (!mCommandDispatcher) {
13992 // Create our command dispatcher and hook it up.
13993 mCommandDispatcher = new nsXULCommandDispatcher(this);
13995 return mCommandDispatcher;
13998 void Document::InitializeXULBroadcastManager() {
13999 if (mXULBroadcastManager) {
14000 return;
14002 mXULBroadcastManager = new XULBroadcastManager(this);
14005 namespace {
14007 class DevToolsMutationObserver final : public nsStubMutationObserver {
14008 NS_DECL_ISUPPORTS
14009 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
14010 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
14011 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
14013 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
14014 // relies on the event firing _before_ the removal happens.
14015 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
14017 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
14018 // data changes right now (maybe intentionally?).
14019 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
14021 DevToolsMutationObserver() = default;
14023 private:
14024 void FireEvent(nsINode* aTarget, const nsAString& aType);
14026 ~DevToolsMutationObserver() = default;
14029 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
14031 void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
14032 const nsAString& aType) {
14033 AsyncEventDispatcher::RunDOMEventWhenSafe(*aTarget, aType, CanBubble::eNo,
14034 ChromeOnlyDispatch::eYes,
14035 Composed::eYes);
14038 void DevToolsMutationObserver::AttributeChanged(Element* aElement,
14039 int32_t aNamespaceID,
14040 nsAtom* aAttribute,
14041 int32_t aModType,
14042 const nsAttrValue* aOldValue) {
14043 FireEvent(aElement, u"devtoolsattrmodified"_ns);
14046 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
14047 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
14048 ContentInserted(c);
14052 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
14053 FireEvent(aChild, u"devtoolschildinserted"_ns);
14056 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
14058 } // namespace
14060 void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
14061 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
14062 return;
14064 mDevToolsWatchingDOMMutations = aValue;
14065 if (aValue) {
14066 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
14067 sDevToolsMutationObserver = new DevToolsMutationObserver();
14068 ClearOnShutdown(&sDevToolsMutationObserver);
14070 AddMutationObserver(sDevToolsMutationObserver);
14071 } else if (sDevToolsMutationObserver) {
14072 RemoveMutationObserver(sDevToolsMutationObserver);
14076 void Document::MaybeWarnAboutZoom() {
14077 if (mHasWarnedAboutZoom) {
14078 return;
14080 const bool usedZoom = Servo_IsPropertyIdRecordedInUseCounter(
14081 mStyleUseCounters.get(), eCSSProperty_zoom);
14082 if (!usedZoom) {
14083 return;
14086 mHasWarnedAboutZoom = true;
14087 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
14088 this, nsContentUtils::eLAYOUT_PROPERTIES,
14089 "ZoomPropertyWarning");
14092 nsIHTMLCollection* Document::Children() {
14093 if (!mChildrenCollection) {
14094 mChildrenCollection =
14095 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
14096 nsGkAtoms::_asterisk, false);
14099 return mChildrenCollection;
14102 uint32_t Document::ChildElementCount() { return Children()->Length(); }
14104 // Singleton class to manage the list of fullscreen documents which are the
14105 // root of a branch which contains fullscreen documents. We maintain this list
14106 // so that we can easily exit all windows from fullscreen when the user
14107 // presses the escape key.
14108 class FullscreenRoots {
14109 public:
14110 // Adds the root of given document to the manager. Calling this method
14111 // with a document whose root is already contained has no effect.
14112 static void Add(Document* aDoc);
14114 // Iterates over every root in the root list, and calls aFunction, passing
14115 // each root once to aFunction. It is safe to call Add() and Remove() while
14116 // iterating over the list (i.e. in aFunction). Documents that are removed
14117 // from the manager during traversal are not traversed, and documents that
14118 // are added to the manager during traversal are also not traversed.
14119 static void ForEach(void (*aFunction)(Document* aDoc));
14121 // Removes the root of a specific document from the manager.
14122 static void Remove(Document* aDoc);
14124 // Returns true if all roots added to the list have been removed.
14125 static bool IsEmpty();
14127 private:
14128 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
14129 MOZ_COUNTED_DTOR(FullscreenRoots)
14131 enum : uint32_t { NotFound = uint32_t(-1) };
14132 // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
14133 static uint32_t Find(Document* aRoot);
14135 // Returns true if aRoot is in the list of fullscreen roots.
14136 static bool Contains(Document* aRoot);
14138 // Singleton instance of the FullscreenRoots. This is instantiated when a
14139 // root is added, and it is deleted when the last root is removed.
14140 static FullscreenRoots* sInstance;
14142 // List of weak pointers to roots.
14143 nsTArray<nsWeakPtr> mRoots;
14146 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
14148 /* static */
14149 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
14150 if (!sInstance) {
14151 return;
14153 // Create a copy of the roots array, and iterate over the copy. This is so
14154 // that if an element is removed from mRoots we don't mess up our iteration.
14155 nsTArray<nsWeakPtr> roots(sInstance->mRoots.Clone());
14156 // Call aFunction on all entries.
14157 for (uint32_t i = 0; i < roots.Length(); i++) {
14158 nsCOMPtr<Document> root = do_QueryReferent(roots[i]);
14159 // Check that the root isn't in the manager. This is so that new additions
14160 // while we were running don't get traversed.
14161 if (root && FullscreenRoots::Contains(root)) {
14162 aFunction(root);
14167 /* static */
14168 bool FullscreenRoots::Contains(Document* aRoot) {
14169 return FullscreenRoots::Find(aRoot) != NotFound;
14172 /* static */
14173 void FullscreenRoots::Add(Document* aDoc) {
14174 nsCOMPtr<Document> root =
14175 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14176 if (!FullscreenRoots::Contains(root)) {
14177 if (!sInstance) {
14178 sInstance = new FullscreenRoots();
14180 sInstance->mRoots.AppendElement(do_GetWeakReference(root));
14184 /* static */
14185 uint32_t FullscreenRoots::Find(Document* aRoot) {
14186 if (!sInstance) {
14187 return NotFound;
14189 nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
14190 for (uint32_t i = 0; i < roots.Length(); i++) {
14191 nsCOMPtr<Document> otherRoot(do_QueryReferent(roots[i]));
14192 if (otherRoot == aRoot) {
14193 return i;
14196 return NotFound;
14199 /* static */
14200 void FullscreenRoots::Remove(Document* aDoc) {
14201 nsCOMPtr<Document> root =
14202 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14203 uint32_t index = Find(root);
14204 NS_ASSERTION(index != NotFound,
14205 "Should only try to remove roots which are still added!");
14206 if (index == NotFound || !sInstance) {
14207 return;
14209 sInstance->mRoots.RemoveElementAt(index);
14210 if (sInstance->mRoots.IsEmpty()) {
14211 delete sInstance;
14212 sInstance = nullptr;
14216 /* static */
14217 bool FullscreenRoots::IsEmpty() { return !sInstance; }
14219 // Any fullscreen change waiting for the widget to finish transition
14220 // is queued here. This is declared static instead of a member of
14221 // Document because in the majority of time, there would be at most
14222 // one document requesting or exiting fullscreen. We shouldn't waste
14223 // the space to hold for it in every document.
14224 class PendingFullscreenChangeList {
14225 public:
14226 PendingFullscreenChangeList() = delete;
14228 template <typename T>
14229 static void Add(UniquePtr<T> aChange) {
14230 sList.insertBack(aChange.release());
14233 static const FullscreenChange* GetLast() { return sList.getLast(); }
14235 enum IteratorOption {
14236 // When we are committing fullscreen changes or preparing for
14237 // that, we generally want to iterate all requests in the same
14238 // window with eDocumentsWithSameRoot option.
14239 eDocumentsWithSameRoot,
14240 // If we are removing a document from the tree, we would only
14241 // want to remove the requests from the given document and its
14242 // descendants. For that case, use eInclusiveDescendants.
14243 eInclusiveDescendants
14246 template <typename T>
14247 class Iterator {
14248 public:
14249 explicit Iterator(Document* aDoc, IteratorOption aOption)
14250 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
14251 if (mCurrent) {
14252 if (aDoc->GetBrowsingContext()) {
14253 mRootBCForIteration = aDoc->GetBrowsingContext();
14254 if (aOption == eDocumentsWithSameRoot) {
14255 RefPtr<BrowsingContext> bc =
14256 GetParentIgnoreChromeBoundary(mRootBCForIteration);
14257 while (bc) {
14258 mRootBCForIteration = bc;
14259 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
14263 SkipToNextMatch();
14267 UniquePtr<T> TakeAndNext() {
14268 auto thisChange = TakeAndNextInternal();
14269 SkipToNextMatch();
14270 return thisChange;
14272 bool AtEnd() const { return mCurrent == nullptr; }
14274 private:
14275 already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
14276 BrowsingContext* aBC) {
14277 // Chrome BrowsingContexts are only available in the parent process, so if
14278 // we're in a content process, we only worry about the context tree.
14279 if (XRE_IsParentProcess()) {
14280 return aBC->Canonical()->GetParentCrossChromeBoundary();
14282 return do_AddRef(aBC->GetParent());
14285 UniquePtr<T> TakeAndNextInternal() {
14286 FullscreenChange* thisChange = mCurrent;
14287 MOZ_ASSERT(thisChange->Type() == T::kType);
14288 mCurrent = mCurrent->removeAndGetNext();
14289 return WrapUnique(static_cast<T*>(thisChange));
14291 void SkipToNextMatch() {
14292 while (mCurrent) {
14293 if (mCurrent->Type() == T::kType) {
14294 RefPtr<BrowsingContext> bc =
14295 mCurrent->Document()->GetBrowsingContext();
14296 if (!bc) {
14297 // Always automatically drop fullscreen changes which are
14298 // from a document detached from the doc shell.
14299 UniquePtr<T> change = TakeAndNextInternal();
14300 change->MayRejectPromise("Document is not active");
14301 continue;
14303 while (bc && bc != mRootBCForIteration) {
14304 bc = GetParentIgnoreChromeBoundary(bc);
14306 if (bc) {
14307 break;
14310 // The current one either don't have matched type, or isn't
14311 // inside the given subtree, so skip this item.
14312 mCurrent = mCurrent->getNext();
14316 FullscreenChange* mCurrent;
14317 RefPtr<BrowsingContext> mRootBCForIteration;
14320 private:
14321 static LinkedList<FullscreenChange> sList;
14324 /* static */
14325 LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
14327 Document* Document::GetFullscreenRoot() {
14328 nsCOMPtr<Document> root = do_QueryReferent(mFullscreenRoot);
14329 return root;
14332 size_t Document::CountFullscreenElements() const {
14333 size_t count = 0;
14334 for (const nsWeakPtr& ptr : mTopLayer) {
14335 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14336 if (elem->State().HasState(ElementState::FULLSCREEN)) {
14337 count++;
14341 return count;
14344 void Document::SetFullscreenRoot(Document* aRoot) {
14345 mFullscreenRoot = do_GetWeakReference(aRoot);
14348 void Document::TryCancelDialog() {
14349 // Check if the document is blocked by modal dialog
14350 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14351 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14352 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14353 dialog->QueueCancelDialog();
14354 break;
14359 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
14360 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
14361 RefPtr<Promise> promise = exit->GetPromise();
14362 RestorePreviousFullscreenState(std::move(exit));
14363 return promise.forget();
14366 static void AskWindowToExitFullscreen(Document* aDoc) {
14367 if (XRE_GetProcessType() == GeckoProcessType_Content) {
14368 nsContentUtils::DispatchEventOnlyToChrome(
14369 aDoc, ToSupports(aDoc), u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
14370 Cancelable::eNo, /* DefaultAction */ nullptr);
14371 } else {
14372 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
14373 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
14378 class nsCallExitFullscreen : public Runnable {
14379 public:
14380 explicit nsCallExitFullscreen(Document* aDoc)
14381 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
14383 NS_IMETHOD Run() final {
14384 if (!mDoc) {
14385 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
14386 } else {
14387 AskWindowToExitFullscreen(mDoc);
14389 return NS_OK;
14392 private:
14393 nsCOMPtr<Document> mDoc;
14396 /* static */
14397 void Document::AsyncExitFullscreen(Document* aDoc) {
14398 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14399 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
14400 if (aDoc) {
14401 aDoc->Dispatch(TaskCategory::Other, exit.forget());
14402 } else {
14403 NS_DispatchToCurrentThread(exit.forget());
14407 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
14408 uint32_t count = 0;
14409 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
14410 auto subDoc = [&count](Document& aSubDoc) {
14411 if (aSubDoc.Fullscreen()) {
14412 count++;
14414 return CallState::Continue;
14416 aDoc.EnumerateSubDocuments(subDoc);
14417 return count;
14420 bool Document::IsFullscreenLeaf() {
14421 // A fullscreen leaf document is fullscreen, and has no fullscreen
14422 // subdocuments.
14424 // FIXME(emilio): This doesn't seem to account for fission iframes, is that
14425 // ok?
14426 return Fullscreen() && CountFullscreenSubDocuments(*this) == 0;
14429 static Document* GetFullscreenLeaf(Document& aDoc) {
14430 if (aDoc.IsFullscreenLeaf()) {
14431 return &aDoc;
14433 if (!aDoc.Fullscreen()) {
14434 return nullptr;
14436 Document* leaf = nullptr;
14437 auto recurse = [&leaf](Document& aSubDoc) {
14438 leaf = GetFullscreenLeaf(aSubDoc);
14439 return leaf ? CallState::Stop : CallState::Continue;
14441 aDoc.EnumerateSubDocuments(recurse);
14442 return leaf;
14445 static Document* GetFullscreenLeaf(Document* aDoc) {
14446 if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
14447 return leaf;
14449 // Otherwise we could be either in a non-fullscreen doc tree, or we're
14450 // below the fullscreen doc. Start the search from the root.
14451 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14452 return GetFullscreenLeaf(*root);
14455 static CallState ResetFullscreen(Document& aDocument) {
14456 if (Element* fsElement = aDocument.GetUnretargetedFullscreenElement()) {
14457 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
14458 "Should have at most 1 fullscreen subdocument.");
14459 aDocument.CleanupFullscreenState();
14460 NS_ASSERTION(!aDocument.Fullscreen(), "Should reset fullscreen");
14461 DispatchFullscreenChange(aDocument, fsElement);
14462 aDocument.EnumerateSubDocuments(ResetFullscreen);
14464 return CallState::Continue;
14467 // Since Document::ExitFullscreenInDocTree() could be called from
14468 // Element::UnbindFromTree() where it is not safe to synchronously run
14469 // script. This runnable is the script part of that function.
14470 class ExitFullscreenScriptRunnable : public Runnable {
14471 public:
14472 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
14473 : mozilla::Runnable("ExitFullscreenScriptRunnable"),
14474 mRoot(aRoot),
14475 mLeaf(aLeaf) {}
14477 NS_IMETHOD Run() override {
14478 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
14479 // document since we want this event to follow the same path that
14480 // MozDOMFullscreen:Entered was dispatched.
14481 nsContentUtils::DispatchEventOnlyToChrome(
14482 mLeaf, ToSupports(mLeaf), u"MozDOMFullscreen:Exited"_ns,
14483 CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
14484 // Ensure the window exits fullscreen, as long as we don't have
14485 // pending fullscreen requests.
14486 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
14487 if (!mRoot->HasPendingFullscreenRequests()) {
14488 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
14489 false);
14492 return NS_OK;
14495 private:
14496 nsCOMPtr<Document> mRoot;
14497 nsCOMPtr<Document> mLeaf;
14500 /* static */
14501 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
14502 MOZ_ASSERT(aMaybeNotARootDoc);
14504 // Unlock the pointer
14505 PointerLockManager::Unlock();
14507 // Resolve all promises which waiting for exit fullscreen.
14508 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
14509 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14510 while (!iter.AtEnd()) {
14511 UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
14512 exit->MayResolvePromise();
14515 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
14516 if (!root || !root->Fullscreen()) {
14517 // If a document was detached before exiting from fullscreen, it is
14518 // possible that the root had left fullscreen state. In this case,
14519 // we would not get anything from the ResetFullscreen() call. Root's
14520 // not being a fullscreen doc also means the widget should have
14521 // exited fullscreen state. It means even if we do not return here,
14522 // we would actually do nothing below except crashing ourselves via
14523 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
14524 // document.
14525 return;
14528 // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
14529 // See ExitFullscreenScriptRunnable::Run for details. We have to
14530 // record it here because we don't have such information after we
14531 // reset the fullscreen state below.
14532 Document* fullscreenLeaf = GetFullscreenLeaf(root);
14534 // Walk the tree of fullscreen documents, and reset their fullscreen state.
14535 ResetFullscreen(*root);
14537 NS_ASSERTION(!root->Fullscreen(),
14538 "Fullscreen root should no longer be a fullscreen doc...");
14540 // Move the top-level window out of fullscreen mode.
14541 FullscreenRoots::Remove(root);
14543 nsContentUtils::AddScriptRunner(
14544 new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
14547 static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
14548 RefPtr<AsyncEventDispatcher> asyncDispatcher =
14549 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
14550 CanBubble::eYes, ChromeOnlyDispatch::eYes);
14551 asyncDispatcher->PostDOMEvent();
14554 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
14555 NS_ASSERTION(!Fullscreen() || !FullscreenRoots::IsEmpty(),
14556 "Should have at least 1 fullscreen root when fullscreen!");
14558 if (!GetWindow()) {
14559 aExit->MayRejectPromise("No active window");
14560 return;
14562 if (!Fullscreen() || FullscreenRoots::IsEmpty()) {
14563 aExit->MayRejectPromise("Not in fullscreen mode");
14564 return;
14567 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
14568 AutoTArray<Element*, 8> exitElements;
14570 Document* doc = fullScreenDoc;
14571 // Collect all subdocuments.
14572 for (; doc != this; doc = doc->GetInProcessParentDocument()) {
14573 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14574 MOZ_ASSERT(fsElement,
14575 "Parent document of "
14576 "a fullscreen document without fullscreen element?");
14577 exitElements.AppendElement(fsElement);
14579 MOZ_ASSERT(doc == this, "Must have reached this doc");
14580 // Collect all ancestor documents which we are going to change.
14581 for (; doc; doc = doc->GetInProcessParentDocument()) {
14582 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14583 MOZ_ASSERT(fsElement,
14584 "Ancestor of fullscreen document must also be in fullscreen");
14585 if (doc != this) {
14586 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
14587 if (iframe->FullscreenFlag()) {
14588 // If this is an iframe, and it explicitly requested
14589 // fullscreen, don't rollback it automatically.
14590 break;
14594 exitElements.AppendElement(fsElement);
14595 if (doc->CountFullscreenElements() > 1) {
14596 break;
14600 Document* lastDoc = exitElements.LastElement()->OwnerDoc();
14601 size_t fullscreenCount = lastDoc->CountFullscreenElements();
14602 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
14603 // If we are fully exiting fullscreen, don't touch anything here,
14604 // just wait for the window to get out from fullscreen first.
14605 PendingFullscreenChangeList::Add(std::move(aExit));
14606 AskWindowToExitFullscreen(this);
14607 return;
14610 // If fullscreen mode is updated the pointer should be unlocked
14611 PointerLockManager::Unlock();
14612 // All documents listed in the array except the last one are going to
14613 // completely exit from the fullscreen state.
14614 for (auto i : IntegerRange(exitElements.Length() - 1)) {
14615 exitElements[i]->OwnerDoc()->CleanupFullscreenState();
14617 // The last document will either rollback one fullscreen element, or
14618 // completely exit from the fullscreen state as well.
14619 Document* newFullscreenDoc;
14620 if (fullscreenCount > 1) {
14621 DebugOnly<bool> removedFullscreenElement = lastDoc->PopFullscreenElement();
14622 MOZ_ASSERT(removedFullscreenElement);
14623 newFullscreenDoc = lastDoc;
14624 } else {
14625 lastDoc->CleanupFullscreenState();
14626 newFullscreenDoc = lastDoc->GetInProcessParentDocument();
14628 // Dispatch the fullscreenchange event to all document listed. Note
14629 // that the loop order is reversed so that events are dispatched in
14630 // the tree order as indicated in the spec.
14631 for (Element* e : Reversed(exitElements)) {
14632 DispatchFullscreenChange(*e->OwnerDoc(), e);
14634 aExit->MayResolvePromise();
14636 MOZ_ASSERT(newFullscreenDoc,
14637 "If we were going to exit from fullscreen on "
14638 "all documents in this doctree, we should've asked the window to "
14639 "exit first instead of reaching here.");
14640 if (fullScreenDoc != newFullscreenDoc &&
14641 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
14642 // We've popped so enough off the stack that we've rolled back to
14643 // a fullscreen element in a parent document. If this document is
14644 // cross origin, dispatch an event to chrome so it knows to show
14645 // the warning UI.
14646 DispatchFullscreenNewOriginEvent(newFullscreenDoc);
14650 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
14651 if (nsPresContext* presContext = aDoc->GetPresContext()) {
14652 presContext->UpdateViewportScrollStylesOverride();
14656 static void NotifyFullScreenChangedForMediaElement(Element& aElement) {
14657 // When a media element enters the fullscreen, we would like to notify that
14658 // to the media controller in order to update its status.
14659 if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) {
14660 mediaElem->NotifyFullScreenChanged();
14664 void Document::CleanupFullscreenState() {
14665 while (PopFullscreenElement(UpdateViewport::No)) {
14666 // Remove the next one if appropriate
14669 UpdateViewportScrollbarOverrideForFullscreen(this);
14670 mFullscreenRoot = nullptr;
14672 // Restore the zoom level that was in place prior to entering fullscreen.
14673 if (PresShell* presShell = GetPresShell()) {
14674 if (presShell->GetMobileViewportManager()) {
14675 presShell->SetResolutionAndScaleTo(
14676 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
14681 bool Document::PopFullscreenElement(UpdateViewport aUpdateViewport) {
14682 Element* removedElement = TopLayerPop([](Element* element) -> bool {
14683 return element->State().HasState(ElementState::FULLSCREEN);
14686 if (!removedElement) {
14687 return false;
14690 MOZ_ASSERT(removedElement->State().HasState(ElementState::FULLSCREEN));
14691 removedElement->RemoveStates(ElementState::FULLSCREEN | ElementState::MODAL);
14692 NotifyFullScreenChangedForMediaElement(*removedElement);
14693 // Reset iframe fullscreen flag.
14694 if (auto* iframe = HTMLIFrameElement::FromNode(removedElement)) {
14695 iframe->SetFullscreenFlag(false);
14697 if (aUpdateViewport == UpdateViewport::Yes) {
14698 UpdateViewportScrollbarOverrideForFullscreen(this);
14700 return true;
14703 void Document::SetFullscreenElement(Element& aElement) {
14704 ElementState statesToAdd = ElementState::FULLSCREEN;
14705 if (StaticPrefs::dom_fullscreen_modal() && !IsInChromeDocShell()) {
14706 // Don't make the document modal in chrome documents, since we don't want
14707 // the browser UI like the context menu / etc to be inert.
14708 statesToAdd |= ElementState::MODAL;
14710 aElement.AddStates(statesToAdd);
14711 TopLayerPush(aElement);
14712 NotifyFullScreenChangedForMediaElement(aElement);
14713 UpdateViewportScrollbarOverrideForFullscreen(this);
14716 void Document::TopLayerPush(Element& aElement) {
14717 const bool modal = aElement.State().HasState(ElementState::MODAL);
14719 TopLayerPop(aElement);
14720 mTopLayer.AppendElement(do_GetWeakReference(&aElement));
14721 NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
14723 if (modal) {
14724 aElement.AddStates(ElementState::TOPMOST_MODAL);
14726 bool foundExistingModalElement = false;
14727 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14728 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14729 if (element && element != &aElement &&
14730 element->State().HasState(ElementState::TOPMOST_MODAL)) {
14731 element->RemoveStates(ElementState::TOPMOST_MODAL);
14732 foundExistingModalElement = true;
14733 break;
14737 if (!foundExistingModalElement) {
14738 Element* root = GetRootElement();
14739 MOZ_RELEASE_ASSERT(root, "top layer element outside of document?");
14740 if (&aElement != root) {
14741 // Add inert to the root element so that the inertness is applied to the
14742 // entire document.
14743 root->AddStates(ElementState::INERT);
14749 void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
14750 aDialogElement.AddStates(ElementState::MODAL);
14751 TopLayerPush(aDialogElement);
14754 void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
14755 DebugOnly<Element*> removedElement = TopLayerPop(aDialogElement);
14756 MOZ_ASSERT(removedElement == &aDialogElement);
14757 aDialogElement.RemoveStates(ElementState::MODAL);
14760 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
14761 if (mTopLayer.IsEmpty()) {
14762 return nullptr;
14765 // Remove the topmost element that qualifies aPredicate; This
14766 // is required is because the top layer contains not only
14767 // fullscreen elements, but also dialog elements.
14768 Element* removedElement = nullptr;
14769 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
14770 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
14771 if (element && aPredicate(element)) {
14772 removedElement = element;
14773 mTopLayer.RemoveElementAt(i);
14774 break;
14778 // Pop from the stack null elements (references to elements which have
14779 // been GC'd since they were added to the stack) and elements which are
14780 // no longer in this document.
14782 // FIXME(emilio): If this loop does something, it'd violate the assertions
14783 // from GetTopLayerTop()... What gives?
14784 while (!mTopLayer.IsEmpty()) {
14785 Element* element = GetTopLayerTop();
14786 if (!element || element->GetComposedDoc() != this) {
14787 mTopLayer.RemoveLastElement();
14788 } else {
14789 // The top element of the stack is now an in-doc element. Return here.
14790 break;
14794 if (!removedElement) {
14795 return nullptr;
14798 const bool modal = removedElement->State().HasState(ElementState::MODAL);
14800 if (modal) {
14801 removedElement->RemoveStates(ElementState::TOPMOST_MODAL);
14802 bool foundExistingModalElement = false;
14803 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14804 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14805 if (element && element->State().HasState(ElementState::MODAL)) {
14806 element->AddStates(ElementState::TOPMOST_MODAL);
14807 foundExistingModalElement = true;
14808 break;
14811 // No more modal elements, make the document not inert anymore.
14812 if (!foundExistingModalElement) {
14813 Element* root = GetRootElement();
14814 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
14815 root->RemoveStates(ElementState::INERT);
14820 return removedElement;
14823 Element* Document::TopLayerPop(Element& aElement) {
14824 auto predictFunc = [&aElement](Element* element) {
14825 return element == &aElement;
14827 return TopLayerPop(predictFunc);
14830 void Document::GetWireframe(bool aIncludeNodes,
14831 Nullable<Wireframe>& aWireframe) {
14832 FlushPendingNotifications(FlushType::Layout);
14833 GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
14836 void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
14837 Nullable<Wireframe>& aWireframe) {
14838 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
14839 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
14841 PresShell* shell = GetPresShell();
14842 if (!shell) {
14843 return;
14846 nsPresContext* pc = shell->GetPresContext();
14847 if (!pc) {
14848 return;
14851 nsIFrame* rootFrame = shell->GetRootFrame();
14852 if (!rootFrame) {
14853 return;
14856 auto& wireframe = aWireframe.SetValue();
14857 wireframe.mCanvasBackground = shell->ComputeCanvasBackground().mColor;
14859 FrameForPointOptions options;
14860 options.mBits += FrameForPointOption::IgnoreCrossDoc;
14861 options.mBits += FrameForPointOption::IgnorePaintSuppression;
14862 options.mBits += FrameForPointOption::OnlyVisible;
14864 AutoTArray<nsIFrame*, 32> frames;
14865 const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
14866 nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
14867 options);
14869 // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
14870 // something perhaps, but seems hard / like it'd involve at least some extra
14871 // copying around, since they don't outlive GetFramesForArea.
14872 auto& rects = wireframe.mRects.Construct();
14873 if (!rects.SetCapacity(frames.Length(), fallible)) {
14874 return;
14876 for (nsIFrame* frame : Reversed(frames)) {
14877 auto [rectColor,
14878 rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> {
14879 if (frame->IsTextFrame()) {
14880 return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
14881 WireframeRectType::Text};
14883 if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
14884 return {0, WireframeRectType::Image};
14886 if (frame->IsThemed()) {
14887 return {0, WireframeRectType::Background};
14889 bool drawImage = false;
14890 bool drawColor = false;
14891 if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) {
14892 const nscolor color = nsCSSRendering::DetermineBackgroundColor(
14893 pc, bgStyle, frame, drawImage, drawColor);
14894 if (drawImage &&
14895 !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
14896 return {color, WireframeRectType::Image};
14898 if (drawColor && !frame->IsCanvasFrame()) {
14899 // Canvas frame background already accounted for in mCanvasBackground.
14900 return {color, WireframeRectType::Background};
14903 return {0, WireframeRectType::Unknown};
14904 }();
14906 if (rectType == WireframeRectType::Unknown) {
14907 continue;
14910 const auto r =
14911 CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
14912 frame, frame->GetRectRelativeToSelf(), relativeTo));
14913 if ((uint32_t)r.Area() <
14914 StaticPrefs::browser_history_wireframeAreaThreshold()) {
14915 continue;
14918 // Can't really fail because SetCapacity succeeded.
14919 auto& taggedRect = *rects.AppendElement(fallible);
14921 if (aIncludeNodes) {
14922 if (nsIContent* c = frame->GetContent()) {
14923 taggedRect.mNode.Construct(c);
14926 taggedRect.mX = r.x;
14927 taggedRect.mY = r.y;
14928 taggedRect.mWidth = r.width;
14929 taggedRect.mHeight = r.height;
14930 taggedRect.mColor = rectColor;
14931 taggedRect.mType.Construct(rectType);
14935 Element* Document::GetTopLayerTop() {
14936 if (mTopLayer.IsEmpty()) {
14937 return nullptr;
14939 uint32_t last = mTopLayer.Length() - 1;
14940 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
14941 NS_ASSERTION(element, "Should have a top layer element!");
14942 NS_ASSERTION(element->IsInComposedDoc(),
14943 "Top layer element should be in doc");
14944 NS_ASSERTION(element->OwnerDoc() == this,
14945 "Top layer element should be in this doc");
14946 return element;
14949 Element* Document::GetUnretargetedFullscreenElement() const {
14950 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14951 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14952 // Per spec, the fullscreen element is the topmost element in the document’s
14953 // top layer whose fullscreen flag is set, if any, and null otherwise.
14954 if (element && element->State().HasState(ElementState::FULLSCREEN)) {
14955 return element;
14958 return nullptr;
14961 nsTArray<Element*> Document::GetTopLayer() const {
14962 nsTArray<Element*> elements;
14963 for (const nsWeakPtr& ptr : mTopLayer) {
14964 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14965 elements.AppendElement(elem);
14968 return elements;
14971 bool Document::TopLayerContains(Element& aElement) const {
14972 if (mTopLayer.IsEmpty()) {
14973 return false;
14975 nsWeakPtr weakElement = do_GetWeakReference(&aElement);
14976 return mTopLayer.Contains(weakElement);
14979 void Document::HideAllPopoversUntil(nsINode& aEndpoint,
14980 bool aFocusPreviousElement,
14981 bool aFireEvents) {
14982 auto closeAllOpenPopovers = [&aFocusPreviousElement, &aFireEvents,
14983 this]() MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
14984 while (RefPtr<Element> topmost = GetTopmostAutoPopover()) {
14985 HidePopover(*topmost, aFocusPreviousElement, aFireEvents, IgnoreErrors());
14989 if (&aEndpoint == this) {
14990 closeAllOpenPopovers();
14991 return;
14994 // https://github.com/whatwg/html/pull/9198
14995 auto needRepeatingHide = [&]() {
14996 auto autoList = AutoPopoverList();
14997 return autoList.Contains(&aEndpoint) &&
14998 &aEndpoint != autoList.LastElement();
15001 MOZ_ASSERT((&aEndpoint)->IsElement() &&
15002 (&aEndpoint)->AsElement()->IsAutoPopover());
15003 bool repeatingHide = false;
15004 bool fireEvents = aFireEvents;
15005 do {
15006 RefPtr<const Element> lastToHide = nullptr;
15007 bool foundEndpoint = false;
15008 for (const Element* popover : AutoPopoverList()) {
15009 if (popover == &aEndpoint) {
15010 foundEndpoint = true;
15011 } else if (foundEndpoint) {
15012 lastToHide = popover;
15013 break;
15017 if (!foundEndpoint) {
15018 closeAllOpenPopovers();
15019 return;
15022 while (lastToHide && lastToHide->IsPopoverOpen()) {
15023 RefPtr<Element> topmost = GetTopmostAutoPopover();
15024 if (!topmost) {
15025 break;
15027 HidePopover(*topmost, aFocusPreviousElement, fireEvents, IgnoreErrors());
15030 repeatingHide = needRepeatingHide();
15031 if (repeatingHide) {
15032 fireEvents = false;
15034 } while (repeatingHide);
15037 MOZ_CAN_RUN_SCRIPT_BOUNDARY void
15038 Document::HideAllPopoversWithoutRunningScript() {
15039 return HideAllPopoversUntil(*this, false, false);
15042 void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement,
15043 bool aFireEvents, ErrorResult& aRv) {
15044 RefPtr<nsGenericHTMLElement> popoverHTMLEl =
15045 nsGenericHTMLElement::FromNode(aPopover);
15046 NS_ASSERTION(popoverHTMLEl, "Not a HTML element");
15048 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15049 nullptr, aRv)) {
15050 return;
15053 bool wasShowingOrHiding =
15054 popoverHTMLEl->GetPopoverData()->IsShowingOrHiding();
15055 popoverHTMLEl->GetPopoverData()->SetIsShowingOrHiding(true);
15056 const bool fireEvents = aFireEvents && !wasShowingOrHiding;
15057 auto cleanupHidingFlag = MakeScopeExit([&]() {
15058 if (auto* popoverData = popoverHTMLEl->GetPopoverData()) {
15059 popoverData->SetIsShowingOrHiding(wasShowingOrHiding);
15063 if (popoverHTMLEl->IsAutoPopover()) {
15064 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, fireEvents);
15065 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15066 nullptr, aRv)) {
15067 return;
15069 // TODO: we can't always guarantee:
15070 // The last item in document's auto popover list is popoverHTMLEl.
15071 // See, https://github.com/whatwg/html/issues/9197
15072 // If popoverHTMLEl is not on top, hide popovers again without firing
15073 // events.
15074 if (NS_WARN_IF(GetTopmostAutoPopover() != popoverHTMLEl)) {
15075 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
15076 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15077 nullptr, aRv)) {
15078 return;
15080 MOZ_ASSERT(GetTopmostAutoPopover() == popoverHTMLEl,
15081 "popoverHTMLEl should be on top of auto popover list");
15085 auto* data = popoverHTMLEl->GetPopoverData();
15086 MOZ_ASSERT(data, "Should have popover data");
15087 data->SetInvoker(nullptr);
15089 // Fire beforetoggle event and re-check popover validity.
15090 if (fireEvents) {
15091 // Intentionally ignore the return value here as only on open event for
15092 // beforetoggle the cancelable attribute is initialized to true.
15093 popoverHTMLEl->FireToggleEvent(PopoverVisibilityState::Showing,
15094 PopoverVisibilityState::Hidden,
15095 u"beforetoggle"_ns);
15096 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15097 nullptr, aRv)) {
15098 return;
15102 RemovePopoverFromTopLayer(aPopover);
15104 popoverHTMLEl->PopoverPseudoStateUpdate(false, true);
15105 popoverHTMLEl->GetPopoverData()->SetPopoverVisibilityState(
15106 PopoverVisibilityState::Hidden);
15108 // Queue popover toggle event task.
15109 if (fireEvents) {
15110 popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing);
15113 if (aFocusPreviousElement) {
15114 popoverHTMLEl->FocusPreviousElementAfterHidingPopover();
15115 } else {
15116 popoverHTMLEl->ForgetPreviouslyFocusedElementAfterHidingPopover();
15120 nsTArray<Element*> Document::AutoPopoverList() const {
15121 nsTArray<Element*> elements;
15122 for (const nsWeakPtr& ptr : mTopLayer) {
15123 if (nsCOMPtr<Element> element = do_QueryReferent(ptr)) {
15124 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15125 elements.AppendElement(element);
15129 return elements;
15132 Element* Document::GetTopmostAutoPopover() const {
15133 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15134 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15135 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15136 return element;
15139 return nullptr;
15142 void Document::AddToAutoPopoverList(Element& aElement) {
15143 MOZ_ASSERT(aElement.IsAutoPopover());
15144 TopLayerPush(aElement);
15147 void Document::RemoveFromAutoPopoverList(Element& aElement) {
15148 MOZ_ASSERT(aElement.IsAutoPopover());
15149 TopLayerPop(aElement);
15152 void Document::AddPopoverToTopLayer(Element& aElement) {
15153 MOZ_ASSERT(aElement.GetPopoverData());
15154 TopLayerPush(aElement);
15157 void Document::RemovePopoverFromTopLayer(Element& aElement) {
15158 MOZ_ASSERT(aElement.GetPopoverData());
15159 TopLayerPop(aElement);
15162 // Returns true if aDoc browsing context is focused.
15163 bool IsInFocusedTab(Document* aDoc) {
15164 BrowsingContext* bc = aDoc->GetBrowsingContext();
15165 if (!bc) {
15166 return false;
15169 nsFocusManager* fm = nsFocusManager::GetFocusManager();
15170 if (!fm) {
15171 return false;
15174 if (XRE_IsParentProcess()) {
15175 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
15176 // by retaining the old code path for the parent process.
15177 nsIDocShell* docshell = aDoc->GetDocShell();
15178 if (!docshell) {
15179 return false;
15181 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15182 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15183 if (!rootItem) {
15184 return false;
15186 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
15187 if (!rootWin) {
15188 return false;
15191 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
15192 activeWindow = fm->GetActiveWindow();
15193 if (!activeWindow) {
15194 return false;
15197 return activeWindow == rootWin;
15200 return fm->GetActiveBrowsingContext() == bc->Top();
15203 // Returns true if aDoc browsing context is focused and is also active.
15204 bool IsInActiveTab(Document* aDoc) {
15205 if (!IsInFocusedTab(aDoc)) {
15206 return false;
15209 BrowsingContext* bc = aDoc->GetBrowsingContext();
15210 MOZ_ASSERT(bc, "With no BrowsingContext, we should have failed earlier.");
15211 return bc->IsActive();
15214 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
15215 // Ensure the frame element is the fullscreen element in this document.
15216 // If the frame element is already the fullscreen element in this document,
15217 // this has no effect.
15218 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
15219 RequestFullscreen(std::move(request), XRE_IsContentProcess());
15222 void Document::RemoteFrameFullscreenReverted() {
15223 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
15224 RestorePreviousFullscreenState(std::move(exit));
15227 static bool HasFullscreenSubDocument(Document& aDoc) {
15228 uint32_t count = CountFullscreenSubDocuments(aDoc);
15229 NS_ASSERTION(count <= 1,
15230 "Fullscreen docs should have at most 1 fullscreen child!");
15231 return count >= 1;
15234 // Returns nullptr if a request for Fullscreen API is currently enabled
15235 // in the given document. Returns a static string indicates the reason
15236 // why it is not enabled otherwise.
15237 const char* Document::GetFullscreenError(CallerType aCallerType) {
15238 if (!StaticPrefs::full_screen_api_enabled()) {
15239 return "FullscreenDeniedDisabled";
15242 if (aCallerType == CallerType::System) {
15243 // Chrome code can always use the fullscreen API, provided it's not
15244 // explicitly disabled.
15245 return nullptr;
15248 if (!IsVisible()) {
15249 return "FullscreenDeniedHidden";
15252 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
15253 return "FullscreenDeniedFeaturePolicy";
15256 // Ensure that all containing elements are <iframe> and have allowfullscreen
15257 // attribute set.
15258 BrowsingContext* bc = GetBrowsingContext();
15259 if (!bc || !bc->FullscreenAllowed()) {
15260 return "FullscreenDeniedContainerNotAllowed";
15263 return nullptr;
15266 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
15267 Element* elem = aRequest.Element();
15268 // Strictly speaking, this isn't part of the fullscreen element ready
15269 // check in the spec, but per steps in the spec, when an element which
15270 // is already the fullscreen element requests fullscreen, nothing
15271 // should change and no event should be dispatched, but we still need
15272 // to resolve the returned promise.
15273 Element* fullscreenElement = GetUnretargetedFullscreenElement();
15274 if (elem == fullscreenElement) {
15275 aRequest.MayResolvePromise();
15276 return false;
15278 if (!elem->IsInComposedDoc()) {
15279 aRequest.Reject("FullscreenDeniedNotInDocument");
15280 return false;
15282 if (elem->IsPopoverOpen()) {
15283 aRequest.Reject("FullscreenDeniedPopoverOpen");
15284 return false;
15286 if (elem->OwnerDoc() != this) {
15287 aRequest.Reject("FullscreenDeniedMovedDocument");
15288 return false;
15290 if (!GetWindow()) {
15291 aRequest.Reject("FullscreenDeniedLostWindow");
15292 return false;
15294 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
15295 aRequest.Reject(msg);
15296 return false;
15298 if (HasFullscreenSubDocument(*this)) {
15299 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
15300 return false;
15302 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
15303 aRequest.Reject("FullscreenDeniedHTMLDialog");
15304 return false;
15306 if (!nsContentUtils::IsChromeDoc(this) && !IsInFocusedTab(this)) {
15307 aRequest.Reject("FullscreenDeniedNotFocusedTab");
15308 return false;
15310 // Deny requests when a windowed plugin is focused.
15311 nsFocusManager* fm = nsFocusManager::GetFocusManager();
15312 if (!fm) {
15313 NS_WARNING("Failed to retrieve focus manager in fullscreen request.");
15314 aRequest.MayRejectPromise("An unexpected error occurred");
15315 return false;
15317 if (nsContentUtils::HasPluginWithUncontrolledEventDispatch(
15318 fm->GetFocusedElement())) {
15319 aRequest.Reject("FullscreenDeniedFocusedPlugin");
15320 return false;
15322 return true;
15325 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
15326 MOZ_ASSERT(XRE_IsParentProcess());
15327 nsIDocShell* docShell = aDoc->GetDocShell();
15328 if (!docShell) {
15329 return nullptr;
15331 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15332 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15333 return rootItem ? rootItem->GetWindow() : nullptr;
15336 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
15337 nsPIDOMWindowOuter* aRootWin) {
15338 MOZ_ASSERT(XRE_IsParentProcess());
15339 // If we are in the chrome process, and the window has not been in
15340 // fullscreen, we certainly need to make that fullscreen first.
15341 if (!aRootWin->GetFullScreen()) {
15342 return false;
15344 // The iterator not being at end indicates there is still some
15345 // pending fullscreen request relates to this document. We have to
15346 // push the request to the pending queue so requests are handled
15347 // in the correct order.
15348 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15349 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15350 if (!iter.AtEnd()) {
15351 return false;
15354 // Same thing for exits. If we have any pending, we have to push
15355 // to the pending queue.
15356 PendingFullscreenChangeList::Iterator<FullscreenExit> iterExit(
15357 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15358 if (!iterExit.AtEnd()) {
15359 return false;
15362 // We have to apply the fullscreen state directly in this case,
15363 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
15364 // if it is already in fullscreen. If we do not apply the state but
15365 // instead add it to the queue and wait for the window as normal,
15366 // we would get stuck.
15367 return true;
15370 static bool CheckFullscreenAllowedElementType(const Element* elem) {
15371 // Per spec only HTML, <svg>, and <math> should be allowed, but
15372 // we also need to allow XUL elements right now.
15373 return elem->IsHTMLElement() || elem->IsXULElement() ||
15374 elem->IsSVGElement(nsGkAtoms::svg) ||
15375 elem->IsMathMLElement(nsGkAtoms::math);
15378 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
15379 bool aApplyFullscreenDirectly) {
15380 if (XRE_IsContentProcess()) {
15381 RequestFullscreenInContentProcess(std::move(aRequest),
15382 aApplyFullscreenDirectly);
15383 } else {
15384 RequestFullscreenInParentProcess(std::move(aRequest),
15385 aApplyFullscreenDirectly);
15389 void Document::RequestFullscreenInContentProcess(
15390 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15391 MOZ_ASSERT(XRE_IsContentProcess());
15393 // If we are in the content process, we can apply the fullscreen
15394 // state directly only if we have been in DOM fullscreen, because
15395 // otherwise we always need to notify the chrome.
15396 if (aApplyFullscreenDirectly ||
15397 nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) {
15398 ApplyFullscreen(std::move(aRequest));
15399 return;
15402 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15403 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15404 return;
15407 // We don't need to check element ready before this point, because
15408 // if we called ApplyFullscreen, it would check that for us.
15409 if (!FullscreenElementReadyCheck(*aRequest)) {
15410 return;
15413 PendingFullscreenChangeList::Add(std::move(aRequest));
15414 // If we are not the top level process, dispatch an event to make
15415 // our parent process go fullscreen first.
15416 nsContentUtils::DispatchEventOnlyToChrome(
15417 this, ToSupports(this), u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
15418 Cancelable::eNo, /* DefaultAction */ nullptr);
15421 void Document::RequestFullscreenInParentProcess(
15422 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15423 MOZ_ASSERT(XRE_IsParentProcess());
15424 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
15425 if (!rootWin) {
15426 aRequest->MayRejectPromise("No active window");
15427 return;
15430 if (aApplyFullscreenDirectly ||
15431 ShouldApplyFullscreenDirectly(this, rootWin)) {
15432 ApplyFullscreen(std::move(aRequest));
15433 return;
15436 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15437 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15438 return;
15441 // See if we're waiting on an exit. If so, just make this one pending.
15442 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
15443 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15444 if (!iter.AtEnd()) {
15445 PendingFullscreenChangeList::Add(std::move(aRequest));
15446 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15447 return;
15450 // We don't need to check element ready before this point, because
15451 // if we called ApplyFullscreen, it would check that for us.
15452 if (!FullscreenElementReadyCheck(*aRequest)) {
15453 return;
15456 PendingFullscreenChangeList::Add(std::move(aRequest));
15457 // Make the window fullscreen.
15458 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15461 /* static */
15462 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
15463 bool handled = false;
15464 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15465 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15466 while (!iter.AtEnd()) {
15467 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15468 Document* doc = request->Document();
15469 if (doc->ApplyFullscreen(std::move(request))) {
15470 handled = true;
15473 return handled;
15476 /* static */
15477 void Document::ClearPendingFullscreenRequests(Document* aDoc) {
15478 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15479 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
15480 while (!iter.AtEnd()) {
15481 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15482 request->MayRejectPromise("Fullscreen request aborted");
15486 bool Document::HasPendingFullscreenRequests() {
15487 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15488 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15489 return !iter.AtEnd();
15492 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
15493 if (!FullscreenElementReadyCheck(*aRequest)) {
15494 return false;
15497 RefPtr<Document> doc = aRequest->Document();
15498 doc->HideAllPopoversWithoutRunningScript();
15500 // Stash a reference to any existing fullscreen doc, we'll use this later
15501 // to detect if the origin which is fullscreen has changed.
15502 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
15504 // Stores a list of documents which we must dispatch "fullscreenchange"
15505 // too. We're required by the spec to dispatch the events in root-to-leaf
15506 // order, but we traverse the doctree in a leaf-to-root order, so we save
15507 // references to the documents we must dispatch to so that we get the order
15508 // as specified.
15509 AutoTArray<Document*, 8> changed;
15511 // Remember the root document, so that if a fullscreen document is hidden
15512 // we can reset fullscreen state in the remaining visible fullscreen
15513 // documents.
15514 Document* fullScreenRootDoc =
15515 nsContentUtils::GetInProcessSubtreeRootDocument(this);
15517 // If a document is already in fullscreen, then unlock the mouse pointer
15518 // before setting a new document to fullscreen
15519 PointerLockManager::Unlock();
15521 // Set the fullscreen element. This sets the fullscreen style on the
15522 // element, and the fullscreen-ancestor styles on ancestors of the element
15523 // in this document.
15524 Element* elem = aRequest->Element();
15525 SetFullscreenElement(*elem);
15526 // Set the iframe fullscreen flag.
15527 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
15528 iframe->SetFullscreenFlag(true);
15530 changed.AppendElement(this);
15532 // Propagate up the document hierarchy, setting the fullscreen element as
15533 // the element's container in ancestor documents. This also sets the
15534 // appropriate css styles as well. Note we don't propagate down the
15535 // document hierarchy, the fullscreen element (or its container) is not
15536 // visible there. Stop when we reach the root document.
15537 Document* child = this;
15538 while (true) {
15539 child->SetFullscreenRoot(fullScreenRootDoc);
15541 // When entering fullscreen, reset the RCD's resolution to the intrinsic
15542 // resolution, otherwise the fullscreen content could be sized larger than
15543 // the screen (since fullscreen is implemented using position:fixed and
15544 // fixed elements are sized to the layout viewport).
15545 // This also ensures that things like video controls aren't zoomed in
15546 // when in fullscreen mode.
15547 if (PresShell* presShell = child->GetPresShell()) {
15548 if (RefPtr<MobileViewportManager> manager =
15549 presShell->GetMobileViewportManager()) {
15550 // Save the previous resolution so it can be restored.
15551 child->mSavedResolution = presShell->GetResolution();
15552 presShell->SetResolutionAndScaleTo(
15553 manager->ComputeIntrinsicResolution(),
15554 ResolutionChangeOrigin::MainThreadRestore);
15558 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
15559 "Fullscreen root should be set!");
15560 if (child == fullScreenRootDoc) {
15561 break;
15564 Element* element = child->GetEmbedderElement();
15565 if (!element) {
15566 // We've reached the root.No more changes need to be made
15567 // to the top layer stacks of documents further up the tree.
15568 break;
15571 Document* parent = child->GetInProcessParentDocument();
15572 parent->SetFullscreenElement(*element);
15573 changed.AppendElement(parent);
15574 child = parent;
15577 FullscreenRoots::Add(this);
15579 // If it is the first entry of the fullscreen, trigger an event so
15580 // that the UI can response to this change, e.g. hide chrome, or
15581 // notifying parent process to enter fullscreen. Note that chrome
15582 // code may also want to listen to MozDOMFullscreen:NewOrigin event
15583 // to pop up warning UI.
15584 if (!previousFullscreenDoc) {
15585 nsContentUtils::DispatchEventOnlyToChrome(
15586 this, ToSupports(elem), u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
15587 Cancelable::eNo, /* DefaultAction */ nullptr);
15590 // The origin which is fullscreen gets changed. Trigger an event so
15591 // that the chrome knows to pop up a warning UI. Note that
15592 // previousFullscreenDoc == nullptr upon first entry, we show the warning UI
15593 // directly as soon as chrome document goes into fullscreen state. Also note
15594 // that, in a multi-process browser, the code in content process is
15595 // responsible for sending message with the origin to its parent, and the
15596 // parent shouldn't rely on this event itself.
15597 if (aRequest->mShouldNotifyNewOrigin && previousFullscreenDoc &&
15598 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
15599 DispatchFullscreenNewOriginEvent(this);
15602 // Dispatch "fullscreenchange" events. Note that the loop order is
15603 // reversed so that events are dispatched in the tree order as
15604 // indicated in the spec.
15605 for (Document* d : Reversed(changed)) {
15606 DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement());
15608 aRequest->MayResolvePromise();
15609 return true;
15612 void Document::ClearOrientationPendingPromise() {
15613 mOrientationPendingPromise = nullptr;
15616 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
15617 if (mIsGoingAway) {
15618 return false;
15621 mOrientationPendingPromise = aPromise;
15622 return true;
15625 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
15626 dom::VisibilityState oldState = mVisibilityState;
15627 mVisibilityState = ComputeVisibilityState();
15628 if (oldState != mVisibilityState) {
15629 if (aDispatchEvent == DispatchVisibilityChange::Yes) {
15630 nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
15631 u"visibilitychange"_ns,
15632 CanBubble::eYes, Cancelable::eNo);
15634 NotifyActivityChanged();
15635 if (mVisibilityState == dom::VisibilityState::Visible) {
15636 MaybeActiveMediaComponents();
15639 bool visible = !Hidden();
15640 for (auto* listener : mWorkerListeners) {
15641 listener->OnVisible(visible);
15646 void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
15647 mWorkerListeners.Insert(aListener);
15648 aListener->OnVisible(!Hidden());
15651 void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
15652 mWorkerListeners.Remove(aListener);
15655 VisibilityState Document::ComputeVisibilityState() const {
15656 // We have to check a few pieces of information here:
15657 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
15658 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
15659 // want to use GetWindow here because it does weird groveling for windows
15660 // in some cases.
15661 // 3) Is our outer window background? If so, we're hidden.
15662 // Otherwise, we're visible.
15663 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
15664 mWindow->GetOuterWindow()->IsBackground()) {
15665 return dom::VisibilityState::Hidden;
15668 return dom::VisibilityState::Visible;
15671 void Document::PostVisibilityUpdateEvent() {
15672 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
15673 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
15674 DispatchVisibilityChange::Yes);
15675 Dispatch(TaskCategory::Other, event.forget());
15678 void Document::MaybeActiveMediaComponents() {
15679 auto* window = GetWindow();
15680 if (!window || !window->ShouldDelayMediaFromStart()) {
15681 return;
15683 window->ActivateMediaComponents();
15686 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
15687 nsINode::AddSizeOfExcludingThis(aWindowSizes,
15688 &aWindowSizes.mDOMSizes.mDOMOtherSize);
15690 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
15691 AddSizeOfNodeTree(*kid, aWindowSizes);
15694 // IMPORTANT: for our ComputedValues measurements, we want to measure
15695 // ComputedValues accessible from DOM elements before ComputedValues not
15696 // accessible from DOM elements (i.e. accessible only from the frame tree).
15698 // Therefore, the measurement of the Document superclass must happen after
15699 // the measurement of DOM nodes (above), because Document contains the
15700 // PresShell, which contains the frame tree.
15701 if (mPresShell) {
15702 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
15705 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
15707 aWindowSizes.mPropertyTablesSize +=
15708 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15710 if (EventListenerManager* elm = GetExistingListenerManager()) {
15711 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15714 if (mNodeInfoManager) {
15715 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
15718 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15719 mDOMMediaQueryLists.sizeOfExcludingThis(
15720 aWindowSizes.mState.mMallocSizeOf);
15722 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
15723 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15724 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15727 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
15729 for (auto& sheetArray : mAdditionalSheets) {
15730 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
15732 // Lumping in the loader with the style-sheets size is not ideal,
15733 // but most of the things in there are in fact stylesheets, so it
15734 // doesn't seem worthwhile to separate it out.
15735 // This can be null if we've already been unlinked.
15736 if (mCSSLoader) {
15737 aWindowSizes.mLayoutStyleSheetsSize +=
15738 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
15741 if (mResizeObserverController) {
15742 mResizeObserverController->AddSizeOfIncludingThis(aWindowSizes);
15745 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15746 mAttrStyleSheet ? mAttrStyleSheet->DOMSizeOfIncludingThis(
15747 aWindowSizes.mState.mMallocSizeOf)
15748 : 0;
15750 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15751 mStyledLinks.ShallowSizeOfExcludingThis(
15752 aWindowSizes.mState.mMallocSizeOf);
15754 // Measurement of the following members may be added later if DMD finds it
15755 // is worthwhile:
15756 // - mMidasCommandManager
15757 // - many!
15760 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
15761 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15762 aWindowSizes.mState.mMallocSizeOf(this);
15763 DocAddSizeOfExcludingThis(aWindowSizes);
15766 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
15767 size_t* aNodeSize) const {
15768 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
15769 // nsDocuments can only appear at the top of the DOM tree, and we use the
15770 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
15771 // be called.
15772 MOZ_CRASH();
15775 /* static */
15776 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
15777 size_t nodeSize = 0;
15778 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
15780 // This is where we transfer the nodeSize obtained from
15781 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
15782 switch (aNode.NodeType()) {
15783 case nsINode::ELEMENT_NODE:
15784 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
15785 break;
15786 case nsINode::TEXT_NODE:
15787 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
15788 break;
15789 case nsINode::CDATA_SECTION_NODE:
15790 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
15791 break;
15792 case nsINode::COMMENT_NODE:
15793 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
15794 break;
15795 default:
15796 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
15797 break;
15800 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
15801 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15804 if (aNode.IsContent()) {
15805 nsTArray<nsIContent*> anonKids;
15806 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
15807 nsIContent::eAllChildren);
15808 for (nsIContent* anonKid : anonKids) {
15809 AddSizeOfNodeTree(*anonKid, aWindowSizes);
15812 if (auto* element = Element::FromNode(aNode)) {
15813 if (ShadowRoot* shadow = element->GetShadowRoot()) {
15814 AddSizeOfNodeTree(*shadow, aWindowSizes);
15819 // NOTE(emilio): If you feel smart and want to change this function to use
15820 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
15821 // sane way, and kids of <content> won't point to the parent, so we'd never
15822 // find the root node where we should stop at.
15823 for (nsIContent* kid = aNode.GetFirstChild(); kid;
15824 kid = kid->GetNextSibling()) {
15825 AddSizeOfNodeTree(*kid, aWindowSizes);
15829 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
15830 ErrorResult& rv) {
15831 nsCOMPtr<nsIScriptGlobalObject> global =
15832 do_QueryInterface(aGlobal.GetAsSupports());
15833 if (!global) {
15834 rv.Throw(NS_ERROR_UNEXPECTED);
15835 return nullptr;
15838 nsCOMPtr<nsIScriptObjectPrincipal> prin =
15839 do_QueryInterface(aGlobal.GetAsSupports());
15840 if (!prin) {
15841 rv.Throw(NS_ERROR_UNEXPECTED);
15842 return nullptr;
15845 nsCOMPtr<nsIURI> uri;
15846 NS_NewURI(getter_AddRefs(uri), "about:blank");
15847 if (!uri) {
15848 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
15849 return nullptr;
15852 nsCOMPtr<Document> doc;
15853 nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
15854 nullptr, uri, uri, prin->GetPrincipal(),
15855 true, global, DocumentFlavorPlain);
15856 if (NS_FAILED(res)) {
15857 rv.Throw(res);
15858 return nullptr;
15861 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
15863 return doc.forget();
15866 XPathExpression* Document::CreateExpression(const nsAString& aExpression,
15867 XPathNSResolver* aResolver,
15868 ErrorResult& rv) {
15869 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
15872 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
15873 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
15876 already_AddRefed<XPathResult> Document::Evaluate(
15877 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
15878 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
15879 ErrorResult& rv) {
15880 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
15881 aType, aResult, rv);
15884 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
15885 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
15886 if (!item) {
15887 return nullptr;
15889 nsCOMPtr<nsIDocShellTreeOwner> owner;
15890 item->GetTreeOwner(getter_AddRefs(owner));
15891 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
15892 if (!appWin) {
15893 return nullptr;
15895 nsCOMPtr<nsIDocShell> appWinShell;
15896 appWin->GetDocShell(getter_AddRefs(appWinShell));
15897 if (!SameCOMIdentity(appWinShell, item)) {
15898 return nullptr;
15900 return appWin.forget();
15903 WindowContext* Document::GetTopLevelWindowContext() const {
15904 WindowContext* windowContext = GetWindowContext();
15905 return windowContext ? windowContext->TopWindowContext() : nullptr;
15908 Document* Document::GetTopLevelContentDocumentIfSameProcess() {
15909 Document* parent;
15911 if (!mLoadedAsData) {
15912 parent = this;
15913 } else {
15914 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15915 if (!window) {
15916 return nullptr;
15919 parent = window->GetExtantDoc();
15920 if (!parent) {
15921 return nullptr;
15925 do {
15926 if (parent->IsTopLevelContentDocument()) {
15927 break;
15930 // If we ever have a non-content parent before we hit a toplevel content
15931 // parent, then we're never going to find one. Just bail.
15932 if (!parent->IsContentDocument()) {
15933 return nullptr;
15936 parent = parent->GetInProcessParentDocument();
15937 } while (parent);
15939 return parent;
15942 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
15943 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
15946 void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
15947 MOZ_ASSERT(IsBeingUsedAsImage());
15948 MOZ_ASSERT(aReferencingDocument);
15950 if (!aReferencingDocument->mShouldReportUseCounters) {
15951 // No need to propagate use counters to a document that itself won't report
15952 // use counters.
15953 return;
15956 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15957 ("PropagateImageUseCounters from %s to %s",
15958 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
15959 nsContentUtils::TruncatedURLForDisplay(
15960 aReferencingDocument->mDocumentURI)
15961 .get()));
15963 if (aReferencingDocument->IsBeingUsedAsImage()) {
15964 NS_WARNING(
15965 "Page use counters from nested image documents may not "
15966 "propagate to the top-level document (bug 1657805)");
15969 SetCssUseCounterBits();
15970 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
15971 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
15974 bool Document::HasScriptsBlockedBySandbox() const {
15975 return mSandboxFlags & SANDBOXED_SCRIPTS;
15978 // Some use-counter sanity-checking.
15979 static_assert(size_t(eUseCounter_EndCSSProperties) -
15980 size_t(eUseCounter_FirstCSSProperty) ==
15981 size_t(eCSSProperty_COUNT_with_aliases),
15982 "We should have the right amount of CSS property use counters");
15983 static_assert(size_t(eUseCounter_Count) -
15984 size_t(eUseCounter_FirstCountedUnknownProperty) ==
15985 size_t(CountedUnknownProperty::Count),
15986 "We should have the right amount of counted unknown properties"
15987 " use counters");
15988 static_assert(size_t(eUseCounter_Count) * 2 ==
15989 size_t(Telemetry::HistogramUseCounterCount),
15990 "There should be two histograms (document and page)"
15991 " for each use counter");
15993 #define ASSERT_CSS_COUNTER(id_, method_) \
15994 static_assert(size_t(eUseCounter_property_##method_) - \
15995 size_t(eUseCounter_FirstCSSProperty) == \
15996 size_t(id_), \
15997 "Order for CSS counters and CSS property id should match");
15998 #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
15999 #define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
16000 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
16001 #define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
16002 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
16003 #define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
16004 ASSERT_CSS_COUNTER(eCSSPropertyAlias_##aliasid_, method_)
16005 #include "mozilla/ServoCSSPropList.h"
16006 #undef CSS_PROP_ALIAS
16007 #undef CSS_PROP_SHORTHAND
16008 #undef CSS_PROP_LONGHAND
16009 #undef CSS_PROP_PUBLIC_OR_PRIVATE
16010 #undef ASSERT_CSS_COUNTER
16012 void Document::SetCssUseCounterBits() {
16013 if (StaticPrefs::layout_css_use_counters_enabled()) {
16014 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
16015 auto id = nsCSSPropertyID(i);
16016 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
16017 SetUseCounter(nsCSSProps::UseCounterFor(id));
16022 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
16023 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
16024 auto id = CountedUnknownProperty(i);
16025 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
16026 id)) {
16027 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
16033 void Document::InitUseCounters() {
16034 // We can be called more than once, e.g. when session history navigation shows
16035 // us a second time.
16036 if (mUseCountersInitialized) {
16037 return;
16039 mUseCountersInitialized = true;
16041 static_assert(Telemetry::HistogramUseCounterCount > 0);
16043 if (!ShouldIncludeInTelemetry(/* aAllowExtensionURIs = */ true)) {
16044 return;
16047 // Now we know for sure that we should report use counters from this document.
16048 mShouldReportUseCounters = true;
16050 WindowContext* top = GetWindowContextForPageUseCounters();
16051 if (!top) {
16052 // This is the case for SVG image documents. They are not displayed in a
16053 // window, but we still do want to record document use counters for them.
16055 // Page use counter propagation is handled in PropagateImageUseCounters,
16056 // so there is no need to use the cross-process machinery to send them.
16057 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16058 ("InitUseCounters for a non-displayed document [%s]",
16059 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16060 return;
16063 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16064 if (!wgc) {
16065 return;
16068 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16069 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
16070 " [from %s]",
16071 wgc->InnerWindowId(), top->Id(),
16072 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16074 // Inform the parent process that we will send it page use counters later on.
16075 wgc->SendExpectPageUseCounters(top);
16076 mShouldSendPageUseCounters = true;
16079 // We keep separate counts for individual documents and top-level
16080 // pages to more accurately track how many web pages might break if
16081 // certain features were removed. Consider the case of a single
16082 // HTML document with several SVG images and/or iframes with
16083 // sub-documents of their own. If we maintained a single set of use
16084 // counters and all the sub-documents use a particular feature, then
16085 // telemetry would indicate that we would be breaking N documents if
16086 // that feature were removed. Whereas with a document/top-level
16087 // page split, we can see that N documents would be affected, but
16088 // only a single web page would be affected.
16090 // The difference between the values of these two histograms and the
16091 // related use counters below tell us how many pages did *not* use
16092 // the feature in question. For instance, if we see that a given
16093 // session has destroyed 30 content documents, but a particular use
16094 // counter shows only a count of 5, we can infer that the use
16095 // counter was *not* used in 25 of those 30 documents.
16097 // We do things this way, rather than accumulating a boolean flag
16098 // for each use counter, to avoid sending histograms for features
16099 // that don't get widely used. Doing things in this fashion means
16100 // smaller telemetry payloads and faster processing on the server
16101 // side.
16102 void Document::ReportDocumentUseCounters() {
16103 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
16104 return;
16107 mReportedDocumentUseCounters = true;
16109 // Note that a document is being destroyed. See the comment above for how
16110 // use counter histograms are interpreted relative to this measurement.
16111 // TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED is recorded in
16112 // WindowGlobalParent::FinishAccumulatingPageUseCounters.
16113 Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
16115 // Ask all of our resource documents to report their own document use
16116 // counters.
16117 EnumerateExternalResources([](Document& aDoc) {
16118 aDoc.ReportDocumentUseCounters();
16119 return CallState::Continue;
16122 // Copy StyleUseCounters into our document use counters.
16123 SetCssUseCounterBits();
16125 Maybe<nsCString> urlForLogging;
16126 const bool dumpCounters = StaticPrefs::dom_use_counters_dump_document();
16127 if (dumpCounters) {
16128 urlForLogging.emplace(
16129 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()));
16132 // Report our per-document use counters.
16133 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
16134 auto uc = static_cast<UseCounter>(c);
16135 if (!mUseCounters[uc]) {
16136 continue;
16139 auto id = static_cast<Telemetry::HistogramID>(
16140 Telemetry::HistogramFirstUseCounter + uc * 2);
16141 if (dumpCounters) {
16142 printf_stderr("USE_COUNTER_DOCUMENT: %s - %s\n",
16143 Telemetry::GetHistogramName(id), urlForLogging->get());
16145 Telemetry::Accumulate(id, 1);
16149 void Document::SendPageUseCounters() {
16150 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
16151 return;
16154 // Ask all of our resource documents to send their own document use
16155 // counters to the parent process to be counted as page use counters.
16156 EnumerateExternalResources([](Document& aDoc) {
16157 aDoc.SendPageUseCounters();
16158 return CallState::Continue;
16161 // Send our use counters to the parent process to accumulate them towards the
16162 // page use counters for the top-level document.
16164 // We take our own document use counters (those in mUseCounters) and any child
16165 // document use counters (those in mChildDocumentUseCounters) that have been
16166 // explicitly propagated up to us, which includes resource documents, static
16167 // clones, and SVG images.
16168 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16169 if (!wgc) {
16170 MOZ_ASSERT_UNREACHABLE(
16171 "SendPageUseCounters should be called while we still have access "
16172 "to our WindowContext");
16173 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16174 (" > too late to send page use counters"));
16175 return;
16178 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16179 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
16180 wgc->WindowContext()->Id(),
16181 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
16183 // Copy StyleUseCounters into our document use counters.
16184 SetCssUseCounterBits();
16186 UseCounters counters = mUseCounters | mChildDocumentUseCounters;
16187 wgc->SendAccumulatePageUseCounters(counters);
16190 bool Document::RecomputeResistFingerprinting() {
16191 const bool previous = mShouldResistFingerprinting;
16193 if (mParentDocument &&
16194 (NodePrincipal()->Equals(mParentDocument->NodePrincipal()) ||
16195 NodePrincipal()->GetIsNullPrincipal())) {
16196 // If we have a parent document, defer to it only when we have a null
16197 // principal (e.g. a sandboxed iframe or a data: uri) or when the parent
16198 // document's principal matches. This means we will defer about:blank,
16199 // about:srcdoc, blob and same-origin iframes to the parent, but not
16200 // cross-origin iframes.
16201 mShouldResistFingerprinting = !nsContentUtils::IsChromeDoc(this) &&
16202 mParentDocument->ShouldResistFingerprinting(
16203 RFPTarget::IsAlwaysEnabledForPrecompute);
16204 } else {
16205 mShouldResistFingerprinting =
16206 !nsContentUtils::IsChromeDoc(this) &&
16207 nsContentUtils::ShouldResistFingerprinting(
16208 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16211 return previous != mShouldResistFingerprinting;
16214 bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const {
16215 return mShouldResistFingerprinting && nsRFPService::IsRFPEnabledFor(aTarget);
16218 WindowContext* Document::GetWindowContextForPageUseCounters() const {
16219 if (mDisplayDocument) {
16220 // If we are a resource document, then go through it to find the
16221 // top-level document.
16222 return mDisplayDocument->GetWindowContextForPageUseCounters();
16225 if (mOriginalDocument) {
16226 // For static clones (print preview documents), contribute page use counters
16227 // towards the original document.
16228 return mOriginalDocument->GetWindowContextForPageUseCounters();
16231 WindowContext* wc = GetTopLevelWindowContext();
16232 if (!wc || !wc->GetBrowsingContext()->IsContent()) {
16233 return nullptr;
16236 return wc;
16239 void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
16240 if (mIntersectionObservers.IsEmpty()) {
16241 return;
16244 DOMHighResTimeStamp time = 0;
16245 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
16246 if (Performance* perf = win->GetPerformance()) {
16247 time = perf->TimeStampToDOMHighResForRendering(aNowTime);
16251 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16252 mIntersectionObservers);
16253 for (const auto& observer : observers) {
16254 if (observer) {
16255 observer->Update(*this, time);
16260 void Document::ScheduleIntersectionObserverNotification() {
16261 if (mIntersectionObservers.IsEmpty()) {
16262 return;
16264 MOZ_RELEASE_ASSERT(NS_IsMainThread());
16265 nsCOMPtr<nsIRunnable> notification =
16266 NewRunnableMethod("Document::NotifyIntersectionObservers", this,
16267 &Document::NotifyIntersectionObservers);
16268 Dispatch(TaskCategory::Other, notification.forget());
16271 void Document::NotifyIntersectionObservers() {
16272 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16273 mIntersectionObservers);
16274 for (const auto& observer : observers) {
16275 if (observer) {
16276 // MOZ_KnownLive because the 'observers' array guarantees to keep it
16277 // alive.
16278 MOZ_KnownLive(observer)->Notify();
16283 DOMIntersectionObserver& Document::EnsureLazyLoadImageObserver() {
16284 if (!mLazyLoadImageObserver) {
16285 mLazyLoadImageObserver =
16286 DOMIntersectionObserver::CreateLazyLoadObserver(*this);
16288 return *mLazyLoadImageObserver;
16291 DOMIntersectionObserver& Document::EnsureContentVisibilityObserver() {
16292 if (!mContentVisibilityObserver) {
16293 mContentVisibilityObserver =
16294 DOMIntersectionObserver::CreateContentVisibilityObserver(*this);
16296 return *mContentVisibilityObserver;
16299 void Document::ObserveForContentVisibility(Element& aElement) {
16300 EnsureContentVisibilityObserver().Observe(aElement);
16303 void Document::UnobserveForContentVisibility(Element& aElement) {
16304 if (mContentVisibilityObserver) {
16305 mContentVisibilityObserver->Unobserve(aElement);
16309 ResizeObserver& Document::EnsureLastRememberedSizeObserver() {
16310 if (!mLastRememberedSizeObserver) {
16311 mLastRememberedSizeObserver =
16312 ResizeObserver::CreateLastRememberedSizeObserver(*this);
16314 return *mLastRememberedSizeObserver;
16317 void Document::ObserveForLastRememberedSize(Element& aElement) {
16318 if (NS_WARN_IF(!IsActive())) {
16319 return;
16321 // Options are initialized with ResizeObserverBoxOptions::Content_box by
16322 // default, which is what we want.
16323 static ResizeObserverOptions options;
16324 EnsureLastRememberedSizeObserver().Observe(aElement, options);
16327 void Document::UnobserveForLastRememberedSize(Element& aElement) {
16328 if (mLastRememberedSizeObserver) {
16329 mLastRememberedSizeObserver->Unobserve(aElement);
16333 void Document::NotifyLayerManagerRecreated() {
16334 NotifyActivityChanged();
16335 EnumerateSubDocuments([](Document& aSubDoc) {
16336 aSubDoc.NotifyLayerManagerRecreated();
16337 return CallState::Continue;
16341 XPathEvaluator* Document::XPathEvaluator() {
16342 if (!mXPathEvaluator) {
16343 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
16345 return mXPathEvaluator.get();
16348 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
16349 return mCachedEncoder.forget();
16352 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
16353 mCachedEncoder = aEncoder;
16356 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
16358 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
16360 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
16361 mStateObjectContainer = scContainer;
16362 mCachedStateObject = JS::UndefinedValue();
16363 mCachedStateObjectValid = false;
16366 bool Document::ComputeDocumentLWTheme() const {
16367 if (!NodePrincipal()->IsSystemPrincipal()) {
16368 return false;
16371 Element* element = GetRootElement();
16372 return element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
16373 nsGkAtoms::_true, eCaseMatters);
16376 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
16377 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
16378 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
16379 ELEMENT_NODE);
16380 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
16382 nsCOMPtr<Element> element;
16383 DebugOnly<nsresult> rv =
16384 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
16385 mozilla::dom::NOT_FROM_PARSER);
16387 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
16388 return element.forget();
16391 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
16392 BrowsingContext* aContext) {
16393 aContext->PreOrderWalk([&](BrowsingContext* aBC) {
16394 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
16395 if (RefPtr<Document> doc = win->GetExtantDoc()) {
16396 SuppressDocument(doc);
16397 mDocuments.AppendElement(doc);
16403 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
16404 BrowsingContextGroup* aGroup) {
16405 for (const auto& bc : aGroup->Toplevels()) {
16406 SuppressBrowsingContext(bc);
16410 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
16411 SyncOperationBehavior aSyncBehavior)
16412 : mSyncBehavior(aSyncBehavior) {
16413 mMicroTaskLevel = 0;
16414 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16415 mMicroTaskLevel = ccjs->MicroTaskLevel();
16416 ccjs->SetMicroTaskLevel(0);
16418 if (aDoc) {
16419 mBrowsingContext = aDoc->GetBrowsingContext();
16420 if (InputTaskManager::CanSuspendInputEvent()) {
16421 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
16422 SuppressBrowsingContextGroup(bcg);
16424 } else if (mBrowsingContext) {
16425 SuppressBrowsingContext(mBrowsingContext->Top());
16427 if (mBrowsingContext &&
16428 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16429 InputTaskManager::CanSuspendInputEvent()) {
16430 mBrowsingContext->Group()->IncInputEventSuspensionLevel();
16435 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
16436 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16437 win->TimeoutManager().BeginSyncOperation();
16439 aDoc->SetIsInSyncOperation(true);
16442 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
16443 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16444 win->TimeoutManager().EndSyncOperation();
16446 aDoc->SetIsInSyncOperation(false);
16449 nsAutoSyncOperation::~nsAutoSyncOperation() {
16450 UnsuppressDocuments();
16451 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
16452 if (ccjs) {
16453 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
16455 if (mBrowsingContext &&
16456 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16457 InputTaskManager::CanSuspendInputEvent()) {
16458 mBrowsingContext->Group()->DecInputEventSuspensionLevel();
16462 void Document::SetIsInSyncOperation(bool aSync) {
16463 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16464 ccjs->UpdateMicroTaskSuppressionGeneration();
16467 if (aSync) {
16468 ++mInSyncOperationCount;
16469 } else {
16470 --mInSyncOperationCount;
16474 gfxUserFontSet* Document::GetUserFontSet() {
16475 if (!mFontFaceSet) {
16476 return nullptr;
16479 return mFontFaceSet->GetImpl();
16482 void Document::FlushUserFontSet() {
16483 if (!mFontFaceSetDirty) {
16484 return;
16487 mFontFaceSetDirty = false;
16489 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
16490 nsTArray<nsFontFaceRuleContainer> rules;
16491 RefPtr<PresShell> presShell = GetPresShell();
16492 if (presShell) {
16493 MOZ_ASSERT(mStyleSetFilled);
16494 mStyleSet->AppendFontFaceRules(rules);
16497 if (!mFontFaceSet && !rules.IsEmpty()) {
16498 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16501 bool changed = false;
16502 if (mFontFaceSet) {
16503 changed = mFontFaceSet->UpdateRules(rules);
16506 // We need to enqueue a style change reflow (for later) to
16507 // reflect that we're modifying @font-face rules. (However,
16508 // without a reflow, nothing will happen to start any downloads
16509 // that are needed.)
16510 if (changed && presShell) {
16511 if (nsPresContext* presContext = presShell->GetPresContext()) {
16512 presContext->UserFontSetUpdated();
16518 void Document::MarkUserFontSetDirty() {
16519 if (mFontFaceSetDirty) {
16520 return;
16522 mFontFaceSetDirty = true;
16523 if (PresShell* presShell = GetPresShell()) {
16524 presShell->EnsureStyleFlush();
16528 FontFaceSet* Document::Fonts() {
16529 if (!mFontFaceSet) {
16530 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16531 FlushUserFontSet();
16533 return mFontFaceSet;
16536 void Document::ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp) {
16537 MOZ_ASSERT(!aTimeStamp.IsNull());
16539 if (!mLastScrollLinkedEffectDetectionTime.IsNull() &&
16540 mLastScrollLinkedEffectDetectionTime >= aTimeStamp) {
16541 return;
16544 if (mLastScrollLinkedEffectDetectionTime.IsNull()) {
16545 // Report to console just once.
16546 nsContentUtils::ReportToConsole(
16547 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
16548 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
16551 mLastScrollLinkedEffectDetectionTime = aTimeStamp;
16554 bool Document::HasScrollLinkedEffect() const {
16555 if (nsPresContext* pc = GetPresContext()) {
16556 return mLastScrollLinkedEffectDetectionTime ==
16557 pc->RefreshDriver()->MostRecentRefresh();
16560 return false;
16563 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
16564 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16565 // Setting has user interction on a discarded browsing context has
16566 // no effect.
16567 Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
16571 bool Document::GetSHEntryHasUserInteraction() {
16572 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16573 return topWc->GetSHEntryHasUserInteraction();
16575 return false;
16578 void Document::SetUserHasInteracted() {
16579 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
16580 ("Document %p has been interacted by user.", this));
16582 // We maybe need to update the user-interaction permission.
16583 MaybeStoreUserInteractionAsPermission();
16585 // For purposes of reducing irrelevant session history entries on
16586 // the back button, we annotate entries with whether they had user
16587 // interaction. This is gated on its own flag on the WindowContext
16588 // (instead of mUserHasInteracted) to account for the fact that multiple
16589 // top-level SH entries can be associated with the same document.
16590 // Thus, whenever we create a new SH entry for this document,
16591 // this flag is reset.
16592 if (!GetSHEntryHasUserInteraction()) {
16593 nsIDocShell* docShell = GetDocShell();
16594 if (docShell) {
16595 nsCOMPtr<nsISHEntry> currentEntry;
16596 bool oshe;
16597 nsresult rv =
16598 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
16599 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
16600 currentEntry->SetHasUserInteraction(true);
16603 SetSHEntryHasUserInteraction(true);
16606 if (mUserHasInteracted) {
16607 return;
16610 mUserHasInteracted = true;
16612 if (mChannel) {
16613 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16614 loadInfo->SetDocumentHasUserInteracted(true);
16616 // Tell the parent process about user interaction
16617 if (auto* wgc = GetWindowGlobalChild()) {
16618 wgc->SendUpdateDocumentHasUserInteracted(true);
16621 MaybeAllowStorageForOpenerAfterUserInteraction();
16624 BrowsingContext* Document::GetBrowsingContext() const {
16625 nsCOMPtr<nsIDocShell> docshell(mDocumentContainer);
16626 return docshell ? docshell->GetBrowsingContext() : nullptr;
16629 void Document::NotifyUserGestureActivation() {
16630 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16631 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16632 WindowContext* windowContext = aBC->GetCurrentWindowContext();
16633 if (!windowContext) {
16634 return;
16637 nsIDocShell* docShell = aBC->GetDocShell();
16638 if (!docShell) {
16639 return;
16642 Document* document = docShell->GetDocument();
16643 if (!document) {
16644 return;
16647 // XXXedgar we probably could just check `IsInProcess()` after fission
16648 // enable.
16649 if (NodePrincipal()->Equals(document->NodePrincipal())) {
16650 windowContext->NotifyUserGestureActivation();
16654 for (bc = bc->GetParent(); bc; bc = bc->GetParent()) {
16655 if (WindowContext* windowContext = bc->GetCurrentWindowContext()) {
16656 windowContext->NotifyUserGestureActivation();
16662 bool Document::HasBeenUserGestureActivated() {
16663 RefPtr<WindowContext> wc = GetWindowContext();
16664 return wc && wc->HasBeenUserGestureActivated();
16667 DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
16668 if (RefPtr<WindowContext> wc = GetWindowContext()) {
16669 if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) {
16670 if (Performance* perf = innerWindow->GetPerformance()) {
16671 return perf->GetDOMTiming()->TimeStampToDOMHighRes(
16672 wc->GetUserGestureStart());
16677 NS_WARNING(
16678 "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
16679 return 0;
16682 void Document::ClearUserGestureActivation() {
16683 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16684 bc = bc->Top();
16685 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16686 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
16687 windowContext->NotifyResetUserGestureActivation();
16693 bool Document::HasValidTransientUserGestureActivation() const {
16694 RefPtr<WindowContext> wc = GetWindowContext();
16695 return wc && wc->HasValidTransientUserGestureActivation();
16698 bool Document::ConsumeTransientUserGestureActivation() {
16699 RefPtr<WindowContext> wc = GetWindowContext();
16700 return wc && wc->ConsumeTransientUserGestureActivation();
16703 void Document::SetDocTreeHadMedia() {
16704 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
16705 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
16706 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
16710 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
16711 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
16712 return;
16715 // This will probably change for project fission, but currently this document
16716 // and the opener are on the same process. In the future, we should make this
16717 // part async.
16718 nsPIDOMWindowInner* inner = GetInnerWindow();
16719 if (NS_WARN_IF(!inner)) {
16720 return;
16723 uint32_t cookieBehavior = CookieJarSettings()->GetCookieBehavior();
16724 if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
16725 cookieBehavior ==
16726 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
16727 // We care about first-party tracking resources only.
16728 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
16729 return;
16731 } else {
16732 MOZ_ASSERT(net::CookieJarSettings::IsRejectThirdPartyWithExceptions(
16733 cookieBehavior));
16736 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16737 if (NS_WARN_IF(!outer)) {
16738 return;
16741 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
16742 if (!openerBC) {
16743 // No opener.
16744 return;
16747 // We want to ensure the following check works for both fission mode and
16748 // non-fission mode:
16749 // "If the opener is not a 3rd party and if this window is not a 3rd party
16750 // with respect to the opener, we should not continue."
16752 // In non-fission mode, the opener and the opened window are in the same
16753 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
16754 // In fission mode, if this window is not a 3rd party with respect to the
16755 // opener, they must be in the same process, so we can still use
16756 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
16757 // party.
16758 if (openerBC->IsInProcess()) {
16759 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
16760 if (NS_WARN_IF(!outerOpener)) {
16761 return;
16764 nsCOMPtr<nsPIDOMWindowInner> openerInner =
16765 outerOpener->GetCurrentInnerWindow();
16766 if (NS_WARN_IF(!openerInner)) {
16767 return;
16770 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
16771 if (NS_WARN_IF(!openerDocument)) {
16772 return;
16775 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
16776 if (NS_WARN_IF(!openerURI)) {
16777 return;
16780 // If the opener is not a 3rd party and if this window is not
16781 // a 3rd party with respect to the opener, we should not continue.
16782 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
16783 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
16784 return;
16788 // We don't care when the asynchronous work finishes here.
16789 Unused << StorageAccessAPIHelper::AllowAccessFor(
16790 NodePrincipal(), openerBC,
16791 ContentBlockingNotifier::eOpenerAfterUserInteraction);
16794 namespace {
16796 // Documents can stay alive for days. We don't want to update the permission
16797 // value at any user-interaction, and, using a timer triggered any X seconds
16798 // should be good enough. 'X' is taken from
16799 // privacy.userInteraction.document.interval pref.
16800 // We also want to store the user-interaction before shutting down, and, for
16801 // this reason, this class implements nsIAsyncShutdownBlocker interface.
16802 class UserInteractionTimer final : public Runnable,
16803 public nsITimerCallback,
16804 public nsIAsyncShutdownBlocker {
16805 public:
16806 NS_DECL_ISUPPORTS_INHERITED
16808 explicit UserInteractionTimer(Document* aDocument)
16809 : Runnable("UserInteractionTimer"),
16810 mPrincipal(aDocument->NodePrincipal()),
16811 mDocument(do_GetWeakReference(aDocument)) {
16812 static int32_t userInteractionTimerId = 0;
16813 // Blocker names must be unique. Let's create it now because when needed,
16814 // the document could be already gone.
16815 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
16816 ++userInteractionTimerId, aDocument);
16819 // Runnable interface
16821 NS_IMETHOD
16822 Run() override {
16823 uint32_t interval =
16824 StaticPrefs::privacy_userInteraction_document_interval();
16825 if (!interval) {
16826 return NS_OK;
16829 RefPtr<UserInteractionTimer> self = this;
16830 auto raii =
16831 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
16833 nsresult rv = NS_NewTimerWithCallback(
16834 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
16835 NS_ENSURE_SUCCESS(rv, NS_OK);
16837 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16838 NS_ENSURE_TRUE(!!phase, NS_OK);
16840 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
16841 __LINE__, u"UserInteractionTimer shutdown"_ns);
16842 NS_ENSURE_SUCCESS(rv, NS_OK);
16844 raii.release();
16845 return NS_OK;
16848 // nsITimerCallback interface
16850 NS_IMETHOD
16851 Notify(nsITimer* aTimer) override {
16852 StoreUserInteraction();
16853 return NS_OK;
16856 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
16857 using nsINamed::GetName;
16858 #endif
16860 // nsIAsyncShutdownBlocker interface
16862 NS_IMETHOD
16863 GetName(nsAString& aName) override {
16864 aName = mBlockerName;
16865 return NS_OK;
16868 NS_IMETHOD
16869 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
16870 CancelTimerAndStoreUserInteraction();
16871 return NS_OK;
16874 NS_IMETHOD
16875 GetState(nsIPropertyBag**) override { return NS_OK; }
16877 private:
16878 ~UserInteractionTimer() = default;
16880 void StoreUserInteraction() {
16881 // Remove the shutting down blocker
16882 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16883 if (phase) {
16884 phase->RemoveBlocker(this);
16887 // If the document is not gone, let's reset its timer flag.
16888 nsCOMPtr<Document> document = do_QueryReferent(mDocument);
16889 if (document) {
16890 ContentBlockingUserInteraction::Observe(mPrincipal);
16891 document->ResetUserInteractionTimer();
16895 void CancelTimerAndStoreUserInteraction() {
16896 if (mTimer) {
16897 mTimer->Cancel();
16898 mTimer = nullptr;
16901 StoreUserInteraction();
16904 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
16905 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
16906 NS_ENSURE_TRUE(!!svc, nullptr);
16908 nsCOMPtr<nsIAsyncShutdownClient> phase;
16909 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
16910 NS_ENSURE_SUCCESS(rv, nullptr);
16912 return phase.forget();
16915 nsCOMPtr<nsIPrincipal> mPrincipal;
16916 nsWeakPtr mDocument;
16918 nsCOMPtr<nsITimer> mTimer;
16920 nsString mBlockerName;
16923 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
16924 nsIAsyncShutdownBlocker)
16926 } // namespace
16928 void Document::MaybeStoreUserInteractionAsPermission() {
16929 // We care about user-interaction stored only for top-level documents
16930 // and documents with access to the Storage Access API
16931 if (!IsTopLevelContentDocument()) {
16932 bool hasSA;
16933 nsresult rv = HasStorageAccessSync(hasSA);
16934 if (NS_FAILED(rv) || !hasSA) {
16935 return;
16939 if (!mUserHasInteracted) {
16940 // First interaction, let's store this info now.
16941 ContentBlockingUserInteraction::Observe(NodePrincipal());
16942 return;
16945 if (mHasUserInteractionTimerScheduled) {
16946 return;
16949 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
16950 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
16951 EventQueuePriority::Idle);
16952 if (NS_WARN_IF(NS_FAILED(rv))) {
16953 return;
16956 // This value will be reset by the timer.
16957 mHasUserInteractionTimerScheduled = true;
16960 void Document::ResetUserInteractionTimer() {
16961 mHasUserInteractionTimerScheduled = false;
16964 bool Document::IsExtensionPage() const {
16965 return BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
16968 void Document::AddResizeObserver(ResizeObserver& aObserver) {
16969 if (!mResizeObserverController) {
16970 mResizeObserverController = MakeUnique<ResizeObserverController>(this);
16972 mResizeObserverController->AddResizeObserver(aObserver);
16975 void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
16976 MOZ_DIAGNOSTIC_ASSERT(mResizeObserverController, "No controller?");
16977 if (MOZ_UNLIKELY(!mResizeObserverController)) {
16978 return;
16980 mResizeObserverController->RemoveResizeObserver(aObserver);
16983 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
16984 if (!mPermissionDelegateHandler) {
16985 mPermissionDelegateHandler =
16986 mozilla::MakeAndAddRef<PermissionDelegateHandler>(this);
16989 if (!mPermissionDelegateHandler->Initialize()) {
16990 mPermissionDelegateHandler = nullptr;
16993 return mPermissionDelegateHandler;
16996 void Document::ScheduleResizeObserversNotification() const {
16997 if (!mResizeObserverController) {
16998 return;
17001 mResizeObserverController->ScheduleNotification();
17004 void Document::ClearStaleServoData() {
17005 DocumentStyleRootIterator iter(this);
17006 while (Element* root = iter.GetNextStyleRoot()) {
17007 RestyleManager::ClearServoDataFromSubtree(root);
17011 Selection* Document::GetSelection(ErrorResult& aRv) {
17012 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
17013 if (!window) {
17014 return nullptr;
17017 if (!window->IsCurrentInnerWindow()) {
17018 return nullptr;
17021 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
17024 void Document::MakeBrowsingContextNonSynthetic() {
17025 if (nsContentUtils::ShouldHideObjectOrEmbedImageDocument()) {
17026 if (BrowsingContext* bc = GetBrowsingContext()) {
17027 if (bc->GetSyntheticDocumentContainer()) {
17028 Unused << bc->SetSyntheticDocumentContainer(false);
17034 nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
17035 // Step 1: check if cookie permissions are available or denied to this
17036 // document's principal
17037 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17038 if (!inner) {
17039 aHasStorageAccess = false;
17040 return NS_OK;
17042 Maybe<bool> resultBecauseCookiesApproved =
17043 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17044 CookieJarSettings(), NodePrincipal());
17045 if (resultBecauseCookiesApproved.isSome()) {
17046 if (resultBecauseCookiesApproved.value()) {
17047 aHasStorageAccess = true;
17048 return NS_OK;
17049 } else {
17050 aHasStorageAccess = false;
17051 return NS_OK;
17055 // Step 2: Check if the browser settings determine whether or not this
17056 // document has access to its unpartitioned cookies.
17057 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17058 bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
17059 bool isOnThirdPartySkipList = false;
17060 if (mChannel) {
17061 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17062 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17063 nsILoadInfo::StoragePermissionAllowListed;
17065 bool isThirdPartyTracker =
17066 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17067 Maybe<bool> resultBecauseBrowserSettings =
17068 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17069 CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
17070 isOnThirdPartySkipList, isThirdPartyTracker);
17071 if (resultBecauseBrowserSettings.isSome()) {
17072 if (resultBecauseBrowserSettings.value()) {
17073 aHasStorageAccess = true;
17074 return NS_OK;
17075 } else {
17076 aHasStorageAccess = false;
17077 return NS_OK;
17081 // Step 3: Check if the location of this call (embedded, top level, same-site)
17082 // determines if cookies are permitted or not.
17083 Maybe<bool> resultBecauseCallContext =
17084 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17085 false);
17086 if (resultBecauseCallContext.isSome()) {
17087 if (resultBecauseCallContext.value()) {
17088 aHasStorageAccess = true;
17089 return NS_OK;
17090 } else {
17091 aHasStorageAccess = false;
17092 return NS_OK;
17096 // Step 4: Check if the permissions for this document determine if if has
17097 // access or is denied cookies.
17098 Maybe<bool> resultBecausePreviousPermission =
17099 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17100 this, false);
17101 if (resultBecausePreviousPermission.isSome()) {
17102 if (resultBecausePreviousPermission.value()) {
17103 aHasStorageAccess = true;
17104 return NS_OK;
17105 } else {
17106 aHasStorageAccess = false;
17107 return NS_OK;
17110 // If you get here, we default to not giving you permission.
17111 aHasStorageAccess = false;
17112 return NS_OK;
17115 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
17116 mozilla::ErrorResult& aRv) {
17117 nsIGlobalObject* global = GetScopeObject();
17118 if (!global) {
17119 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17120 return nullptr;
17123 RefPtr<Promise> promise =
17124 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
17125 if (aRv.Failed()) {
17126 return nullptr;
17129 bool hasStorageAccess;
17130 nsresult rv = HasStorageAccessSync(hasStorageAccess);
17131 if (NS_FAILED(rv)) {
17132 promise->MaybeRejectWithUndefined();
17133 } else {
17134 promise->MaybeResolve(hasStorageAccess);
17137 return promise.forget();
17140 RefPtr<Document::GetContentBlockingEventsPromise>
17141 Document::GetContentBlockingEvents() {
17142 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
17143 if (!wgc) {
17144 return nullptr;
17147 return wgc->SendGetContentBlockingEvents()->Then(
17148 GetCurrentSerialEventTarget(), __func__,
17149 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
17150 ResolveOrRejectValue& aValue) {
17151 if (aValue.IsResolve()) {
17152 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
17153 aValue.ResolveValue(), __func__);
17156 return Document::GetContentBlockingEventsPromise::CreateAndReject(
17157 false, __func__);
17161 StorageAccessAPIHelper::PerformPermissionGrant
17162 Document::CreatePermissionGrantPromise(
17163 nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal,
17164 bool aHasUserInteraction, const Maybe<nsCString>& aTopLevelBaseDomain) {
17165 MOZ_ASSERT(aInnerWindow);
17166 MOZ_ASSERT(aPrincipal);
17167 RefPtr<Document> self(this);
17168 RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
17169 RefPtr<nsIPrincipal> principal(aPrincipal);
17171 return [inner, self, principal, aHasUserInteraction, aTopLevelBaseDomain]() {
17172 // Create the user prompt
17173 RefPtr<StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::Private>
17174 p = new StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
17175 Private(__func__);
17176 RefPtr<StorageAccessPermissionRequest> sapr =
17177 StorageAccessPermissionRequest::Create(
17178 inner, principal, aTopLevelBaseDomain,
17179 // Allow
17180 [p] {
17181 Telemetry::AccumulateCategorical(
17182 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
17183 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17185 // Block
17186 [p] {
17187 Telemetry::AccumulateCategorical(
17188 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
17189 p->Reject(false, __func__);
17192 using PromptResult = ContentPermissionRequestBase::PromptResult;
17193 PromptResult pr = sapr->CheckPromptPrefs();
17195 if (pr == PromptResult::Pending) {
17196 // We're about to show a prompt, record the request attempt
17197 Telemetry::AccumulateCategorical(
17198 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
17201 // Try to auto-grant the storage access so the user doesn't see the prompt.
17202 self->AutomaticStorageAccessPermissionCanBeGranted(aHasUserInteraction)
17203 ->Then(
17204 GetCurrentSerialEventTarget(), __func__,
17205 // If the autogrant check didn't fail, call this function
17206 [p, pr, sapr, inner](
17207 const Document::AutomaticStorageAccessPermissionGrantPromise::
17208 ResolveOrRejectValue& aValue) -> void {
17209 // Make a copy because we can't modified copy-captured lambda
17210 // variables.
17211 PromptResult pr2 = pr;
17213 // If the user didn't already click "allow" and we can autogrant,
17214 // do that!
17215 bool storageAccessCanBeGrantedAutomatically =
17216 aValue.IsResolve() && aValue.ResolveValue();
17217 bool autoGrant = false;
17218 if (pr2 == PromptResult::Pending &&
17219 storageAccessCanBeGrantedAutomatically) {
17220 pr2 = PromptResult::Granted;
17221 autoGrant = true;
17223 Telemetry::AccumulateCategorical(
17224 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
17225 AllowAutomatically);
17228 // If we can complete the permission request, do so.
17229 if (pr2 != PromptResult::Pending) {
17230 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
17231 pr2 == PromptResult::Denied);
17232 if (pr2 == PromptResult::Granted) {
17233 StorageAccessAPIHelper::StorageAccessPromptChoices choice =
17234 StorageAccessAPIHelper::eAllow;
17235 if (autoGrant) {
17236 choice = StorageAccessAPIHelper::eAllowAutoGrant;
17238 if (!autoGrant) {
17239 p->Resolve(choice, __func__);
17240 } else {
17241 sapr->MaybeDelayAutomaticGrants()->Then(
17242 GetCurrentSerialEventTarget(), __func__,
17243 [p, choice] { p->Resolve(choice, __func__); },
17244 [p] { p->Reject(false, __func__); });
17246 return;
17248 p->Reject(false, __func__);
17249 return;
17252 // If we get here, the auto-decision failed and we need to
17253 // wait for the user prompt to complete.
17254 sapr->RequestDelayedTask(
17255 inner->EventTargetFor(TaskCategory::Other),
17256 ContentPermissionRequestBase::DelayedTaskType::Request);
17259 return p;
17263 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
17264 mozilla::ErrorResult& aRv) {
17265 nsIGlobalObject* global = GetScopeObject();
17266 if (!global) {
17267 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17268 return nullptr;
17271 RefPtr<Promise> promise = Promise::Create(global, aRv);
17272 if (aRv.Failed()) {
17273 return nullptr;
17276 // Step 0: Check that we have user activation before proceeding to prevent
17277 // rapid calls to the API to leak information.
17278 if (!HasValidTransientUserGestureActivation()) {
17279 // Report an error to the console for this case
17280 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17281 nsLiteralCString("requestStorageAccess"),
17282 this, nsContentUtils::eDOM_PROPERTIES,
17283 "RequestStorageAccessUserGesture");
17284 ConsumeTransientUserGestureActivation();
17285 promise->MaybeRejectWithNotAllowedError(
17286 "requestStorageAccess not allowed"_ns);
17287 return promise.forget();
17290 // Get a pointer to the inner window- We need this for convenience sake
17291 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17292 if (!inner) {
17293 ConsumeTransientUserGestureActivation();
17294 promise->MaybeRejectWithNotAllowedError(
17295 "requestStorageAccess not allowed"_ns);
17296 return promise.forget();
17299 // Step 1: Check if the principal calling this has a permission that lets
17300 // them use cookies or forbids them from using cookies.
17301 // This is outside of the spec of the StorageAccess API, but makes the return
17302 // values to have proper semantics.
17303 Maybe<bool> resultBecauseCookiesApproved =
17304 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17305 CookieJarSettings(), NodePrincipal());
17306 if (resultBecauseCookiesApproved.isSome()) {
17307 if (resultBecauseCookiesApproved.value()) {
17308 promise->MaybeResolveWithUndefined();
17309 return promise.forget();
17310 } else {
17311 ConsumeTransientUserGestureActivation();
17312 promise->MaybeRejectWithNotAllowedError(
17313 "requestStorageAccess not allowed"_ns);
17314 return promise.forget();
17318 // Step 2: Check if the browser settings always allow or deny cookies.
17319 // We should always return a resolved promise if the cookieBehavior is ACCEPT.
17320 // This is outside of the spec of the StorageAccess API, but makes the return
17321 // values to have proper semantics.
17322 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17323 bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
17324 bool isOnThirdPartySkipList = false;
17325 if (mChannel) {
17326 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17327 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17328 nsILoadInfo::StoragePermissionAllowListed;
17330 bool isThirdPartyTracker =
17331 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17332 Maybe<bool> resultBecauseBrowserSettings =
17333 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17334 CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
17335 isOnThirdPartySkipList, isThirdPartyTracker);
17336 if (resultBecauseBrowserSettings.isSome()) {
17337 if (resultBecauseBrowserSettings.value()) {
17338 promise->MaybeResolveWithUndefined();
17339 return promise.forget();
17340 } else {
17341 ConsumeTransientUserGestureActivation();
17342 promise->MaybeRejectWithNotAllowedError(
17343 "requestStorageAccess not allowed"_ns);
17344 return promise.forget();
17348 // Step 3: Check if the Document calling requestStorageAccess has anything to
17349 // gain from storage access. It should be embedded, non-null, etc.
17350 Maybe<bool> resultBecauseCallContext =
17351 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17352 true);
17353 if (resultBecauseCallContext.isSome()) {
17354 if (resultBecauseCallContext.value()) {
17355 promise->MaybeResolveWithUndefined();
17356 return promise.forget();
17357 } else {
17358 ConsumeTransientUserGestureActivation();
17359 promise->MaybeRejectWithNotAllowedError(
17360 "requestStorageAccess not allowed"_ns);
17361 return promise.forget();
17365 // Step 4: Check if we already allowed or denied storage access for this
17366 // document's storage key.
17367 Maybe<bool> resultBecausePreviousPermission =
17368 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17369 this, true);
17370 if (resultBecausePreviousPermission.isSome()) {
17371 if (resultBecausePreviousPermission.value()) {
17372 promise->MaybeResolveWithUndefined();
17373 return promise.forget();
17374 } else {
17375 ConsumeTransientUserGestureActivation();
17376 promise->MaybeRejectWithNotAllowedError(
17377 "requestStorageAccess not allowed"_ns);
17378 return promise.forget();
17382 // Get pointers to some objects that will be used in the async portion
17383 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17384 RefPtr<nsGlobalWindowOuter> outer =
17385 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17386 if (!outer) {
17387 ConsumeTransientUserGestureActivation();
17388 promise->MaybeRejectWithNotAllowedError(
17389 "requestStorageAccess not allowed"_ns);
17390 return promise.forget();
17392 RefPtr<Document> self(this);
17394 // Consume user activation before entering the async part of this method.
17395 // This prevents usage of other transient activation-gated APIs.
17396 ConsumeTransientUserGestureActivation();
17398 // Step 5. Start an async call to request storage access. This will either
17399 // perform an automatic decision or notify the user, then perform some follow
17400 // on work changing state to reflect the result of the API. If it resolves,
17401 // the request was granted. If it rejects it was denied.
17402 StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17403 this, inner, bc, NodePrincipal(), true,
17404 ContentBlockingNotifier::eStorageAccessAPI, true)
17405 ->Then(
17406 GetCurrentSerialEventTarget(), __func__,
17407 [self, inner, promise] {
17408 inner->SaveStorageAccessPermissionGranted();
17409 self->NotifyUserGestureActivation();
17410 promise->MaybeResolveWithUndefined();
17412 [promise] {
17413 promise->MaybeRejectWithNotAllowedError(
17414 "requestStorageAccess not allowed"_ns);
17417 return promise.forget();
17420 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
17421 const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
17422 mozilla::ErrorResult& aRv) {
17423 nsIGlobalObject* global = GetScopeObject();
17424 if (!global) {
17425 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17426 return nullptr;
17428 RefPtr<Promise> promise = Promise::Create(global, aRv);
17429 if (aRv.Failed()) {
17430 return nullptr;
17433 // Step 0: Check that we have user activation before proceeding to prevent
17434 // rapid calls to the API to leak information.
17435 if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) {
17436 // Report an error to the console for this case
17437 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17438 nsLiteralCString("requestStorageAccess"),
17439 this, nsContentUtils::eDOM_PROPERTIES,
17440 "RequestStorageAccessUserGesture");
17441 ConsumeTransientUserGestureActivation();
17442 promise->MaybeRejectWithNotAllowedError(
17443 "requestStorageAccess not allowed"_ns);
17444 return promise.forget();
17447 // Step 1: Check if the provided URI is different-site to this Document
17448 nsCOMPtr<nsIURI> thirdPartyURI;
17449 nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
17450 if (NS_WARN_IF(NS_FAILED(rv))) {
17451 aRv.Throw(rv);
17452 return nullptr;
17454 bool isThirdPartyDocument;
17455 rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument);
17456 if (NS_WARN_IF(NS_FAILED(rv))) {
17457 aRv.Throw(rv);
17458 return nullptr;
17460 bool isOnRejectForeignAllowList =
17461 RejectForeignAllowList::Check(thirdPartyURI);
17462 Maybe<bool> resultBecauseBrowserSettings =
17463 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17464 CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
17465 false, true);
17466 if (resultBecauseBrowserSettings.isSome()) {
17467 if (resultBecauseBrowserSettings.value()) {
17468 promise->MaybeResolveWithUndefined();
17469 return promise.forget();
17471 ConsumeTransientUserGestureActivation();
17472 promise->MaybeRejectWithNotAllowedError(
17473 "requestStorageAccess not allowed"_ns);
17474 return promise.forget();
17477 // Step 2: Check that this Document is same-site to the top, and check that
17478 // we have user activation if we require it.
17479 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17480 CheckSameSiteCallingContextDecidesStorageAccessAPI(
17481 this, aRequireUserActivation);
17482 if (resultBecauseCallContext.isSome()) {
17483 if (resultBecauseCallContext.value()) {
17484 promise->MaybeResolveWithUndefined();
17485 return promise.forget();
17487 ConsumeTransientUserGestureActivation();
17488 promise->MaybeRejectWithNotAllowedError(
17489 "requestStorageAccess not allowed"_ns);
17490 return promise.forget();
17493 // Step 3: Get some useful variables that can be captured by the lambda for
17494 // the asynchronous portion
17495 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17496 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17497 if (!inner) {
17498 ConsumeTransientUserGestureActivation();
17499 promise->MaybeRejectWithNotAllowedError(
17500 "requestStorageAccess not allowed"_ns);
17501 return promise.forget();
17503 RefPtr<nsGlobalWindowOuter> outer =
17504 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17505 if (!outer) {
17506 ConsumeTransientUserGestureActivation();
17507 promise->MaybeRejectWithNotAllowedError(
17508 "requestStorageAccess not allowed"_ns);
17509 return promise.forget();
17511 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17512 thirdPartyURI, NodePrincipal()->OriginAttributesRef());
17513 if (!principal) {
17514 ConsumeTransientUserGestureActivation();
17515 promise->MaybeRejectWithNotAllowedError(
17516 "requestStorageAccess not allowed"_ns);
17517 return promise.forget();
17520 RefPtr<Document> self(this);
17521 bool hasUserActivation = HasValidTransientUserGestureActivation();
17523 // Consume user activation before entering the async part of this method.
17524 // This prevents usage of other transient activation-gated APIs.
17525 ConsumeTransientUserGestureActivation();
17527 // Step 4a: Start the async part of this function. Check the cookie
17528 // permission, but this can't be done in this process. We needs the cookie
17529 // permission of the URL as if it were embedded on this page, so we need to
17530 // make this check in the ContentParent.
17531 StorageAccessAPIHelper::AsyncCheckCookiesPermittedDecidesStorageAccessAPI(
17532 GetBrowsingContext(), principal)
17533 ->Then(
17534 GetCurrentSerialEventTarget(), __func__,
17535 [inner, thirdPartyURI, bc, principal, hasUserActivation, self,
17536 promise](Maybe<bool> cookieResult) {
17537 // Handle the result of the cookie permission check that took place
17538 // in the ContentParent.
17539 if (cookieResult.isSome()) {
17540 if (cookieResult.value()) {
17541 return MozPromise<int, bool, true>::CreateAndResolve(true,
17542 __func__);
17544 return MozPromise<int, bool, true>::CreateAndReject(false,
17545 __func__);
17548 // Step 4b: Check for the existing storage access permission
17549 nsAutoCString type;
17550 bool ok =
17551 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
17552 if (!ok) {
17553 return MozPromise<int, bool, true>::CreateAndReject(false,
17554 __func__);
17556 if (AntiTrackingUtils::CheckStoragePermission(
17557 self->NodePrincipal(), type,
17558 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
17559 return MozPromise<int, bool, true>::CreateAndResolve(true,
17560 __func__);
17563 // Step 4c: Try to request storage access, either automatically or
17564 // with a user-prompt. This is the part that is async in the
17565 // typical requestStorageAccess function.
17566 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17567 self, inner, bc, principal, hasUserActivation,
17568 ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI,
17569 true);
17571 // If the IPC rejects, we should reject our promise here which will
17572 // cause a rejection of the promise we already returned
17573 [promise]() {
17574 return MozPromise<int, bool, true>::CreateAndReject(false,
17575 __func__);
17577 ->Then(
17578 GetCurrentSerialEventTarget(), __func__,
17579 // If the previous handlers resolved, we should reinstate user
17580 // activation and resolve the promise we returned in Step 5.
17581 [self, inner, promise] {
17582 inner->SaveStorageAccessPermissionGranted();
17583 self->NotifyUserGestureActivation();
17584 promise->MaybeResolveWithUndefined();
17586 // If the previous handler rejected, we should reject the promise
17587 // returned by this function.
17588 [promise] {
17589 promise->MaybeRejectWithNotAllowedError(
17590 "requestStorageAccess not allowed"_ns);
17593 // Step 5: While the async stuff is happening, we should return the promise so
17594 // our caller can continue executing.
17595 return promise.forget();
17598 already_AddRefed<Promise> Document::RequestStorageAccessUnderSite(
17599 const nsAString& aSerializedSite, ErrorResult& aRv) {
17600 nsIGlobalObject* global = GetScopeObject();
17601 if (!global) {
17602 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17603 return nullptr;
17605 RefPtr<Promise> promise = Promise::Create(global, aRv);
17606 if (aRv.Failed()) {
17607 return nullptr;
17610 // Check that we have user activation before proceeding to prevent
17611 // rapid calls to the API to leak information.
17612 if (!ConsumeTransientUserGestureActivation()) {
17613 // Report an error to the console for this case
17614 nsContentUtils::ReportToConsole(
17615 nsIScriptError::errorFlag, "requestStorageAccess"_ns, this,
17616 nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture");
17617 promise->MaybeRejectWithUndefined();
17618 return promise.forget();
17621 // Check if the provided URI is different-site to this Document
17622 nsCOMPtr<nsIURI> siteURI;
17623 nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite);
17624 if (NS_WARN_IF(NS_FAILED(rv))) {
17625 promise->MaybeRejectWithUndefined();
17626 return promise.forget();
17628 bool isCrossSiteArgument;
17629 rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument);
17630 if (NS_WARN_IF(NS_FAILED(rv))) {
17631 aRv.Throw(rv);
17632 return nullptr;
17634 if (!isCrossSiteArgument) {
17635 promise->MaybeRejectWithUndefined();
17636 return promise.forget();
17639 // Check if this party has broad cookie permissions.
17640 Maybe<bool> resultBecauseCookiesApproved =
17641 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17642 CookieJarSettings(), NodePrincipal());
17643 if (resultBecauseCookiesApproved.isSome()) {
17644 if (resultBecauseCookiesApproved.value()) {
17645 promise->MaybeResolveWithUndefined();
17646 return promise.forget();
17648 promise->MaybeRejectWithUndefined();
17649 return promise.forget();
17652 // Check if browser settings preclude this document getting storage
17653 // access under the provided site
17654 bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
17655 Maybe<bool> resultBecauseBrowserSettings =
17656 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17657 CookieJarSettings(), true, isOnRejectForeignAllowList, false, true);
17658 if (resultBecauseBrowserSettings.isSome()) {
17659 if (resultBecauseBrowserSettings.value()) {
17660 promise->MaybeResolveWithUndefined();
17661 return promise.forget();
17663 promise->MaybeRejectWithUndefined();
17664 return promise.forget();
17667 // Check that this Document is same-site to the top
17668 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17669 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
17670 if (resultBecauseCallContext.isSome()) {
17671 if (resultBecauseCallContext.value()) {
17672 promise->MaybeResolveWithUndefined();
17673 return promise.forget();
17675 promise->MaybeRejectWithUndefined();
17676 return promise.forget();
17679 nsCOMPtr<nsIPrincipal> principal(NodePrincipal());
17681 // Test if the permission this is requesting is already set
17682 nsCOMPtr<nsIPrincipal> argumentPrincipal =
17683 BasePrincipal::CreateContentPrincipal(
17684 siteURI, NodePrincipal()->OriginAttributesRef());
17685 if (!argumentPrincipal) {
17686 ConsumeTransientUserGestureActivation();
17687 promise->MaybeRejectWithUndefined();
17688 return promise.forget();
17690 nsCString originNoSuffix;
17691 rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix);
17692 if (NS_WARN_IF(NS_FAILED(rv))) {
17693 promise->MaybeRejectWithUndefined();
17694 return promise.forget();
17697 ContentChild* cc = ContentChild::GetSingleton();
17698 MOZ_ASSERT(cc);
17699 RefPtr<Document> self(this);
17700 cc->SendTestStorageAccessPermission(argumentPrincipal, originNoSuffix)
17701 ->Then(
17702 GetCurrentSerialEventTarget(), __func__,
17703 [promise, siteURI,
17704 self](const ContentChild::TestStorageAccessPermissionPromise::
17705 ResolveValueType& aResult) {
17706 if (aResult) {
17707 return StorageAccessAPIHelper::
17708 StorageAccessPermissionGrantPromise::CreateAndResolve(
17709 StorageAccessAPIHelper::eAllow, __func__);
17711 // Get a grant for the storage access permission that will be set
17712 // when this is completed in the embedding context
17713 nsCString serializedSite;
17714 RefPtr<nsEffectiveTLDService> etld =
17715 nsEffectiveTLDService::GetInstance();
17716 if (!etld) {
17717 return StorageAccessAPIHelper::
17718 StorageAccessPermissionGrantPromise::CreateAndReject(
17719 false, __func__);
17721 nsresult rv = etld->GetSite(siteURI, serializedSite);
17722 if (NS_FAILED(rv)) {
17723 return StorageAccessAPIHelper::
17724 StorageAccessPermissionGrantPromise::CreateAndReject(
17725 false, __func__);
17727 return self->CreatePermissionGrantPromise(
17728 self->GetInnerWindow(), self->NodePrincipal(), true,
17729 Some(serializedSite))();
17731 [](const ContentChild::TestStorageAccessPermissionPromise::
17732 RejectValueType& aResult) {
17733 return StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
17734 CreateAndReject(false, __func__);
17736 ->Then(
17737 GetCurrentSerialEventTarget(), __func__,
17738 [promise, principal, siteURI](int result) {
17739 ContentChild* cc = ContentChild::GetSingleton();
17740 if (!cc) {
17741 // TODO(bug 1778561): Make this work in non-content processes.
17742 promise->MaybeRejectWithUndefined();
17743 return;
17745 // Set a permission in the parent process that this document wants
17746 // storage access under the argument's site, resolving our returned
17747 // promise on success
17748 cc->SendSetAllowStorageAccessRequestFlag(principal, siteURI)
17749 ->Then(
17750 GetCurrentSerialEventTarget(), __func__,
17751 [promise](bool success) {
17752 if (success) {
17753 promise->MaybeResolveWithUndefined();
17754 } else {
17755 promise->MaybeRejectWithUndefined();
17758 [promise](mozilla::ipc::ResponseRejectReason reason) {
17759 promise->MaybeRejectWithUndefined();
17762 [promise](bool result) { promise->MaybeRejectWithUndefined(); });
17764 // Return the promise that is resolved in the async handler above
17765 return promise.forget();
17768 already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite(
17769 const nsAString& aSerializedOrigin, ErrorResult& aRv) {
17770 nsIGlobalObject* global = GetScopeObject();
17771 if (!global) {
17772 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17773 return nullptr;
17775 RefPtr<Promise> promise = Promise::Create(global, aRv);
17776 if (aRv.Failed()) {
17777 return nullptr;
17780 // Check that the provided URI is different-site to this Document
17781 nsCOMPtr<nsIURI> argumentURI;
17782 nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin);
17783 if (NS_WARN_IF(NS_FAILED(rv))) {
17784 promise->MaybeRejectWithUndefined();
17785 return promise.forget();
17787 bool isCrossSiteArgument;
17788 rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument);
17789 if (NS_WARN_IF(NS_FAILED(rv))) {
17790 aRv.Throw(rv);
17791 return nullptr;
17793 if (!isCrossSiteArgument) {
17794 promise->MaybeRejectWithUndefined();
17795 return promise.forget();
17798 // Check if browser settings preclude this document getting storage
17799 // access under the provided site
17800 bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(argumentURI);
17801 Maybe<bool> resultBecauseBrowserSettings =
17802 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17803 CookieJarSettings(), true, isOnRejectForeignAllowList, false, true);
17804 if (resultBecauseBrowserSettings.isSome()) {
17805 if (resultBecauseBrowserSettings.value()) {
17806 promise->MaybeResolveWithUndefined();
17807 return promise.forget();
17809 promise->MaybeRejectWithUndefined();
17810 return promise.forget();
17813 // Check that this Document is same-site to the top
17814 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17815 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
17816 if (resultBecauseCallContext.isSome()) {
17817 if (resultBecauseCallContext.value()) {
17818 promise->MaybeResolveWithUndefined();
17819 return promise.forget();
17821 promise->MaybeRejectWithUndefined();
17822 return promise.forget();
17825 // Create principal of the embedded site requesting storage access
17826 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17827 argumentURI, NodePrincipal()->OriginAttributesRef());
17828 if (!principal) {
17829 promise->MaybeRejectWithUndefined();
17830 return promise.forget();
17833 // Get versions of these objects that we can use in lambdas for callbacks
17834 RefPtr<Document> self(this);
17835 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17836 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17838 // Test that the permission was set by a call to RequestStorageAccessUnderSite
17839 // from a top level document that is same-site with the argument
17840 ContentChild* cc = ContentChild::GetSingleton();
17841 if (!cc) {
17842 // TODO(bug 1778561): Make this work in non-content processes.
17843 promise->MaybeRejectWithUndefined();
17844 return promise.forget();
17846 cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI)
17847 ->Then(
17848 GetCurrentSerialEventTarget(), __func__,
17849 [inner, bc, self, principal](bool success) {
17850 if (success) {
17851 // If that resolved with true, check that we don't already have a
17852 // permission that gives cookie access.
17853 return StorageAccessAPIHelper::
17854 AsyncCheckCookiesPermittedDecidesStorageAccessAPI(bc,
17855 principal);
17857 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
17858 NS_ERROR_FAILURE, __func__);
17860 [](mozilla::ipc::ResponseRejectReason reason) {
17861 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
17862 NS_ERROR_FAILURE, __func__);
17864 ->Then(
17865 GetCurrentSerialEventTarget(), __func__,
17866 [inner, bc, principal, self, promise](Maybe<bool> cookieResult) {
17867 // Handle the result of the cookie permission check that took place
17868 // in the ContentParent.
17869 if (cookieResult.isSome()) {
17870 if (cookieResult.value()) {
17871 return StorageAccessAPIHelper::
17872 StorageAccessPermissionGrantPromise::CreateAndResolve(
17873 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
17875 return StorageAccessAPIHelper::
17876 StorageAccessPermissionGrantPromise::CreateAndReject(
17877 false, __func__);
17880 // Check for the existing storage access permission
17881 nsAutoCString type;
17882 bool ok =
17883 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
17884 if (!ok) {
17885 return StorageAccessAPIHelper::
17886 StorageAccessPermissionGrantPromise::CreateAndReject(
17887 false, __func__);
17889 if (AntiTrackingUtils::CheckStoragePermission(
17890 self->NodePrincipal(), type,
17891 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
17892 return StorageAccessAPIHelper::
17893 StorageAccessPermissionGrantPromise::CreateAndResolve(
17894 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
17897 // Try to request storage access, ignoring the final checks.
17898 // We ignore the final checks because this is where the "grant"
17899 // either by prompt doorhanger or autogrant takes place. We already
17900 // gathered an equivalent grant in requestStorageAccessUnderSite.
17901 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17902 self, inner, bc, principal, true,
17903 ContentBlockingNotifier::eStorageAccessAPI, false);
17905 // If the IPC rejects, we should reject our promise here which will
17906 // cause a rejection of the promise we already returned
17907 [promise]() {
17908 return MozPromise<int, bool, true>::CreateAndReject(false,
17909 __func__);
17911 ->Then(
17912 GetCurrentSerialEventTarget(), __func__,
17913 // If the previous handlers resolved, we should reinstate user
17914 // activation and resolve the promise we returned in Step 5.
17915 [self, inner, promise] {
17916 inner->SaveStorageAccessPermissionGranted();
17917 promise->MaybeResolveWithUndefined();
17919 // If the previous handler rejected, we should reject the promise
17920 // returned by this function.
17921 [promise] { promise->MaybeRejectWithUndefined(); });
17923 return promise.forget();
17926 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
17927 Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
17928 // requestStorageAccessForOrigin may not require user activation. If we don't
17929 // have user activation at this point we should always show the prompt.
17930 if (!hasUserActivation ||
17931 !StaticPrefs::privacy_antitracking_enableWebcompat()) {
17932 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
17933 false, __func__);
17935 if (XRE_IsContentProcess()) {
17936 // In the content process, we need to ask the parent process to compute
17937 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
17938 // isn't accessible in the content process.
17939 ContentChild* cc = ContentChild::GetSingleton();
17940 MOZ_ASSERT(cc);
17942 return cc->SendAutomaticStorageAccessPermissionCanBeGranted(NodePrincipal())
17943 ->Then(GetCurrentSerialEventTarget(), __func__,
17944 [](const ContentChild::
17945 AutomaticStorageAccessPermissionCanBeGrantedPromise::
17946 ResolveOrRejectValue& aValue) {
17947 if (aValue.IsResolve()) {
17948 return AutomaticStorageAccessPermissionGrantPromise::
17949 CreateAndResolve(aValue.ResolveValue(), __func__);
17952 return AutomaticStorageAccessPermissionGrantPromise::
17953 CreateAndReject(false, __func__);
17957 if (XRE_IsParentProcess()) {
17958 // In the parent process, we can directly compute this.
17959 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
17960 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
17961 __func__);
17964 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
17965 false, __func__);
17968 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
17969 nsIPrincipal* aPrincipal) {
17970 if (!StaticPrefs::dom_storage_access_auto_grants()) {
17971 return false;
17974 if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
17975 return false;
17978 nsCOMPtr<nsIBrowserUsage> bu = do_ImportModule(
17979 "resource:///modules/BrowserUsageTelemetry.jsm", fallible);
17980 if (NS_WARN_IF(!bu)) {
17981 return false;
17984 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
17985 nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours(
17986 &uniqueDomainsVisitedInPast24Hours);
17987 if (NS_WARN_IF(NS_FAILED(rv))) {
17988 return false;
17991 Maybe<size_t> maybeOriginsThirdPartyHasAccessTo =
17992 AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal);
17993 if (maybeOriginsThirdPartyHasAccessTo.isNothing()) {
17994 return false;
17996 size_t originsThirdPartyHasAccessTo =
17997 maybeOriginsThirdPartyHasAccessTo.value();
17999 // one percent of the number of top-levels origins visited in the current
18000 // session (but not to exceed 24 hours), or the value of the
18001 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
18002 // higher.
18003 size_t maxConcurrentAutomaticGrants = std::max(
18004 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
18005 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
18008 return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
18011 void Document::RecordNavigationTiming(ReadyState aReadyState) {
18012 if (!XRE_IsContentProcess()) {
18013 return;
18015 if (!IsTopLevelContentDocument()) {
18016 return;
18018 // If we dont have the timing yet (mostly because the doc is still loading),
18019 // get it from docshell.
18020 RefPtr<nsDOMNavigationTiming> timing = mTiming;
18021 if (!timing) {
18022 if (!mDocumentContainer) {
18023 return;
18025 timing = mDocumentContainer->GetNavigationTiming();
18026 if (!timing) {
18027 return;
18030 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
18031 switch (aReadyState) {
18032 case READYSTATE_LOADING:
18033 if (!mDOMLoadingSet) {
18034 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
18035 startTime);
18036 mDOMLoadingSet = true;
18038 break;
18039 case READYSTATE_INTERACTIVE:
18040 if (!mDOMInteractiveSet) {
18041 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
18042 startTime);
18043 mDOMInteractiveSet = true;
18045 break;
18046 case READYSTATE_COMPLETE:
18047 if (!mDOMCompleteSet) {
18048 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
18049 startTime);
18050 mDOMCompleteSet = true;
18052 break;
18053 default:
18054 NS_WARNING("Unexpected ReadyState value");
18055 break;
18059 bool Document::ModuleScriptsEnabled() {
18060 return nsContentUtils::IsChromeDoc(this) ||
18061 StaticPrefs::dom_moduleScripts_enabled();
18064 bool Document::ImportMapsEnabled() {
18065 return nsContentUtils::IsChromeDoc(this) ||
18066 StaticPrefs::dom_importMaps_enabled();
18069 void Document::ReportShadowDOMUsage() {
18070 nsPIDOMWindowInner* inner = GetInnerWindow();
18071 if (NS_WARN_IF(!inner)) {
18072 return;
18075 WindowContext* wc = inner->GetWindowContext();
18076 if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
18077 return;
18080 WindowContext* topWc = wc->TopWindowContext();
18081 if (topWc->GetHasReportedShadowDOMUsage()) {
18082 return;
18085 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
18088 // static
18089 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
18090 return StaticPrefs::dom_storage_access_enabled() &&
18091 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
18094 bool Document::StorageAccessSandboxed() const {
18095 return Document::StorageAccessSandboxed(GetSandboxFlags());
18098 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
18099 if (mCachedTabSizeGeneration == 0 ||
18100 GetGeneration() != mCachedTabSizeGeneration) {
18101 return false;
18103 aSizes->mDom += mCachedTabSizes.mDom;
18104 aSizes->mStyle += mCachedTabSizes.mStyle;
18105 aSizes->mOther += mCachedTabSizes.mOther;
18106 return true;
18109 void Document::SetCachedSizes(nsTabSizes* aSizes) {
18110 mCachedTabSizes.mDom = aSizes->mDom;
18111 mCachedTabSizes.mStyle = aSizes->mStyle;
18112 mCachedTabSizes.mOther = aSizes->mOther;
18113 mCachedTabSizeGeneration = GetGeneration();
18116 already_AddRefed<nsAtom> Document::GetContentLanguageAsAtomForStyle() const {
18117 nsAutoString contentLang;
18118 GetContentLanguage(contentLang);
18119 contentLang.StripWhitespace();
18121 // Content-Language may be a comma-separated list of language codes,
18122 // in which case the HTML5 spec says to treat it as unknown
18123 if (!contentLang.IsEmpty() && !contentLang.Contains(char16_t(','))) {
18124 return NS_Atomize(contentLang);
18127 return nullptr;
18130 already_AddRefed<nsAtom> Document::GetLanguageForStyle() const {
18131 RefPtr<nsAtom> lang = GetContentLanguageAsAtomForStyle();
18132 if (!lang) {
18133 lang = mLanguageFromCharset;
18135 return lang.forget();
18138 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
18139 nsAtom* aLanguage, bool* aNeedsToCache) const {
18140 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
18141 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
18144 void Document::DoCacheAllKnownLangPrefs() {
18145 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
18146 RefPtr<nsAtom> lang = GetLanguageForStyle();
18147 StaticPresData* data = StaticPresData::Get();
18148 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
18149 data->GetFontPrefsForLang(nsGkAtoms::x_math);
18150 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
18151 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
18152 for (const auto& key : mLanguagesUsed) {
18153 data->GetFontPrefsForLang(key);
18155 mMayNeedFontPrefsUpdate = false;
18158 void Document::RecomputeLanguageFromCharset() {
18159 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
18160 RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
18161 if (language == nsGkAtoms::Unicode) {
18162 language = service->GetLocaleLanguage();
18165 if (language == mLanguageFromCharset) {
18166 return;
18169 mMayNeedFontPrefsUpdate = true;
18170 mLanguageFromCharset = std::move(language);
18173 nsICookieJarSettings* Document::CookieJarSettings() {
18174 // If we are here, this is probably a javascript: URL document. In any case,
18175 // we must have a nsCookieJarSettings. Let's create it.
18176 if (!mCookieJarSettings) {
18177 Document* inProcessParent = GetInProcessParentDocument();
18179 if (inProcessParent) {
18180 mCookieJarSettings = net::CookieJarSettings::Create(
18181 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
18182 mozilla::net::CookieJarSettings::Cast(
18183 inProcessParent->CookieJarSettings())
18184 ->GetPartitionKey(),
18185 inProcessParent->CookieJarSettings()->GetIsFirstPartyIsolated(),
18186 inProcessParent->CookieJarSettings()
18187 ->GetIsOnContentBlockingAllowList(),
18188 inProcessParent->CookieJarSettings()
18189 ->GetShouldResistFingerprinting());
18191 // Inherit the fingerprinting random key from the parent.
18192 nsTArray<uint8_t> randomKey;
18193 nsresult rv = inProcessParent->CookieJarSettings()
18194 ->GetFingerprintingRandomizationKey(randomKey);
18196 if (NS_SUCCEEDED(rv)) {
18197 net::CookieJarSettings::Cast(mCookieJarSettings)
18198 ->SetFingerprintingRandomizationKey(randomKey);
18200 } else {
18201 mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
18204 if (auto* wgc = GetWindowGlobalChild()) {
18205 net::CookieJarSettingsArgs csArgs;
18206 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
18207 // Update cookie settings in the parent process
18208 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
18209 NS_WARNING(
18210 "Failed to update document's cookie jar settings on the "
18211 "WindowGlobalParent");
18216 return mCookieJarSettings;
18219 bool Document::HasStorageAccessPermissionGranted() {
18220 // The HasStoragePermission flag in LoadInfo remains fixed when
18221 // it is set in the parent process, so we need to check the cache
18222 // to see if the permission is granted afterwards.
18223 nsPIDOMWindowInner* inner = GetInnerWindow();
18224 if (inner && inner->HasStorageAccessPermissionGranted()) {
18225 return true;
18228 if (!mChannel) {
18229 return false;
18232 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18233 return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
18236 bool Document::HasStorageAccessPermissionGrantedByAllowList() {
18237 // We only care about if the document gets the storage permission via the
18238 // allow list here. So we don't check the storage access cache in the inner
18239 // window.
18241 if (!mChannel) {
18242 return false;
18245 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18246 return loadInfo->GetStoragePermission() ==
18247 nsILoadInfo::StoragePermissionAllowListed;
18250 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
18251 if (!StaticPrefs::
18252 privacy_partition_always_partition_third_party_non_cookie_storage()) {
18253 return EffectiveCookiePrincipal();
18256 nsPIDOMWindowInner* inner = GetInnerWindow();
18257 if (!inner) {
18258 return NodePrincipal();
18261 // Return our cached storage principal if one exists.
18262 if (mActiveStoragePrincipal) {
18263 return mActiveStoragePrincipal;
18266 // Calling StorageAllowedForDocument will notify the ContentBlockLog. This
18267 // loads TrackingDBService.jsm, which in turn pulls in osfile.jsm, making us
18268 // fail // browser/base/content/test/performance/browser_startup.js. To avoid
18269 // that, we short-circuit the check here by allowing storage access to system
18270 // and addon principles, avoiding the test-failure.
18271 nsIPrincipal* principal = NodePrincipal();
18272 if (principal && (principal->IsSystemPrincipal() ||
18273 principal->GetIsAddonOrExpandedAddonPrincipal())) {
18274 return mActiveStoragePrincipal = NodePrincipal();
18277 auto cookieJarSettings = const_cast<Document*>(this)->CookieJarSettings();
18278 if (cookieJarSettings->GetIsOnContentBlockingAllowList()) {
18279 return mActiveStoragePrincipal = NodePrincipal();
18282 StorageAccess storageAccess = StorageAllowedForDocument(this);
18283 if (!ShouldPartitionStorage(storageAccess) ||
18284 !StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
18285 return mActiveStoragePrincipal = NodePrincipal();
18288 Unused << NS_WARN_IF(NS_FAILED(StoragePrincipalHelper::GetPrincipal(
18289 nsGlobalWindowInner::Cast(inner),
18290 StoragePrincipalHelper::eForeignPartitionedPrincipal,
18291 getter_AddRefs(mActiveStoragePrincipal))));
18292 return mActiveStoragePrincipal;
18295 nsIPrincipal* Document::EffectiveCookiePrincipal() const {
18296 nsPIDOMWindowInner* inner = GetInnerWindow();
18297 if (!inner) {
18298 return NodePrincipal();
18301 // Return our cached storage principal if one exists.
18302 if (mActiveCookiePrincipal) {
18303 return mActiveCookiePrincipal;
18306 // We use the lower-level ContentBlocking API here to ensure this
18307 // check doesn't send notifications.
18308 uint32_t rejectedReason = 0;
18309 if (ShouldAllowAccessFor(inner, GetDocumentURI(), &rejectedReason)) {
18310 return mActiveCookiePrincipal = NodePrincipal();
18313 // Let's use the storage principal only if we need to partition the cookie
18314 // jar. When the permission is granted, access will be different and the
18315 // normal principal will be used.
18316 if (ShouldPartitionStorage(rejectedReason) &&
18317 !StoragePartitioningEnabled(
18318 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
18319 return mActiveCookiePrincipal = NodePrincipal();
18322 return mActiveCookiePrincipal = mPartitionedPrincipal;
18325 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
18326 // If the document is sandboxed document or data: document, we should
18327 // get URI of the parent document.
18328 for (const Document* document = this;
18329 document && document->IsContentDocument();
18330 document = document->GetInProcessParentDocument()) {
18331 // The document URI may be about:blank even if it comes from actual web
18332 // site. Therefore, we need to check the URI of its principal.
18333 nsIPrincipal* principal = document->NodePrincipal();
18334 if (principal->GetIsNullPrincipal()) {
18335 continue;
18337 return principal;
18339 return nullptr;
18342 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
18343 mIsInitialDocumentInWindow = aIsInitialDocument;
18345 // Asynchronously tell the parent process that we are, or are no longer, the
18346 // initial document. This happens async.
18347 if (auto* wgc = GetWindowGlobalChild()) {
18348 wgc->SendSetIsInitialDocument(aIsInitialDocument);
18352 // static
18353 void Document::AddToplevelLoadingDocument(Document* aDoc) {
18354 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18355 // Currently we're interested in foreground documents only, so bail out early.
18356 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
18357 return;
18360 if (!sLoadingForegroundTopLevelContentDocument) {
18361 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
18362 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18363 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18364 if (idleScheduler) {
18365 idleScheduler->SendRunningPrioritizedOperation();
18368 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
18369 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
18373 // static
18374 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
18375 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18376 if (sLoadingForegroundTopLevelContentDocument) {
18377 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
18378 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
18379 delete sLoadingForegroundTopLevelContentDocument;
18380 sLoadingForegroundTopLevelContentDocument = nullptr;
18382 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18383 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18384 if (idleScheduler) {
18385 idleScheduler->SendPrioritizedOperationDone();
18391 ColorScheme Document::DefaultColorScheme() const {
18392 return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
18395 ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
18396 if (ShouldResistFingerprinting(RFPTarget::CSSPrefersColorScheme) &&
18397 aIgnoreRFP == IgnoreRFP::No) {
18398 return ColorScheme::Light;
18401 if (nsPresContext* pc = GetPresContext()) {
18402 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
18403 return *scheme;
18407 // NOTE(emilio): We use IsInChromeDocShell rather than IsChromeDoc
18408 // intentionally, to make chrome documents in content docshells (like about
18409 // pages) use the content color scheme.
18410 if (IsInChromeDocShell()) {
18411 return LookAndFeel::ColorSchemeForChrome();
18413 return LookAndFeel::PreferredColorSchemeForContent();
18416 bool Document::HasRecentlyStartedForegroundLoads() {
18417 if (!sLoadingForegroundTopLevelContentDocument) {
18418 return false;
18421 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
18422 ++i) {
18423 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
18424 // A page loaded in foreground could be in background now.
18425 if (!doc->IsInBackgroundWindow()) {
18426 nsPIDOMWindowInner* win = doc->GetInnerWindow();
18427 if (win) {
18428 Performance* perf = win->GetPerformance();
18429 if (perf &&
18430 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
18431 return true;
18437 // Didn't find any loading foreground documents, just clear the array.
18438 delete sLoadingForegroundTopLevelContentDocument;
18439 sLoadingForegroundTopLevelContentDocument = nullptr;
18441 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18442 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18443 if (idleScheduler) {
18444 idleScheduler->SendPrioritizedOperationDone();
18446 return false;
18449 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
18450 nsFrameLoader* aStaticCloneOf) {
18451 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
18452 clone->mElement = aElement;
18453 clone->mStaticCloneOf = aStaticCloneOf;
18456 bool Document::ShouldAvoidNativeTheme() const {
18457 return StaticPrefs::widget_non_native_theme_enabled() &&
18458 (!IsInChromeDocShell() || XRE_IsContentProcess());
18461 bool Document::UseRegularPrincipal() const {
18462 return EffectiveStoragePrincipal() == NodePrincipal();
18465 bool Document::HasThirdPartyChannel() {
18466 nsCOMPtr<nsIChannel> channel = GetChannel();
18467 if (channel) {
18468 // We assume that the channel is a third-party by default.
18469 bool thirdParty = true;
18471 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
18472 components::ThirdPartyUtil::Service();
18473 if (!thirdPartyUtil) {
18474 return thirdParty;
18477 // Check that if the channel is a third-party to its parent.
18478 nsresult rv =
18479 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
18480 if (NS_FAILED(rv)) {
18481 // Assume third-party in case of failure
18482 thirdParty = true;
18485 return thirdParty;
18488 if (mParentDocument) {
18489 return mParentDocument->HasThirdPartyChannel();
18492 return false;
18495 bool Document::ShouldIncludeInTelemetry(bool aAllowExtensionURIs) {
18496 if (!(IsContentDocument() || IsResourceDoc())) {
18497 return false;
18500 if (!aAllowExtensionURIs &&
18501 NodePrincipal()->GetIsAddonOrExpandedAddonPrincipal()) {
18502 return false;
18505 return !NodePrincipal()->SchemeIs("about") &&
18506 !NodePrincipal()->SchemeIs("chrome") &&
18507 !NodePrincipal()->SchemeIs("resource");
18510 void Document::GetConnectedShadowRoots(
18511 nsTArray<RefPtr<ShadowRoot>>& aOut) const {
18512 AppendToArray(aOut, mComposedShadowRoots);
18515 bool Document::HasPictureInPictureChildElement() const {
18516 return mPictureInPictureChildElementCount > 0;
18519 void Document::EnableChildElementInPictureInPictureMode() {
18520 mPictureInPictureChildElementCount++;
18521 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
18524 void Document::DisableChildElementInPictureInPictureMode() {
18525 mPictureInPictureChildElementCount--;
18526 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
18529 void Document::AddMediaElementWithMSE() {
18530 if (mMediaElementWithMSECount++ == 0) {
18531 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18532 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18537 void Document::RemoveMediaElementWithMSE() {
18538 MOZ_ASSERT(mMediaElementWithMSECount > 0);
18539 if (--mMediaElementWithMSECount == 0) {
18540 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18541 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18546 void Document::UnregisterFromMemoryReportingForDataDocument() {
18547 if (!mAddedToMemoryReportingAsDataDocument) {
18548 return;
18550 mAddedToMemoryReportingAsDataDocument = false;
18551 nsIGlobalObject* global = GetScopeObject();
18552 if (global) {
18553 if (nsPIDOMWindowInner* win = global->AsInnerWindow()) {
18554 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
18555 this);
18559 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
18560 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
18561 mOOPChildrenLoading.AppendElement(aChild);
18562 if (mOOPChildrenLoading.Length() == 1) {
18563 // Let's block unload so that we're blocked from going into the BFCache
18564 // until the child has actually notified us that it has done loading.
18565 BlockOnload();
18569 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
18570 // aChild will not be in the list if nsDocLoader::Stop() was called, since
18571 // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
18572 // so we don't need to call DocLoaderIsEmpty in that case.
18573 if (mOOPChildrenLoading.RemoveElement(aChild)) {
18574 if (mOOPChildrenLoading.IsEmpty()) {
18575 UnblockOnload(false);
18577 RefPtr<nsDocLoader> docLoader(mDocumentContainer);
18578 if (docLoader) {
18579 docLoader->OOPChildrenLoadingIsEmpty();
18584 void Document::ClearOOPChildrenLoading() {
18585 nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
18586 mOOPChildrenLoading.SwapElements(oopChildrenLoading);
18587 if (!oopChildrenLoading.IsEmpty()) {
18588 UnblockOnload(false);
18592 HighlightRegistry& Document::HighlightRegistry() {
18593 if (!mHighlightRegistry) {
18594 mHighlightRegistry = MakeRefPtr<class HighlightRegistry>(this);
18596 return *mHighlightRegistry;
18599 } // namespace mozilla::dom