Bug 1852754: part 9) Add tests for dynamically loading <link rel="prefetch"> elements...
[gecko.git] / dom / base / Document.cpp
blob7983ac03553a36c613e82c9ba257b4128e897bb7
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/AttributeStyles.h"
59 #include "mozilla/DocumentStyleRootIterator.h"
60 #include "mozilla/EditorBase.h"
61 #include "mozilla/EditorCommands.h"
62 #include "mozilla/Encoding.h"
63 #include "mozilla/ErrorResult.h"
64 #include "mozilla/EventDispatcher.h"
65 #include "mozilla/EventListenerManager.h"
66 #include "mozilla/EventQueue.h"
67 #include "mozilla/EventStateManager.h"
68 #include "mozilla/ExtensionPolicyService.h"
69 #include "mozilla/FullscreenChange.h"
70 #include "mozilla/GlobalStyleSheetCache.h"
71 #include "mozilla/MappedDeclarationsBuilder.h"
72 #include "mozilla/HTMLEditor.h"
73 #include "mozilla/HoldDropJSObjects.h"
74 #include "mozilla/IdentifierMapEntry.h"
75 #include "mozilla/InputTaskManager.h"
76 #include "mozilla/IntegerRange.h"
77 #include "mozilla/InternalMutationEvent.h"
78 #include "mozilla/Likely.h"
79 #include "mozilla/Logging.h"
80 #include "mozilla/LookAndFeel.h"
81 #include "mozilla/MacroForEach.h"
82 #include "mozilla/Maybe.h"
83 #include "mozilla/MediaFeatureChange.h"
84 #include "mozilla/MediaManager.h"
85 #include "mozilla/MemoryReporting.h"
86 #include "mozilla/NullPrincipal.h"
87 #include "mozilla/OriginAttributes.h"
88 #include "mozilla/OwningNonNull.h"
89 #include "mozilla/PendingAnimationTracker.h"
90 #include "mozilla/PendingFullscreenEvent.h"
91 #include "mozilla/PermissionDelegateHandler.h"
92 #include "mozilla/PermissionManager.h"
93 #include "mozilla/Preferences.h"
94 #include "mozilla/PreloadHashKey.h"
95 #include "mozilla/PresShell.h"
96 #include "mozilla/PresShellForwards.h"
97 #include "mozilla/PresShellInlines.h"
98 #include "mozilla/PseudoStyleType.h"
99 #include "mozilla/RefCountType.h"
100 #include "mozilla/RelativeTo.h"
101 #include "mozilla/RestyleManager.h"
102 #include "mozilla/ReverseIterator.h"
103 #include "mozilla/ScrollTimelineAnimationTracker.h"
104 #include "mozilla/SMILAnimationController.h"
105 #include "mozilla/SMILTimeContainer.h"
106 #include "mozilla/ScopeExit.h"
107 #include "mozilla/Components.h"
108 #include "mozilla/ServoStyleConsts.h"
109 #include "mozilla/ServoStyleSet.h"
110 #include "mozilla/ServoTypes.h"
111 #include "mozilla/SizeOfState.h"
112 #include "mozilla/Span.h"
113 #include "mozilla/Sprintf.h"
114 #include "mozilla/StaticAnalysisFunctions.h"
115 #include "mozilla/StaticPrefs_apz.h"
116 #include "mozilla/StaticPrefs_browser.h"
117 #include "mozilla/StaticPrefs_docshell.h"
118 #include "mozilla/StaticPrefs_dom.h"
119 #include "mozilla/StaticPrefs_editor.h"
120 #include "mozilla/StaticPrefs_fission.h"
121 #include "mozilla/StaticPrefs_full_screen_api.h"
122 #include "mozilla/StaticPrefs_layout.h"
123 #include "mozilla/StaticPrefs_network.h"
124 #include "mozilla/StaticPrefs_page_load.h"
125 #include "mozilla/StaticPrefs_privacy.h"
126 #include "mozilla/StaticPrefs_security.h"
127 #include "mozilla/StaticPrefs_widget.h"
128 #include "mozilla/StaticPresData.h"
129 #include "mozilla/StorageAccess.h"
130 #include "mozilla/StoragePrincipalHelper.h"
131 #include "mozilla/StyleSheet.h"
132 #include "mozilla/Telemetry.h"
133 #include "mozilla/TelemetryHistogramEnums.h"
134 #include "mozilla/TelemetryScalarEnums.h"
135 #include "mozilla/TextControlElement.h"
136 #include "mozilla/TextEditor.h"
137 #include "mozilla/TimelineConsumers.h"
138 #include "mozilla/TypedEnumBits.h"
139 #include "mozilla/URLDecorationStripper.h"
140 #include "mozilla/URLExtraData.h"
141 #include "mozilla/Unused.h"
142 #include "mozilla/css/ImageLoader.h"
143 #include "mozilla/css/Loader.h"
144 #include "mozilla/css/Rule.h"
145 #include "mozilla/css/SheetParsingMode.h"
146 #include "mozilla/dom/AnonymousContent.h"
147 #include "mozilla/dom/BlobURLProtocolHandler.h"
148 #include "mozilla/dom/BrowserChild.h"
149 #include "mozilla/dom/BrowsingContext.h"
150 #include "mozilla/dom/BrowsingContextGroup.h"
151 #include "mozilla/dom/CDATASection.h"
152 #include "mozilla/dom/CSPDictionariesBinding.h"
153 #include "mozilla/dom/CanonicalBrowsingContext.h"
154 #include "mozilla/dom/ChromeObserver.h"
155 #include "mozilla/dom/ClientInfo.h"
156 #include "mozilla/dom/ClientState.h"
157 #include "mozilla/dom/Comment.h"
158 #include "mozilla/dom/ContentChild.h"
159 #include "mozilla/dom/DOMImplementation.h"
160 #include "mozilla/dom/DOMIntersectionObserver.h"
161 #include "mozilla/dom/DOMStringList.h"
162 #include "mozilla/dom/DocGroup.h"
163 #include "mozilla/dom/DocumentBinding.h"
164 #include "mozilla/dom/DocumentFragment.h"
165 #include "mozilla/dom/DocumentL10n.h"
166 #include "mozilla/dom/DocumentTimeline.h"
167 #include "mozilla/dom/DocumentType.h"
168 #include "mozilla/dom/ElementBinding.h"
169 #include "mozilla/dom/Event.h"
170 #include "mozilla/dom/EventListenerBinding.h"
171 #include "mozilla/dom/FailedCertSecurityInfoBinding.h"
172 #include "mozilla/dom/FeaturePolicy.h"
173 #include "mozilla/dom/FeaturePolicyUtils.h"
174 #include "mozilla/dom/FontFaceSet.h"
175 #include "mozilla/dom/FromParser.h"
176 #include "mozilla/dom/HighlightRegistry.h"
177 #include "mozilla/dom/HTMLAllCollection.h"
178 #include "mozilla/dom/HTMLBodyElement.h"
179 #include "mozilla/dom/HTMLCollectionBinding.h"
180 #include "mozilla/dom/HTMLDialogElement.h"
181 #include "mozilla/dom/HTMLFormElement.h"
182 #include "mozilla/dom/HTMLIFrameElement.h"
183 #include "mozilla/dom/HTMLImageElement.h"
184 #include "mozilla/dom/HTMLInputElement.h"
185 #include "mozilla/dom/HTMLLinkElement.h"
186 #include "mozilla/dom/HTMLMediaElement.h"
187 #include "mozilla/dom/HTMLMetaElement.h"
188 #include "mozilla/dom/HTMLSharedElement.h"
189 #include "mozilla/dom/HTMLTextAreaElement.h"
190 #include "mozilla/dom/ImageTracker.h"
191 #include "mozilla/dom/Link.h"
192 #include "mozilla/dom/MediaQueryList.h"
193 #include "mozilla/dom/MediaSource.h"
194 #include "mozilla/dom/MutationObservers.h"
195 #include "mozilla/dom/NameSpaceConstants.h"
196 #include "mozilla/dom/Navigator.h"
197 #include "mozilla/dom/NetErrorInfoBinding.h"
198 #include "mozilla/dom/NodeInfo.h"
199 #include "mozilla/dom/NodeIterator.h"
200 #include "mozilla/dom/PContentChild.h"
201 #include "mozilla/dom/PWindowGlobalChild.h"
202 #include "mozilla/dom/PageTransitionEvent.h"
203 #include "mozilla/dom/PageTransitionEventBinding.h"
204 #include "mozilla/dom/Performance.h"
205 #include "mozilla/dom/PermissionMessageUtils.h"
206 #include "mozilla/dom/PostMessageEvent.h"
207 #include "mozilla/dom/ProcessingInstruction.h"
208 #include "mozilla/dom/Promise.h"
209 #include "mozilla/dom/PromiseNativeHandler.h"
210 #include "mozilla/dom/ResizeObserverController.h"
211 #include "mozilla/dom/RustTypes.h"
212 #include "mozilla/dom/SVGElement.h"
213 #include "mozilla/dom/SVGDocument.h"
214 #include "mozilla/dom/SVGSVGElement.h"
215 #include "mozilla/dom/SVGUseElement.h"
216 #include "mozilla/dom/ScriptLoader.h"
217 #include "mozilla/dom/ScriptSettings.h"
218 #include "mozilla/dom/Selection.h"
219 #include "mozilla/dom/ServiceWorkerContainer.h"
220 #include "mozilla/dom/ServiceWorkerDescriptor.h"
221 #include "mozilla/dom/ServiceWorkerManager.h"
222 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
223 #include "mozilla/dom/ShadowRoot.h"
224 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
225 #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
226 #include "mozilla/dom/StyleSheetList.h"
227 #include "mozilla/dom/StyleSheetRemovedEvent.h"
228 #include "mozilla/dom/StyleSheetRemovedEventBinding.h"
229 #include "mozilla/dom/TimeoutManager.h"
230 #include "mozilla/dom/ToggleEvent.h"
231 #include "mozilla/dom/Touch.h"
232 #include "mozilla/dom/TouchEvent.h"
233 #include "mozilla/dom/TreeOrderedArrayInlines.h"
234 #include "mozilla/dom/TreeWalker.h"
235 #include "mozilla/dom/URL.h"
236 #include "mozilla/dom/UserActivation.h"
237 #include "mozilla/dom/WindowBinding.h"
238 #include "mozilla/dom/WindowContext.h"
239 #include "mozilla/dom/WindowGlobalChild.h"
240 #include "mozilla/dom/WindowProxyHolder.h"
241 #include "mozilla/dom/WorkerDocumentListener.h"
242 #include "mozilla/dom/XPathEvaluator.h"
243 #include "mozilla/dom/nsCSPContext.h"
244 #include "mozilla/dom/nsCSPUtils.h"
245 #include "mozilla/extensions/WebExtensionPolicy.h"
246 #include "mozilla/fallible.h"
247 #include "mozilla/gfx/BaseCoord.h"
248 #include "mozilla/gfx/BaseSize.h"
249 #include "mozilla/gfx/Coord.h"
250 #include "mozilla/gfx/Point.h"
251 #include "mozilla/gfx/ScaleFactor.h"
252 #include "mozilla/glean/GleanMetrics.h"
253 #include "mozilla/intl/LocaleService.h"
254 #include "mozilla/ipc/IdleSchedulerChild.h"
255 #include "mozilla/ipc/MessageChannel.h"
256 #include "mozilla/net/ChannelEventQueue.h"
257 #include "mozilla/net/CookieJarSettings.h"
258 #include "mozilla/net/NeckoChannelParams.h"
259 #include "mozilla/net/RequestContextService.h"
260 #include "nsAboutProtocolUtils.h"
261 #include "nsAlgorithm.h"
262 #include "nsAttrValue.h"
263 #include "nsAttrValueInlines.h"
264 #include "nsBaseHashtable.h"
265 #include "nsBidiUtils.h"
266 #include "nsCRT.h"
267 #include "nsCSSPropertyID.h"
268 #include "nsCSSProps.h"
269 #include "nsCSSPseudoElements.h"
270 #include "nsCSSRendering.h"
271 #include "nsCanvasFrame.h"
272 #include "nsCaseTreatment.h"
273 #include "nsCharsetSource.h"
274 #include "nsCommandManager.h"
275 #include "nsCommandParams.h"
276 #include "nsComponentManagerUtils.h"
277 #include "nsContentCreatorFunctions.h"
278 #include "nsContentList.h"
279 #include "nsContentPermissionHelper.h"
280 #include "nsContentSecurityUtils.h"
281 #include "nsContentUtils.h"
282 #include "nsCoord.h"
283 #include "nsCycleCollectionNoteChild.h"
284 #include "nsCycleCollectionTraversalCallback.h"
285 #include "nsDOMAttributeMap.h"
286 #include "nsDOMCaretPosition.h"
287 #include "nsDOMNavigationTiming.h"
288 #include "nsDOMString.h"
289 #include "nsDeviceContext.h"
290 #include "nsDocShell.h"
291 #include "nsDocShellLoadTypes.h"
292 #include "nsEffectiveTLDService.h"
293 #include "nsError.h"
294 #include "nsEscape.h"
295 #include "nsFocusManager.h"
296 #include "nsFrameLoader.h"
297 #include "nsFrameLoaderOwner.h"
298 #include "nsGenericHTMLElement.h"
299 #include "nsGlobalWindowInner.h"
300 #include "nsGlobalWindowOuter.h"
301 #include "nsHTMLDocument.h"
302 #include "nsHtml5Module.h"
303 #include "nsHtml5Parser.h"
304 #include "nsHtml5TreeOpExecutor.h"
305 #include "nsIAsyncShutdown.h"
306 #include "nsIAuthPrompt.h"
307 #include "nsIAuthPrompt2.h"
308 #include "nsIBFCacheEntry.h"
309 #include "nsIBaseWindow.h"
310 #include "nsIBrowserChild.h"
311 #include "nsIBrowserUsage.h"
312 #include "nsICSSLoaderObserver.h"
313 #include "nsICategoryManager.h"
314 #include "nsICertOverrideService.h"
315 #include "nsIContent.h"
316 #include "nsIContentInlines.h"
317 #include "nsIContentPolicy.h"
318 #include "nsIContentSecurityPolicy.h"
319 #include "nsIContentSink.h"
320 #include "nsICookieJarSettings.h"
321 #include "nsICookieService.h"
322 #include "nsIDOMXULCommandDispatcher.h"
323 #include "nsIDocShell.h"
324 #include "nsIDocShellTreeItem.h"
325 #include "nsIDocumentActivity.h"
326 #include "nsIDocumentEncoder.h"
327 #include "nsIDocumentLoader.h"
328 #include "nsIDocumentLoaderFactory.h"
329 #include "nsIDocumentObserver.h"
330 #include "nsIDNSService.h"
331 #include "nsIEditingSession.h"
332 #include "nsIEditor.h"
333 #include "nsIEffectiveTLDService.h"
334 #include "nsIFile.h"
335 #include "nsIFileChannel.h"
336 #include "nsIFrame.h"
337 #include "nsIGlobalObject.h"
338 #include "nsIHTMLCollection.h"
339 #include "nsIHttpChannel.h"
340 #include "nsIHttpChannelInternal.h"
341 #include "nsIIOService.h"
342 #include "nsIImageLoadingContent.h"
343 #include "nsIInlineSpellChecker.h"
344 #include "nsIInputStreamChannel.h"
345 #include "nsIInterfaceRequestorUtils.h"
346 #include "nsILayoutHistoryState.h"
347 #include "nsIMultiPartChannel.h"
348 #include "nsIMutationObserver.h"
349 #include "nsINSSErrorsService.h"
350 #include "nsINamed.h"
351 #include "nsINodeList.h"
352 #include "nsIObjectLoadingContent.h"
353 #include "nsIObserverService.h"
354 #include "nsIPermission.h"
355 #include "nsIPrompt.h"
356 #include "nsIPropertyBag2.h"
357 #include "nsIPublicKeyPinningService.h"
358 #include "nsIReferrerInfo.h"
359 #include "nsIRefreshURI.h"
360 #include "nsIRequest.h"
361 #include "nsIRequestContext.h"
362 #include "nsIRunnable.h"
363 #include "nsISHEntry.h"
364 #include "nsIScriptElement.h"
365 #include "nsIScriptError.h"
366 #include "nsIScriptGlobalObject.h"
367 #include "nsIScriptSecurityManager.h"
368 #include "nsISecurityConsoleMessage.h"
369 #include "nsISelectionController.h"
370 #include "nsISerialEventTarget.h"
371 #include "nsISimpleEnumerator.h"
372 #include "nsISiteSecurityService.h"
373 #include "nsISocketProvider.h"
374 #include "nsISpeculativeConnect.h"
375 #include "nsIStructuredCloneContainer.h"
376 #include "nsIThread.h"
377 #include "nsITimedChannel.h"
378 #include "nsITimer.h"
379 #include "nsITransportSecurityInfo.h"
380 #include "nsIURIMutator.h"
381 #include "nsIVariant.h"
382 #include "nsIWeakReference.h"
383 #include "nsIWebNavigation.h"
384 #include "nsIWidget.h"
385 #include "nsIX509Cert.h"
386 #include "nsIX509CertValidity.h"
387 #include "nsIXMLContentSink.h"
388 #include "nsIHTMLContentSink.h"
389 #include "nsIXULRuntime.h"
390 #include "nsImageLoadingContent.h"
391 #include "nsImportModule.h"
392 #include "nsLanguageAtomService.h"
393 #include "nsLayoutUtils.h"
394 #include "nsMimeTypes.h"
395 #include "nsNetCID.h"
396 #include "nsNetUtil.h"
397 #include "nsNodeInfoManager.h"
398 #include "nsObjectLoadingContent.h"
399 #include "nsPIDOMWindowInlines.h"
400 #include "nsPIWindowRoot.h"
401 #include "nsPoint.h"
402 #include "nsPointerHashKeys.h"
403 #include "nsPresContext.h"
404 #include "nsQueryFrame.h"
405 #include "nsQueryObject.h"
406 #include "nsRange.h"
407 #include "nsRect.h"
408 #include "nsRefreshDriver.h"
409 #include "nsSandboxFlags.h"
410 #include "nsSerializationHelper.h"
411 #include "nsServiceManagerUtils.h"
412 #include "nsStringFlags.h"
413 #include "nsStyleUtil.h"
414 #include "nsStringIterator.h"
415 #include "nsStyleSheetService.h"
416 #include "nsStyleStruct.h"
417 #include "nsTextNode.h"
418 #include "nsUnicharUtils.h"
419 #include "nsWrapperCache.h"
420 #include "nsWrapperCacheInlines.h"
421 #include "nsXPCOMCID.h"
422 #include "nsXULAppAPI.h"
423 #include "prthread.h"
424 #include "prtime.h"
425 #include "prtypes.h"
426 #include "xpcpublic.h"
428 // XXX Must be included after mozilla/Encoding.h
429 #include "encoding_rs.h"
431 #include "mozilla/dom/XULBroadcastManager.h"
432 #include "mozilla/dom/XULPersist.h"
433 #include "nsIAppWindow.h"
434 #include "nsXULPrototypeDocument.h"
435 #include "nsXULCommandDispatcher.h"
436 #include "nsXULPopupManager.h"
437 #include "nsIDocShellTreeOwner.h"
439 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
440 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
441 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
442 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
444 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
446 mozilla::LazyLogModule gPageCacheLog("PageCache");
447 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
448 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
449 mozilla::LazyLogModule gUseCountersLog("UseCounters");
451 namespace mozilla {
452 namespace dom {
454 class Document::HeaderData {
455 public:
456 HeaderData(nsAtom* aField, const nsAString& aData)
457 : mField(aField), mData(aData) {}
459 ~HeaderData() {
460 // Delete iteratively to avoid blowing up the stack, though it shouldn't
461 // happen in practice.
462 UniquePtr<HeaderData> next = std::move(mNext);
463 while (next) {
464 next = std::move(next->mNext);
468 RefPtr<nsAtom> mField;
469 nsString mData;
470 UniquePtr<HeaderData> mNext;
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 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1273 // ==================================================================
1274 // =
1275 // ==================================================================
1277 Document::InternalCommandDataHashtable*
1278 Document::sInternalCommandDataHashtable = nullptr;
1280 // static
1281 void Document::Shutdown() {
1282 if (sInternalCommandDataHashtable) {
1283 sInternalCommandDataHashtable->Clear();
1284 delete sInternalCommandDataHashtable;
1285 sInternalCommandDataHashtable = nullptr;
1289 Document::Document(const char* aContentType)
1290 : nsINode(nullptr),
1291 DocumentOrShadowRoot(this),
1292 mCharacterSet(WINDOWS_1252_ENCODING),
1293 mCharacterSetSource(0),
1294 mParentDocument(nullptr),
1295 mCachedRootElement(nullptr),
1296 mNodeInfoManager(nullptr),
1297 #ifdef DEBUG
1298 mStyledLinksCleared(false),
1299 #endif
1300 mCachedStateObjectValid(false),
1301 mBlockAllMixedContent(false),
1302 mBlockAllMixedContentPreloads(false),
1303 mUpgradeInsecureRequests(false),
1304 mUpgradeInsecurePreloads(false),
1305 mDevToolsWatchingDOMMutations(false),
1306 mBidiEnabled(false),
1307 mMayNeedFontPrefsUpdate(true),
1308 mMathMLEnabled(false),
1309 mIsInitialDocumentInWindow(false),
1310 mIgnoreDocGroupMismatches(false),
1311 mLoadedAsData(false),
1312 mAddedToMemoryReportingAsDataDocument(false),
1313 mMayStartLayout(true),
1314 mHaveFiredTitleChange(false),
1315 mIsShowing(false),
1316 mVisible(true),
1317 mRemovedFromDocShell(false),
1318 // mAllowDNSPrefetch starts true, so that we can always reliably && it
1319 // with various values that might disable it. Since we never prefetch
1320 // unless we get a window, and in that case the docshell value will get
1321 // &&-ed in, this is safe.
1322 mAllowDNSPrefetch(true),
1323 mIsStaticDocument(false),
1324 mCreatingStaticClone(false),
1325 mHasPrintCallbacks(false),
1326 mInUnlinkOrDeletion(false),
1327 mHasHadScriptHandlingObject(false),
1328 mIsBeingUsedAsImage(false),
1329 mChromeRulesEnabled(false),
1330 mInChromeDocShell(false),
1331 mIsDevToolsDocument(false),
1332 mIsSyntheticDocument(false),
1333 mHasLinksToUpdateRunnable(false),
1334 mFlushingPendingLinkUpdates(false),
1335 mMayHaveDOMMutationObservers(false),
1336 mMayHaveAnimationObservers(false),
1337 mHasCSPDeliveredThroughHeader(false),
1338 mBFCacheDisallowed(false),
1339 mHasHadDefaultView(false),
1340 mStyleSheetChangeEventsEnabled(false),
1341 mDevToolsAnonymousAndShadowEventsEnabled(false),
1342 mIsSrcdocDocument(false),
1343 mHasDisplayDocument(false),
1344 mFontFaceSetDirty(true),
1345 mDidFireDOMContentLoaded(true),
1346 mFrameRequestCallbacksScheduled(false),
1347 mIsTopLevelContentDocument(false),
1348 mIsContentDocument(false),
1349 mDidCallBeginLoad(false),
1350 mEncodingMenuDisabled(false),
1351 mLinksEnabled(true),
1352 mIsSVGGlyphsDocument(false),
1353 mInDestructor(false),
1354 mIsGoingAway(false),
1355 mInXBLUpdate(false),
1356 mStyleSetFilled(false),
1357 mQuirkSheetAdded(false),
1358 mContentEditableSheetAdded(false),
1359 mDesignModeSheetAdded(false),
1360 mMayHaveTitleElement(false),
1361 mDOMLoadingSet(false),
1362 mDOMInteractiveSet(false),
1363 mDOMCompleteSet(false),
1364 mAutoFocusFired(false),
1365 mScrolledToRefAlready(false),
1366 mChangeScrollPosWhenScrollingToRef(false),
1367 mDelayFrameLoaderInitialization(false),
1368 mSynchronousDOMContentLoaded(false),
1369 mMaybeServiceWorkerControlled(false),
1370 mAllowZoom(false),
1371 mValidScaleFloat(false),
1372 mValidMinScale(false),
1373 mValidMaxScale(false),
1374 mWidthStrEmpty(false),
1375 mParserAborted(false),
1376 mReportedDocumentUseCounters(false),
1377 mHasReportedShadowDOMUsage(false),
1378 mHasDelayedRefreshEvent(false),
1379 mLoadEventFiring(false),
1380 mSkipLoadEventAfterClose(false),
1381 mDisableCookieAccess(false),
1382 mDisableDocWrite(false),
1383 mTooDeepWriteRecursion(false),
1384 mPendingMaybeEditingStateChanged(false),
1385 mHasBeenEditable(false),
1386 mHasWarnedAboutZoom(false),
1387 mIsRunningExecCommand(false),
1388 mSetCompleteAfterDOMContentLoaded(false),
1389 mDidHitCompleteSheetCache(false),
1390 mUseCountersInitialized(false),
1391 mShouldReportUseCounters(false),
1392 mShouldSendPageUseCounters(false),
1393 mUserHasInteracted(false),
1394 mHasUserInteractionTimerScheduled(false),
1395 mShouldResistFingerprinting(false),
1396 mCloningForSVGUse(false),
1397 mXMLDeclarationBits(0),
1398 mOnloadBlockCount(0),
1399 mWriteLevel(0),
1400 mContentEditableCount(0),
1401 mEditingState(EditingState::eOff),
1402 mCompatMode(eCompatibility_FullStandards),
1403 mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
1404 mAncestorIsLoading(false),
1405 mVisibilityState(dom::VisibilityState::Hidden),
1406 mType(eUnknown),
1407 mDefaultElementType(0),
1408 mAllowXULXBL(eTriUnset),
1409 mSkipDTDSecurityChecks(false),
1410 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
1411 mSandboxFlags(0),
1412 mPartID(0),
1413 mMarkedCCGeneration(0),
1414 mPresShell(nullptr),
1415 mSubtreeModifiedDepth(0),
1416 mPreloadPictureDepth(0),
1417 mEventsSuppressed(0),
1418 mIgnoreDestructiveWritesCounter(0),
1419 mStaticCloneCount(0),
1420 mWindow(nullptr),
1421 mBFCacheEntry(nullptr),
1422 mInSyncOperationCount(0),
1423 mBlockDOMContentLoaded(0),
1424 mUpdateNestLevel(0),
1425 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
1426 mViewportType(Unknown),
1427 mViewportFit(ViewportFitType::Auto),
1428 mSubDocuments(nullptr),
1429 mHeaderData(nullptr),
1430 mServoRestyleRootDirtyBits(0),
1431 mThrowOnDynamicMarkupInsertionCounter(0),
1432 mIgnoreOpensDuringUnloadCounter(0),
1433 mSavedResolution(1.0f),
1434 mSavedResolutionBeforeMVM(1.0f),
1435 mGeneration(0),
1436 mCachedTabSizeGeneration(0),
1437 mNextFormNumber(0),
1438 mNextControlNumber(0),
1439 mPreloadService(this),
1440 mShouldNotifyFetchSuccess(false),
1441 mShouldNotifyFormOrPasswordRemoved(false) {
1442 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
1444 SetIsInDocument();
1445 SetIsConnected(true);
1447 // Create these unconditionally, they will be used to warn about the `zoom`
1448 // property, even if use counters are disabled.
1449 mStyleUseCounters.reset(Servo_UseCounters_Create());
1451 SetContentType(nsDependentCString(aContentType));
1453 // Start out mLastStyleSheetSet as null, per spec
1454 SetDOMStringToNull(mLastStyleSheetSet);
1456 // void state used to differentiate an empty source from an unselected source
1457 mPreloadPictureFoundSource.SetIsVoid(true);
1459 RecomputeLanguageFromCharset();
1461 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
1462 mReferrerInfo = new dom::ReferrerInfo(nullptr);
1465 #ifndef ANDROID
1466 // unused by GeckoView
1467 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
1468 if (NS_WARN_IF(!aWin)) {
1469 return false;
1472 nsIURI* uri = aWin->GetDocumentURI();
1473 if (NS_WARN_IF(!uri)) {
1474 return false;
1476 // getSpec is an expensive operation, hence we first check the scheme
1477 // to see if the caller is actually an about: page.
1478 if (!uri->SchemeIs("about")) {
1479 return false;
1482 nsAutoCString aboutSpec;
1483 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
1484 NS_ENSURE_SUCCESS(rv, false);
1486 return aboutSpec.EqualsASCII(aSpec);
1488 #endif
1490 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
1491 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1492 #ifdef ANDROID
1493 // GeckoView uses data URLs for error pages, so for now just check for any
1494 // error page
1495 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1496 #else
1497 return win && IsAboutErrorPage(win, "neterror");
1498 #endif
1501 bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
1502 JSObject* aObject) {
1503 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1504 #ifdef ANDROID
1505 // GeckoView uses data URLs for error pages, so for now just check for any
1506 // error page
1507 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1508 #else
1509 return win && IsAboutErrorPage(win, "httpsonlyerror");
1510 #endif
1513 already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
1514 bool aIsTemporary, ErrorResult& aError) {
1515 RefPtr<Promise> promise = Promise::Create(GetScopeObject(), aError,
1516 Promise::ePropagateUserInteraction);
1517 if (aError.Failed()) {
1518 return nullptr;
1521 nsresult rv = NS_OK;
1522 if (NS_WARN_IF(!mFailedChannel)) {
1523 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1524 return promise.forget();
1527 nsCOMPtr<nsIURI> failedChannelURI;
1528 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
1529 if (!failedChannelURI) {
1530 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1531 return promise.forget();
1534 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI);
1535 if (!innerURI) {
1536 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1537 return promise.forget();
1540 nsAutoCString host;
1541 innerURI->GetAsciiHost(host);
1542 int32_t port;
1543 innerURI->GetPort(&port);
1545 nsCOMPtr<nsITransportSecurityInfo> tsi;
1546 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1547 if (NS_WARN_IF(NS_FAILED(rv))) {
1548 promise->MaybeReject(rv);
1549 return promise.forget();
1551 if (NS_WARN_IF(!tsi)) {
1552 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1553 return promise.forget();
1556 nsCOMPtr<nsIX509Cert> cert;
1557 rv = tsi->GetServerCert(getter_AddRefs(cert));
1558 if (NS_WARN_IF(NS_FAILED(rv))) {
1559 promise->MaybeReject(rv);
1560 return promise.forget();
1562 if (NS_WARN_IF(!cert)) {
1563 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1564 return promise.forget();
1567 if (XRE_IsContentProcess()) {
1568 ContentChild* cc = ContentChild::GetSingleton();
1569 MOZ_ASSERT(cc);
1570 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1571 cc->SendAddCertException(cert, host, port, attrs, aIsTemporary)
1572 ->Then(GetCurrentSerialEventTarget(), __func__,
1573 [promise](const mozilla::MozPromise<
1574 nsresult, mozilla::ipc::ResponseRejectReason,
1575 true>::ResolveOrRejectValue& aValue) {
1576 if (aValue.IsResolve()) {
1577 promise->MaybeResolve(aValue.ResolveValue());
1578 } else {
1579 promise->MaybeRejectWithUndefined();
1582 return promise.forget();
1585 if (XRE_IsParentProcess()) {
1586 nsCOMPtr<nsICertOverrideService> overrideService =
1587 do_GetService(NS_CERTOVERRIDE_CONTRACTID);
1588 if (!overrideService) {
1589 promise->MaybeReject(NS_ERROR_FAILURE);
1590 return promise.forget();
1593 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1594 rv = overrideService->RememberValidityOverride(host, port, attrs, cert,
1595 aIsTemporary);
1596 if (NS_WARN_IF(NS_FAILED(rv))) {
1597 promise->MaybeReject(rv);
1598 return promise.forget();
1601 promise->MaybeResolveWithUndefined();
1602 return promise.forget();
1605 promise->MaybeReject(NS_ERROR_FAILURE);
1606 return promise.forget();
1609 void Document::ReloadWithHttpsOnlyException() {
1610 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
1611 wgc->SendReloadWithHttpsOnlyException();
1615 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
1616 nsresult rv = NS_OK;
1617 if (NS_WARN_IF(!mFailedChannel)) {
1618 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1619 return;
1622 nsCOMPtr<nsITransportSecurityInfo> tsi;
1623 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1624 if (NS_WARN_IF(NS_FAILED(rv))) {
1625 aRv.Throw(rv);
1626 return;
1628 if (NS_WARN_IF(!tsi)) {
1629 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1630 return;
1633 nsAutoString errorCodeString;
1634 rv = tsi->GetErrorCodeString(errorCodeString);
1635 if (NS_WARN_IF(NS_FAILED(rv))) {
1636 aRv.Throw(rv);
1637 return;
1639 aInfo.mErrorCodeString.Assign(errorCodeString);
1642 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
1643 JSObject* aObject) {
1644 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1645 #ifdef ANDROID
1646 // GeckoView uses data URLs for error pages, so for now just check for any
1647 // error page
1648 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1649 #else
1650 return win && IsAboutErrorPage(win, "certerror");
1651 #endif
1654 bool Document::CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject) {
1655 RefPtr<BasePrincipal> principal =
1656 BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx));
1658 if (!principal) {
1659 return false;
1662 // We allow the privilege SSA to be called from system principal.
1663 if (principal->IsSystemPrincipal()) {
1664 return true;
1667 // We only allow calling the privilege SSA from the content script of the
1668 // webcompat extension.
1669 if (auto* policy = principal->ContentScriptAddonPolicy()) {
1670 nsAutoString addonID;
1671 policy->GetId(addonID);
1673 return addonID.EqualsLiteral("webcompat@mozilla.org");
1676 return false;
1679 bool Document::IsErrorPage() const {
1680 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
1681 return loadInfo && loadInfo->GetLoadErrorPage();
1684 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
1685 ErrorResult& aRv) {
1686 nsresult rv = NS_OK;
1687 if (NS_WARN_IF(!mFailedChannel)) {
1688 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1689 return;
1692 nsCOMPtr<nsITransportSecurityInfo> tsi;
1693 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1694 if (NS_WARN_IF(NS_FAILED(rv))) {
1695 aRv.Throw(rv);
1696 return;
1698 if (NS_WARN_IF(!tsi)) {
1699 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1700 return;
1703 nsAutoString errorCodeString;
1704 rv = tsi->GetErrorCodeString(errorCodeString);
1705 if (NS_WARN_IF(NS_FAILED(rv))) {
1706 aRv.Throw(rv);
1707 return;
1709 aInfo.mErrorCodeString.Assign(errorCodeString);
1711 nsITransportSecurityInfo::OverridableErrorCategory errorCategory;
1712 rv = tsi->GetOverridableErrorCategory(&errorCategory);
1713 if (NS_WARN_IF(NS_FAILED(rv))) {
1714 aRv.Throw(rv);
1715 return;
1717 switch (errorCategory) {
1718 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST:
1719 aInfo.mOverridableErrorCategory =
1720 dom::OverridableErrorCategory::Trust_error;
1721 break;
1722 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN:
1723 aInfo.mOverridableErrorCategory =
1724 dom::OverridableErrorCategory::Domain_mismatch;
1725 break;
1726 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME:
1727 aInfo.mOverridableErrorCategory =
1728 dom::OverridableErrorCategory::Expired_or_not_yet_valid;
1729 break;
1730 default:
1731 aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Unset;
1732 break;
1735 nsCOMPtr<nsIX509Cert> cert;
1736 nsCOMPtr<nsIX509CertValidity> validity;
1737 rv = tsi->GetServerCert(getter_AddRefs(cert));
1738 if (NS_WARN_IF(NS_FAILED(rv))) {
1739 aRv.Throw(rv);
1740 return;
1742 if (NS_WARN_IF(!cert)) {
1743 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1744 return;
1747 rv = cert->GetValidity(getter_AddRefs(validity));
1748 if (NS_WARN_IF(NS_FAILED(rv))) {
1749 aRv.Throw(rv);
1750 return;
1752 if (NS_WARN_IF(!validity)) {
1753 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1754 return;
1757 PRTime validityResult;
1758 rv = validity->GetNotBefore(&validityResult);
1759 if (NS_WARN_IF(NS_FAILED(rv))) {
1760 aRv.Throw(rv);
1761 return;
1763 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1765 rv = validity->GetNotAfter(&validityResult);
1766 if (NS_WARN_IF(NS_FAILED(rv))) {
1767 aRv.Throw(rv);
1768 return;
1770 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1772 nsAutoString issuerCommonName;
1773 nsAutoString certChainPEMString;
1774 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
1775 int64_t maxValidity = std::numeric_limits<int64_t>::max();
1776 int64_t minValidity = 0;
1777 PRTime notBefore, notAfter;
1778 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
1779 rv = tsi->GetFailedCertChain(failedCertArray);
1780 if (NS_WARN_IF(NS_FAILED(rv))) {
1781 aRv.Throw(rv);
1782 return;
1785 if (NS_WARN_IF(failedCertArray.IsEmpty())) {
1786 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1787 return;
1790 for (const auto& certificate : failedCertArray) {
1791 rv = certificate->GetIssuerCommonName(issuerCommonName);
1792 if (NS_WARN_IF(NS_FAILED(rv))) {
1793 aRv.Throw(rv);
1794 return;
1797 rv = certificate->GetValidity(getter_AddRefs(validity));
1798 if (NS_WARN_IF(NS_FAILED(rv))) {
1799 aRv.Throw(rv);
1800 return;
1802 if (NS_WARN_IF(!validity)) {
1803 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1804 return;
1807 rv = validity->GetNotBefore(&notBefore);
1808 if (NS_WARN_IF(NS_FAILED(rv))) {
1809 aRv.Throw(rv);
1810 return;
1813 rv = validity->GetNotAfter(&notAfter);
1814 if (NS_WARN_IF(NS_FAILED(rv))) {
1815 aRv.Throw(rv);
1816 return;
1819 notBefore = std::max(minValidity, notBefore);
1820 notAfter = std::min(maxValidity, notAfter);
1821 nsTArray<uint8_t> certArray;
1822 rv = certificate->GetRawDER(certArray);
1823 if (NS_WARN_IF(NS_FAILED(rv))) {
1824 aRv.Throw(rv);
1825 return;
1828 nsAutoString der64;
1829 rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()),
1830 certArray.Length(), der64);
1831 if (NS_WARN_IF(NS_FAILED(rv))) {
1832 aRv.Throw(rv);
1833 return;
1835 if (!certChainStrings.AppendElement(der64, fallible)) {
1836 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1837 return;
1841 aInfo.mIssuerCommonName.Assign(issuerCommonName);
1842 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
1843 aInfo.mCertValidityRangeNotBefore =
1844 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
1846 int32_t errorCode;
1847 rv = tsi->GetErrorCode(&errorCode);
1848 if (NS_WARN_IF(NS_FAILED(rv))) {
1849 aRv.Throw(rv);
1850 return;
1853 nsCOMPtr<nsINSSErrorsService> nsserr =
1854 do_GetService("@mozilla.org/nss_errors_service;1");
1855 if (NS_WARN_IF(!nsserr)) {
1856 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1857 return;
1859 nsresult res;
1860 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
1861 if (NS_WARN_IF(NS_FAILED(rv))) {
1862 aRv.Throw(rv);
1863 return;
1865 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
1866 if (NS_WARN_IF(NS_FAILED(rv))) {
1867 aRv.Throw(rv);
1868 return;
1871 OriginAttributes attrs;
1872 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1873 nsCOMPtr<nsIURI> aURI;
1874 mFailedChannel->GetURI(getter_AddRefs(aURI));
1875 if (XRE_IsContentProcess()) {
1876 ContentChild* cc = ContentChild::GetSingleton();
1877 MOZ_ASSERT(cc);
1878 cc->SendIsSecureURI(aURI, attrs, &aInfo.mHasHSTS);
1879 } else {
1880 nsCOMPtr<nsISiteSecurityService> sss =
1881 do_GetService(NS_SSSERVICE_CONTRACTID);
1882 if (NS_WARN_IF(!sss)) {
1883 return;
1885 Unused << NS_WARN_IF(
1886 NS_FAILED(sss->IsSecureURI(aURI, attrs, &aInfo.mHasHSTS)));
1888 nsCOMPtr<nsIPublicKeyPinningService> pkps =
1889 do_GetService(NS_PKPSERVICE_CONTRACTID);
1890 if (NS_WARN_IF(!pkps)) {
1891 return;
1893 Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP)));
1896 bool Document::IsAboutPage() const {
1897 return NodePrincipal()->SchemeIs("about");
1900 void Document::ConstructUbiNode(void* storage) {
1901 JS::ubi::Concrete<Document>::construct(storage, this);
1904 void Document::LoadEventFired() {
1905 // Object used to collect some telemetry data so we don't need to query for it
1906 // twice.
1907 glean::perf::PageLoadExtra pageLoadEventData;
1909 // Accumulate timing data located in each document's realm and report to
1910 // telemetry.
1911 AccumulateJSTelemetry(pageLoadEventData);
1913 // Collect page load timings
1914 AccumulatePageLoadTelemetry(pageLoadEventData);
1916 // Record page load event
1917 RecordPageLoadEventTelemetry(pageLoadEventData);
1919 // Release the JS bytecode cache from its wait on the load event, and
1920 // potentially dispatch the encoding of the bytecode.
1921 if (ScriptLoader()) {
1922 ScriptLoader()->LoadEventFired();
1926 static uint32_t ConvertToUnsignedFromDouble(double aNumber) {
1927 return aNumber < 0 ? 0 : static_cast<uint32_t>(aNumber);
1930 void Document::RecordPageLoadEventTelemetry(
1931 glean::perf::PageLoadExtra& aEventTelemetryData) {
1932 // If the page load time is empty, then the content wasn't something we want
1933 // to report (i.e. not a top level document).
1934 if (!aEventTelemetryData.loadTime) {
1935 return;
1937 MOZ_ASSERT(IsTopLevelContentDocument());
1939 nsPIDOMWindowOuter* window = GetWindow();
1940 if (!window) {
1941 return;
1944 nsIDocShell* docshell = window->GetDocShell();
1945 if (!docshell) {
1946 return;
1949 nsAutoCString loadTypeStr;
1950 switch (docshell->GetLoadType()) {
1951 case LOAD_NORMAL:
1952 case LOAD_NORMAL_REPLACE:
1953 case LOAD_NORMAL_BYPASS_CACHE:
1954 case LOAD_NORMAL_BYPASS_PROXY:
1955 case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
1956 loadTypeStr.Append("NORMAL");
1957 break;
1958 case LOAD_HISTORY:
1959 loadTypeStr.Append("HISTORY");
1960 break;
1961 case LOAD_RELOAD_NORMAL:
1962 case LOAD_RELOAD_BYPASS_CACHE:
1963 case LOAD_RELOAD_BYPASS_PROXY:
1964 case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
1965 case LOAD_REFRESH:
1966 case LOAD_REFRESH_REPLACE:
1967 case LOAD_RELOAD_CHARSET_CHANGE:
1968 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
1969 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
1970 loadTypeStr.Append("RELOAD");
1971 break;
1972 case LOAD_LINK:
1973 loadTypeStr.Append("LINK");
1974 break;
1975 case LOAD_STOP_CONTENT:
1976 case LOAD_STOP_CONTENT_AND_REPLACE:
1977 loadTypeStr.Append("STOP");
1978 break;
1979 case LOAD_ERROR_PAGE:
1980 loadTypeStr.Append("ERROR");
1981 break;
1982 default:
1983 loadTypeStr.Append("OTHER");
1984 break;
1987 nsCOMPtr<nsIEffectiveTLDService> tldService =
1988 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
1989 if (tldService && mReferrerInfo &&
1990 (docshell->GetLoadType() & nsIDocShell::LOAD_CMD_NORMAL)) {
1991 nsAutoCString currentBaseDomain, referrerBaseDomain;
1992 nsCOMPtr<nsIURI> referrerURI = mReferrerInfo->GetComputedReferrer();
1993 if (referrerURI) {
1994 auto result = NS_SUCCEEDED(
1995 tldService->GetBaseDomain(referrerURI, 0, referrerBaseDomain));
1996 if (result) {
1997 bool sameOrigin = false;
1998 NodePrincipal()->IsSameOrigin(referrerURI, &sameOrigin);
1999 aEventTelemetryData.sameOriginNav = mozilla::Some(sameOrigin);
2004 aEventTelemetryData.loadType = mozilla::Some(loadTypeStr);
2006 // Sending a glean ping must be done on the parent process.
2007 if (ContentChild* cc = ContentChild::GetSingleton()) {
2008 cc->SendRecordPageLoadEvent(aEventTelemetryData);
2012 void Document::AccumulatePageLoadTelemetry(
2013 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2014 // Interested only in top level documents for real websites that are in the
2015 // foreground.
2016 if (!ShouldIncludeInTelemetry(false) || !IsTopLevelContentDocument() ||
2017 !GetNavigationTiming() ||
2018 !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
2019 return;
2022 if (!GetChannel()) {
2023 return;
2026 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
2027 if (!timedChannel) {
2028 return;
2031 // Default duration is 0, use this to check for bogus negative values.
2032 const TimeDuration zeroDuration;
2034 TimeStamp responseStart;
2035 timedChannel->GetResponseStart(&responseStart);
2037 TimeStamp redirectStart, redirectEnd;
2038 timedChannel->GetRedirectStart(&redirectStart);
2039 timedChannel->GetRedirectEnd(&redirectEnd);
2041 uint8_t redirectCount;
2042 timedChannel->GetRedirectCount(&redirectCount);
2043 if (redirectCount) {
2044 aEventTelemetryDataOut.redirectCount =
2045 mozilla::Some(static_cast<uint32_t>(redirectCount));
2048 if (!redirectStart.IsNull() && !redirectEnd.IsNull()) {
2049 TimeDuration redirectTime = redirectEnd - redirectStart;
2050 if (redirectTime > zeroDuration) {
2051 aEventTelemetryDataOut.redirectTime =
2052 mozilla::Some(static_cast<uint32_t>(redirectTime.ToMilliseconds()));
2056 TimeStamp dnsLookupStart, dnsLookupEnd;
2057 timedChannel->GetDomainLookupStart(&dnsLookupStart);
2058 timedChannel->GetDomainLookupEnd(&dnsLookupEnd);
2060 if (!dnsLookupStart.IsNull() && !dnsLookupEnd.IsNull()) {
2061 TimeDuration dnsLookupTime = dnsLookupEnd - dnsLookupStart;
2062 if (dnsLookupTime > zeroDuration) {
2063 aEventTelemetryDataOut.dnsLookupTime =
2064 mozilla::Some(static_cast<uint32_t>(dnsLookupTime.ToMilliseconds()));
2068 TimeStamp navigationStart =
2069 GetNavigationTiming()->GetNavigationStartTimeStamp();
2071 if (!responseStart || !navigationStart) {
2072 return;
2075 nsAutoCString dnsKey("Native");
2076 nsAutoCString http3Key;
2077 nsAutoCString http3WithPriorityKey;
2078 nsAutoCString earlyHintKey;
2079 nsCOMPtr<nsIHttpChannelInternal> httpChannel =
2080 do_QueryInterface(GetChannel());
2081 if (httpChannel) {
2082 bool resolvedByTRR = false;
2083 Unused << httpChannel->GetIsResolvedByTRR(&resolvedByTRR);
2084 if (resolvedByTRR) {
2085 if (nsCOMPtr<nsIDNSService> dns =
2086 do_GetService(NS_DNSSERVICE_CONTRACTID)) {
2087 dns->GetTRRDomainKey(dnsKey);
2088 } else {
2089 // Failed to get the DNS service.
2090 dnsKey = "(fail)"_ns;
2092 aEventTelemetryDataOut.trrDomain = mozilla::Some(dnsKey);
2095 uint32_t major;
2096 uint32_t minor;
2097 if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
2098 if (major == 3) {
2099 http3Key = "http3"_ns;
2100 nsCOMPtr<nsIHttpChannel> httpChannel2 = do_QueryInterface(GetChannel());
2101 nsCString header;
2102 if (httpChannel2 &&
2103 NS_SUCCEEDED(
2104 httpChannel2->GetResponseHeader("priority"_ns, header)) &&
2105 !header.IsEmpty()) {
2106 http3WithPriorityKey = "with_priority"_ns;
2107 } else {
2108 http3WithPriorityKey = "without_priority"_ns;
2110 } else if (major == 2) {
2111 bool supportHttp3 = false;
2112 if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
2113 supportHttp3 = false;
2115 if (supportHttp3) {
2116 http3Key = "supports_http3"_ns;
2120 aEventTelemetryDataOut.httpVer = mozilla::Some(major);
2123 uint32_t earlyHintType = 0;
2124 Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
2125 if (earlyHintType & LinkStyle::ePRECONNECT) {
2126 earlyHintKey.Append("preconnect_"_ns);
2128 if (earlyHintType & LinkStyle::ePRELOAD) {
2129 earlyHintKey.Append("preload_"_ns);
2130 earlyHintKey.Append(mPreloadService.GetEarlyHintUsed() ? "1"_ns : "0"_ns);
2134 TimeStamp asyncOpen;
2135 timedChannel->GetAsyncOpen(&asyncOpen);
2136 if (asyncOpen) {
2137 Telemetry::AccumulateTimeDelta(Telemetry::DNS_PERF_FIRST_BYTE_MS, dnsKey,
2138 asyncOpen, responseStart);
2141 // First Contentful Composite
2142 if (TimeStamp firstContentfulComposite =
2143 GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) {
2144 Telemetry::AccumulateTimeDelta(Telemetry::PERF_FIRST_CONTENTFUL_PAINT_MS,
2145 navigationStart, firstContentfulComposite);
2147 if (!http3Key.IsEmpty()) {
2148 Telemetry::AccumulateTimeDelta(
2149 Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key,
2150 navigationStart, firstContentfulComposite);
2153 if (!http3WithPriorityKey.IsEmpty()) {
2154 Telemetry::AccumulateTimeDelta(
2155 Telemetry::H3P_PERF_FIRST_CONTENTFUL_PAINT_MS, http3WithPriorityKey,
2156 navigationStart, firstContentfulComposite);
2159 if (!earlyHintKey.IsEmpty()) {
2160 Telemetry::AccumulateTimeDelta(
2161 Telemetry::EH_PERF_FIRST_CONTENTFUL_PAINT_MS, earlyHintKey,
2162 navigationStart, firstContentfulComposite);
2165 Telemetry::AccumulateTimeDelta(
2166 Telemetry::DNS_PERF_FIRST_CONTENTFUL_PAINT_MS, dnsKey, navigationStart,
2167 firstContentfulComposite);
2169 Telemetry::AccumulateTimeDelta(
2170 Telemetry::PERF_FIRST_CONTENTFUL_PAINT_FROM_RESPONSESTART_MS,
2171 responseStart, firstContentfulComposite);
2173 TimeDuration fcpTime = firstContentfulComposite - navigationStart;
2174 if (fcpTime > zeroDuration) {
2175 aEventTelemetryDataOut.fcpTime =
2176 mozilla::Some(static_cast<uint32_t>(fcpTime.ToMilliseconds()));
2180 // DOM Content Loaded event
2181 if (TimeStamp dclEventStart =
2182 GetNavigationTiming()->GetDOMContentLoadedEventStartTimeStamp()) {
2183 Telemetry::AccumulateTimeDelta(Telemetry::PERF_DOM_CONTENT_LOADED_TIME_MS,
2184 navigationStart, dclEventStart);
2185 Telemetry::AccumulateTimeDelta(
2186 Telemetry::PERF_DOM_CONTENT_LOADED_TIME_FROM_RESPONSESTART_MS,
2187 responseStart, dclEventStart);
2190 // Load event
2191 if (TimeStamp loadEventStart =
2192 GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
2193 Telemetry::AccumulateTimeDelta(Telemetry::PERF_PAGE_LOAD_TIME_MS,
2194 navigationStart, loadEventStart);
2195 if (!http3Key.IsEmpty()) {
2196 Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
2197 http3Key, navigationStart, loadEventStart);
2200 if (!http3WithPriorityKey.IsEmpty()) {
2201 Telemetry::AccumulateTimeDelta(Telemetry::H3P_PERF_PAGE_LOAD_TIME_MS,
2202 http3WithPriorityKey, navigationStart,
2203 loadEventStart);
2206 if (!earlyHintKey.IsEmpty()) {
2207 Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_PAGE_LOAD_TIME_MS,
2208 earlyHintKey, navigationStart,
2209 loadEventStart);
2212 Telemetry::AccumulateTimeDelta(
2213 Telemetry::PERF_PAGE_LOAD_TIME_FROM_RESPONSESTART_MS, responseStart,
2214 loadEventStart);
2216 TimeDuration responseTime = responseStart - navigationStart;
2217 if (responseTime > zeroDuration) {
2218 aEventTelemetryDataOut.responseTime =
2219 mozilla::Some(static_cast<uint32_t>(responseTime.ToMilliseconds()));
2222 TimeDuration loadTime = loadEventStart - navigationStart;
2223 if (loadTime > zeroDuration) {
2224 aEventTelemetryDataOut.loadTime =
2225 mozilla::Some(static_cast<uint32_t>(loadTime.ToMilliseconds()));
2230 void Document::AccumulateJSTelemetry(
2231 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2232 if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry(false)) {
2233 return;
2236 if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
2237 return;
2240 AutoJSContext cx;
2241 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
2242 JSAutoRealm ar(cx, globalObject);
2243 JS::JSTimers timers = JS::GetJSTimers(cx);
2245 if (!timers.executionTime.IsZero()) {
2246 Telemetry::Accumulate(
2247 Telemetry::JS_PAGELOAD_EXECUTION_MS,
2248 ConvertToUnsignedFromDouble(timers.executionTime.ToMilliseconds()));
2249 aEventTelemetryDataOut.jsExecTime = mozilla::Some(
2250 static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
2253 if (!timers.delazificationTime.IsZero()) {
2254 Telemetry::Accumulate(Telemetry::JS_PAGELOAD_DELAZIFICATION_MS,
2255 ConvertToUnsignedFromDouble(
2256 timers.delazificationTime.ToMilliseconds()));
2259 if (!timers.xdrEncodingTime.IsZero()) {
2260 Telemetry::Accumulate(
2261 Telemetry::JS_PAGELOAD_XDR_ENCODING_MS,
2262 ConvertToUnsignedFromDouble(timers.xdrEncodingTime.ToMilliseconds()));
2265 if (!timers.baselineCompileTime.IsZero()) {
2266 Telemetry::Accumulate(Telemetry::JS_PAGELOAD_BASELINE_COMPILE_MS,
2267 ConvertToUnsignedFromDouble(
2268 timers.baselineCompileTime.ToMilliseconds()));
2271 if (!timers.gcTime.IsZero()) {
2272 Telemetry::Accumulate(
2273 Telemetry::JS_PAGELOAD_GC_MS,
2274 ConvertToUnsignedFromDouble(timers.gcTime.ToMilliseconds()));
2277 if (!timers.protectTime.IsZero()) {
2278 Telemetry::Accumulate(
2279 Telemetry::JS_PAGELOAD_PROTECT_MS,
2280 ConvertToUnsignedFromDouble(timers.protectTime.ToMilliseconds()));
2284 Document::~Document() {
2285 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
2286 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
2287 "Can't be top-level and a resource doc at the same time");
2289 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
2291 if (IsTopLevelContentDocument()) {
2292 RemoveToplevelLoadingDocument(this);
2294 // don't report for about: pages
2295 if (!IsAboutPage()) {
2296 if (MOZ_UNLIKELY(mMathMLEnabled)) {
2297 ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
2300 if (IsHTMLDocument()) {
2301 switch (GetCompatibilityMode()) {
2302 case eCompatibility_FullStandards:
2303 Telemetry::AccumulateCategorical(
2304 Telemetry::LABELS_QUIRKS_MODE::FullStandards);
2305 break;
2306 case eCompatibility_AlmostStandards:
2307 Telemetry::AccumulateCategorical(
2308 Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
2309 break;
2310 case eCompatibility_NavQuirks:
2311 Telemetry::AccumulateCategorical(
2312 Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
2313 break;
2314 default:
2315 MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
2316 break;
2322 mInDestructor = true;
2323 mInUnlinkOrDeletion = true;
2325 mozilla::DropJSObjects(this);
2327 // Clear mObservers to keep it in sync with the mutationobserver list
2328 mObservers.Clear();
2330 mIntersectionObservers.Clear();
2332 if (mStyleSheetSetList) {
2333 mStyleSheetSetList->Disconnect();
2336 if (mAnimationController) {
2337 mAnimationController->Disconnect();
2340 MOZ_ASSERT(mTimelines.isEmpty());
2342 mParentDocument = nullptr;
2344 // Kill the subdocument map, doing this will release its strong
2345 // references, if any.
2346 delete mSubDocuments;
2347 mSubDocuments = nullptr;
2349 nsAutoScriptBlocker scriptBlocker;
2351 // Destroy link map now so we don't waste time removing
2352 // links one by one
2353 DestroyElementMaps();
2355 // Invalidate cached array of child nodes
2356 InvalidateChildNodes();
2358 // We should not have child nodes when destructor is called,
2359 // since child nodes keep their owner document alive.
2360 MOZ_ASSERT(!HasChildren());
2362 mCachedRootElement = nullptr;
2364 for (auto& sheets : mAdditionalSheets) {
2365 UnlinkStyleSheets(sheets);
2368 if (mAttributeStyles) {
2369 mAttributeStyles->SetOwningDocument(nullptr);
2372 if (mListenerManager) {
2373 mListenerManager->Disconnect();
2374 UnsetFlags(NODE_HAS_LISTENERMANAGER);
2377 if (mScriptLoader) {
2378 mScriptLoader->DropDocumentReference();
2381 if (mCSSLoader) {
2382 // Could be null here if Init() failed or if we have been unlinked.
2383 mCSSLoader->DropDocumentReference();
2386 if (mStyleImageLoader) {
2387 mStyleImageLoader->DropDocumentReference();
2390 if (mXULBroadcastManager) {
2391 mXULBroadcastManager->DropDocumentReference();
2394 if (mXULPersist) {
2395 mXULPersist->DropDocumentReference();
2398 if (mPermissionDelegateHandler) {
2399 mPermissionDelegateHandler->DropDocumentReference();
2402 mHeaderData = nullptr;
2404 mPendingTitleChangeEvent.Revoke();
2406 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
2407 "must not have media query lists left");
2409 if (mNodeInfoManager) {
2410 mNodeInfoManager->DropDocumentReference();
2413 if (mDocGroup) {
2414 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
2415 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
2418 UnlinkOriginalDocumentIfStatic();
2420 UnregisterFromMemoryReportingForDataDocument();
2423 NS_INTERFACE_TABLE_HEAD(Document)
2424 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
2425 NS_INTERFACE_TABLE_BEGIN
2426 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
2427 NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
2428 NS_INTERFACE_TABLE_ENTRY(Document, Document)
2429 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
2430 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
2431 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
2432 NS_INTERFACE_TABLE_ENTRY(Document, nsIRadioGroupContainer)
2433 NS_INTERFACE_TABLE_END
2434 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
2435 NS_INTERFACE_MAP_END
2437 NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
2438 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Document, LastRelease())
2440 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2441 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2442 EventListenerManager* elm = tmp->GetExistingListenerManager();
2443 if (elm) {
2444 elm->MarkForCC();
2446 return true;
2448 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2450 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2451 return Element::CanSkipInCC(tmp);
2452 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2454 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2455 return Element::CanSkipThis(tmp);
2456 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2458 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2459 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2460 char name[512];
2461 nsAutoCString loadedAsData;
2462 if (tmp->IsLoadedAsData()) {
2463 loadedAsData.AssignLiteral("data");
2464 } else {
2465 loadedAsData.AssignLiteral("normal");
2467 uint32_t nsid = tmp->GetDefaultNamespaceID();
2468 nsAutoCString uri;
2469 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2470 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2471 "(xhtml)", "(XLink)", "(XSLT)",
2472 "(MathML)", "(RDF)", "(XUL)"};
2473 if (nsid < ArrayLength(kNSURIs)) {
2474 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2475 kNSURIs[nsid], uri.get());
2476 } else {
2477 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2479 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2480 } else {
2481 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2484 if (!nsINode::Traverse(tmp, cb)) {
2485 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2488 tmp->mExternalResourceMap.Traverse(&cb);
2490 // Traverse all Document pointer members.
2491 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2492 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2493 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2494 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2495 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2496 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry)
2498 // Traverse all Document nsCOMPtrs.
2499 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2500 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2501 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2502 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2503 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2505 DocumentOrShadowRoot::Traverse(tmp, cb);
2507 for (auto& sheets : tmp->mAdditionalSheets) {
2508 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2511 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2512 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserver)
2513 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserverViewport)
2514 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRememberedSizeObserver)
2515 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentVisibilityObserver)
2516 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2517 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2518 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2519 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2520 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2521 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2522 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
2523 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker)
2524 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2525 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2526 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2527 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2528 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2529 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2530 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2531 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2532 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2533 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2534 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2535 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2536 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2537 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2538 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2539 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2540 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2541 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
2543 // Traverse all our nsCOMArrays.
2544 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2546 // Traverse animation components
2547 if (tmp->mAnimationController) {
2548 tmp->mAnimationController->Traverse(&cb);
2551 if (tmp->mSubDocuments) {
2552 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2553 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2555 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2556 cb.NoteXPCOMChild(entry->mKey);
2557 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2558 "mSubDocuments entry->mSubDocument");
2559 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2563 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2565 // We own only the items in mDOMMediaQueryLists that have listeners;
2566 // this reference is managed by their AddListener and RemoveListener
2567 // methods.
2568 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2569 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2570 if (mql->HasListeners() &&
2571 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2572 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2573 cb.NoteXPCOMChild(mql);
2577 // XXX: This should be not needed once bug 1569185 lands.
2578 for (const auto& entry : tmp->mL10nProtoElements) {
2579 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2580 cb.NoteXPCOMChild(entry.GetKey());
2581 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
2584 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2585 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2586 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2587 mPendingFrameStaticClones[i].mStaticCloneOf);
2589 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2591 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2593 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document)
2594 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
2595 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject)
2596 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2598 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2599 tmp->mInUnlinkOrDeletion = true;
2601 tmp->SetStateObject(nullptr);
2603 // Clear out our external resources
2604 tmp->mExternalResourceMap.Shutdown();
2606 nsAutoScriptBlocker scriptBlocker;
2608 nsINode::Unlink(tmp);
2610 while (tmp->HasChildren()) {
2611 // Hold a strong ref to the node when we remove it, because we may be
2612 // the last reference to it.
2613 // If this code changes, change the corresponding code in Document's
2614 // unlink impl and ContentUnbinder::UnbindSubtree.
2615 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2616 tmp->DisconnectChild(child);
2617 child->UnbindFromTree();
2620 tmp->UnlinkOriginalDocumentIfStatic();
2622 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2624 tmp->SetScriptGlobalObject(nullptr);
2626 for (auto& sheets : tmp->mAdditionalSheets) {
2627 tmp->UnlinkStyleSheets(sheets);
2630 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2631 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2632 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserver)
2633 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserverViewport)
2634 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentVisibilityObserver)
2635 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastRememberedSizeObserver)
2636 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2637 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2638 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2639 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry)
2640 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2641 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2642 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2643 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2644 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2645 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2646 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2647 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2648 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
2649 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker)
2650 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2651 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2652 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2653 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2654 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2655 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2656 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2657 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2658 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2659 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2660 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2661 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2662 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2663 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2664 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2665 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2666 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2667 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2669 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
2670 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
2671 tmp->mDocGroup);
2673 tmp->mDocGroup = nullptr;
2675 if (tmp->IsTopLevelContentDocument()) {
2676 RemoveToplevelLoadingDocument(tmp);
2679 tmp->mParentDocument = nullptr;
2681 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2683 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2685 if (tmp->mListenerManager) {
2686 tmp->mListenerManager->Disconnect();
2687 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2688 tmp->mListenerManager = nullptr;
2691 if (tmp->mStyleSheetSetList) {
2692 tmp->mStyleSheetSetList->Disconnect();
2693 tmp->mStyleSheetSetList = nullptr;
2696 delete tmp->mSubDocuments;
2697 tmp->mSubDocuments = nullptr;
2699 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
2700 MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
2701 "How did we get here without our presshell going away "
2702 "first?");
2704 DocumentOrShadowRoot::Unlink(tmp);
2706 // Document has a pretty complex destructor, so we're going to
2707 // assume that *most* cycles you actually want to break somewhere
2708 // else, and not unlink an awful lot here.
2710 tmp->mExpandoAndGeneration.OwnerUnlinked();
2712 if (tmp->mAnimationController) {
2713 tmp->mAnimationController->Unlink();
2716 tmp->mPendingTitleChangeEvent.Revoke();
2718 if (tmp->mCSSLoader) {
2719 tmp->mCSSLoader->DropDocumentReference();
2720 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2723 // We own only the items in mDOMMediaQueryLists that have listeners;
2724 // this reference is managed by their AddListener and RemoveListener
2725 // methods.
2726 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2727 MediaQueryList* next =
2728 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2729 mql->Disconnect();
2730 mql = next;
2733 tmp->mPendingFrameStaticClones.Clear();
2735 tmp->mInUnlinkOrDeletion = false;
2737 tmp->UnregisterFromMemoryReportingForDataDocument();
2739 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2740 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2741 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2742 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2744 nsresult Document::Init(nsIPrincipal* aPrincipal,
2745 nsIPrincipal* aPartitionedPrincipal) {
2746 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2747 return NS_ERROR_ALREADY_INITIALIZED;
2750 // Force initialization.
2751 mOnloadBlocker = new OnloadBlocker();
2752 mStyleImageLoader = new css::ImageLoader(this);
2754 mNodeInfoManager = new nsNodeInfoManager(this, aPrincipal);
2756 // mNodeInfo keeps NodeInfoManager alive!
2757 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2758 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2759 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2760 "Bad NodeType in aNodeInfo");
2762 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2764 mCSSLoader = new css::Loader(this);
2765 // Assume we're not quirky, until we know otherwise
2766 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2768 // If after creation the owner js global is not set for a document
2769 // we use the default compartment for this document, instead of creating
2770 // wrapper in some random compartment when the document is exposed to js
2771 // via some events.
2772 nsCOMPtr<nsIGlobalObject> global =
2773 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2774 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2775 mScopeObject = do_GetWeakReference(global);
2776 MOZ_ASSERT(mScopeObject);
2778 mScriptLoader = new dom::ScriptLoader(this);
2780 // we need to create a policy here so getting the policy within
2781 // ::Policy() can *always* return a non null policy
2782 mFeaturePolicy = new dom::FeaturePolicy(this);
2783 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2785 mStyleSet = MakeUnique<ServoStyleSet>(*this);
2787 if (aPrincipal) {
2788 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2789 } else {
2790 RecomputeResistFingerprinting();
2793 return NS_OK;
2796 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2798 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2799 PropertyTable().RemoveAllPropertiesFor(aNode);
2802 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2803 nsCOMPtr<nsIURI> uri;
2804 nsCOMPtr<nsIPrincipal> principal;
2805 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2806 if (aChannel) {
2807 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2808 // nsScriptSecurityManager::GetChannelResultPrincipals.
2809 // Note: this should match the uri used for the OnNewURI call in
2810 // nsDocShell::CreateContentViewer.
2811 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2813 nsIScriptSecurityManager* securityManager =
2814 nsContentUtils::GetSecurityManager();
2815 if (securityManager) {
2816 securityManager->GetChannelResultPrincipals(
2817 aChannel, getter_AddRefs(principal),
2818 getter_AddRefs(partitionedPrincipal));
2822 bool equal = principal->Equals(partitionedPrincipal);
2824 principal = MaybeDowngradePrincipal(principal);
2825 if (equal) {
2826 partitionedPrincipal = principal;
2827 } else {
2828 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2831 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2833 // Note that, since mTiming does not change during a reset, the
2834 // navigationStart time remains unchanged and therefore any future new
2835 // timeline will have the same global clock time as the old one.
2836 mDocumentTimeline = nullptr;
2838 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
2839 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
2840 mDocumentBaseURI = baseURI.forget();
2841 mChromeXHRDocBaseURI = nullptr;
2845 mChannel = aChannel;
2846 RecomputeResistFingerprinting();
2849 void Document::DisconnectNodeTree() {
2850 // Delete references to sub-documents and kill the subdocument map,
2851 // if any. This is not strictly needed, but makes the node tree
2852 // teardown a bit faster.
2853 delete mSubDocuments;
2854 mSubDocuments = nullptr;
2856 bool oldVal = mInUnlinkOrDeletion;
2857 mInUnlinkOrDeletion = true;
2858 { // Scope for update
2859 MOZ_AUTO_DOC_UPDATE(this, true);
2861 // Destroy link map now so we don't waste time removing
2862 // links one by one
2863 DestroyElementMaps();
2865 // Invalidate cached array of child nodes
2866 InvalidateChildNodes();
2868 while (HasChildren()) {
2869 nsMutationGuard::DidMutate();
2870 nsCOMPtr<nsIContent> content = GetLastChild();
2871 nsIContent* previousSibling = content->GetPreviousSibling();
2872 DisconnectChild(content);
2873 if (content == mCachedRootElement) {
2874 // Immediately clear mCachedRootElement, now that it's been removed
2875 // from mChildren, so that GetRootElement() will stop returning this
2876 // now-stale value.
2877 mCachedRootElement = nullptr;
2879 MutationObservers::NotifyContentRemoved(this, content, previousSibling);
2880 content->UnbindFromTree();
2882 MOZ_ASSERT(!mCachedRootElement,
2883 "After removing all children, there should be no root elem");
2885 mInUnlinkOrDeletion = oldVal;
2888 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2889 nsIPrincipal* aPrincipal,
2890 nsIPrincipal* aPartitionedPrincipal) {
2891 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2892 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2894 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2895 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2897 mSecurityInfo = nullptr;
2899 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2900 if (!aLoadGroup || group != aLoadGroup) {
2901 mDocumentLoadGroup = nullptr;
2904 DisconnectNodeTree();
2906 // Reset our stylesheets
2907 ResetStylesheetsToURI(aURI);
2909 // Release the listener manager
2910 if (mListenerManager) {
2911 mListenerManager->Disconnect();
2912 mListenerManager = nullptr;
2915 // Release the stylesheets list.
2916 mDOMStyleSheets = nullptr;
2918 // Release our principal after tearing down the document, rather than before.
2919 // This ensures that, during teardown, the document and the dying window
2920 // (which already nulled out its document pointer and cached the principal)
2921 // have matching principals.
2922 SetPrincipals(nullptr, nullptr);
2924 // Clear the original URI so SetDocumentURI sets it.
2925 mOriginalURI = nullptr;
2927 SetDocumentURI(aURI);
2928 mChromeXHRDocURI = nullptr;
2929 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
2930 // mDocumentURI.
2931 mDocumentBaseURI = nullptr;
2932 mChromeXHRDocBaseURI = nullptr;
2934 // Check if the current document is the top-level DevTools document.
2935 // For inner DevTools frames, mIsDevToolsDocument will be set when
2936 // calling SetDocumentParent.
2937 if (aURI && aURI->SchemeIs("about") &&
2938 aURI->GetSpecOrDefault().EqualsLiteral("about:devtools-toolbox")) {
2939 mIsDevToolsDocument = true;
2942 if (aLoadGroup) {
2943 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
2944 // there was an assertion here that aLoadGroup was not null. This
2945 // is no longer valid: nsDocShell::SetDocument does not create a
2946 // load group, and it works just fine
2948 // XXXbz what does "just fine" mean exactly? And given that there
2949 // is no nsDocShell::SetDocument, what is this talking about?
2951 if (IsContentDocument()) {
2952 // Inform the associated request context about this load start so
2953 // any of its internal load progress flags gets reset.
2954 nsCOMPtr<nsIRequestContextService> rcsvc =
2955 net::RequestContextService::GetOrCreate();
2956 if (rcsvc) {
2957 nsCOMPtr<nsIRequestContext> rc;
2958 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
2959 if (rc) {
2960 rc->BeginLoad();
2966 mLastModified.Truncate();
2967 // XXXbz I guess we're assuming that the caller will either pass in
2968 // a channel with a useful type or call SetContentType?
2969 SetContentType(""_ns);
2970 mContentLanguage.Truncate();
2971 mBaseTarget.Truncate();
2973 mXMLDeclarationBits = 0;
2975 // Now get our new principal
2976 if (aPrincipal) {
2977 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2978 } else {
2979 nsIScriptSecurityManager* securityManager =
2980 nsContentUtils::GetSecurityManager();
2981 if (securityManager) {
2982 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
2984 if (!loadContext && aLoadGroup) {
2985 nsCOMPtr<nsIInterfaceRequestor> cbs;
2986 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
2987 loadContext = do_GetInterface(cbs);
2990 MOZ_ASSERT(loadContext,
2991 "must have a load context or pass in an explicit principal");
2993 nsCOMPtr<nsIPrincipal> principal;
2994 nsresult rv = securityManager->GetLoadContextContentPrincipal(
2995 mDocumentURI, loadContext, getter_AddRefs(principal));
2996 if (NS_SUCCEEDED(rv)) {
2997 SetPrincipals(principal, principal);
3002 if (mFontFaceSet) {
3003 mFontFaceSet->RefreshStandardFontLoadPrincipal();
3006 // Refresh the principal on the realm.
3007 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
3008 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
3012 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
3013 nsIPrincipal* aPrincipal) {
3014 if (!aPrincipal) {
3015 return nullptr;
3018 // We can't load a document with an expanded principal. If we're given one,
3019 // automatically downgrade it to the last principal it subsumes (which is the
3020 // extension principal, in the case of extension content scripts).
3021 auto* basePrin = BasePrincipal::Cast(aPrincipal);
3022 if (basePrin->Is<ExpandedPrincipal>()) {
3023 MOZ_DIAGNOSTIC_ASSERT(false,
3024 "Should never try to create a document with "
3025 "an expanded principal");
3027 auto* expanded = basePrin->As<ExpandedPrincipal>();
3028 return do_AddRef(expanded->AllowList().LastElement());
3031 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
3032 // We basically want the parent document here, but because this is very
3033 // early in the load, GetInProcessParentDocument() returns null, so we use
3034 // the docshell hierarchy to get this information instead.
3035 if (RefPtr<BrowsingContext> parent =
3036 mDocumentContainer->GetBrowsingContext()->GetParent()) {
3037 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
3038 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
3039 nsCOMPtr<nsIPrincipal> nullPrincipal =
3040 NullPrincipal::CreateWithoutOriginAttributes();
3041 return nullPrincipal.forget();
3045 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
3046 return principal.forget();
3049 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
3050 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3052 // lowest index first
3053 int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
3055 size_t count = mStyleSet->SheetCount(StyleOrigin::Author);
3056 size_t index = 0;
3057 for (; index < count; index++) {
3058 auto* sheet = mStyleSet->SheetAt(StyleOrigin::Author, index);
3059 MOZ_ASSERT(sheet);
3060 int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
3061 if (sheetDocIndex > newDocIndex) {
3062 break;
3065 // If the sheet is not owned by the document it can be an author
3066 // sheet registered at nsStyleSheetService or an additional author
3067 // sheet on the document, which means the new
3068 // doc sheet should end up before it.
3069 if (sheetDocIndex < 0) {
3070 if (sheetService) {
3071 auto& authorSheets = *sheetService->AuthorStyleSheets();
3072 if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
3073 break;
3076 if (sheet == GetFirstAdditionalAuthorSheet()) {
3077 break;
3082 return index;
3085 void Document::ResetStylesheetsToURI(nsIURI* aURI) {
3086 MOZ_ASSERT(aURI);
3088 ClearAdoptedStyleSheets();
3090 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
3091 for (auto& sheet : Reversed(aSheetList)) {
3092 sheet->ClearAssociatedDocumentOrShadowRoot();
3093 if (mStyleSetFilled) {
3094 mStyleSet->RemoveStyleSheet(*sheet);
3097 aSheetList.Clear();
3099 ClearSheetList(mStyleSheets);
3100 for (auto& sheets : mAdditionalSheets) {
3101 ClearSheetList(sheets);
3103 if (mStyleSetFilled) {
3104 if (auto* ss = nsStyleSheetService::GetInstance()) {
3105 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
3106 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
3107 if (sheet->IsApplicable()) {
3108 mStyleSet->RemoveStyleSheet(*sheet);
3114 // Now reset our inline style and attribute sheets.
3115 if (mAttributeStyles) {
3116 mAttributeStyles->Reset();
3117 mAttributeStyles->SetOwningDocument(this);
3118 } else {
3119 mAttributeStyles = new AttributeStyles(this);
3122 if (mStyleSetFilled) {
3123 FillStyleSetDocumentSheets();
3125 if (mStyleSet->StyleSheetsHaveChanged()) {
3126 ApplicableStylesChanged();
3131 static void AppendSheetsToStyleSet(
3132 ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
3133 for (StyleSheet* sheet : Reversed(aSheets)) {
3134 aStyleSet->AppendStyleSheet(*sheet);
3138 void Document::FillStyleSetUserAndUASheets() {
3139 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
3140 // ordering.
3142 // The document will fill in the document sheets when we create the presshell
3143 auto* cache = GlobalStyleSheetCache::Singleton();
3145 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3146 MOZ_ASSERT(sheetService,
3147 "should never be creating a StyleSet after the style sheet "
3148 "service has gone");
3150 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
3151 mStyleSet->AppendStyleSheet(*sheet);
3154 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
3155 : cache->GetUserContentSheet();
3156 if (sheet) {
3157 mStyleSet->AppendStyleSheet(*sheet);
3160 mStyleSet->AppendStyleSheet(*cache->UASheet());
3162 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
3163 mStyleSet->AppendStyleSheet(*cache->MathMLSheet());
3166 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
3167 mStyleSet->AppendStyleSheet(*cache->SVGSheet());
3170 mStyleSet->AppendStyleSheet(*cache->HTMLSheet());
3172 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
3173 mStyleSet->AppendStyleSheet(*cache->NoFramesSheet());
3176 mStyleSet->AppendStyleSheet(*cache->CounterStylesSheet());
3178 // Only load the full XUL sheet if we'll need it.
3179 if (LoadsFullXULStyleSheetUpFront()) {
3180 mStyleSet->AppendStyleSheet(*cache->XULSheet());
3183 mStyleSet->AppendStyleSheet(*cache->FormsSheet());
3184 mStyleSet->AppendStyleSheet(*cache->ScrollbarsSheet());
3186 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
3187 mStyleSet->AppendStyleSheet(*sheet);
3190 MOZ_ASSERT(!mQuirkSheetAdded);
3191 if (NeedsQuirksSheet()) {
3192 mStyleSet->AppendStyleSheet(*cache->QuirkSheet());
3193 mQuirkSheetAdded = true;
3197 void Document::FillStyleSet() {
3198 MOZ_ASSERT(!mStyleSetFilled);
3199 FillStyleSetUserAndUASheets();
3200 FillStyleSetDocumentSheets();
3201 mStyleSetFilled = true;
3204 void Document::RemoveContentEditableStyleSheets() {
3205 MOZ_ASSERT(IsHTMLOrXHTML());
3207 auto* cache = GlobalStyleSheetCache::Singleton();
3208 bool changed = false;
3209 if (mDesignModeSheetAdded) {
3210 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3211 mDesignModeSheetAdded = false;
3212 changed = true;
3214 if (mContentEditableSheetAdded) {
3215 mStyleSet->RemoveStyleSheet(*cache->ContentEditableSheet());
3216 mContentEditableSheetAdded = false;
3217 changed = true;
3219 if (changed) {
3220 MOZ_ASSERT(mStyleSetFilled);
3221 ApplicableStylesChanged();
3225 void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
3226 MOZ_ASSERT(IsHTMLOrXHTML());
3227 MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
3228 "Caller should ensure we're being rendered");
3230 auto* cache = GlobalStyleSheetCache::Singleton();
3231 bool changed = false;
3232 if (!mContentEditableSheetAdded) {
3233 mStyleSet->AppendStyleSheet(*cache->ContentEditableSheet());
3234 mContentEditableSheetAdded = true;
3235 changed = true;
3237 if (mDesignModeSheetAdded != aDesignMode) {
3238 if (mDesignModeSheetAdded) {
3239 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3240 } else {
3241 mStyleSet->AppendStyleSheet(*cache->DesignModeSheet());
3243 mDesignModeSheetAdded = !mDesignModeSheetAdded;
3244 changed = true;
3246 if (changed) {
3247 ApplicableStylesChanged();
3251 void Document::FillStyleSetDocumentSheets() {
3252 MOZ_ASSERT(mStyleSet->SheetCount(StyleOrigin::Author) == 0,
3253 "Style set already has document sheets?");
3255 // Sheets are added in reverse order to avoid worst-case time complexity when
3256 // looking up the index of a sheet.
3258 // Note that usually appending is faster (rebuilds less stuff in the
3259 // styleset), but in this case it doesn't matter since we're filling the
3260 // styleset from scratch anyway.
3261 for (StyleSheet* sheet : Reversed(mStyleSheets)) {
3262 if (sheet->IsApplicable()) {
3263 mStyleSet->AddDocStyleSheet(*sheet);
3267 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
3268 if (aSheet.IsApplicable()) {
3269 mStyleSet->AddDocStyleSheet(aSheet);
3273 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3274 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
3275 mStyleSet->AppendStyleSheet(*sheet);
3278 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAgentSheet]);
3279 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eUserSheet]);
3280 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAuthorSheet]);
3283 void Document::CompatibilityModeChanged() {
3284 MOZ_ASSERT(IsHTMLOrXHTML());
3285 CSSLoader()->SetCompatibilityMode(mCompatMode);
3286 mStyleSet->CompatibilityModeChanged();
3287 if (PresShell* presShell = GetPresShell()) {
3288 // Selectors may have become case-sensitive / case-insensitive, the stylist
3289 // has already performed the relevant invalidation.
3290 presShell->EnsureStyleFlush();
3292 if (!mStyleSetFilled) {
3293 MOZ_ASSERT(!mQuirkSheetAdded);
3294 return;
3296 if (mQuirkSheetAdded == NeedsQuirksSheet()) {
3297 return;
3299 auto* cache = GlobalStyleSheetCache::Singleton();
3300 StyleSheet* sheet = cache->QuirkSheet();
3301 if (mQuirkSheetAdded) {
3302 mStyleSet->RemoveStyleSheet(*sheet);
3303 } else {
3304 mStyleSet->AppendStyleSheet(*sheet);
3306 mQuirkSheetAdded = !mQuirkSheetAdded;
3307 ApplicableStylesChanged();
3310 void Document::SetCompatibilityMode(nsCompatibility aMode) {
3311 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
3312 "Bad compat mode for XHTML document!");
3314 if (mCompatMode == aMode) {
3315 return;
3317 mCompatMode = aMode;
3318 CompatibilityModeChanged();
3319 // Trigger recomputation of the nsViewportInfo the next time it's queried.
3320 mViewportType = Unknown;
3323 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
3324 uint32_t aSandboxFlags,
3325 nsIChannel* aChannel) {
3326 // If the document permits allow-top-navigation and
3327 // allow-top-navigation-by-user-activation this will permit all top
3328 // navigation.
3329 if (aSandboxFlags != SANDBOXED_NONE &&
3330 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
3331 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
3332 nsContentUtils::ReportToConsole(
3333 nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
3334 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
3335 "BothAllowTopNavigationAndUserActivationPresent");
3337 // If the document is sandboxed (via the HTML5 iframe sandbox
3338 // attribute) and both the allow-scripts and allow-same-origin
3339 // keywords are supplied, the sandboxed document can call into its
3340 // parent document and remove its sandboxing entirely - we print a
3341 // warning to the web console in this case.
3342 if (aSandboxFlags & SANDBOXED_NAVIGATION &&
3343 !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
3344 !(aSandboxFlags & SANDBOXED_ORIGIN)) {
3345 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
3346 MOZ_ASSERT(bc->IsInProcess());
3348 RefPtr<BrowsingContext> parentBC = bc->GetParent();
3349 if (!parentBC || !parentBC->IsInProcess()) {
3350 // If parent document is not in process, then by construction it
3351 // cannot be same origin.
3352 return;
3355 // Don't warn if our parent is not the top-level document.
3356 if (!parentBC->IsTopContent()) {
3357 return;
3360 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
3361 MOZ_ASSERT(parentDocShell);
3363 nsCOMPtr<nsIChannel> parentChannel;
3364 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
3365 if (!parentChannel) {
3366 return;
3368 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
3369 if (NS_FAILED(rv)) {
3370 return;
3373 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
3374 nsCOMPtr<nsIURI> iframeUri;
3375 parentChannel->GetURI(getter_AddRefs(iframeUri));
3376 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3377 "Iframe Sandbox"_ns, parentDocument,
3378 nsContentUtils::eSECURITY_PROPERTIES,
3379 "BothAllowScriptsAndSameOriginPresent",
3380 nsTArray<nsString>(), iframeUri);
3384 bool Document::IsSynthesized() {
3385 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
3386 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
3389 // static
3390 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
3391 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
3392 return principal && (principal->IsSystemPrincipal() ||
3393 principal->GetIsAddonOrExpandedAddonPrincipal());
3396 static void CheckIsBadPolicy(nsILoadInfo::CrossOriginOpenerPolicy aPolicy,
3397 BrowsingContext* aContext, nsIChannel* aChannel) {
3398 #if defined(EARLY_BETA_OR_EARLIER)
3399 auto requireCORP =
3400 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
3402 if (aContext->GetOpenerPolicy() == aPolicy ||
3403 (aContext->GetOpenerPolicy() != requireCORP && aPolicy != requireCORP)) {
3404 return;
3407 nsCOMPtr<nsIURI> uri;
3408 bool hasURI = NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(uri)));
3410 bool isViewSource = hasURI && uri->SchemeIs("view-source");
3412 nsCString contentType;
3413 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3414 bool isPDFJS = bag &&
3415 NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3416 contentType)) &&
3417 contentType.EqualsLiteral(APPLICATION_PDF);
3419 MOZ_DIAGNOSTIC_ASSERT(!isViewSource,
3420 "Bug 1834864: Assert due to view-source.");
3421 MOZ_DIAGNOSTIC_ASSERT(!isPDFJS, "Bug 1834864: Assert due to pdfjs.");
3422 MOZ_DIAGNOSTIC_ASSERT(aPolicy == requireCORP,
3423 "Assert due to clearing REQUIRE_CORP.");
3424 MOZ_DIAGNOSTIC_ASSERT(aContext->GetOpenerPolicy() == requireCORP,
3425 "Assert due to setting REQUIRE_CORP.");
3426 #endif // defined(EARLY_BETA_OR_EARLIER)
3429 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
3430 nsILoadGroup* aLoadGroup,
3431 nsISupports* aContainer,
3432 nsIStreamListener** aDocListener,
3433 bool aReset) {
3434 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
3435 nsCOMPtr<nsIURI> uri;
3436 aChannel->GetURI(getter_AddRefs(uri));
3437 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
3438 ("DOCUMENT %p StartDocumentLoad %s", this,
3439 uri ? uri->GetSpecOrDefault().get() : ""));
3442 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
3443 "Bad readyState");
3444 SetReadyStateInternal(READYSTATE_LOADING);
3446 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
3447 mLoadedAsData = true;
3448 SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true);
3449 // We need to disable script & style loading in this case.
3450 // We leave them disabled even in EndLoad(), and let anyone
3451 // who puts the document on display to worry about enabling.
3453 // Do not load/process scripts when loading as data
3454 ScriptLoader()->SetEnabled(false);
3456 // styles
3457 CSSLoader()->SetEnabled(
3458 false); // Do not load/process styles when loading as data
3459 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
3460 // Allow CSS, but not scripts
3461 ScriptLoader()->SetEnabled(false);
3464 mMayStartLayout = false;
3465 MOZ_ASSERT(!mReadyForIdle,
3466 "We should never hit DOMContentLoaded before this point");
3468 if (aReset) {
3469 Reset(aChannel, aLoadGroup);
3472 nsAutoCString contentType;
3473 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3474 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3475 contentType))) ||
3476 NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
3477 // XXX this is only necessary for viewsource:
3478 nsACString::const_iterator start, end, semicolon;
3479 contentType.BeginReading(start);
3480 contentType.EndReading(end);
3481 semicolon = start;
3482 FindCharInReadable(';', semicolon, end);
3483 SetContentType(Substring(start, semicolon));
3486 RetrieveRelevantHeaders(aChannel);
3488 mChannel = aChannel;
3489 RecomputeResistFingerprinting();
3490 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
3491 if (inStrmChan) {
3492 bool isSrcdocChannel;
3493 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
3494 if (isSrcdocChannel) {
3495 mIsSrcdocDocument = true;
3499 if (mChannel) {
3500 nsLoadFlags loadFlags;
3501 mChannel->GetLoadFlags(&loadFlags);
3502 bool isDocument = false;
3503 mChannel->GetIsDocument(&isDocument);
3504 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
3505 IsSynthesized() && XRE_IsContentProcess()) {
3506 ContentChild::UpdateCookieStatus(mChannel);
3509 // Store the security info for future use.
3510 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
3513 // If this document is being loaded by a docshell, copy its sandbox flags
3514 // to the document, and store the fullscreen enabled flag. These are
3515 // immutable after being set here.
3516 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
3518 // If this is an error page, don't inherit sandbox flags
3519 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3520 if (docShell && !loadInfo->GetLoadErrorPage()) {
3521 mSandboxFlags = loadInfo->GetSandboxFlags();
3522 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
3525 // Set the opener policy for the top level content document.
3526 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
3527 nsILoadInfo::CrossOriginOpenerPolicy policy =
3528 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
3529 if (IsTopLevelContentDocument() && httpChan &&
3530 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
3531 docShell->GetBrowsingContext()) {
3532 CheckIsBadPolicy(policy, docShell->GetBrowsingContext(), aChannel);
3534 // Setting the opener policy on a discarded context has no effect.
3535 Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
3538 // The CSP directives upgrade-insecure-requests as well as
3539 // block-all-mixed-content not only apply to the toplevel document,
3540 // but also to nested documents. The loadInfo of a subdocument
3541 // load already holds the correct flag, so let's just set it here
3542 // on the document. Please note that we set the appropriate preload
3543 // bits just for the sake of completeness here, because the preloader
3544 // does not reach into subdocuments.
3545 mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
3546 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3547 mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
3548 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3550 // HTTPS-Only Mode flags
3551 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
3552 // sub-resources and sub-documents.
3553 mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
3555 nsresult rv = InitReferrerInfo(aChannel);
3556 NS_ENSURE_SUCCESS(rv, rv);
3558 rv = InitCOEP(aChannel);
3559 NS_ENSURE_SUCCESS(rv, rv);
3561 // Check CSP navigate-to
3562 // We need to enforce the CSP of the document that initiated the load,
3563 // which is the CSP to inherit.
3564 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3565 if (cspToInherit) {
3566 bool allowsNavigateTo = false;
3567 rv = cspToInherit->GetAllowsNavigateTo(
3568 mDocumentURI, loadInfo->GetIsFormSubmission(),
3569 !loadInfo->RedirectChain().IsEmpty(), /* aWasRedirected */
3570 true, /* aEnforceWhitelist */
3571 &allowsNavigateTo);
3572 NS_ENSURE_SUCCESS(rv, rv);
3574 if (!allowsNavigateTo) {
3575 aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
3576 return NS_OK;
3580 rv = InitCSP(aChannel);
3581 NS_ENSURE_SUCCESS(rv, rv);
3583 // Initialize FeaturePolicy
3584 rv = InitFeaturePolicy(aChannel);
3585 NS_ENSURE_SUCCESS(rv, rv);
3587 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3588 NS_ENSURE_SUCCESS(rv, rv);
3590 // Generally XFO and CSP frame-ancestors is handled within
3591 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3592 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3593 nsContentPolicyType internalContentType =
3594 loadInfo->InternalContentPolicyType();
3595 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3596 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3597 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3599 nsresult status;
3600 aChannel->GetStatus(&status);
3601 if (status == NS_ERROR_XFO_VIOLATION) {
3602 // stop! ERROR page!
3603 // But before we have to reset the principal of the document
3604 // because the onload() event fires before the error page
3605 // is displayed and we do not want the enclosing document
3606 // to access the contentDocument.
3607 RefPtr<NullPrincipal> nullPrincipal =
3608 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3609 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3610 // and also GetInnerWindow() is still null at this point, before
3611 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3612 // within Document.cpp
3613 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3614 SetPrincipals(nullPrincipal, nullPrincipal);
3618 return NS_OK;
3621 void Document::SetLoadedAsData(bool aLoadedAsData,
3622 bool aConsiderForMemoryReporting) {
3623 mLoadedAsData = aLoadedAsData;
3624 if (aConsiderForMemoryReporting) {
3625 nsIGlobalObject* global = GetScopeObject();
3626 if (global) {
3627 if (nsPIDOMWindowInner* window = global->GetAsInnerWindow()) {
3628 nsGlobalWindowInner::Cast(window)
3629 ->RegisterDataDocumentForMemoryReporting(this);
3635 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3637 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
3639 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3640 return mPreloadCSP;
3643 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3644 mPreloadCSP = aPreloadCSP;
3647 void Document::GetCspJSON(nsString& aJSON) {
3648 aJSON.Truncate();
3650 if (!mCSP) {
3651 dom::CSPPolicies jsonPolicies;
3652 jsonPolicies.ToJSON(aJSON);
3653 return;
3655 mCSP->ToJSON(aJSON);
3658 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3659 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3660 nsAutoString messageTag;
3661 aMessages[i]->GetTag(messageTag);
3663 nsAutoString category;
3664 aMessages[i]->GetCategory(category);
3666 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3667 NS_ConvertUTF16toUTF8(category), this,
3668 nsContentUtils::eSECURITY_PROPERTIES,
3669 NS_ConvertUTF16toUTF8(messageTag).get());
3673 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3674 nsresult rv = NS_OK;
3675 if (!aSpeculative) {
3676 // 1) apply settings from regular CSP
3677 if (mCSP) {
3678 // Set up 'block-all-mixed-content' if not already inherited
3679 // from the parent context or set by any other CSP.
3680 if (!mBlockAllMixedContent) {
3681 bool block = false;
3682 rv = mCSP->GetBlockAllMixedContent(&block);
3683 NS_ENSURE_SUCCESS_VOID(rv);
3684 mBlockAllMixedContent = block;
3686 if (!mBlockAllMixedContentPreloads) {
3687 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3690 // Set up 'upgrade-insecure-requests' if not already inherited
3691 // from the parent context or set by any other CSP.
3692 if (!mUpgradeInsecureRequests) {
3693 bool upgrade = false;
3694 rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
3695 NS_ENSURE_SUCCESS_VOID(rv);
3696 mUpgradeInsecureRequests = upgrade;
3698 if (!mUpgradeInsecurePreloads) {
3699 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3701 // Update csp settings in the parent process
3702 if (auto* wgc = GetWindowGlobalChild()) {
3703 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3704 mUpgradeInsecureRequests);
3707 return;
3710 // 2) apply settings from speculative csp
3711 if (mPreloadCSP) {
3712 if (!mBlockAllMixedContentPreloads) {
3713 bool block = false;
3714 rv = mPreloadCSP->GetBlockAllMixedContent(&block);
3715 NS_ENSURE_SUCCESS_VOID(rv);
3716 mBlockAllMixedContent = block;
3718 if (!mUpgradeInsecurePreloads) {
3719 bool upgrade = false;
3720 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
3721 NS_ENSURE_SUCCESS_VOID(rv);
3722 mUpgradeInsecurePreloads = upgrade;
3727 nsresult Document::InitCSP(nsIChannel* aChannel) {
3728 MOZ_ASSERT(!mScriptGlobalObject,
3729 "CSP must be initialized before mScriptGlobalObject is set!");
3731 // If this is a data document - no need to set CSP.
3732 if (mLoadedAsData) {
3733 return NS_OK;
3736 // If this is an image, no need to set a CSP. Otherwise SVG images
3737 // served with a CSP might block internally applied inline styles.
3738 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3739 if (loadInfo->GetExternalContentPolicyType() ==
3740 ExtContentPolicy::TYPE_IMAGE ||
3741 loadInfo->GetExternalContentPolicyType() ==
3742 ExtContentPolicy::TYPE_IMAGESET) {
3743 return NS_OK;
3746 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3748 // If there is a CSP that needs to be inherited from whatever
3749 // global is considered the client of the document fetch then
3750 // we query it here from the loadinfo in case the newly created
3751 // document needs to inherit the CSP. See:
3752 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3753 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
3754 if (inheritedCSP) {
3755 mCSP = loadInfo->GetCspToInherit();
3758 // If there is no CSP to inherit, then we create a new CSP here so
3759 // that history entries always have the right reference in case a
3760 // Meta CSP gets dynamically added after the history entry has
3761 // already been created.
3762 if (!mCSP) {
3763 mCSP = new nsCSPContext();
3766 // Always overwrite the requesting context of the CSP so that any new
3767 // 'self' keyword added to an inherited CSP translates correctly.
3768 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3769 if (NS_WARN_IF(NS_FAILED(rv))) {
3770 return rv;
3773 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3775 nsCOMPtr<nsIHttpChannel> httpChannel;
3776 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3777 if (NS_WARN_IF(NS_FAILED(rv))) {
3778 return rv;
3781 if (httpChannel) {
3782 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3783 tCspHeaderValue);
3785 Unused << httpChannel->GetResponseHeader(
3786 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3788 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3789 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3791 // Check if this is a document from a WebExtension.
3792 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3793 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3795 // If there's no CSP to apply, go ahead and return early
3796 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
3797 cspROHeaderValue.IsEmpty()) {
3798 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3799 nsCOMPtr<nsIURI> chanURI;
3800 aChannel->GetURI(getter_AddRefs(chanURI));
3801 nsAutoCString aspec;
3802 chanURI->GetAsciiSpec(aspec);
3803 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3804 ("no CSP for document, %s", aspec.get()));
3807 return NS_OK;
3810 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3811 ("Document is an add-on or CSP header specified %p", this));
3813 // ----- if the doc is an addon, apply its CSP.
3814 if (addonPolicy) {
3815 mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
3817 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3818 // Bug 1548468: Move CSP off ExpandedPrincipal
3819 // Currently the LoadInfo holds the source of truth for every resource load
3820 // because LoadInfo::GetCsp() queries the CSP from an ExpandedPrincipal
3821 // (and not from the Client) if the load was triggered by an extension.
3822 auto* basePrin = BasePrincipal::Cast(principal);
3823 if (basePrin->Is<ExpandedPrincipal>()) {
3824 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3828 // ----- if there's a full-strength CSP header, apply it.
3829 if (!cspHeaderValue.IsEmpty()) {
3830 mHasCSPDeliveredThroughHeader = true;
3831 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3832 NS_ENSURE_SUCCESS(rv, rv);
3835 // ----- if there's a report-only CSP header, apply it.
3836 if (!cspROHeaderValue.IsEmpty()) {
3837 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3838 NS_ENSURE_SUCCESS(rv, rv);
3841 // ----- Enforce sandbox policy if supplied in CSP header
3842 // The document may already have some sandbox flags set (e.g. if the document
3843 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3844 // directive, intersect the CSP sandbox flags with the existing flags. This
3845 // corresponds to the _least_ permissive policy.
3846 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3847 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3848 NS_ENSURE_SUCCESS(rv, rv);
3850 // Probably the iframe sandbox attribute already caused the creation of a
3851 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3852 // and no one has been created yet.
3853 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3854 !(mSandboxFlags & SANDBOXED_ORIGIN);
3856 mSandboxFlags |= cspSandboxFlags;
3858 if (needNewNullPrincipal) {
3859 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3860 // Skip setting the content blocking allowlist principal to NullPrincipal.
3861 // The principal is only used to enable/disable trackingprotection via
3862 // permission and can be shared with the top level sandboxed site.
3863 // See Bug 1654546.
3864 SetPrincipals(principal, principal);
3867 ApplySettingsFromCSP(false);
3868 return NS_OK;
3871 static Document* GetInProcessParentDocumentFrom(BrowsingContext* aContext) {
3872 BrowsingContext* parentContext = aContext->GetParent();
3873 if (!parentContext) {
3874 return nullptr;
3877 WindowContext* windowContext = parentContext->GetCurrentWindowContext();
3878 if (!windowContext) {
3879 return nullptr;
3882 return windowContext->GetDocument();
3885 already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
3886 BrowsingContext* browsingContext = GetBrowsingContext();
3887 if (!browsingContext) {
3888 return nullptr;
3890 if (!browsingContext->IsContentSubframe()) {
3891 return nullptr;
3894 HTMLIFrameElement* iframe =
3895 HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement());
3896 if (iframe) {
3897 return do_AddRef(iframe->FeaturePolicy());
3900 if (XRE_IsParentProcess()) {
3901 return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy());
3904 if (Document* parentDocument =
3905 GetInProcessParentDocumentFrom(browsingContext)) {
3906 return do_AddRef(parentDocument->FeaturePolicy());
3909 WindowContext* windowContext = browsingContext->GetCurrentWindowContext();
3910 if (!windowContext) {
3911 return nullptr;
3914 WindowGlobalChild* child = windowContext->GetWindowGlobalChild();
3915 if (!child) {
3916 return nullptr;
3919 return do_AddRef(child->GetContainerFeaturePolicy());
3922 void Document::InitFeaturePolicy() {
3923 MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created");
3925 mFeaturePolicy->ResetDeclaredPolicy();
3927 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
3929 RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
3930 if (parentPolicy) {
3931 // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
3932 mFeaturePolicy->InheritPolicy(parentPolicy);
3933 mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
3937 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
3938 InitFeaturePolicy();
3940 // We don't want to parse the http Feature-Policy header if this pref is off.
3941 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
3942 return NS_OK;
3945 nsCOMPtr<nsIHttpChannel> httpChannel;
3946 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3947 if (NS_WARN_IF(NS_FAILED(rv))) {
3948 return rv;
3951 if (!httpChannel) {
3952 return NS_OK;
3955 // query the policy from the header
3956 nsAutoCString value;
3957 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
3958 if (NS_SUCCEEDED(rv)) {
3959 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
3960 NodePrincipal(), nullptr);
3963 return NS_OK;
3966 void Document::EnsureNotEnteringAndExitFullscreen() {
3967 Document::ClearPendingFullscreenRequests(this);
3968 if (GetFullscreenElement()) {
3969 Document::AsyncExitFullscreen(this);
3973 void Document::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
3974 mReferrerInfo = aReferrerInfo;
3975 mCachedReferrerInfoForInternalCSSAndSVGResources = nullptr;
3976 mCachedURLData = nullptr;
3979 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
3980 MOZ_ASSERT(mReferrerInfo);
3981 MOZ_ASSERT(mPreloadReferrerInfo);
3983 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
3984 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
3985 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
3986 // they have an opaque origin.
3987 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
3988 if (BrowsingContext* bc = GetBrowsingContext()) {
3989 // At this point the document is not fully created and mParentDocument has
3990 // not been set yet,
3991 Document* parentDoc = bc->GetEmbedderElement()
3992 ? bc->GetEmbedderElement()->OwnerDoc()
3993 : nullptr;
3994 if (parentDoc) {
3995 SetReferrerInfo(parentDoc->GetReferrerInfo());
3996 mPreloadReferrerInfo = mReferrerInfo;
3997 return NS_OK;
4000 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
4001 "srcdoc without null principal as toplevel!");
4005 nsCOMPtr<nsIHttpChannel> httpChannel;
4006 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4007 if (NS_WARN_IF(NS_FAILED(rv))) {
4008 return rv;
4011 if (!httpChannel) {
4012 return NS_OK;
4015 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo()) {
4016 SetReferrerInfo(referrerInfo);
4019 // Override policy if we get one from Referrerr-Policy header
4020 mozilla::dom::ReferrerPolicy policy =
4021 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
4022 nsCOMPtr<nsIReferrerInfo> clone =
4023 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
4024 ->CloneWithNewPolicy(policy);
4025 SetReferrerInfo(clone);
4026 mPreloadReferrerInfo = mReferrerInfo;
4027 return NS_OK;
4030 nsresult Document::InitCOEP(nsIChannel* aChannel) {
4031 nsCOMPtr<nsIHttpChannel> httpChannel;
4032 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4033 if (NS_FAILED(rv)) {
4034 return NS_OK;
4037 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
4039 if (!intChannel) {
4040 return NS_OK;
4043 nsILoadInfo::CrossOriginEmbedderPolicy policy =
4044 nsILoadInfo::EMBEDDER_POLICY_NULL;
4045 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(
4046 mTrials.IsEnabled(OriginTrial::CoepCredentialless), &policy))) {
4047 mEmbedderPolicy = Some(policy);
4050 return NS_OK;
4053 void Document::StopDocumentLoad() {
4054 if (mParser) {
4055 mParserAborted = true;
4056 mParser->Terminate();
4060 void Document::SetDocumentURI(nsIURI* aURI) {
4061 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
4062 mDocumentURI = aURI;
4063 nsIURI* newBase = GetDocBaseURI();
4065 mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI);
4067 bool equalBases = false;
4068 // Changing just the ref of a URI does not change how relative URIs would
4069 // resolve wrt to it, so we can treat the bases as equal as long as they're
4070 // equal ignoring the ref.
4071 if (oldBase && newBase) {
4072 oldBase->EqualsExceptRef(newBase, &equalBases);
4073 } else {
4074 equalBases = !oldBase && !newBase;
4077 // If this is the first time we're setting the document's URI, set the
4078 // document's original URI.
4079 if (!mOriginalURI) mOriginalURI = mDocumentURI;
4081 // If changing the document's URI changed the base URI of the document, we
4082 // need to refresh the hrefs of all the links on the page.
4083 if (!equalBases) {
4084 mCachedURLData = nullptr;
4085 RefreshLinkHrefs();
4088 // Recalculate our base domain
4089 mBaseDomain.Truncate();
4090 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
4091 if (thirdPartyUtil) {
4092 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
4095 // Tell our WindowGlobalParent that the document's URI has been changed.
4096 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
4097 wgc->SetDocumentURI(mDocumentURI);
4101 static void GetFormattedTimeString(PRTime aTime,
4102 nsAString& aFormattedTimeString) {
4103 PRExplodedTime prtime;
4104 PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
4105 // "MM/DD/YYYY hh:mm:ss"
4106 char formatedTime[24];
4107 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
4108 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
4109 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
4110 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
4111 } else {
4112 // If we for whatever reason failed to find the last modified time
4113 // (or even the current time), fall back to what NS4.x returned.
4114 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
4118 void Document::GetLastModified(nsAString& aLastModified) const {
4119 if (!mLastModified.IsEmpty()) {
4120 aLastModified.Assign(mLastModified);
4121 } else {
4122 GetFormattedTimeString(PR_Now(), aLastModified);
4126 static void IncrementExpandoGeneration(Document& aDoc) {
4127 ++aDoc.mExpandoAndGeneration.generation;
4130 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
4131 MOZ_ASSERT(
4132 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
4133 "Only put elements that need to be exposed as document['name'] in "
4134 "the named table.");
4136 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
4138 // Null for out-of-memory
4139 if (entry) {
4140 if (!entry->HasNameElement() &&
4141 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4142 IncrementExpandoGeneration(*this);
4144 entry->AddNameElement(this, aElement);
4148 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
4149 // Speed up document teardown
4150 if (mIdentifierMap.Count() == 0) return;
4152 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
4153 if (!entry) // Could be false if the element was anonymous, hence never added
4154 return;
4156 entry->RemoveNameElement(aElement);
4157 if (!entry->HasNameElement() &&
4158 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4159 IncrementExpandoGeneration(*this);
4163 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
4164 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
4166 if (entry) { /* True except on OOM */
4167 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4168 !entry->HasNameElement() &&
4169 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4170 IncrementExpandoGeneration(*this);
4172 entry->AddIdElement(aElement);
4176 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
4177 NS_ASSERTION(aId, "huhwhatnow?");
4179 // Speed up document teardown
4180 if (mIdentifierMap.Count() == 0) {
4181 return;
4184 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
4185 if (!entry) // Can be null for XML elements with changing ids.
4186 return;
4188 entry->RemoveIdElement(aElement);
4189 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4190 !entry->HasNameElement() &&
4191 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4192 IncrementExpandoGeneration(*this);
4194 if (entry->IsEmpty()) {
4195 mIdentifierMap.RemoveEntry(entry);
4199 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
4200 bool aPreload) {
4201 ReferrerPolicyEnum policy =
4202 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
4203 // The empty string "" corresponds to no referrer policy, causing a fallback
4204 // to a referrer policy defined elsewhere.
4205 if (policy == ReferrerPolicy::_empty) {
4206 return;
4209 MOZ_ASSERT(mReferrerInfo);
4210 MOZ_ASSERT(mPreloadReferrerInfo);
4212 if (aPreload) {
4213 mPreloadReferrerInfo =
4214 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
4215 ->CloneWithNewPolicy(policy);
4216 } else {
4217 nsCOMPtr<nsIReferrerInfo> clone =
4218 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
4219 ->CloneWithNewPolicy(policy);
4220 SetReferrerInfo(clone);
4224 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
4225 nsIPrincipal* aNewPartitionedPrincipal) {
4226 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
4227 if (aNewPrincipal && mAllowDNSPrefetch &&
4228 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
4229 if (aNewPrincipal->SchemeIs("https")) {
4230 mAllowDNSPrefetch = false;
4234 mCSSLoader->DeregisterFromSheetCache();
4236 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
4237 mPartitionedPrincipal = aNewPartitionedPrincipal;
4239 mCachedURLData = nullptr;
4241 mCSSLoader->RegisterInSheetCache();
4243 RecomputeResistFingerprinting();
4245 #ifdef DEBUG
4246 // Validate that the docgroup is set correctly by calling its getter and
4247 // triggering its sanity check.
4249 // If we're setting the principal to null, we don't want to perform the check,
4250 // as the document is entering an intermediate state where it does not have a
4251 // principal. It will be given another real principal shortly which we will
4252 // check. It's not unsafe to have a document which has a null principal in the
4253 // same docgroup as another document, so this should not be a problem.
4254 if (aNewPrincipal) {
4255 GetDocGroup();
4257 #endif
4260 #ifdef DEBUG
4261 void Document::AssertDocGroupMatchesKey() const {
4262 // Sanity check that we have an up-to-date and accurate docgroup
4263 // We only check if the principal when we can get the browsing context.
4265 // Note that we can be invoked during cycle collection, so we need to handle
4266 // the browsingcontext being partially unlinked - normally you shouldn't
4267 // null-check `Group()` as it shouldn't return nullptr.
4268 if (!GetBrowsingContext() || !GetBrowsingContext()->Group()) {
4269 return;
4272 if (mDocGroup && mDocGroup->GetBrowsingContextGroup()) {
4273 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
4274 GetBrowsingContext()->Group());
4276 // GetKey() can fail, e.g. after the TLD service has shut down.
4277 nsAutoCString docGroupKey;
4278 nsresult rv = mozilla::dom::DocGroup::GetKey(
4279 NodePrincipal(),
4280 GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated(),
4281 docGroupKey);
4282 if (NS_SUCCEEDED(rv)) {
4283 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
4287 #endif
4289 nsresult Document::Dispatch(TaskCategory aCategory,
4290 already_AddRefed<nsIRunnable>&& aRunnable) {
4291 // Note that this method may be called off the main thread.
4292 if (mDocGroup) {
4293 return mDocGroup->Dispatch(aCategory, std::move(aRunnable));
4295 return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
4298 nsISerialEventTarget* Document::EventTargetFor(TaskCategory aCategory) const {
4299 if (mDocGroup) {
4300 return mDocGroup->EventTargetFor(aCategory);
4302 return DispatcherTrait::EventTargetFor(aCategory);
4305 AbstractThread* Document::AbstractMainThreadFor(
4306 mozilla::TaskCategory aCategory) {
4307 MOZ_ASSERT(NS_IsMainThread());
4308 if (mDocGroup) {
4309 return mDocGroup->AbstractMainThreadFor(aCategory);
4311 return DispatcherTrait::AbstractMainThreadFor(aCategory);
4314 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
4315 bool aIsTracking) {
4316 if (aIsTracking) {
4317 mTrackingScripts.Insert(aURL);
4318 } else {
4319 MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
4323 bool Document::IsScriptTracking(JSContext* aCx) const {
4324 JS::AutoFilename filename;
4325 if (!JS::DescribeScriptedCaller(aCx, &filename)) {
4326 return false;
4328 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
4331 void Document::GetContentType(nsAString& aContentType) {
4332 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
4335 void Document::SetContentType(const nsACString& aContentType) {
4336 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
4337 aContentType.EqualsLiteral("application/xhtml+xml")) {
4338 mDefaultElementType = kNameSpaceID_XHTML;
4341 mCachedEncoder = nullptr;
4342 mContentType = aContentType;
4345 bool Document::GetAllowPlugins() {
4346 // First, we ask our docshell if it allows plugins.
4347 auto* browsingContext = GetBrowsingContext();
4349 if (browsingContext) {
4350 if (!browsingContext->GetAllowPlugins()) {
4351 return false;
4354 // If the docshell allows plugins, we check whether
4355 // we are sandboxed and plugins should not be allowed.
4356 if (mSandboxFlags & SANDBOXED_PLUGINS) {
4357 return false;
4361 return true;
4364 bool Document::HasPendingInitialTranslation() {
4365 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
4368 bool Document::HasPendingL10nMutations() const {
4369 return mDocumentL10n && mDocumentL10n->HasPendingMutations();
4372 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
4373 JS::Rooted<JSObject*> object(aCx, aObject);
4374 nsCOMPtr<nsIPrincipal> callerPrincipal =
4375 nsContentUtils::SubjectPrincipal(aCx);
4376 nsGlobalWindowInner* win = xpc::WindowOrNull(object);
4377 bool allowed = false;
4378 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
4379 &allowed);
4380 return allowed;
4383 void Document::LocalizationLinkAdded(Element* aLinkElement) {
4384 if (!AllowsL10n()) {
4385 return;
4388 nsAutoString href;
4389 aLinkElement->GetAttr(nsGkAtoms::href, href);
4391 if (!mDocumentL10n) {
4392 Element* elem = GetDocumentElement();
4393 MOZ_DIAGNOSTIC_ASSERT(elem);
4395 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
4396 mDocumentL10n = DocumentL10n::Create(this, isSync);
4397 if (NS_WARN_IF(!mDocumentL10n)) {
4398 return;
4402 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
4404 if (mReadyState >= READYSTATE_INTERACTIVE) {
4405 nsContentUtils::AddScriptRunner(NewRunnableMethod(
4406 "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
4407 &DocumentL10n::TriggerInitialTranslation));
4408 } else {
4409 if (!mDocumentL10n->mBlockingLayout) {
4410 // Our initial translation is going to block layout start. Make sure
4411 // we don't fire the load event until after that stops happening and
4412 // layout has a chance to start.
4413 BlockOnload();
4414 mDocumentL10n->mBlockingLayout = true;
4419 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
4420 if (!AllowsL10n()) {
4421 return;
4424 if (mDocumentL10n) {
4425 nsAutoString href;
4426 aLinkElement->GetAttr(nsGkAtoms::href, href);
4427 uint32_t remaining =
4428 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
4429 if (remaining == 0) {
4430 if (mDocumentL10n->mBlockingLayout) {
4431 mDocumentL10n->mBlockingLayout = false;
4432 UnblockOnload(/* aFireSync = */ false);
4434 mDocumentL10n = nullptr;
4440 * This method should be called once the end of the l10n
4441 * resource container has been parsed.
4443 * In XUL this is the end of the first </linkset>,
4444 * In XHTML/HTML this is the end of </head>.
4446 * This milestone is used to allow for batch
4447 * localization context I/O and building done
4448 * once when all resources in the document have been
4449 * collected.
4451 void Document::OnL10nResourceContainerParsed() {
4452 // XXX: This is a scaffolding for where we might inject prefetch
4453 // in bug 1717241.
4456 void Document::OnParsingCompleted() {
4457 // Let's call it again, in case the resource
4458 // container has not been closed, and only
4459 // now we're closing the document.
4460 OnL10nResourceContainerParsed();
4462 if (mDocumentL10n) {
4463 RefPtr<DocumentL10n> l10n = mDocumentL10n;
4464 l10n->TriggerInitialTranslation();
4468 void Document::InitialTranslationCompleted(bool aL10nCached) {
4469 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4470 // This means we blocked the load event in LocalizationLinkAdded. It's
4471 // important that the load blocker removal here be async, because our caller
4472 // will notify the content sink after us, and we want the content sync's
4473 // work to happen before the load event fires.
4474 mDocumentL10n->mBlockingLayout = false;
4475 UnblockOnload(/* aFireSync = */ false);
4478 mL10nProtoElements.Clear();
4480 nsXULPrototypeDocument* proto = GetPrototype();
4481 if (proto) {
4482 proto->SetIsL10nCached(aL10nCached);
4486 bool Document::AllowsL10n() const {
4487 if (IsStaticDocument()) {
4488 // We don't allow l10n on static documents, because the nodes are already
4489 // cloned translated, and static docs don't get parsed so we never
4490 // TriggerInitialTranslation, etc, so a load blocker would keep hanging
4491 // forever.
4492 return false;
4494 bool allowed = false;
4495 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4496 return allowed;
4499 bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
4500 JSObject* /*unused*/
4502 MOZ_ASSERT(NS_IsMainThread());
4504 return nsContentUtils::IsSystemCaller(aCx) ||
4505 StaticPrefs::dom_animations_api_getAnimations_enabled();
4508 bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
4509 JSObject* /*unused*/
4511 MOZ_ASSERT(NS_IsMainThread());
4513 return nsContentUtils::IsSystemCaller(aCx) ||
4514 StaticPrefs::dom_animations_api_timelines_enabled();
4517 DocumentTimeline* Document::Timeline() {
4518 if (!mDocumentTimeline) {
4519 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4522 return mDocumentTimeline;
4525 SVGSVGElement* Document::GetSVGRootElement() const {
4526 Element* root = GetRootElement();
4527 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4528 return nullptr;
4530 return static_cast<SVGSVGElement*>(root);
4533 /* Return true if the document is in the focused top-level window, and is an
4534 * ancestor of the focused DOMWindow. */
4535 bool Document::HasFocus(ErrorResult& rv) const {
4536 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4537 if (!fm) {
4538 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4539 return false;
4542 BrowsingContext* bc = GetBrowsingContext();
4543 if (!bc) {
4544 return false;
4547 if (!fm->IsInActiveWindow(bc)) {
4548 return false;
4551 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
4554 bool Document::ThisDocumentHasFocus() const {
4555 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4556 return fm && fm->GetFocusedWindow() &&
4557 fm->GetFocusedWindow()->GetExtantDoc() == this;
4560 void Document::GetDesignMode(nsAString& aDesignMode) {
4561 if (IsInDesignMode()) {
4562 aDesignMode.AssignLiteral("on");
4563 } else {
4564 aDesignMode.AssignLiteral("off");
4568 void Document::SetDesignMode(const nsAString& aDesignMode,
4569 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4570 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4573 static void NotifyEditableStateChange(Document& aDoc) {
4574 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4575 nsMutationGuard g;
4576 #endif
4577 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4578 node = node->GetNextNode(&aDoc)) {
4579 if (auto* element = Element::FromNode(node)) {
4580 element->UpdateEditableState(true);
4583 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4586 void Document::SetDesignMode(const nsAString& aDesignMode,
4587 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4588 ErrorResult& rv) {
4589 if (aSubjectPrincipal.isSome() &&
4590 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4591 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4592 return;
4594 const bool editableMode = IsInDesignMode();
4595 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4596 SetEditableFlag(!editableMode);
4597 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4598 // state of all descendant elements of it. Update that now.
4599 NotifyEditableStateChange(*this);
4600 rv = EditingStateChanged();
4604 nsCommandManager* Document::GetMidasCommandManager() {
4605 // check if we have it cached
4606 if (mMidasCommandManager) {
4607 return mMidasCommandManager;
4610 nsPIDOMWindowOuter* window = GetWindow();
4611 if (!window) {
4612 return nullptr;
4615 nsIDocShell* docshell = window->GetDocShell();
4616 if (!docshell) {
4617 return nullptr;
4620 mMidasCommandManager = docshell->GetCommandManager();
4621 return mMidasCommandManager;
4624 // static
4625 void Document::EnsureInitializeInternalCommandDataHashtable() {
4626 if (sInternalCommandDataHashtable) {
4627 return;
4629 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
4630 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4631 // clang-format off
4632 sInternalCommandDataHashtable->InsertOrUpdate(
4633 u"bold"_ns,
4634 InternalCommandData(
4635 "cmd_bold",
4636 Command::FormatBold,
4637 ExecCommandParam::Ignore,
4638 StyleUpdatingCommand::GetInstance,
4639 CommandOnTextEditor::Disabled));
4640 sInternalCommandDataHashtable->InsertOrUpdate(
4641 u"italic"_ns,
4642 InternalCommandData(
4643 "cmd_italic",
4644 Command::FormatItalic,
4645 ExecCommandParam::Ignore,
4646 StyleUpdatingCommand::GetInstance,
4647 CommandOnTextEditor::Disabled));
4648 sInternalCommandDataHashtable->InsertOrUpdate(
4649 u"underline"_ns,
4650 InternalCommandData(
4651 "cmd_underline",
4652 Command::FormatUnderline,
4653 ExecCommandParam::Ignore,
4654 StyleUpdatingCommand::GetInstance,
4655 CommandOnTextEditor::Disabled));
4656 sInternalCommandDataHashtable->InsertOrUpdate(
4657 u"strikethrough"_ns,
4658 InternalCommandData(
4659 "cmd_strikethrough",
4660 Command::FormatStrikeThrough,
4661 ExecCommandParam::Ignore,
4662 StyleUpdatingCommand::GetInstance,
4663 CommandOnTextEditor::Disabled));
4664 sInternalCommandDataHashtable->InsertOrUpdate(
4665 u"subscript"_ns,
4666 InternalCommandData(
4667 "cmd_subscript",
4668 Command::FormatSubscript,
4669 ExecCommandParam::Ignore,
4670 StyleUpdatingCommand::GetInstance,
4671 CommandOnTextEditor::Disabled));
4672 sInternalCommandDataHashtable->InsertOrUpdate(
4673 u"superscript"_ns,
4674 InternalCommandData(
4675 "cmd_superscript",
4676 Command::FormatSuperscript,
4677 ExecCommandParam::Ignore,
4678 StyleUpdatingCommand::GetInstance,
4679 CommandOnTextEditor::Disabled));
4680 sInternalCommandDataHashtable->InsertOrUpdate(
4681 u"cut"_ns,
4682 InternalCommandData(
4683 "cmd_cut",
4684 Command::Cut,
4685 ExecCommandParam::Ignore,
4686 CutCommand::GetInstance,
4687 CommandOnTextEditor::Enabled));
4688 sInternalCommandDataHashtable->InsertOrUpdate(
4689 u"copy"_ns,
4690 InternalCommandData(
4691 "cmd_copy",
4692 Command::Copy,
4693 ExecCommandParam::Ignore,
4694 CopyCommand::GetInstance,
4695 CommandOnTextEditor::Enabled));
4696 sInternalCommandDataHashtable->InsertOrUpdate(
4697 u"paste"_ns,
4698 InternalCommandData(
4699 "cmd_paste",
4700 Command::Paste,
4701 ExecCommandParam::Ignore,
4702 PasteCommand::GetInstance,
4703 CommandOnTextEditor::Enabled));
4704 sInternalCommandDataHashtable->InsertOrUpdate(
4705 u"delete"_ns,
4706 InternalCommandData(
4707 "cmd_deleteCharBackward",
4708 Command::DeleteCharBackward,
4709 ExecCommandParam::Ignore,
4710 DeleteCommand::GetInstance,
4711 CommandOnTextEditor::Enabled));
4712 sInternalCommandDataHashtable->InsertOrUpdate(
4713 u"forwarddelete"_ns,
4714 InternalCommandData(
4715 "cmd_deleteCharForward",
4716 Command::DeleteCharForward,
4717 ExecCommandParam::Ignore,
4718 DeleteCommand::GetInstance,
4719 CommandOnTextEditor::Enabled));
4720 sInternalCommandDataHashtable->InsertOrUpdate(
4721 u"selectall"_ns,
4722 InternalCommandData(
4723 "cmd_selectAll",
4724 Command::SelectAll,
4725 ExecCommandParam::Ignore,
4726 SelectAllCommand::GetInstance,
4727 CommandOnTextEditor::Enabled));
4728 sInternalCommandDataHashtable->InsertOrUpdate(
4729 u"undo"_ns,
4730 InternalCommandData(
4731 "cmd_undo",
4732 Command::HistoryUndo,
4733 ExecCommandParam::Ignore,
4734 UndoCommand::GetInstance,
4735 CommandOnTextEditor::Enabled));
4736 sInternalCommandDataHashtable->InsertOrUpdate(
4737 u"redo"_ns,
4738 InternalCommandData(
4739 "cmd_redo",
4740 Command::HistoryRedo,
4741 ExecCommandParam::Ignore,
4742 RedoCommand::GetInstance,
4743 CommandOnTextEditor::Enabled));
4744 sInternalCommandDataHashtable->InsertOrUpdate(
4745 u"indent"_ns,
4746 InternalCommandData("cmd_indent",
4747 Command::FormatIndent,
4748 ExecCommandParam::Ignore,
4749 IndentCommand::GetInstance,
4750 CommandOnTextEditor::Disabled));
4751 sInternalCommandDataHashtable->InsertOrUpdate(
4752 u"outdent"_ns,
4753 InternalCommandData(
4754 "cmd_outdent",
4755 Command::FormatOutdent,
4756 ExecCommandParam::Ignore,
4757 OutdentCommand::GetInstance,
4758 CommandOnTextEditor::Disabled));
4759 sInternalCommandDataHashtable->InsertOrUpdate(
4760 u"backcolor"_ns,
4761 InternalCommandData(
4762 "cmd_highlight",
4763 Command::FormatBackColor,
4764 ExecCommandParam::String,
4765 HighlightColorStateCommand::GetInstance,
4766 CommandOnTextEditor::Disabled));
4767 sInternalCommandDataHashtable->InsertOrUpdate(
4768 u"hilitecolor"_ns,
4769 InternalCommandData(
4770 "cmd_highlight",
4771 Command::FormatBackColor,
4772 ExecCommandParam::String,
4773 HighlightColorStateCommand::GetInstance,
4774 CommandOnTextEditor::Disabled));
4775 sInternalCommandDataHashtable->InsertOrUpdate(
4776 u"forecolor"_ns,
4777 InternalCommandData(
4778 "cmd_fontColor",
4779 Command::FormatFontColor,
4780 ExecCommandParam::String,
4781 FontColorStateCommand::GetInstance,
4782 CommandOnTextEditor::Disabled));
4783 sInternalCommandDataHashtable->InsertOrUpdate(
4784 u"fontname"_ns,
4785 InternalCommandData(
4786 "cmd_fontFace",
4787 Command::FormatFontName,
4788 ExecCommandParam::String,
4789 FontFaceStateCommand::GetInstance,
4790 CommandOnTextEditor::Disabled));
4791 sInternalCommandDataHashtable->InsertOrUpdate(
4792 u"fontsize"_ns,
4793 InternalCommandData(
4794 "cmd_fontSize",
4795 Command::FormatFontSize,
4796 ExecCommandParam::String,
4797 FontSizeStateCommand::GetInstance,
4798 CommandOnTextEditor::Disabled));
4799 sInternalCommandDataHashtable->InsertOrUpdate(
4800 u"inserthorizontalrule"_ns,
4801 InternalCommandData(
4802 "cmd_insertHR",
4803 Command::InsertHorizontalRule,
4804 ExecCommandParam::Ignore,
4805 InsertTagCommand::GetInstance,
4806 CommandOnTextEditor::Disabled));
4807 sInternalCommandDataHashtable->InsertOrUpdate(
4808 u"createlink"_ns,
4809 InternalCommandData(
4810 "cmd_insertLinkNoUI",
4811 Command::InsertLink,
4812 ExecCommandParam::String,
4813 InsertTagCommand::GetInstance,
4814 CommandOnTextEditor::Disabled));
4815 sInternalCommandDataHashtable->InsertOrUpdate(
4816 u"insertimage"_ns,
4817 InternalCommandData(
4818 "cmd_insertImageNoUI",
4819 Command::InsertImage,
4820 ExecCommandParam::String,
4821 InsertTagCommand::GetInstance,
4822 CommandOnTextEditor::Disabled));
4823 sInternalCommandDataHashtable->InsertOrUpdate(
4824 u"inserthtml"_ns,
4825 InternalCommandData(
4826 "cmd_insertHTML",
4827 Command::InsertHTML,
4828 ExecCommandParam::String,
4829 InsertHTMLCommand::GetInstance,
4830 // TODO: Chromium inserts text content of the document fragment
4831 // created from the param.
4832 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
4833 CommandOnTextEditor::Disabled));
4834 sInternalCommandDataHashtable->InsertOrUpdate(
4835 u"inserttext"_ns,
4836 InternalCommandData(
4837 "cmd_insertText",
4838 Command::InsertText,
4839 ExecCommandParam::String,
4840 InsertPlaintextCommand::GetInstance,
4841 CommandOnTextEditor::Enabled));
4842 sInternalCommandDataHashtable->InsertOrUpdate(
4843 u"justifyleft"_ns,
4844 InternalCommandData(
4845 "cmd_align",
4846 Command::FormatJustifyLeft,
4847 ExecCommandParam::Ignore, // Will be set to "left"
4848 AlignCommand::GetInstance,
4849 CommandOnTextEditor::Disabled));
4850 sInternalCommandDataHashtable->InsertOrUpdate(
4851 u"justifyright"_ns,
4852 InternalCommandData(
4853 "cmd_align",
4854 Command::FormatJustifyRight,
4855 ExecCommandParam::Ignore, // Will be set to "right"
4856 AlignCommand::GetInstance,
4857 CommandOnTextEditor::Disabled));
4858 sInternalCommandDataHashtable->InsertOrUpdate(
4859 u"justifycenter"_ns,
4860 InternalCommandData(
4861 "cmd_align",
4862 Command::FormatJustifyCenter,
4863 ExecCommandParam::Ignore, // Will be set to "center"
4864 AlignCommand::GetInstance,
4865 CommandOnTextEditor::Disabled));
4866 sInternalCommandDataHashtable->InsertOrUpdate(
4867 u"justifyfull"_ns,
4868 InternalCommandData(
4869 "cmd_align",
4870 Command::FormatJustifyFull,
4871 ExecCommandParam::Ignore, // Will be set to "justify"
4872 AlignCommand::GetInstance,
4873 CommandOnTextEditor::Disabled));
4874 sInternalCommandDataHashtable->InsertOrUpdate(
4875 u"removeformat"_ns,
4876 InternalCommandData(
4877 "cmd_removeStyles",
4878 Command::FormatRemove,
4879 ExecCommandParam::Ignore,
4880 RemoveStylesCommand::GetInstance,
4881 CommandOnTextEditor::Disabled));
4882 sInternalCommandDataHashtable->InsertOrUpdate(
4883 u"unlink"_ns,
4884 InternalCommandData(
4885 "cmd_removeLinks",
4886 Command::FormatRemoveLink,
4887 ExecCommandParam::Ignore,
4888 StyleUpdatingCommand::GetInstance,
4889 CommandOnTextEditor::Disabled));
4890 sInternalCommandDataHashtable->InsertOrUpdate(
4891 u"insertorderedlist"_ns,
4892 InternalCommandData(
4893 "cmd_ol",
4894 Command::InsertOrderedList,
4895 ExecCommandParam::Ignore,
4896 ListCommand::GetInstance,
4897 CommandOnTextEditor::Disabled));
4898 sInternalCommandDataHashtable->InsertOrUpdate(
4899 u"insertunorderedlist"_ns,
4900 InternalCommandData(
4901 "cmd_ul",
4902 Command::InsertUnorderedList,
4903 ExecCommandParam::Ignore,
4904 ListCommand::GetInstance,
4905 CommandOnTextEditor::Disabled));
4906 sInternalCommandDataHashtable->InsertOrUpdate(
4907 u"insertparagraph"_ns,
4908 InternalCommandData(
4909 "cmd_insertParagraph",
4910 Command::InsertParagraph,
4911 ExecCommandParam::Ignore,
4912 InsertParagraphCommand::GetInstance,
4913 CommandOnTextEditor::Enabled));
4914 sInternalCommandDataHashtable->InsertOrUpdate(
4915 u"insertlinebreak"_ns,
4916 InternalCommandData(
4917 "cmd_insertLineBreak",
4918 Command::InsertLineBreak,
4919 ExecCommandParam::Ignore,
4920 InsertLineBreakCommand::GetInstance,
4921 CommandOnTextEditor::Enabled));
4922 sInternalCommandDataHashtable->InsertOrUpdate(
4923 u"formatblock"_ns,
4924 InternalCommandData(
4925 "cmd_paragraphState",
4926 Command::FormatBlock,
4927 ExecCommandParam::String,
4928 ParagraphStateCommand::GetInstance,
4929 CommandOnTextEditor::Disabled));
4930 sInternalCommandDataHashtable->InsertOrUpdate(
4931 u"styleWithCSS"_ns,
4932 InternalCommandData(
4933 "cmd_setDocumentUseCSS",
4934 Command::SetDocumentUseCSS,
4935 ExecCommandParam::Boolean,
4936 SetDocumentStateCommand::GetInstance,
4937 CommandOnTextEditor::FallThrough));
4938 sInternalCommandDataHashtable->InsertOrUpdate(
4939 u"usecss"_ns, // Legacy command
4940 InternalCommandData(
4941 "cmd_setDocumentUseCSS",
4942 Command::SetDocumentUseCSS,
4943 ExecCommandParam::InvertedBoolean,
4944 SetDocumentStateCommand::GetInstance,
4945 CommandOnTextEditor::FallThrough));
4946 sInternalCommandDataHashtable->InsertOrUpdate(
4947 u"contentReadOnly"_ns,
4948 InternalCommandData(
4949 "cmd_setDocumentReadOnly",
4950 Command::SetDocumentReadOnly,
4951 ExecCommandParam::Boolean,
4952 SetDocumentStateCommand::GetInstance,
4953 CommandOnTextEditor::Enabled));
4954 sInternalCommandDataHashtable->InsertOrUpdate(
4955 u"insertBrOnReturn"_ns,
4956 InternalCommandData(
4957 "cmd_insertBrOnReturn",
4958 Command::SetDocumentInsertBROnEnterKeyPress,
4959 ExecCommandParam::Boolean,
4960 SetDocumentStateCommand::GetInstance,
4961 CommandOnTextEditor::FallThrough));
4962 sInternalCommandDataHashtable->InsertOrUpdate(
4963 u"defaultParagraphSeparator"_ns,
4964 InternalCommandData(
4965 "cmd_defaultParagraphSeparator",
4966 Command::SetDocumentDefaultParagraphSeparator,
4967 ExecCommandParam::String,
4968 SetDocumentStateCommand::GetInstance,
4969 CommandOnTextEditor::FallThrough));
4970 sInternalCommandDataHashtable->InsertOrUpdate(
4971 u"enableObjectResizing"_ns,
4972 InternalCommandData(
4973 "cmd_enableObjectResizing",
4974 Command::ToggleObjectResizers,
4975 ExecCommandParam::Boolean,
4976 SetDocumentStateCommand::GetInstance,
4977 CommandOnTextEditor::FallThrough));
4978 sInternalCommandDataHashtable->InsertOrUpdate(
4979 u"enableInlineTableEditing"_ns,
4980 InternalCommandData(
4981 "cmd_enableInlineTableEditing",
4982 Command::ToggleInlineTableEditor,
4983 ExecCommandParam::Boolean,
4984 SetDocumentStateCommand::GetInstance,
4985 CommandOnTextEditor::FallThrough));
4986 sInternalCommandDataHashtable->InsertOrUpdate(
4987 u"enableAbsolutePositionEditing"_ns,
4988 InternalCommandData(
4989 "cmd_enableAbsolutePositionEditing",
4990 Command::ToggleAbsolutePositionEditor,
4991 ExecCommandParam::Boolean,
4992 SetDocumentStateCommand::GetInstance,
4993 CommandOnTextEditor::FallThrough));
4994 sInternalCommandDataHashtable->InsertOrUpdate(
4995 u"enableCompatibleJoinSplitDirection"_ns,
4996 InternalCommandData("cmd_enableCompatibleJoinSplitNodeDirection",
4997 Command::EnableCompatibleJoinSplitNodeDirection,
4998 ExecCommandParam::Boolean,
4999 SetDocumentStateCommand::GetInstance,
5000 CommandOnTextEditor::FallThrough));
5001 #if 0
5002 // with empty string
5003 sInternalCommandDataHashtable->InsertOrUpdate(
5004 u"justifynone"_ns,
5005 InternalCommandData(
5006 "cmd_align",
5007 Command::Undefined,
5008 ExecCommandParam::Ignore,
5009 nullptr,
5010 CommandOnTextEditor::Disabled)); // Not implemented yet.
5011 // REQUIRED SPECIAL REVIEW special review
5012 sInternalCommandDataHashtable->InsertOrUpdate(
5013 u"saveas"_ns,
5014 InternalCommandData(
5015 "cmd_saveAs",
5016 Command::Undefined,
5017 ExecCommandParam::Boolean,
5018 nullptr,
5019 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5020 // REQUIRED SPECIAL REVIEW special review
5021 sInternalCommandDataHashtable->InsertOrUpdate(
5022 u"print"_ns,
5023 InternalCommandData(
5024 "cmd_print",
5025 Command::Undefined,
5026 ExecCommandParam::Boolean,
5027 nullptr,
5028 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5029 #endif // #if 0
5030 // clang-format on
5033 Document::InternalCommandData Document::ConvertToInternalCommand(
5034 const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */,
5035 nsAString* aAdjustedValue /* = nullptr */) {
5036 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
5037 EnsureInitializeInternalCommandDataHashtable();
5038 InternalCommandData commandData;
5039 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
5040 return InternalCommandData();
5042 // Ignore if the command is disabled by a corresponding pref due to Gecko
5043 // specific.
5044 switch (commandData.mCommand) {
5045 case Command::SetDocumentReadOnly:
5046 if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() &&
5047 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) {
5048 return InternalCommandData();
5050 break;
5051 case Command::SetDocumentInsertBROnEnterKeyPress:
5052 MOZ_DIAGNOSTIC_ASSERT(
5053 aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn"));
5054 if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) {
5055 return InternalCommandData();
5057 break;
5058 default:
5059 break;
5061 if (!aAdjustedValue) {
5062 // No further work to do
5063 return commandData;
5065 switch (commandData.mExecCommandParam) {
5066 case ExecCommandParam::Ignore:
5067 // Just have to copy it, no checking
5068 switch (commandData.mCommand) {
5069 case Command::FormatJustifyLeft:
5070 aAdjustedValue->AssignLiteral("left");
5071 break;
5072 case Command::FormatJustifyRight:
5073 aAdjustedValue->AssignLiteral("right");
5074 break;
5075 case Command::FormatJustifyCenter:
5076 aAdjustedValue->AssignLiteral("center");
5077 break;
5078 case Command::FormatJustifyFull:
5079 aAdjustedValue->AssignLiteral("justify");
5080 break;
5081 default:
5082 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
5083 EditorCommandParamType::None);
5084 break;
5086 return commandData;
5088 case ExecCommandParam::Boolean:
5089 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5090 EditorCommandParamType::Bool));
5091 // If this is a boolean value and it's not explicitly false (e.g. no
5092 // value). We default to "true" (see bug 301490).
5093 if (!aValue.LowerCaseEqualsLiteral("false")) {
5094 aAdjustedValue->AssignLiteral("true");
5095 } else {
5096 aAdjustedValue->AssignLiteral("false");
5098 return commandData;
5100 case ExecCommandParam::InvertedBoolean:
5101 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5102 EditorCommandParamType::Bool));
5103 // For old backwards commands we invert the check.
5104 if (aValue.LowerCaseEqualsLiteral("false")) {
5105 aAdjustedValue->AssignLiteral("true");
5106 } else {
5107 aAdjustedValue->AssignLiteral("false");
5109 return commandData;
5111 case ExecCommandParam::String:
5112 MOZ_ASSERT(!!(
5113 EditorCommand::GetParamType(commandData.mCommand) &
5114 (EditorCommandParamType::String | EditorCommandParamType::CString)));
5115 switch (commandData.mCommand) {
5116 case Command::FormatBlock: {
5117 const char16_t* start = aValue.BeginReading();
5118 const char16_t* end = aValue.EndReading();
5119 if (start != end && *start == '<' && *(end - 1) == '>') {
5120 ++start;
5121 --end;
5123 // XXX Should we reorder this array with actual usage?
5124 static const nsStaticAtom* kFormattableBlockTags[] = {
5125 // clang-format off
5126 nsGkAtoms::address,
5127 nsGkAtoms::blockquote,
5128 nsGkAtoms::dd,
5129 nsGkAtoms::div,
5130 nsGkAtoms::dl,
5131 nsGkAtoms::dt,
5132 nsGkAtoms::h1,
5133 nsGkAtoms::h2,
5134 nsGkAtoms::h3,
5135 nsGkAtoms::h4,
5136 nsGkAtoms::h5,
5137 nsGkAtoms::h6,
5138 nsGkAtoms::p,
5139 nsGkAtoms::pre,
5140 // clang-format on
5142 nsAutoString value(nsDependentSubstring(start, end));
5143 ToLowerCase(value);
5144 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
5145 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
5146 if (valueAtom == kTag) {
5147 kTag->ToString(*aAdjustedValue);
5148 return commandData;
5151 return InternalCommandData();
5153 case Command::FormatFontSize: {
5154 // Per editing spec as of April 23, 2012, we need to reject the value
5155 // if it's not a valid floating-point number surrounded by optional
5156 // whitespace. Otherwise, we parse it as a legacy font size. For
5157 // now, we just parse as a legacy font size regardless (matching
5158 // WebKit) -- bug 747879.
5159 int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
5160 if (!size) {
5161 return InternalCommandData();
5163 MOZ_ASSERT(aAdjustedValue->IsEmpty());
5164 aAdjustedValue->AppendInt(size);
5165 return commandData;
5167 case Command::InsertImage:
5168 case Command::InsertLink:
5169 if (aValue.IsEmpty()) {
5170 // Invalid value, return false
5171 return InternalCommandData();
5173 aAdjustedValue->Assign(aValue);
5174 return commandData;
5175 case Command::SetDocumentDefaultParagraphSeparator:
5176 if (!aValue.LowerCaseEqualsLiteral("div") &&
5177 !aValue.LowerCaseEqualsLiteral("p") &&
5178 !aValue.LowerCaseEqualsLiteral("br")) {
5179 // Invalid value
5180 return InternalCommandData();
5182 aAdjustedValue->Assign(aValue);
5183 return commandData;
5184 default:
5185 aAdjustedValue->Assign(aValue);
5186 return commandData;
5189 default:
5190 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
5191 return InternalCommandData();
5195 Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
5196 Document& aDocument, const InternalCommandData& aCommandData)
5197 : mCommandData(aCommandData) {
5198 // We'll retrieve an editor with current DOM tree and layout information.
5199 // However, JS may have already hidden or remove exposed root content of
5200 // the editor. Therefore, we need the latest layout information here.
5201 aDocument.FlushPendingNotifications(FlushType::Layout);
5202 if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) {
5203 mDoNothing = true;
5204 return;
5207 if (nsPresContext* presContext = aDocument.GetPresContext()) {
5208 // Consider context of command handling which is automatically resolved
5209 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5210 // The order is:
5211 // 1. TextEditor if there is an active element and it has TextEditor like
5212 // <input type="text"> or <textarea>.
5213 // 2. HTMLEditor for the document, if there is.
5214 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
5215 if (aCommandData.IsCutOrCopyCommand()) {
5216 // Note that we used to use DocShell to handle `cut` and `copy` command
5217 // for dispatching corresponding events for making possible web apps to
5218 // implement their own editor without editable elements but supports
5219 // standard shortcut keys, etc. In this case, we prefer to use active
5220 // element's editor to keep same behavior.
5221 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5222 } else {
5223 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5224 mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
5225 if (!mActiveEditor) {
5226 mActiveEditor = mHTMLEditor;
5231 // Then, retrieve editor command class instance which should handle it
5232 // and can handle it now.
5233 if (!mActiveEditor) {
5234 // If the command is available without editor, we should redirect the
5235 // command to focused descendant with DocShell.
5236 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5237 mDoNothing = true;
5238 return;
5240 return;
5243 // Otherwise, we should use EditorCommand instance (which is singleton
5244 // instance) when it's enabled.
5245 mEditorCommand = aCommandData.mGetEditorCommandFunc
5246 ? aCommandData.mGetEditorCommandFunc()
5247 : nullptr;
5248 if (!mEditorCommand) {
5249 mDoNothing = true;
5250 mActiveEditor = nullptr;
5251 mHTMLEditor = nullptr;
5252 return;
5255 if (IsCommandEnabled()) {
5256 return;
5259 // If the EditorCommand instance is disabled, we should do nothing if
5260 // the command requires an editor.
5261 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5262 // Do nothing if editor specific commands is disabled (bug 760052).
5263 mDoNothing = true;
5264 return;
5267 // Otherwise, we should redirect it to focused descendant with DocShell.
5268 mEditorCommand = nullptr;
5269 mActiveEditor = nullptr;
5270 mHTMLEditor = nullptr;
5273 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
5274 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
5275 switch (mCommandData.mCommandOnTextEditor) {
5276 case CommandOnTextEditor::Enabled:
5277 return mActiveEditor;
5278 case CommandOnTextEditor::Disabled:
5279 return mActiveEditor && mActiveEditor->IsTextEditor()
5280 ? nullptr
5281 : mActiveEditor.get();
5282 case CommandOnTextEditor::FallThrough:
5283 return mHTMLEditor;
5285 return nullptr;
5288 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
5289 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
5290 // Make sure frames are up to date, since that can affect whether
5291 // we're editable.
5292 doc->FlushPendingNotifications(FlushType::Frames);
5294 EditorBase* targetEditor = GetTargetEditor();
5295 if (targetEditor && targetEditor->IsTextEditor()) {
5296 // FYI: When `disabled` attribute is set, `TextEditor` treats it as
5297 // "readonly" too.
5298 return !targetEditor->IsReadonly();
5300 return aDocument->IsEditingOn();
5303 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
5304 EditorBase* targetEditor = GetTargetEditor();
5305 if (!targetEditor) {
5306 return false;
5308 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5309 return MOZ_KnownLive(mEditorCommand)
5310 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
5313 nsresult Document::AutoEditorCommandTarget::DoCommand(
5314 nsIPrincipal* aPrincipal) const {
5315 MOZ_ASSERT(!DoNothing());
5316 MOZ_ASSERT(mEditorCommand);
5317 EditorBase* targetEditor = GetTargetEditor();
5318 if (!targetEditor) {
5319 return NS_SUCCESS_DOM_NO_OPERATION;
5321 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5322 return MOZ_KnownLive(mEditorCommand)
5323 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
5324 aPrincipal);
5327 template <typename ParamType>
5328 nsresult Document::AutoEditorCommandTarget::DoCommandParam(
5329 const ParamType& aParam, nsIPrincipal* aPrincipal) const {
5330 MOZ_ASSERT(!DoNothing());
5331 MOZ_ASSERT(mEditorCommand);
5332 EditorBase* targetEditor = GetTargetEditor();
5333 if (!targetEditor) {
5334 return NS_SUCCESS_DOM_NO_OPERATION;
5336 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5337 return MOZ_KnownLive(mEditorCommand)
5338 ->DoCommandParam(mCommandData.mCommand, aParam,
5339 MOZ_KnownLive(*targetEditor), aPrincipal);
5342 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
5343 nsCommandParams& aParams) const {
5344 MOZ_ASSERT(mEditorCommand);
5345 EditorBase* targetEditor = GetTargetEditor();
5346 if (!targetEditor) {
5347 return NS_OK;
5349 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5350 return MOZ_KnownLive(mEditorCommand)
5351 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
5352 MOZ_KnownLive(targetEditor), nullptr);
5355 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
5356 const nsAString& aValue,
5357 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
5358 // Only allow on HTML documents.
5359 if (!IsHTMLOrXHTML()) {
5360 aRv.ThrowInvalidStateError(
5361 "execCommand is only supported on HTML documents");
5362 return false;
5364 // Otherwise, don't throw exception for compatibility with Chrome.
5366 // if they are requesting UI from us, let's fail since we have no UI
5367 if (aShowUI) {
5368 return false;
5371 // If we're running an execCommand, we should just return false.
5372 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
5373 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
5374 mIsRunningExecCommand) {
5375 return false;
5378 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
5379 // this might add some ugly JS dependencies?
5381 nsAutoString adjustedValue;
5382 InternalCommandData commandData =
5383 ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
5384 switch (commandData.mCommand) {
5385 case Command::DoNothing:
5386 return false;
5387 case Command::SetDocumentReadOnly:
5388 SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly);
5389 break;
5390 case Command::EnableCompatibleJoinSplitNodeDirection:
5391 // We don't allow to take the legacy behavior back if the new one is
5392 // enabled by default.
5393 if (StaticPrefs::
5394 editor_join_split_direction_compatible_with_the_other_browsers() &&
5395 !adjustedValue.EqualsLiteral("true") &&
5396 !aSubjectPrincipal.IsSystemPrincipal()) {
5397 return false;
5399 break;
5400 default:
5401 break;
5404 // Do security check first.
5405 if (commandData.IsCutOrCopyCommand()) {
5406 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
5407 // We have rejected the event due to it not being performed in an
5408 // input-driven context therefore, we report the error to the console.
5409 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
5410 this, nsContentUtils::eDOM_PROPERTIES,
5411 "ExecCommandCutCopyDeniedNotInputDriven");
5412 return false;
5414 } else if (commandData.IsPasteCommand()) {
5415 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
5416 nsGkAtoms::clipboardRead)) {
5417 return false;
5421 AutoRunningExecCommandMarker markRunningExecCommand(*this);
5423 // Next, consider context of command handling which is automatically resolved
5424 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5425 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5426 if (commandData.IsAvailableOnlyWhenEditable() &&
5427 !editCommandTarget.IsEditable(this)) {
5428 return false;
5431 if (editCommandTarget.DoNothing()) {
5432 return false;
5435 // If we cannot use EditorCommand instance directly, we need to handle the
5436 // command with traditional path (i.e., with DocShell or nsCommandManager).
5437 if (!editCommandTarget.IsEditor()) {
5438 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
5440 // Special case clipboard write commands like Command::Cut and
5441 // Command::Copy. For such commands, we need the behaviour from
5442 // nsWindowRoot::GetControllers() which is to look at the focused element,
5443 // and defer to a focused textbox's controller. The code past taken by
5444 // other commands in ExecCommand() always uses the window directly, rather
5445 // than deferring to the textbox, which is desireable for most editor
5446 // commands, but not these commands (as those should allow copying out of
5447 // embedded editors). This behaviour is invoked if we call DoCommand()
5448 // directly on the docShell.
5449 // XXX This means that we allow web app to pick up selected content in
5450 // descendant document and write it into the clipboard when a
5451 // descendant document has focus. However, Chromium does not allow
5452 // this and this seems that it's not good behavior from point of view
5453 // of security. We should treat this issue in another bug.
5454 if (commandData.IsCutOrCopyCommand()) {
5455 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
5456 if (!docShell) {
5457 return false;
5459 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
5460 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
5461 return false;
5463 return NS_SUCCEEDED(rv);
5466 // Otherwise (currently, only clipboard read commands like Command::Paste),
5467 // we don't need to redirect the command to focused subdocument.
5468 // Therefore, we should handle it with nsCommandManager as used to be.
5469 // It may dispatch only preceding event of editing on non-editable element
5470 // to make web apps possible to handle standard shortcut key, etc in
5471 // their own editor.
5472 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5473 if (!commandManager) {
5474 return false;
5477 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5478 if (!window) {
5479 return false;
5482 // Return false for disabled commands (bug 760052)
5483 if (!commandManager->IsCommandEnabled(
5484 nsDependentCString(commandData.mXULCommandName), window)) {
5485 return false;
5488 MOZ_ASSERT(commandData.IsPasteCommand() ||
5489 commandData.mCommand == Command::SelectAll);
5490 nsresult rv =
5491 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
5492 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5495 // Now, our target is fixed to the editor. So, we can use EditorCommand
5496 // in EditorCommandTarget directly.
5498 EditorCommandParamType paramType =
5499 EditorCommand::GetParamType(commandData.mCommand);
5501 // If we don't have meaningful parameter or the EditorCommand does not
5502 // require additional parameter, we can use `DoCommand()`.
5503 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
5504 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
5505 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
5506 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5509 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
5510 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
5511 // a `bool` value.
5512 if (!!(paramType & EditorCommandParamType::Bool)) {
5513 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
5514 adjustedValue.EqualsLiteral("false"));
5515 nsresult rv = editCommandTarget.DoCommandParam(
5516 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
5517 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5520 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
5521 // in this case. However, `paramType` may contain both `String` and
5522 // `CString` but in such case, we should use `DoCommandParam()` which
5523 // takes `nsAString`. So, we should check whether `paramType` contains
5524 // `String` or not first.
5525 if (!!(paramType & EditorCommandParamType::String)) {
5526 MOZ_ASSERT(!adjustedValue.IsVoid());
5527 nsresult rv =
5528 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
5529 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5532 // Finally, `paramType` should have `CString`. We should use
5533 // `DoCommandParam()` which takes `nsACString`.
5534 if (!!(paramType & EditorCommandParamType::CString)) {
5535 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
5536 MOZ_ASSERT(!utf8Value.IsVoid());
5537 nsresult rv =
5538 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
5539 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5542 MOZ_ASSERT_UNREACHABLE(
5543 "Not yet implemented to handle new EditorCommandParamType");
5544 return false;
5547 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
5548 nsIPrincipal& aSubjectPrincipal,
5549 ErrorResult& aRv) {
5550 // Only allow on HTML documents.
5551 if (!IsHTMLOrXHTML()) {
5552 aRv.ThrowInvalidStateError(
5553 "queryCommandEnabled is only supported on HTML documents");
5554 return false;
5556 // Otherwise, don't throw exception for compatibility with Chrome.
5558 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5559 switch (commandData.mCommand) {
5560 case Command::DoNothing:
5561 return false;
5562 case Command::SetDocumentReadOnly:
5563 SetUseCounter(
5564 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5565 break;
5566 case Command::SetDocumentInsertBROnEnterKeyPress:
5567 SetUseCounter(
5568 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5569 break;
5570 default:
5571 break;
5574 // cut & copy are always allowed
5575 if (commandData.IsCutOrCopyCommand()) {
5576 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
5579 // Report false for restricted commands
5580 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
5581 return false;
5584 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5585 if (commandData.IsAvailableOnlyWhenEditable() &&
5586 !editCommandTarget.IsEditable(this)) {
5587 return false;
5590 if (editCommandTarget.IsEditor()) {
5591 return editCommandTarget.IsCommandEnabled();
5594 // get command manager and dispatch command to our window if it's acceptable
5595 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5596 if (!commandManager) {
5597 return false;
5600 nsPIDOMWindowOuter* window = GetWindow();
5601 if (!window) {
5602 return false;
5605 return commandManager->IsCommandEnabled(
5606 nsDependentCString(commandData.mXULCommandName), window);
5609 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5610 ErrorResult& aRv) {
5611 // Only allow on HTML documents.
5612 if (!IsHTMLOrXHTML()) {
5613 aRv.ThrowInvalidStateError(
5614 "queryCommandIndeterm is only supported on HTML documents");
5615 return false;
5617 // Otherwise, don't throw exception for compatibility with Chrome.
5619 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5620 if (commandData.mCommand == Command::DoNothing) {
5621 return false;
5624 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5625 if (commandData.IsAvailableOnlyWhenEditable() &&
5626 !editCommandTarget.IsEditable(this)) {
5627 return false;
5629 RefPtr<nsCommandParams> params = new nsCommandParams();
5630 if (editCommandTarget.IsEditor()) {
5631 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5632 return false;
5634 } else {
5635 // get command manager and dispatch command to our window if it's acceptable
5636 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5637 if (!commandManager) {
5638 return false;
5641 nsPIDOMWindowOuter* window = GetWindow();
5642 if (!window) {
5643 return false;
5646 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5647 window, params))) {
5648 return false;
5652 // If command does not have a state_mixed value, this call fails and sets
5653 // retval to false. This is fine -- we want to return false in that case
5654 // anyway (bug 738385), so we just don't throw regardless.
5655 return params->GetBool("state_mixed");
5658 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5659 ErrorResult& aRv) {
5660 // Only allow on HTML documents.
5661 if (!IsHTMLOrXHTML()) {
5662 aRv.ThrowInvalidStateError(
5663 "queryCommandState is only supported on HTML documents");
5664 return false;
5666 // Otherwise, don't throw exception for compatibility with Chrome.
5668 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5669 switch (commandData.mCommand) {
5670 case Command::DoNothing:
5671 return false;
5672 case Command::SetDocumentReadOnly:
5673 SetUseCounter(
5674 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5675 break;
5676 case Command::SetDocumentInsertBROnEnterKeyPress:
5677 SetUseCounter(
5678 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5679 break;
5680 default:
5681 break;
5684 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5685 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5686 // return false always.
5687 return false;
5690 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5691 if (commandData.IsAvailableOnlyWhenEditable() &&
5692 !editCommandTarget.IsEditable(this)) {
5693 return false;
5695 RefPtr<nsCommandParams> params = new nsCommandParams();
5696 if (editCommandTarget.IsEditor()) {
5697 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5698 return false;
5700 } else {
5701 // get command manager and dispatch command to our window if it's acceptable
5702 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5703 if (!commandManager) {
5704 return false;
5707 nsPIDOMWindowOuter* window = GetWindow();
5708 if (!window) {
5709 return false;
5712 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5713 window, params))) {
5714 return false;
5718 // handle alignment as a special case (possibly other commands too?)
5719 // Alignment is special because the external api is individual
5720 // commands but internally we use cmd_align with different
5721 // parameters. When getting the state of this command, we need to
5722 // return the boolean for this particular alignment rather than the
5723 // string of 'which alignment is this?'
5724 switch (commandData.mCommand) {
5725 case Command::FormatJustifyLeft: {
5726 nsAutoCString currentValue;
5727 nsresult rv = params->GetCString("state_attribute", currentValue);
5728 if (NS_FAILED(rv)) {
5729 return false;
5731 return currentValue.EqualsLiteral("left");
5733 case Command::FormatJustifyRight: {
5734 nsAutoCString currentValue;
5735 nsresult rv = params->GetCString("state_attribute", currentValue);
5736 if (NS_FAILED(rv)) {
5737 return false;
5739 return currentValue.EqualsLiteral("right");
5741 case Command::FormatJustifyCenter: {
5742 nsAutoCString currentValue;
5743 nsresult rv = params->GetCString("state_attribute", currentValue);
5744 if (NS_FAILED(rv)) {
5745 return false;
5747 return currentValue.EqualsLiteral("center");
5749 case Command::FormatJustifyFull: {
5750 nsAutoCString currentValue;
5751 nsresult rv = params->GetCString("state_attribute", currentValue);
5752 if (NS_FAILED(rv)) {
5753 return false;
5755 return currentValue.EqualsLiteral("justify");
5757 default:
5758 break;
5761 // If command does not have a state_all value, this call fails and sets
5762 // retval to false. This is fine -- we want to return false in that case
5763 // anyway (bug 738385), so we just succeed and return false regardless.
5764 return params->GetBool("state_all");
5767 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5768 CallerType aCallerType, ErrorResult& aRv) {
5769 // Only allow on HTML documents.
5770 if (!IsHTMLOrXHTML()) {
5771 aRv.ThrowInvalidStateError(
5772 "queryCommandSupported is only supported on HTML documents");
5773 return false;
5775 // Otherwise, don't throw exception for compatibility with Chrome.
5777 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5778 switch (commandData.mCommand) {
5779 case Command::DoNothing:
5780 return false;
5781 case Command::SetDocumentReadOnly:
5782 SetUseCounter(
5783 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5784 break;
5785 case Command::SetDocumentInsertBROnEnterKeyPress:
5786 SetUseCounter(
5787 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5788 break;
5789 default:
5790 break;
5793 // Gecko technically supports all the clipboard commands including
5794 // cut/copy/paste, but non-privileged content will be unable to call
5795 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5796 // may also be disallowed to be called from non-privileged content.
5797 // For that reason, we report the support status of corresponding
5798 // command accordingly.
5799 if (aCallerType != CallerType::System) {
5800 if (commandData.IsPasteCommand()) {
5801 return false;
5803 if (commandData.IsCutOrCopyCommand() &&
5804 !StaticPrefs::dom_allow_cut_copy()) {
5805 // XXXbz should we worry about correctly reporting "true" in the
5806 // "restricted, but we're an addon with clipboardWrite permissions" case?
5807 // See also nsContentUtils::IsCutCopyAllowed.
5808 return false;
5812 // aHTMLCommandName is supported if it can be converted to a Midas command
5813 return true;
5816 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5817 nsAString& aValue, ErrorResult& aRv) {
5818 aValue.Truncate();
5820 // Only allow on HTML documents.
5821 if (!IsHTMLOrXHTML()) {
5822 aRv.ThrowInvalidStateError(
5823 "queryCommandValue is only supported on HTML documents");
5824 return;
5826 // Otherwise, don't throw exception for compatibility with Chrome.
5828 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5829 switch (commandData.mCommand) {
5830 case Command::DoNothing:
5831 // Return empty string
5832 return;
5833 case Command::SetDocumentReadOnly:
5834 SetUseCounter(
5835 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5836 break;
5837 case Command::SetDocumentInsertBROnEnterKeyPress:
5838 SetUseCounter(
5839 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5840 break;
5841 default:
5842 break;
5845 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5846 if (commandData.IsAvailableOnlyWhenEditable() &&
5847 !editCommandTarget.IsEditable(this)) {
5848 return;
5850 RefPtr<nsCommandParams> params = new nsCommandParams();
5851 if (editCommandTarget.IsEditor()) {
5852 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5853 return;
5856 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5857 return;
5859 } else {
5860 // get command manager and dispatch command to our window if it's acceptable
5861 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5862 if (!commandManager) {
5863 return;
5866 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5867 if (!window) {
5868 return;
5871 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5872 return;
5875 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5876 window, params))) {
5877 return;
5881 // If command does not have a state_attribute value, this call fails, and
5882 // aValue will wind up being the empty string. This is fine -- we want to
5883 // return "" in that case anyway (bug 738385), so we just return NS_OK
5884 // regardless.
5885 nsAutoCString result;
5886 params->GetCString("state_attribute", result);
5887 CopyUTF8toUTF16(result, aValue);
5890 void Document::MaybeEditingStateChanged() {
5891 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
5892 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
5893 if (nsContentUtils::IsSafeToRunScript()) {
5894 EditingStateChanged();
5895 } else if (!mInDestructor) {
5896 nsContentUtils::AddScriptRunner(
5897 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
5898 &Document::MaybeEditingStateChanged));
5903 void Document::NotifyFetchOrXHRSuccess() {
5904 if (mShouldNotifyFetchSuccess) {
5905 nsContentUtils::DispatchEventOnlyToChrome(
5906 this, this, u"DOMDocFetchSuccess"_ns, CanBubble::eNo, Cancelable::eNo,
5907 /* DefaultAction */ nullptr);
5911 void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
5912 mShouldNotifyFetchSuccess = aShouldNotify;
5915 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
5916 mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
5919 void Document::TearingDownEditor() {
5920 if (IsEditingOn()) {
5921 mEditingState = EditingState::eTearingDown;
5922 if (IsHTMLOrXHTML()) {
5923 RemoveContentEditableStyleSheets();
5928 nsresult Document::TurnEditingOff() {
5929 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
5931 nsPIDOMWindowOuter* window = GetWindow();
5932 if (!window) {
5933 return NS_ERROR_FAILURE;
5936 nsIDocShell* docshell = window->GetDocShell();
5937 if (!docshell) {
5938 return NS_ERROR_FAILURE;
5941 bool isBeingDestroyed = false;
5942 docshell->IsBeingDestroyed(&isBeingDestroyed);
5943 if (isBeingDestroyed) {
5944 return NS_ERROR_FAILURE;
5947 nsCOMPtr<nsIEditingSession> editSession;
5948 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5949 NS_ENSURE_SUCCESS(rv, rv);
5951 // turn editing off
5952 rv = editSession->TearDownEditorOnWindow(window);
5953 NS_ENSURE_SUCCESS(rv, rv);
5955 mEditingState = EditingState::eOff;
5957 // Editor resets selection since it is being destroyed. But if focus is
5958 // still into editable control, we have to initialize selection again.
5959 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
5960 if (RefPtr<TextControlElement> textControlElement =
5961 TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
5962 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
5963 textEditor->ReinitializeSelection(*textControlElement);
5968 return NS_OK;
5971 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
5972 nsIDocShell* docShell = aWindow->GetDocShell();
5973 if (!docShell) {
5974 return false;
5976 return docShell->GetPresShell() != nullptr;
5979 HTMLEditor* Document::GetHTMLEditor() const {
5980 nsPIDOMWindowOuter* window = GetWindow();
5981 if (!window) {
5982 return nullptr;
5985 nsIDocShell* docshell = window->GetDocShell();
5986 if (!docshell) {
5987 return nullptr;
5990 return docshell->GetHTMLEditor();
5993 nsresult Document::EditingStateChanged() {
5994 if (mRemovedFromDocShell) {
5995 return NS_OK;
5998 if (mEditingState == EditingState::eSettingUp ||
5999 mEditingState == EditingState::eTearingDown) {
6000 // XXX We shouldn't recurse
6001 return NS_OK;
6004 const bool designMode = IsInDesignMode();
6005 EditingState newState =
6006 designMode ? EditingState::eDesignMode
6007 : (mContentEditableCount > 0 ? EditingState::eContentEditable
6008 : EditingState::eOff);
6009 if (mEditingState == newState) {
6010 // No changes in editing mode.
6011 return NS_OK;
6014 const bool thisDocumentHasFocus = ThisDocumentHasFocus();
6015 if (newState == EditingState::eOff) {
6016 // Editing is being turned off.
6017 nsAutoScriptBlocker scriptBlocker;
6018 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6019 NotifyEditableStateChange(*this);
6020 nsresult rv = TurnEditingOff();
6021 // If this document has focus and the editing state of this document
6022 // becomes "off", it means that HTMLEditor won't handle any inputs nor
6023 // modify the DOM tree. However, HTMLEditor may not receive `blur`
6024 // event for this state change since this may occur without focus change.
6025 // Therefore, let's notify HTMLEditor of this editing state change.
6026 // Note that even if focusedElement is an editable text control element,
6027 // it becomes not editable from HTMLEditor point of view since text
6028 // control elements are manged by TextEditor.
6029 RefPtr<Element> focusedElement =
6030 nsFocusManager::GetFocusManager()
6031 ? nsFocusManager::GetFocusManager()->GetFocusedElement()
6032 : nullptr;
6033 DebugOnly<nsresult> rvIgnored =
6034 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6035 htmlEditor, *this, focusedElement);
6036 NS_WARNING_ASSERTION(
6037 NS_SUCCEEDED(rvIgnored),
6038 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but "
6039 "ignored");
6040 return rv;
6043 // Flush out style changes on our _parent_ document, if any, so that
6044 // our check for a presshell won't get stale information.
6045 if (mParentDocument) {
6046 mParentDocument->FlushPendingNotifications(FlushType::Style);
6049 // get editing session, make sure this is a strong reference so the
6050 // window can't get deleted during the rest of this call.
6051 const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6052 if (!window) {
6053 return NS_ERROR_FAILURE;
6056 nsIDocShell* docshell = window->GetDocShell();
6057 if (!docshell) {
6058 return NS_ERROR_FAILURE;
6061 // FlushPendingNotifications might destroy our docshell.
6062 bool isBeingDestroyed = false;
6063 docshell->IsBeingDestroyed(&isBeingDestroyed);
6064 if (isBeingDestroyed) {
6065 return NS_ERROR_FAILURE;
6068 nsCOMPtr<nsIEditingSession> editSession;
6069 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6070 NS_ENSURE_SUCCESS(rv, rv);
6072 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
6073 if (htmlEditor) {
6074 // We might already have an editor if it was set up for mail, let's see
6075 // if this is actually the case.
6076 uint32_t flags = 0;
6077 htmlEditor->GetFlags(&flags);
6078 if (flags & nsIEditor::eEditorMailMask) {
6079 // We already have a mail editor, then we should not attempt to create
6080 // another one.
6081 return NS_OK;
6085 if (!HasPresShell(window)) {
6086 // We should not make the window editable or setup its editor.
6087 // It's probably style=display:none.
6088 return NS_OK;
6091 bool makeWindowEditable = mEditingState == EditingState::eOff;
6092 bool spellRecheckAll = false;
6093 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
6094 htmlEditor = nullptr;
6097 EditingState oldState = mEditingState;
6098 nsAutoEditingState push(this, EditingState::eSettingUp);
6100 RefPtr<PresShell> presShell = GetPresShell();
6101 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
6103 // If we're entering the design mode from non-editable state, put the
6104 // selection at the beginning of the document for compatibility reasons.
6105 bool collapseSelectionAtBeginningOfDocument =
6106 designMode && oldState == EditingState::eOff;
6107 // However, mEditingState may be eOff even if there is some
6108 // `contenteditable` area and selection has been initialized for it because
6109 // mEditingState for `contenteditable` may have been scheduled to modify
6110 // when safe. In such case, we should not reinitialize selection.
6111 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
6112 Selection* selection =
6113 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
6114 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
6115 if (selection && selection->RangeCount()) {
6116 // Perhaps, we don't need to check whether the selection is in
6117 // an editing host or not because all contents will be editable
6118 // in designMode. (And we don't want to make this code so complicated
6119 // because of legacy API.)
6120 collapseSelectionAtBeginningOfDocument = false;
6124 MOZ_ASSERT(mStyleSetFilled);
6126 // Before making this window editable, we need to modify UA style sheet
6127 // because new style may change whether focused element will be focusable
6128 // or not.
6129 if (IsHTMLOrXHTML()) {
6130 AddContentEditableStyleSheetsToStyleSet(designMode);
6133 if (designMode) {
6134 // designMode is being turned on (overrides contentEditable).
6135 spellRecheckAll = oldState == EditingState::eContentEditable;
6138 // Adjust focused element with new style but blur event shouldn't be fired
6139 // until mEditingState is modified with newState.
6140 nsAutoScriptBlocker scriptBlocker;
6141 if (designMode) {
6142 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6143 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6144 window, nsFocusManager::eOnlyCurrentWindow,
6145 getter_AddRefs(focusedWindow));
6146 if (focusedContent) {
6147 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
6148 bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable()
6149 : !focusedContent->IsFocusable();
6150 if (clearFocus) {
6151 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
6152 fm->ClearFocus(window);
6153 // If we need to dispatch blur event, we should put off after
6154 // modifying mEditingState since blur event handler may change
6155 // designMode state again.
6156 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
6162 if (makeWindowEditable) {
6163 // Editing is being turned on (through designMode or contentEditable)
6164 // Turn on editor.
6165 // XXX This can cause flushing which can change the editing state, so make
6166 // sure to avoid recursing.
6167 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
6168 NS_ENSURE_SUCCESS(rv, rv);
6171 // XXX Need to call TearDownEditorOnWindow for all failures.
6172 htmlEditor = docshell->GetHTMLEditor();
6173 if (!htmlEditor) {
6174 // Return NS_OK even though we've failed to create an editor here. This
6175 // is so that the setter of designMode on non-HTML documents does not
6176 // fail.
6177 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
6178 // would detect that we can't support the mimetype if appropriate and
6179 // would fall onto the eEditorErrorCantEditMimeType path.
6180 return NS_OK;
6183 if (collapseSelectionAtBeginningOfDocument) {
6184 htmlEditor->BeginningOfDocument();
6187 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6188 nsContentUtils::AddScriptBlocker();
6192 mEditingState = newState;
6193 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6194 nsContentUtils::RemoveScriptBlocker();
6195 // If mEditingState is overwritten by another call and already disabled
6196 // the editing, we shouldn't keep making window editable.
6197 if (mEditingState == EditingState::eOff) {
6198 return NS_OK;
6202 if (makeWindowEditable) {
6203 // TODO: We should do this earlier in this method.
6204 // Previously, we called `ExecCommand` with `insertBrOnReturn` command
6205 // whose argument is false here. Then, if it returns error, we
6206 // stopped making it editable. However, after bug 1697078 fixed,
6207 // `ExecCommand` returns error only when the document is not XHTML's
6208 // nor HTML's. Therefore, we use same error handling for now.
6209 if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
6210 // Editor setup failed. Editing is not on after all.
6211 // XXX Should we reset the editable flag on nodes?
6212 editSession->TearDownEditorOnWindow(window);
6213 mEditingState = EditingState::eOff;
6214 return NS_ERROR_DOM_INVALID_STATE_ERR;
6216 // Set the editor to not insert <br> elements on return when in <p> elements
6217 // by default.
6218 htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
6221 // Resync the editor's spellcheck state.
6222 if (spellRecheckAll) {
6223 nsCOMPtr<nsISelectionController> selectionController =
6224 htmlEditor->GetSelectionController();
6225 if (NS_WARN_IF(!selectionController)) {
6226 return NS_ERROR_FAILURE;
6229 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
6230 nsISelectionController::SELECTION_SPELLCHECK);
6231 if (spellCheckSelection) {
6232 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
6235 htmlEditor->SyncRealTimeSpell();
6237 MaybeDispatchCheckKeyPressEventModelEvent();
6239 // If this document keeps having focus and the HTMLEditor is in the design
6240 // mode, it may not receive `focus` event for this editing state change since
6241 // this may occur without a focus change. Therefore, let's notify HTMLEditor
6242 // of this editing state change.
6243 if (thisDocumentHasFocus && htmlEditor->IsInDesignMode() &&
6244 ThisDocumentHasFocus()) {
6245 DebugOnly<nsresult> rvIgnored =
6246 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, nullptr);
6247 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6248 "HTMLEditor::FocusedElementOrDocumentBecomesEditable()"
6249 " failed, but ignored");
6252 return NS_OK;
6255 // Helper class, used below in ChangeContentEditableCount().
6256 class DeferredContentEditableCountChangeEvent : public Runnable {
6257 public:
6258 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
6259 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
6260 mDoc(aDoc),
6261 mElement(aElement) {}
6263 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
6264 if (mElement && mElement->OwnerDoc() == mDoc) {
6265 RefPtr<Document> doc = std::move(mDoc);
6266 RefPtr<Element> element = std::move(mElement);
6267 doc->DeferredContentEditableCountChange(element);
6269 return NS_OK;
6272 private:
6273 RefPtr<Document> mDoc;
6274 RefPtr<Element> mElement;
6277 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
6278 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
6279 "Trying to decrement too much.");
6281 mContentEditableCount += aChange;
6283 if (aElement) {
6284 nsContentUtils::AddScriptRunner(
6285 new DeferredContentEditableCountChangeEvent(this, aElement));
6289 void Document::DeferredContentEditableCountChange(Element* aElement) {
6290 const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
6291 const bool elementHasFocus =
6292 aElement && fm && fm->GetFocusedElement() == aElement;
6293 if (elementHasFocus) {
6294 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
6295 // When contenteditable of aElement is changed and HTMLEditor works with it
6296 // or needs to start working with it, HTMLEditor may not receive `focus`
6297 // event nor `blur` event because this may occur without a focus change.
6298 // Therefore, we need to notify HTMLEditor of this contenteditable attribute
6299 // change.
6300 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6301 if (aElement->HasFlag(NODE_IS_EDITABLE)) {
6302 if (htmlEditor) {
6303 DebugOnly<nsresult> rvIgnored =
6304 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this,
6305 aElement);
6306 NS_WARNING_ASSERTION(
6307 NS_SUCCEEDED(rvIgnored),
6308 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6309 "ignored");
6311 } else {
6312 DebugOnly<nsresult> rvIgnored =
6313 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6314 htmlEditor, *this, aElement);
6315 NS_WARNING_ASSERTION(
6316 NS_SUCCEEDED(rvIgnored),
6317 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, "
6318 "but ignored");
6322 if (mParser ||
6323 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
6324 return;
6327 EditingState oldState = mEditingState;
6329 nsresult rv = EditingStateChanged();
6330 NS_ENSURE_SUCCESS_VOID(rv);
6332 if (oldState == mEditingState &&
6333 mEditingState == EditingState::eContentEditable) {
6334 // We just changed the contentEditable state of a node, we need to reset
6335 // the spellchecking state of that node.
6336 if (aElement) {
6337 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6338 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
6339 rv = htmlEditor->GetInlineSpellChecker(false,
6340 getter_AddRefs(spellChecker));
6341 NS_ENSURE_SUCCESS_VOID(rv);
6343 if (spellChecker &&
6344 aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) {
6345 RefPtr<nsRange> range = nsRange::Create(aElement);
6346 IgnoredErrorResult res;
6347 range->SelectNode(*aElement, res);
6348 if (res.Failed()) {
6349 // The node might be detached from the document at this point,
6350 // which would cause this call to fail. In this case, we can
6351 // safely ignore the contenteditable count change.
6352 return;
6355 rv = spellChecker->SpellCheckRange(range);
6356 NS_ENSURE_SUCCESS_VOID(rv);
6362 // aElement causes creating new HTMLEditor and the element had and keep
6363 // having focus, the HTMLEditor won't receive `focus` event. Therefore, we
6364 // need to notify HTMLEditor of it becomes editable.
6365 if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) &&
6366 fm->GetFocusedElement() == aElement) {
6367 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6368 DebugOnly<nsresult> rvIgnored =
6369 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement);
6370 NS_WARNING_ASSERTION(
6371 NS_SUCCEEDED(rvIgnored),
6372 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6373 "ignored");
6378 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
6379 // Currently, we need to check only when we're becoming editable for
6380 // contenteditable.
6381 if (mEditingState != EditingState::eContentEditable) {
6382 return;
6385 if (mHasBeenEditable) {
6386 return;
6388 mHasBeenEditable = true;
6390 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
6391 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
6392 // with proper keypress event for the active web app.
6393 WidgetEvent checkEvent(true, eUnidentifiedEvent);
6394 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
6395 checkEvent.mFlags.mCancelable = false;
6396 checkEvent.mFlags.mBubbles = false;
6397 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
6398 // Post the event rather than dispatching it synchronously because we need
6399 // a call of SetKeyPressEventModel() before first key input. Therefore, we
6400 // can avoid paying unnecessary runtime cost for most web apps.
6401 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
6404 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
6405 PresShell* presShell = GetPresShell();
6406 if (!presShell) {
6407 return;
6409 presShell->SetKeyPressEventModel(aKeyPressEventModel);
6412 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
6414 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
6415 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
6416 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
6417 aFocusTime >= mLastFocusTime);
6418 mLastFocusTime = aFocusTime;
6421 void Document::GetReferrer(nsAString& aReferrer) const {
6422 aReferrer.Truncate();
6423 if (!mReferrerInfo) {
6424 return;
6427 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
6428 if (!referrer) {
6429 return;
6432 nsAutoCString uri;
6433 nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
6434 if (NS_WARN_IF(NS_FAILED(rv))) {
6435 return;
6438 CopyUTF8toUTF16(uri, aReferrer);
6441 void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
6442 aCookie.Truncate(); // clear current cookie in case service fails;
6443 // no cookie isn't an error condition.
6445 if (mDisableCookieAccess) {
6446 return;
6449 // If the document's sandboxed origin flag is set, then reading cookies
6450 // is prohibited.
6451 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6452 aRv.ThrowSecurityError(
6453 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6454 "flag.");
6455 return;
6458 StorageAccess storageAccess = CookieAllowedForDocument(this);
6459 if (storageAccess == StorageAccess::eDeny) {
6460 return;
6463 if (ShouldPartitionStorage(storageAccess) &&
6464 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6465 return;
6468 // If the document is a cookie-averse Document... return the empty string.
6469 if (IsCookieAverse()) {
6470 return;
6473 // not having a cookie service isn't an error
6474 nsCOMPtr<nsICookieService> service =
6475 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6476 if (service) {
6477 nsAutoCString cookie;
6478 service->GetCookieStringFromDocument(this, cookie);
6479 // CopyUTF8toUTF16 doesn't handle error
6480 // because it assumes that the input is valid.
6481 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
6485 void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
6486 if (mDisableCookieAccess) {
6487 return;
6490 // If the document's sandboxed origin flag is set, then setting cookies
6491 // is prohibited.
6492 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6493 aRv.ThrowSecurityError(
6494 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6495 "flag.");
6496 return;
6499 StorageAccess storageAccess = CookieAllowedForDocument(this);
6500 if (storageAccess == StorageAccess::eDeny) {
6501 return;
6504 if (ShouldPartitionStorage(storageAccess) &&
6505 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6506 return;
6509 // If the document is a cookie-averse Document... do nothing.
6510 if (IsCookieAverse()) {
6511 return;
6514 if (!mDocumentURI) {
6515 return;
6518 // not having a cookie service isn't an error
6519 nsCOMPtr<nsICookieService> service =
6520 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6521 if (!service) {
6522 return;
6525 NS_ConvertUTF16toUTF8 cookie(aCookie);
6526 nsresult rv = service->SetCookieStringFromDocument(this, cookie);
6528 // No warning messages here.
6529 if (NS_FAILED(rv)) {
6530 return;
6533 nsCOMPtr<nsIObserverService> observerService =
6534 mozilla::services::GetObserverService();
6535 if (observerService) {
6536 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
6537 nsString(aCookie).get());
6541 ReferrerPolicy Document::GetReferrerPolicy() const {
6542 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
6543 : ReferrerPolicy::_empty;
6546 void Document::GetAlinkColor(nsAString& aAlinkColor) {
6547 aAlinkColor.Truncate();
6549 HTMLBodyElement* body = GetBodyElement();
6550 if (body) {
6551 body->GetALink(aAlinkColor);
6555 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
6556 HTMLBodyElement* body = GetBodyElement();
6557 if (body) {
6558 body->SetALink(aAlinkColor);
6562 void Document::GetLinkColor(nsAString& aLinkColor) {
6563 aLinkColor.Truncate();
6565 HTMLBodyElement* body = GetBodyElement();
6566 if (body) {
6567 body->GetLink(aLinkColor);
6571 void Document::SetLinkColor(const nsAString& aLinkColor) {
6572 HTMLBodyElement* body = GetBodyElement();
6573 if (body) {
6574 body->SetLink(aLinkColor);
6578 void Document::GetVlinkColor(nsAString& aVlinkColor) {
6579 aVlinkColor.Truncate();
6581 HTMLBodyElement* body = GetBodyElement();
6582 if (body) {
6583 body->GetVLink(aVlinkColor);
6587 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
6588 HTMLBodyElement* body = GetBodyElement();
6589 if (body) {
6590 body->SetVLink(aVlinkColor);
6594 void Document::GetBgColor(nsAString& aBgColor) {
6595 aBgColor.Truncate();
6597 HTMLBodyElement* body = GetBodyElement();
6598 if (body) {
6599 body->GetBgColor(aBgColor);
6603 void Document::SetBgColor(const nsAString& aBgColor) {
6604 HTMLBodyElement* body = GetBodyElement();
6605 if (body) {
6606 body->SetBgColor(aBgColor);
6610 void Document::GetFgColor(nsAString& aFgColor) {
6611 aFgColor.Truncate();
6613 HTMLBodyElement* body = GetBodyElement();
6614 if (body) {
6615 body->GetText(aFgColor);
6619 void Document::SetFgColor(const nsAString& aFgColor) {
6620 HTMLBodyElement* body = GetBodyElement();
6621 if (body) {
6622 body->SetText(aFgColor);
6626 void Document::CaptureEvents() {
6627 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
6630 void Document::ReleaseEvents() {
6631 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
6634 HTMLAllCollection* Document::All() {
6635 if (!mAll) {
6636 mAll = new HTMLAllCollection(this);
6638 return mAll;
6641 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
6642 if (mIsSrcdocDocument) {
6643 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
6644 if (inStrmChan) {
6645 return inStrmChan->GetSrcdocData(aSrcdocData);
6648 aSrcdocData = VoidString();
6649 return NS_OK;
6652 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
6653 nsPIDOMWindowOuter* win = GetWindow();
6654 if (!win) {
6655 return nullptr;
6657 return WindowProxyHolder(win->GetBrowsingContext());
6660 nsIContent* Document::GetUnretargetedFocusedContent(
6661 IncludeChromeOnly aIncludeChromeOnly) const {
6662 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6663 if (!window) {
6664 return nullptr;
6666 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6667 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6668 window, nsFocusManager::eOnlyCurrentWindow,
6669 getter_AddRefs(focusedWindow));
6670 if (!focusedContent) {
6671 return nullptr;
6673 // be safe and make sure the element is from this document
6674 if (focusedContent->OwnerDoc() != this) {
6675 return nullptr;
6677 if (focusedContent->ChromeOnlyAccess() &&
6678 aIncludeChromeOnly == IncludeChromeOnly::No) {
6679 return focusedContent->FindFirstNonChromeOnlyAccessContent();
6681 return focusedContent;
6684 Element* Document::GetActiveElement() {
6685 // Get the focused element.
6686 Element* focusedElement = GetRetargetedFocusedElement();
6687 if (focusedElement) {
6688 return focusedElement;
6691 // No focused element anywhere in this document. Try to get the BODY.
6692 if (IsHTMLOrXHTML()) {
6693 Element* bodyElement = AsHTMLDocument()->GetBody();
6694 if (bodyElement) {
6695 return bodyElement;
6697 // Special case to handle the transition to XHTML from XUL documents
6698 // where there currently isn't a body element, but we need to match the
6699 // XUL behavior. This should be removed when bug 1540278 is resolved.
6700 if (nsContentUtils::IsChromeDoc(this)) {
6701 Element* docElement = GetDocumentElement();
6702 if (docElement && docElement->IsXULElement()) {
6703 return docElement;
6706 // Because of IE compatibility, return null when html document doesn't have
6707 // a body.
6708 return nullptr;
6711 // If we couldn't get a BODY, return the root element.
6712 return GetDocumentElement();
6715 Element* Document::GetCurrentScript() {
6716 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
6717 return el;
6720 void Document::ReleaseCapture() const {
6721 // only release the capture if the caller can access it. This prevents a
6722 // page from stopping a scrollbar grab for example.
6723 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
6724 if (node && nsContentUtils::CanCallerAccess(node)) {
6725 PresShell::ReleaseCapturingContent();
6729 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
6730 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
6731 return mChromeXHRDocBaseURI;
6734 return GetDocBaseURI();
6737 void Document::SetBaseURI(nsIURI* aURI) {
6738 if (!aURI && !mDocumentBaseURI) {
6739 return;
6742 // Don't do anything if the URI wasn't actually changed.
6743 if (aURI && mDocumentBaseURI) {
6744 bool equalBases = false;
6745 mDocumentBaseURI->Equals(aURI, &equalBases);
6746 if (equalBases) {
6747 return;
6751 mDocumentBaseURI = aURI;
6752 mCachedURLData = nullptr;
6753 RefreshLinkHrefs();
6756 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
6757 const nsAString& aURI) {
6758 RefPtr<nsIURI> resolvedURI;
6759 MOZ_TRY(
6760 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
6761 return OwningNonNull<nsIURI>(std::move(resolvedURI));
6764 nsIReferrerInfo* Document::ReferrerInfoForInternalCSSAndSVGResources() {
6765 if (!mCachedReferrerInfoForInternalCSSAndSVGResources) {
6766 mCachedReferrerInfoForInternalCSSAndSVGResources =
6767 ReferrerInfo::CreateForInternalCSSAndSVGResources(this);
6769 return mCachedReferrerInfoForInternalCSSAndSVGResources;
6772 URLExtraData* Document::DefaultStyleAttrURLData() {
6773 MOZ_ASSERT(NS_IsMainThread());
6774 if (!mCachedURLData) {
6775 mCachedURLData = new URLExtraData(
6776 GetDocBaseURI(), ReferrerInfoForInternalCSSAndSVGResources(),
6777 NodePrincipal());
6779 return mCachedURLData;
6782 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
6783 if (mCharacterSet != aEncoding) {
6784 mCharacterSet = aEncoding;
6785 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
6786 RecomputeLanguageFromCharset();
6788 if (nsPresContext* context = GetPresContext()) {
6789 context->DocumentCharSetChanged(aEncoding);
6794 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
6795 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
6798 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
6799 aData.Truncate();
6800 const HeaderData* data = mHeaderData.get();
6801 while (data) {
6802 if (data->mField == aHeaderField) {
6803 aData = data->mData;
6804 break;
6806 data = data->mNext.get();
6810 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
6811 if (!aHeaderField) {
6812 NS_ERROR("null headerField");
6813 return;
6816 if (!mHeaderData) {
6817 if (!aData.IsEmpty()) { // don't bother storing empty string
6818 mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
6820 } else {
6821 HeaderData* data = mHeaderData.get();
6822 UniquePtr<HeaderData>* lastPtr = &mHeaderData;
6823 bool found = false;
6824 do { // look for existing and replace
6825 if (data->mField == aHeaderField) {
6826 if (!aData.IsEmpty()) {
6827 data->mData.Assign(aData);
6828 } else { // don't store empty string
6829 // Note that data->mNext is moved to a temporary before the old value
6830 // of *lastPtr is deleted.
6831 *lastPtr = std::move(data->mNext);
6833 found = true;
6835 break;
6837 lastPtr = &data->mNext;
6838 data = lastPtr->get();
6839 } while (data);
6841 if (!aData.IsEmpty() && !found) {
6842 // didn't find, append
6843 *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
6847 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
6848 CopyUTF16toUTF8(aData, mContentLanguage);
6849 mMayNeedFontPrefsUpdate = true;
6850 if (auto* presContext = GetPresContext()) {
6851 presContext->ContentLanguageChanged();
6855 if (aHeaderField == nsGkAtoms::origin_trial) {
6856 mTrials.UpdateFromToken(aData, NodePrincipal());
6857 if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) {
6858 InitCOEP(mChannel);
6860 // If we still don't have a WindowContext, WindowContext::OnNewDocument
6861 // will take care of this.
6862 if (WindowContext* ctx = GetWindowContext()) {
6863 if (mEmbedderPolicy) {
6864 Unused << ctx->SetEmbedderPolicy(mEmbedderPolicy.value());
6870 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
6871 SetPreferredStyleSheetSet(aData);
6874 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
6875 // We get into this code before we have a script global yet, so get to our
6876 // container via mDocumentContainer.
6877 if (mDocumentContainer) {
6878 // Note: using mDocumentURI instead of mBaseURI here, for consistency
6879 // (used to just use the current URI of our webnavigation, but that
6880 // should really be the same thing). Note that this code can run
6881 // before the current URI of the webnavigation has been updated, so we
6882 // can't assert equality here.
6883 mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
6887 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
6888 mAllowDNSPrefetch) {
6889 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
6890 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
6893 if (aHeaderField == nsGkAtoms::handheldFriendly) {
6894 mViewportType = Unknown;
6898 void Document::SetEarlyHints(
6899 nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) {
6900 mEarlyHints = std::move(aEarlyHints);
6903 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
6904 NotNull<const Encoding*>& aEncoding,
6905 nsHtml5TreeOpExecutor* aExecutor) {
6906 if (aChannel) {
6907 nsAutoCString charsetVal;
6908 nsresult rv = aChannel->GetContentCharset(charsetVal);
6909 if (NS_SUCCEEDED(rv)) {
6910 const Encoding* preferred = Encoding::ForLabel(charsetVal);
6911 if (preferred) {
6912 if (aExecutor && preferred == REPLACEMENT_ENCODING) {
6913 aExecutor->ComplainAboutBogusProtocolCharset(this, false);
6915 aEncoding = WrapNotNull(preferred);
6916 aCharsetSource = kCharsetFromChannel;
6917 return;
6918 } else if (aExecutor && !charsetVal.IsEmpty()) {
6919 aExecutor->ComplainAboutBogusProtocolCharset(this, true);
6925 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
6926 #ifdef DEBUG
6927 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
6928 const Element* element = Element::FromNode(node);
6929 if (!element) {
6930 continue;
6932 MOZ_ASSERT(!element->HasServoData());
6934 #endif
6937 already_AddRefed<PresShell> Document::CreatePresShell(
6938 nsPresContext* aContext, nsViewManager* aViewManager) {
6939 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
6941 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
6943 AssertNoStaleServoDataIn(*this);
6945 RefPtr<PresShell> presShell = new PresShell(this);
6946 // Note: we don't hold a ref to the shell (it holds a ref to us)
6947 mPresShell = presShell;
6949 if (!mStyleSetFilled) {
6950 FillStyleSet();
6953 presShell->Init(aContext, aViewManager);
6954 if (RefPtr<class HighlightRegistry> highlightRegistry = mHighlightRegistry) {
6955 highlightRegistry->AddHighlightSelectionsToFrameSelection();
6957 // Gaining a shell causes changes in how media queries are evaluated, so
6958 // invalidate that.
6959 aContext->MediaFeatureValuesChanged(
6960 {MediaFeatureChange::kAllChanges},
6961 MediaFeatureChangePropagation::JustThisDocument);
6963 // Make sure to never paint if we belong to an invisible DocShell.
6964 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
6965 if (docShell && docShell->IsInvisible()) {
6966 presShell->SetNeverPainting(true);
6969 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
6970 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
6971 presShell.get(), docShell.get()));
6973 mExternalResourceMap.ShowViewers();
6975 UpdateFrameRequestCallbackSchedulingState();
6977 if (mDocumentL10n) {
6978 // In case we already accumulated mutations,
6979 // we'll trigger the refresh driver now.
6980 mDocumentL10n->OnCreatePresShell();
6983 if (HasAutoFocusCandidates()) {
6984 ScheduleFlushAutoFocusCandidates();
6986 // Now that we have a shell, we might have @font-face rules (the presence of a
6987 // shell may change which rules apply to us). We don't need to do anything
6988 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
6989 // is ready to update we'll flush the font set.
6990 MarkUserFontSetDirty();
6992 // Take the author style disabled state from the top browsing cvontext.
6993 // (PageStyleChild.sys.mjs ensures this is up to date.)
6994 if (BrowsingContext* bc = GetBrowsingContext()) {
6995 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
6998 return presShell.forget();
7001 void Document::UpdateFrameRequestCallbackSchedulingState(
7002 PresShell* aOldPresShell) {
7003 // If this condition changes to depend on some other variable, make sure to
7004 // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
7005 // that variable can change. Also consider if you should change
7006 // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
7007 // condition.
7008 bool shouldBeScheduled =
7009 WouldScheduleFrameRequestCallbacks() && !mFrameRequestManager.IsEmpty();
7010 if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
7011 // nothing to do
7012 return;
7015 PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
7016 MOZ_RELEASE_ASSERT(presShell);
7018 nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
7019 if (shouldBeScheduled) {
7020 rd->ScheduleFrameRequestCallbacks(this);
7021 } else {
7022 rd->RevokeFrameRequestCallbacks(this);
7025 mFrameRequestCallbacksScheduled = shouldBeScheduled;
7028 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
7029 MOZ_ASSERT(aCallbacks.IsEmpty());
7030 mFrameRequestManager.Take(aCallbacks);
7031 // No need to manually remove ourselves from the refresh driver; it will
7032 // handle that part. But we do have to update our state.
7033 mFrameRequestCallbacksScheduled = false;
7036 bool Document::ShouldThrottleFrameRequests() const {
7037 if (mStaticCloneCount > 0) {
7038 // Even if we're not visible, a static clone may be, so run at full speed.
7039 return false;
7042 if (Hidden()) {
7043 // We're not visible (probably in a background tab or the bf cache).
7044 return true;
7047 if (!mPresShell) {
7048 // Can't do anything smarter. We don't run frame requests in documents
7049 // without a pres shell anyways.
7050 return false;
7053 if (!mPresShell->IsActive()) {
7054 // The pres shell is not active (we're an invisible OOP iframe or such), so
7055 // throttle.
7056 return true;
7059 if (mPresShell->IsPaintingSuppressed()) {
7060 // Historically we have throttled frame requests until we've painted at
7061 // least once, so keep doing that.
7062 return true;
7065 if (mPresShell->IsUnderHiddenEmbedderElement()) {
7066 // For display: none and visibility: hidden we always throttle, for
7067 // consistency with OOP iframes.
7068 return true;
7071 Element* el = GetEmbedderElement();
7072 if (!el) {
7073 // If we're not in-process, our refresh driver is throttled separately (via
7074 // PresShell::SetIsActive, so not much more we can do here.
7075 return false;
7078 if (!StaticPrefs::layout_throttle_in_process_iframes()) {
7079 return false;
7082 // Note that because we have to scroll this document into view at least once
7083 // to unthrottle it, we will drop one requestAnimationFrame frame when a
7084 // document that previously wasn't visible scrolls into view. This is
7085 // acceptable / unlikely to be human-perceivable, though we could improve on
7086 // it if needed by adding an intersection margin or something of that sort.
7087 const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
7088 *el->OwnerDoc(), /* aRoot = */ nullptr, /* aRootMargin = */ nullptr);
7089 const IntersectionOutput output =
7090 DOMIntersectionObserver::Intersect(input, *el);
7091 return !output.Intersects();
7094 void Document::DeletePresShell() {
7095 mExternalResourceMap.HideViewers();
7096 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
7097 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
7098 presContext->RefreshDriver()->CancelFlushAutoFocus(this);
7101 // When our shell goes away, request that all our images be immediately
7102 // discarded, so we don't carry around decoded image data for a document we
7103 // no longer intend to paint.
7104 ImageTracker()->RequestDiscardAll();
7106 // Now that we no longer have a shell, we need to forget about any FontFace
7107 // objects for @font-face rules that came from the style set. There's no need
7108 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
7109 // no point on it.
7110 MarkUserFontSetDirty();
7112 if (mResizeObserverController) {
7113 mResizeObserverController->ShellDetachedFromDocument();
7116 if (IsEditingOn()) {
7117 TurnEditingOff();
7120 PresShell* oldPresShell = mPresShell;
7121 mPresShell = nullptr;
7122 UpdateFrameRequestCallbackSchedulingState(oldPresShell);
7124 ClearStaleServoData();
7125 AssertNoStaleServoDataIn(*this);
7127 mStyleSet->ShellDetachedFromDocument();
7128 mStyleSetFilled = false;
7129 mQuirkSheetAdded = false;
7130 mContentEditableSheetAdded = false;
7131 mDesignModeSheetAdded = false;
7134 void Document::DisallowBFCaching(uint32_t aStatus) {
7135 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
7136 if (!mBFCacheDisallowed) {
7137 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
7138 wgc->SendUpdateBFCacheStatus(aStatus, 0);
7141 mBFCacheDisallowed = true;
7144 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
7145 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
7147 if (mPresShell) {
7148 if (aEntry) {
7149 mPresShell->StopObservingRefreshDriver();
7150 } else if (mBFCacheEntry) {
7151 mPresShell->StartObservingRefreshDriver();
7154 mBFCacheEntry = aEntry;
7157 bool Document::RemoveFromBFCacheSync() {
7158 bool removed = false;
7159 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
7160 entry->RemoveFromBFCacheSync();
7161 removed = true;
7162 } else if (!IsCurrentActiveDocument()) {
7163 // In the old bfcache implementation while the new page is loading, but
7164 // before nsIContentViewer.show() has been called, the previous page doesn't
7165 // yet have nsIBFCacheEntry. However, the previous page isn't the current
7166 // active document anymore.
7167 DisallowBFCaching();
7168 removed = true;
7171 if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
7172 if (BrowsingContext* bc = GetBrowsingContext()) {
7173 if (bc->IsInBFCache()) {
7174 ContentChild* cc = ContentChild::GetSingleton();
7175 // IPC is asynchronous but the caller is supposed to check the return
7176 // value. The reason for 'Sync' in the method name is that the old
7177 // implementation may run scripts. There is Async variant in
7178 // the old session history implementation for the cases where
7179 // synchronous operation isn't safe.
7180 cc->SendRemoveFromBFCache(bc->Top());
7181 removed = true;
7185 return removed;
7188 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
7189 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
7191 NS_RELEASE(e->mKey);
7192 if (e->mSubDocument) {
7193 e->mSubDocument->SetParentDocument(nullptr);
7194 NS_RELEASE(e->mSubDocument);
7198 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
7199 SubDocMapEntry* e =
7200 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
7202 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
7203 NS_ADDREF(e->mKey);
7205 e->mSubDocument = nullptr;
7208 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
7209 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
7211 if (!aSubDoc) {
7212 // aSubDoc is nullptr, remove the mapping
7214 if (mSubDocuments) {
7215 mSubDocuments->Remove(aElement);
7217 } else {
7218 if (!mSubDocuments) {
7219 // Create a new hashtable
7221 static const PLDHashTableOps hash_table_ops = {
7222 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
7223 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
7225 mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
7228 // Add a mapping to the hash table
7229 auto entry =
7230 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
7232 if (!entry) {
7233 return NS_ERROR_OUT_OF_MEMORY;
7236 if (entry->mSubDocument) {
7237 entry->mSubDocument->SetParentDocument(nullptr);
7239 // Release the old sub document
7240 NS_RELEASE(entry->mSubDocument);
7243 entry->mSubDocument = aSubDoc;
7244 NS_ADDREF(entry->mSubDocument);
7246 aSubDoc->SetParentDocument(this);
7249 return NS_OK;
7252 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
7253 if (mSubDocuments && aContent->IsElement()) {
7254 auto entry = static_cast<SubDocMapEntry*>(
7255 mSubDocuments->Search(aContent->AsElement()));
7257 if (entry) {
7258 return entry->mSubDocument;
7262 return nullptr;
7265 Element* Document::GetEmbedderElement() const {
7266 // We check if we're the active document in our BrowsingContext
7267 // by comparing against its document, rather than checking if the
7268 // WindowContext is cached, since mWindow may be null when we're
7269 // called (such as in nsPresContext::Init).
7270 if (BrowsingContext* bc = GetBrowsingContext()) {
7271 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
7274 return nullptr;
7277 Element* Document::GetRootElement() const {
7278 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
7279 ? mCachedRootElement
7280 : GetRootElementInternal();
7283 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
7285 Element* Document::GetRootElementInternal() const {
7286 // We invoke GetRootElement() immediately before the servo traversal, so we
7287 // should always have a cache hit from Servo.
7288 MOZ_ASSERT(NS_IsMainThread());
7290 // Loop backwards because any non-elements, such as doctypes and PIs
7291 // are likely to appear before the root element.
7292 for (nsIContent* child = GetLastChild(); child;
7293 child = child->GetPreviousSibling()) {
7294 if (Element* element = Element::FromNode(child)) {
7295 const_cast<Document*>(this)->mCachedRootElement = element;
7296 return element;
7300 const_cast<Document*>(this)->mCachedRootElement = nullptr;
7301 return nullptr;
7304 void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
7305 bool aNotify, ErrorResult& aRv) {
7306 if (aKid->IsElement() && GetRootElement()) {
7307 NS_WARNING("Inserting root element when we already have one");
7308 aRv.ThrowHierarchyRequestError("There is already a root element.");
7309 return;
7312 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
7315 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
7316 Maybe<mozAutoDocUpdate> updateBatch;
7317 if (aKid->IsElement()) {
7318 updateBatch.emplace(this, aNotify);
7319 // Destroy the link map up front before we mess with the child list.
7320 DestroyElementMaps();
7323 // Preemptively clear mCachedRootElement, since we may be about to remove it
7324 // from our child list, and we don't want to return this maybe-obsolete value
7325 // from any GetRootElement() calls that happen inside of RemoveChildNode().
7326 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
7327 // GetRootElement() calls until after it's removed the child from mChildren.
7328 // Any call before that point would restore this soon-to-be-obsolete cached
7329 // answer, and our clearing here would be fruitless.)
7330 mCachedRootElement = nullptr;
7331 nsINode::RemoveChildNode(aKid, aNotify);
7332 MOZ_ASSERT(mCachedRootElement != aKid,
7333 "Stale pointer in mCachedRootElement, after we tried to clear it "
7334 "(maybe somebody called GetRootElement() too early?)");
7337 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
7338 if (mStyleSetFilled) {
7339 mStyleSet->AddDocStyleSheet(aSheet);
7340 ApplicableStylesChanged();
7344 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
7345 mStyleSet->RecordShadowStyleChange(aShadowRoot);
7346 ApplicableStylesChanged(/* aKnownInShadowTree= */ true);
7349 void Document::ApplicableStylesChanged(bool aKnownInShadowTree) {
7350 // TODO(emilio): if we decide to resolve style in display: none iframes, then
7351 // we need to always track style changes and remove the mStyleSetFilled.
7352 if (!mStyleSetFilled) {
7353 return;
7355 if (!aKnownInShadowTree) {
7356 MarkUserFontSetDirty();
7358 PresShell* ps = GetPresShell();
7359 if (!ps) {
7360 return;
7363 ps->EnsureStyleFlush();
7364 nsPresContext* pc = ps->GetPresContext();
7365 if (!pc) {
7366 return;
7369 if (!aKnownInShadowTree) {
7370 pc->MarkCounterStylesDirty();
7371 pc->MarkFontFeatureValuesDirty();
7372 pc->MarkFontPaletteValuesDirty();
7374 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
7377 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
7378 if (mStyleSetFilled) {
7379 mStyleSet->RemoveStyleSheet(aSheet);
7380 ApplicableStylesChanged();
7384 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
7385 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
7387 if (aSheet.IsApplicable()) {
7388 AddStyleSheetToStyleSets(aSheet);
7392 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
7393 const bool applicable = aSheet.IsApplicable();
7394 // If we're actually in the document style sheet list
7395 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
7396 if (applicable) {
7397 AddStyleSheetToStyleSets(aSheet);
7398 } else {
7399 RemoveStyleSheetFromStyleSets(aSheet);
7404 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
7405 if (!StyleSheetChangeEventsEnabled()) {
7406 return;
7409 StyleSheetApplicableStateChangeEventInit init;
7410 init.mBubbles = true;
7411 init.mCancelable = true;
7412 init.mStylesheet = &aSheet;
7413 init.mApplicable = aSheet.IsApplicable();
7415 RefPtr<StyleSheetApplicableStateChangeEvent> event =
7416 StyleSheetApplicableStateChangeEvent::Constructor(
7417 this, u"StyleSheetApplicableStateChanged"_ns, init);
7418 event->SetTrusted(true);
7419 event->SetTarget(this);
7420 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7421 new AsyncEventDispatcher(this, event);
7422 asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
7423 asyncDispatcher->PostDOMEvent();
7426 void Document::PostStyleSheetRemovedEvent(StyleSheet& aSheet) {
7427 if (!StyleSheetChangeEventsEnabled()) {
7428 return;
7431 StyleSheetRemovedEventInit init;
7432 init.mBubbles = true;
7433 init.mCancelable = false;
7434 init.mStylesheet = &aSheet;
7436 RefPtr<StyleSheetRemovedEvent> event =
7437 StyleSheetRemovedEvent::Constructor(this, u"StyleSheetRemoved"_ns, init);
7438 event->SetTrusted(true);
7439 event->SetTarget(this);
7440 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7441 new AsyncEventDispatcher(this, event);
7442 asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
7443 asyncDispatcher->PostDOMEvent();
7446 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
7447 nsIURI* aSheetURI) {
7448 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
7449 bool bEqual;
7450 nsIURI* uri = aSheets[i]->GetSheetURI();
7452 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
7453 return i;
7456 return -1;
7459 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
7460 nsIURI* aSheetURI) {
7461 MOZ_ASSERT(aSheetURI, "null arg");
7463 // Checking if we have loaded this one already.
7464 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
7465 return NS_ERROR_INVALID_ARG;
7467 // Loading the sheet sync.
7468 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
7470 css::SheetParsingMode parsingMode;
7471 switch (aType) {
7472 case Document::eAgentSheet:
7473 parsingMode = css::eAgentSheetFeatures;
7474 break;
7476 case Document::eUserSheet:
7477 parsingMode = css::eUserSheetFeatures;
7478 break;
7480 case Document::eAuthorSheet:
7481 parsingMode = css::eAuthorSheetFeatures;
7482 break;
7484 default:
7485 MOZ_CRASH("impossible value for aType");
7488 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
7489 css::Loader::UseSystemPrincipal::Yes);
7490 if (result.isErr()) {
7491 return result.unwrapErr();
7494 RefPtr<StyleSheet> sheet = result.unwrap();
7496 sheet->SetAssociatedDocumentOrShadowRoot(this);
7497 MOZ_ASSERT(sheet->IsApplicable());
7499 return AddAdditionalStyleSheet(aType, sheet);
7502 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
7503 StyleSheet* aSheet) {
7504 if (mAdditionalSheets[aType].Contains(aSheet)) {
7505 return NS_ERROR_INVALID_ARG;
7508 if (!aSheet->IsApplicable()) {
7509 return NS_ERROR_INVALID_ARG;
7512 mAdditionalSheets[aType].AppendElement(aSheet);
7514 if (mStyleSetFilled) {
7515 mStyleSet->AppendStyleSheet(*aSheet);
7516 ApplicableStylesChanged();
7518 return NS_OK;
7521 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
7522 nsIURI* aSheetURI) {
7523 MOZ_ASSERT(aSheetURI);
7525 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
7527 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
7528 if (i >= 0) {
7529 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
7530 sheets.RemoveElementAt(i);
7532 if (!mIsGoingAway) {
7533 MOZ_ASSERT(sheetRef->IsApplicable());
7534 if (mStyleSetFilled) {
7535 mStyleSet->RemoveStyleSheet(*sheetRef);
7536 ApplicableStylesChanged();
7539 sheetRef->ClearAssociatedDocumentOrShadowRoot();
7543 nsIGlobalObject* Document::GetScopeObject() const {
7544 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
7545 return scope;
7548 DocGroup* Document::GetDocGroupOrCreate() {
7549 if (!mDocGroup && GetBrowsingContext()) {
7550 BrowsingContextGroup* group = GetBrowsingContext()->Group();
7551 MOZ_ASSERT(group);
7553 nsAutoCString docGroupKey;
7554 nsresult rv = mozilla::dom::DocGroup::GetKey(
7555 NodePrincipal(), group->IsPotentiallyCrossOriginIsolated(),
7556 docGroupKey);
7557 if (NS_SUCCEEDED(rv)) {
7558 mDocGroup = group->AddDocument(docGroupKey, this);
7561 return mDocGroup;
7564 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
7565 mScopeObject = do_GetWeakReference(aGlobal);
7566 if (aGlobal) {
7567 mHasHadScriptHandlingObject = true;
7569 nsPIDOMWindowInner* window = aGlobal->GetAsInnerWindow();
7570 if (!window) {
7571 return;
7574 // Same origin data documents should have the same docGroup as their scope
7575 // window.
7576 if (mLoadedAsData && window->GetExtantDoc() &&
7577 window->GetExtantDoc() != this &&
7578 window->GetExtantDoc()->NodePrincipal() == NodePrincipal()) {
7579 DocGroup* docGroup = window->GetExtantDoc()->GetDocGroup();
7581 if (docGroup) {
7582 if (!mDocGroup) {
7583 mDocGroup = docGroup;
7584 mDocGroup->AddDocument(this);
7585 } else {
7586 MOZ_ASSERT(mDocGroup == docGroup,
7587 "Data document has a mismatched doc group?");
7589 #ifdef DEBUG
7590 AssertDocGroupMatchesKey();
7591 #endif
7592 return;
7595 MOZ_ASSERT_UNREACHABLE(
7596 "Scope window doesn't have DocGroup when creating data document?");
7597 // ... but fall through to be safe.
7600 BrowsingContextGroup* browsingContextGroup =
7601 window->GetBrowsingContextGroup();
7603 // We should already have the principal, and now that we have been added
7604 // to a window, we should be able to join a DocGroup!
7605 nsAutoCString docGroupKey;
7606 nsresult rv = mozilla::dom::DocGroup::GetKey(
7607 NodePrincipal(),
7608 browsingContextGroup->IsPotentiallyCrossOriginIsolated(), docGroupKey);
7609 if (mDocGroup) {
7610 if (NS_SUCCEEDED(rv)) {
7611 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
7613 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
7614 browsingContextGroup);
7615 } else {
7616 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
7618 MOZ_ASSERT(mDocGroup);
7621 MOZ_ASSERT_IF(
7622 mNodeInfoManager->GetArenaAllocator(),
7623 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
7627 bool Document::ContainsEMEContent() {
7628 nsPIDOMWindowInner* win = GetInnerWindow();
7629 // Note this case is different from checking just media elements in that
7630 // it covers when we've created MediaKeys but not associated them with a
7631 // media element.
7632 return win && win->HasActiveMediaKeysInstance();
7635 bool Document::ContainsMSEContent() {
7636 bool containsMSE = false;
7638 auto check = [&containsMSE](nsISupports* aSupports) {
7639 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7640 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7641 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
7642 if (ms) {
7643 containsMSE = true;
7648 EnumerateActivityObservers(check);
7649 return containsMSE;
7652 static void NotifyActivityChangedCallback(nsISupports* aSupports) {
7653 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7654 if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7655 mediaElem->NotifyOwnerDocumentActivityChanged();
7657 nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
7658 do_QueryInterface(aSupports));
7659 if (objectLoadingContent) {
7660 nsObjectLoadingContent* olc =
7661 static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
7662 olc->NotifyOwnerDocumentActivityChanged();
7664 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
7665 do_QueryInterface(aSupports));
7666 if (objectDocumentActivity) {
7667 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
7668 } else {
7669 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
7670 do_QueryInterface(aSupports));
7671 if (imageLoadingContent) {
7672 auto ilc = static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
7673 ilc->NotifyOwnerDocumentActivityChanged();
7678 void Document::NotifyActivityChanged() {
7679 EnumerateActivityObservers(NotifyActivityChangedCallback);
7682 bool Document::IsTopLevelWindowInactive() const {
7683 if (BrowsingContext* bc = GetBrowsingContext()) {
7684 return !bc->GetIsActiveBrowserWindow();
7687 return false;
7690 void Document::SetContainer(nsDocShell* aContainer) {
7691 if (aContainer) {
7692 mDocumentContainer = aContainer;
7693 } else {
7694 mDocumentContainer = WeakPtr<nsDocShell>();
7697 mInChromeDocShell =
7698 aContainer && aContainer->GetBrowsingContext()->IsChrome();
7700 NotifyActivityChanged();
7702 // IsTopLevelWindowInactive depends on the docshell, so
7703 // update the cached value now that it's available.
7704 UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false);
7705 if (!aContainer) {
7706 return;
7709 BrowsingContext* context = aContainer->GetBrowsingContext();
7710 MOZ_ASSERT_IF(context && mDocGroup,
7711 context->Group() == mDocGroup->GetBrowsingContextGroup());
7712 if (context && context->IsContent()) {
7713 SetIsTopLevelContentDocument(context->IsTopContent());
7714 SetIsContentDocument(true);
7715 } else {
7716 SetIsTopLevelContentDocument(false);
7717 SetIsContentDocument(false);
7721 nsISupports* Document::GetContainer() const {
7722 return static_cast<nsIDocShell*>(mDocumentContainer);
7725 void Document::SetScriptGlobalObject(
7726 nsIScriptGlobalObject* aScriptGlobalObject) {
7727 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
7728 mAnimationController->IsPausedByType(
7729 SMILTimeContainer::PAUSE_PAGEHIDE |
7730 SMILTimeContainer::PAUSE_BEGIN),
7731 "Clearing window pointer while animations are unpaused");
7733 if (mScriptGlobalObject && !aScriptGlobalObject) {
7734 // We're detaching from the window. We need to grab a pointer to
7735 // our layout history state now.
7736 mLayoutHistoryState = GetLayoutHistoryState();
7738 // Also make sure to remove our onload blocker now if we haven't done it yet
7739 if (mOnloadBlockCount != 0) {
7740 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
7741 if (loadGroup) {
7742 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
7746 if (GetController().isSome()) {
7747 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
7748 loader->ClearCacheForControlledDocument(this);
7751 // We may become controlled again if this document comes back out
7752 // of bfcache. Clear our state to allow that to happen. Only
7753 // clear this flag if we are actually controlled, though, so pages
7754 // that were force reloaded don't become controlled when they
7755 // come out of bfcache.
7756 mMaybeServiceWorkerControlled = false;
7759 if (GetWindowContext()) {
7760 // The document is about to lose its window, so this is a good time to
7761 // send our page use counters, while we still have access to our
7762 // WindowContext.
7764 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
7765 // catches some cases of documents losing their window that don't
7766 // get in here.)
7767 SendPageUseCounters();
7771 // BlockOnload() might be called before mScriptGlobalObject is set.
7772 // We may need to add the blocker once mScriptGlobalObject is set.
7773 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
7775 mScriptGlobalObject = aScriptGlobalObject;
7777 if (needOnloadBlocker) {
7778 EnsureOnloadBlocker();
7781 UpdateFrameRequestCallbackSchedulingState();
7783 if (aScriptGlobalObject) {
7784 // Go back to using the docshell for the layout history state
7785 mLayoutHistoryState = nullptr;
7786 SetScopeObject(aScriptGlobalObject);
7787 mHasHadDefaultView = true;
7789 if (mAllowDNSPrefetch) {
7790 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7791 if (docShell) {
7792 #ifdef DEBUG
7793 nsCOMPtr<nsIWebNavigation> webNav =
7794 do_GetInterface(aScriptGlobalObject);
7795 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
7796 "Unexpected container or script global?");
7797 #endif
7798 bool allowDNSPrefetch;
7799 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
7800 mAllowDNSPrefetch = allowDNSPrefetch;
7804 // If we are set in a window that is already focused we should remember this
7805 // as the time the document gained focus.
7806 if (HasFocus(IgnoreErrors())) {
7807 SetLastFocusTime(TimeStamp::Now());
7811 // Remember the pointer to our window (or lack there of), to avoid
7812 // having to QI every time it's asked for.
7813 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
7814 mWindow = window;
7816 // Now that we know what our window is, we can flush the CSP errors to the
7817 // Web Console. We are flushing all messages that occurred and were stored in
7818 // the queue prior to this point.
7819 if (mCSP) {
7820 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
7823 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
7824 do_QueryInterface(GetChannel());
7825 if (internalChannel) {
7826 nsCOMArray<nsISecurityConsoleMessage> messages;
7827 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
7828 MOZ_ASSERT(NS_SUCCEEDED(rv));
7829 SendToConsole(messages);
7832 // Set our visibility state, but do not fire the event. This is correct
7833 // because either we're coming out of bfcache (in which case IsVisible() will
7834 // still test false at this point and no state change will happen) or we're
7835 // doing the initial document load and don't want to fire the event for this
7836 // change.
7838 // When the visibility is changed, notify it to observers.
7839 // Some observers need the notification, for example HTMLMediaElement uses
7840 // it to update internal media resource allocation.
7841 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
7842 // creation are already done before Document::SetScriptGlobalObject() call.
7843 // MediaDecoder decides whether starting decoding is decided based on
7844 // document's visibility. When the MediaDecoder is created,
7845 // Document::SetScriptGlobalObject() is not yet called and document is
7846 // hidden state. Therefore the MediaDecoder decides that decoding is
7847 // not yet necessary. But soon after Document::SetScriptGlobalObject()
7848 // call, the document becomes not hidden. At the time, MediaDecoder needs
7849 // to know it and needs to start updating decoding.
7850 UpdateVisibilityState(DispatchVisibilityChange::No);
7852 // The global in the template contents owner document should be the same.
7853 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
7854 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
7857 // Tell the script loader about the new global object.
7858 if (mScriptLoader && !IsTemplateContentsOwner()) {
7859 mScriptLoader->SetGlobalObject(mScriptGlobalObject);
7862 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
7863 mScriptGlobalObject && GetChannel()) {
7864 // If we are shift-reloaded, don't associate with a ServiceWorker.
7865 if (mDocumentContainer->IsForceReloading()) {
7866 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
7867 return;
7870 mMaybeServiceWorkerControlled = true;
7874 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
7875 MOZ_ASSERT(!mScriptGlobalObject,
7876 "Do not call this when mScriptGlobalObject is set!");
7877 if (mHasHadDefaultView) {
7878 return nullptr;
7881 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
7882 do_QueryReferent(mScopeObject);
7883 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
7884 if (win) {
7885 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
7886 if (!outer || outer->GetCurrentInnerWindow() != win) {
7887 NS_WARNING("Wrong inner/outer window combination!");
7888 return nullptr;
7891 return scriptHandlingObject;
7893 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
7894 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
7895 "Wrong script object!");
7896 if (aScriptObject) {
7897 SetScopeObject(aScriptObject);
7898 mHasHadDefaultView = false;
7902 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
7903 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
7904 // Let's use mScriptGlobalObject. Even if the document is already removed from
7905 // the docshell, the outer window might be still obtainable from the it.
7906 nsCOMPtr<nsPIDOMWindowOuter> win;
7907 if (mRemovedFromDocShell) {
7908 // The docshell returns the outer window we are done.
7909 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
7910 if (kungFuDeathGrip) {
7911 win = kungFuDeathGrip->GetWindow();
7913 } else {
7914 if (nsCOMPtr<nsPIDOMWindowInner> inner =
7915 do_QueryInterface(mScriptGlobalObject)) {
7916 // mScriptGlobalObject is always the inner window, let's get the outer.
7917 win = inner->GetOuterWindow();
7921 return win;
7924 bool Document::InternalAllowXULXBL() {
7925 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
7926 mAllowXULXBL = eTriTrue;
7927 return true;
7930 mAllowXULXBL = eTriFalse;
7931 return false;
7934 // Note: We don't hold a reference to the document observer; we assume
7935 // that it has a live reference to the document.
7936 void Document::AddObserver(nsIDocumentObserver* aObserver) {
7937 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
7938 "Observer already in the list");
7939 mObservers.AppendElement(aObserver);
7940 AddMutationObserver(aObserver);
7943 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
7944 // If we're in the process of destroying the document (and we're
7945 // informing the observers of the destruction), don't remove the
7946 // observers from the list. This is not a big deal, since we
7947 // don't hold a live reference to the observers.
7948 if (!mInDestructor) {
7949 RemoveMutationObserver(aObserver);
7950 return mObservers.RemoveElement(aObserver);
7953 return mObservers.Contains(aObserver);
7956 void Document::BeginUpdate() {
7957 ++mUpdateNestLevel;
7958 nsContentUtils::AddScriptBlocker();
7959 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
7962 void Document::EndUpdate() {
7963 const bool reset = !mPendingMaybeEditingStateChanged;
7964 mPendingMaybeEditingStateChanged = true;
7966 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
7968 --mUpdateNestLevel;
7970 nsContentUtils::RemoveScriptBlocker();
7972 if (mXULBroadcastManager) {
7973 mXULBroadcastManager->MaybeBroadcast();
7976 if (reset) {
7977 mPendingMaybeEditingStateChanged = false;
7979 MaybeEditingStateChanged();
7982 void Document::BeginLoad() {
7983 if (IsEditingOn()) {
7984 // Reset() blows away all event listeners in the document, and our
7985 // editor relies heavily on those. Midas is turned on, to make it
7986 // work, re-initialize it to give it a chance to add its event
7987 // listeners again.
7989 TurnEditingOff();
7990 EditingStateChanged();
7993 MOZ_ASSERT(!mDidCallBeginLoad);
7994 mDidCallBeginLoad = true;
7996 // Block onload here to prevent having to deal with blocking and
7997 // unblocking it while we know the document is loading.
7998 BlockOnload();
7999 mDidFireDOMContentLoaded = false;
8000 BlockDOMContentLoaded();
8002 if (mScriptLoader) {
8003 mScriptLoader->BeginDeferringScripts();
8006 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
8009 void Document::MozSetImageElement(const nsAString& aImageElementId,
8010 Element* aElement) {
8011 if (aImageElementId.IsEmpty()) return;
8013 // Hold a script blocker while calling SetImageElement since that can call
8014 // out to id-observers
8015 nsAutoScriptBlocker scriptBlocker;
8017 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
8018 if (entry) {
8019 entry->SetImageElement(aElement);
8020 if (entry->IsEmpty()) {
8021 mIdentifierMap.RemoveEntry(entry);
8026 void Document::DispatchContentLoadedEvents() {
8027 // If you add early returns from this method, make sure you're
8028 // calling UnblockOnload properly.
8030 // Unpin references to preloaded images
8031 mPreloadingImages.Clear();
8033 // DOM manipulation after content loaded should not care if the element
8034 // came from the preloader.
8035 mPreloadedPreconnects.Clear();
8037 if (mTiming) {
8038 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
8041 // Dispatch observer notification to notify observers document is interactive.
8042 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
8043 if (os) {
8044 nsIPrincipal* principal = NodePrincipal();
8045 os->NotifyObservers(ToSupports(this),
8046 principal->IsSystemPrincipal()
8047 ? "chrome-document-interactive"
8048 : "content-document-interactive",
8049 nullptr);
8052 // Fire a DOM event notifying listeners that this document has been
8053 // loaded (excluding images and other loads initiated by this
8054 // document).
8055 nsContentUtils::DispatchTrustedEvent(this, this, u"DOMContentLoaded"_ns,
8056 CanBubble::eYes, Cancelable::eNo);
8058 if (auto* const window = GetInnerWindow()) {
8059 const RefPtr<ServiceWorkerContainer> serviceWorker =
8060 window->Navigator()->ServiceWorker();
8062 // This could cause queued messages from a service worker to get
8063 // dispatched on serviceWorker.
8064 serviceWorker->StartMessages();
8067 if (MayStartLayout()) {
8068 MaybeResolveReadyForIdle();
8071 nsIDocShell* docShell = GetDocShell();
8073 if (TimelineConsumers::HasConsumer(docShell)) {
8074 TimelineConsumers::AddMarkerForDocShell(
8075 docShell,
8076 MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
8079 if (mTiming) {
8080 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
8083 // If this document is a [i]frame, fire a DOMFrameContentLoaded
8084 // event on all parent documents notifying that the HTML (excluding
8085 // other external files such as images and stylesheets) in a frame
8086 // has finished loading.
8088 // target_frame is the [i]frame element that will be used as the
8089 // target for the event. It's the [i]frame whose content is done
8090 // loading.
8091 nsCOMPtr<Element> target_frame = GetEmbedderElement();
8093 if (target_frame && target_frame->IsInComposedDoc()) {
8094 nsCOMPtr<Document> parent = target_frame->OwnerDoc();
8095 while (parent) {
8096 RefPtr<Event> event;
8097 if (parent) {
8098 IgnoredErrorResult ignored;
8099 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
8102 if (event) {
8103 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
8105 event->SetTarget(target_frame);
8106 event->SetTrusted(true);
8108 // To dispatch this event we must manually call
8109 // EventDispatcher::Dispatch() on the ancestor document since the
8110 // target is not in the same document, so the event would never reach
8111 // the ancestor document if we used the normal event
8112 // dispatching code.
8114 WidgetEvent* innerEvent = event->WidgetEventPtr();
8115 if (innerEvent) {
8116 nsEventStatus status = nsEventStatus_eIgnore;
8118 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
8119 EventDispatcher::Dispatch(parent, context, innerEvent, event,
8120 &status);
8125 parent = parent->GetInProcessParentDocument();
8129 nsPIDOMWindowInner* inner = GetInnerWindow();
8130 if (inner) {
8131 inner->NoteDOMContentLoaded();
8134 // TODO
8135 if (mMaybeServiceWorkerControlled) {
8136 using mozilla::dom::ServiceWorkerManager;
8137 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
8138 if (swm) {
8139 Maybe<ClientInfo> clientInfo = GetClientInfo();
8140 if (clientInfo.isSome()) {
8141 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
8146 if (mSetCompleteAfterDOMContentLoaded) {
8147 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
8148 mSetCompleteAfterDOMContentLoaded = false;
8151 UnblockOnload(true);
8154 void Document::EndLoad() {
8155 bool turnOnEditing =
8156 mParser && (IsInDesignMode() || mContentEditableCount > 0);
8158 #if defined(DEBUG)
8159 // only assert if nothing stopped the load on purpose
8160 if (!mParserAborted) {
8161 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
8163 #endif
8165 // EndLoad may have been called without a matching call to BeginLoad, in the
8166 // case of a failed parse (for example, due to timeout). In such a case, we
8167 // still want to execute part of this code to do appropriate cleanup, but we
8168 // gate part of it because it is intended to match 1-for-1 with calls to
8169 // BeginLoad. We have an explicit flag bit for this purpose, since it's
8170 // complicated and error prone to derive this condition from other related
8171 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
8173 // Part 1: Code that always executes to cleanup end of parsing, whether
8174 // that parsing was successful or not.
8176 // Drop the ref to our parser, if any, but keep hold of the sink so that we
8177 // can flush it from FlushPendingNotifications as needed. We might have to
8178 // do that to get a StartLayout() to happen.
8179 if (mParser) {
8180 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
8181 mParser = nullptr;
8184 // Update the attributes on the PerformanceNavigationTiming before notifying
8185 // the onload observers.
8186 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
8187 if (RefPtr<Performance> performance = window->GetPerformance()) {
8188 performance->UpdateNavigationTimingEntry();
8192 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
8194 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
8196 if (!mDidCallBeginLoad) {
8197 return;
8199 mDidCallBeginLoad = false;
8201 UnblockDOMContentLoaded();
8203 if (turnOnEditing) {
8204 EditingStateChanged();
8207 if (!GetWindow()) {
8208 // This is a document that's not in a window. For example, this could be an
8209 // XMLHttpRequest responseXML document, or a document created via DOMParser
8210 // or DOMImplementation. We don't reach this code normally for such
8211 // documents (which is not obviously correct), but can reach it via
8212 // document.open()/document.close().
8214 // Such documents don't fire load events, but per spec should set their
8215 // readyState to "complete" when parsing and all loading of subresources is
8216 // done. Parsing is done now, and documents not in a window don't load
8217 // subresources, so just go ahead and mark ourselves as complete.
8218 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
8219 /* updateTimingInformation = */ false);
8221 // Reset mSkipLoadEventAfterClose just in case.
8222 mSkipLoadEventAfterClose = false;
8226 void Document::UnblockDOMContentLoaded() {
8227 MOZ_ASSERT(mBlockDOMContentLoaded);
8228 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
8229 return;
8232 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
8233 ("DOCUMENT %p UnblockDOMContentLoaded", this));
8235 mDidFireDOMContentLoaded = true;
8236 if (PresShell* presShell = GetPresShell()) {
8237 presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
8240 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
8241 if (!mSynchronousDOMContentLoaded) {
8242 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8243 nsCOMPtr<nsIRunnable> ev =
8244 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
8245 &Document::DispatchContentLoadedEvents);
8246 Dispatch(TaskCategory::Other, ev.forget());
8247 } else {
8248 DispatchContentLoadedEvents();
8252 void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) {
8253 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
8254 "Someone forgot a scriptblocker");
8255 NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged,
8256 (this, aElement, aStateMask));
8259 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
8260 StyleRuleChangeKind) {
8261 if (aSheet.IsApplicable()) {
8262 ApplicableStylesChanged();
8266 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
8267 if (aRule.IsIncompleteImportRule()) {
8268 return;
8271 if (aSheet.IsApplicable()) {
8272 ApplicableStylesChanged();
8276 void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
8277 if (aSheet.IsApplicable()) {
8278 ApplicableStylesChanged();
8282 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
8283 if (aSheet.IsApplicable()) {
8284 ApplicableStylesChanged();
8288 static Element* GetCustomContentContainer(PresShell* aPresShell) {
8289 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
8290 return nullptr;
8293 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
8296 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
8297 bool aForce, ErrorResult& aRv) {
8298 RefPtr<PresShell> shell = GetPresShell();
8299 if (aForce && !GetCustomContentContainer(shell)) {
8300 FlushPendingNotifications(FlushType::Layout);
8301 shell = GetPresShell();
8304 nsAutoScriptBlocker scriptBlocker;
8306 RefPtr<AnonymousContent> anonContent = AnonymousContent::Create(*this);
8307 if (!anonContent) {
8308 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
8309 return nullptr;
8312 mAnonymousContents.AppendElement(anonContent);
8314 if (RefPtr<Element> container = GetCustomContentContainer(shell)) {
8315 // If the container is empty and we have other anon content we should be
8316 // about to show all the other anonymous content nodes.
8317 if (container->HasChildren() || mAnonymousContents.Length() == 1) {
8318 container->AppendChildTo(anonContent->Host(), true, IgnoreErrors());
8319 if (auto* canvasFrame = shell->GetCanvasFrame()) {
8320 canvasFrame->ShowCustomContentContainer();
8325 return anonContent.forget();
8328 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
8329 PresShell* aPresShell) {
8330 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
8331 if (!container) {
8332 return;
8334 container->RemoveChild(*aAnonContent.Host(), IgnoreErrors());
8337 void Document::RemoveAnonymousContent(AnonymousContent& aContent) {
8338 nsAutoScriptBlocker scriptBlocker;
8340 auto index = mAnonymousContents.IndexOf(&aContent);
8341 if (index == mAnonymousContents.NoIndex) {
8342 return;
8345 mAnonymousContents.RemoveElementAt(index);
8346 RemoveAnonContentFromCanvas(aContent, GetPresShell());
8348 if (mAnonymousContents.IsEmpty() &&
8349 GetCustomContentContainer(GetPresShell())) {
8350 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
8354 Element* Document::GetAnonRootIfInAnonymousContentContainer(
8355 nsINode* aNode) const {
8356 if (!aNode->IsInNativeAnonymousSubtree()) {
8357 return nullptr;
8360 PresShell* presShell = GetPresShell();
8361 if (!presShell || !presShell->GetCanvasFrame()) {
8362 return nullptr;
8365 nsAutoScriptBlocker scriptBlocker;
8366 nsCOMPtr<Element> customContainer =
8367 presShell->GetCanvasFrame()->GetCustomContentContainer();
8368 if (!customContainer) {
8369 return nullptr;
8372 // An arbitrary number of elements can be inserted as children of the custom
8373 // container frame. We want the one that was added that contains aNode, so
8374 // we need to keep track of the last child separately using |child| here.
8375 nsINode* child = aNode;
8376 nsINode* parent = aNode->GetParentNode();
8377 while (parent && parent->IsInNativeAnonymousSubtree()) {
8378 if (parent == customContainer) {
8379 return Element::FromNode(child);
8381 child = parent;
8382 parent = child->GetParentNode();
8384 return nullptr;
8387 Maybe<ClientInfo> Document::GetClientInfo() const {
8388 if (const Document* orig = GetOriginalDocument()) {
8389 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
8390 return info;
8394 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8395 return inner->GetClientInfo();
8398 return Maybe<ClientInfo>();
8401 Maybe<ClientState> Document::GetClientState() const {
8402 if (const Document* orig = GetOriginalDocument()) {
8403 if (Maybe<ClientState> state = orig->GetClientState()) {
8404 return state;
8408 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8409 return inner->GetClientState();
8412 return Maybe<ClientState>();
8415 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
8416 if (const Document* orig = GetOriginalDocument()) {
8417 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
8418 return controller;
8422 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8423 return inner->GetController();
8426 return Maybe<ServiceWorkerDescriptor>();
8430 // Document interface
8432 DocumentType* Document::GetDoctype() const {
8433 for (nsIContent* child = GetFirstChild(); child;
8434 child = child->GetNextSibling()) {
8435 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
8436 return static_cast<DocumentType*>(child);
8439 return nullptr;
8442 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
8443 if (!mDOMImplementation) {
8444 nsCOMPtr<nsIURI> uri;
8445 NS_NewURI(getter_AddRefs(uri), "about:blank");
8446 if (!uri) {
8447 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
8448 return nullptr;
8450 bool hasHadScriptObject = true;
8451 nsIScriptGlobalObject* scriptObject =
8452 GetScriptHandlingObject(hasHadScriptObject);
8453 if (!scriptObject && hasHadScriptObject) {
8454 rv.Throw(NS_ERROR_UNEXPECTED);
8455 return nullptr;
8457 mDOMImplementation = new DOMImplementation(
8458 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
8461 return mDOMImplementation;
8464 bool IsLowercaseASCII(const nsAString& aValue) {
8465 int32_t len = aValue.Length();
8466 for (int32_t i = 0; i < len; ++i) {
8467 char16_t c = aValue[i];
8468 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
8469 return false;
8472 return true;
8475 already_AddRefed<Element> Document::CreateElement(
8476 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8477 ErrorResult& rv) {
8478 rv = nsContentUtils::CheckQName(aTagName, false);
8479 if (rv.Failed()) {
8480 return nullptr;
8483 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
8484 nsAutoString lcTagName;
8485 if (needsLowercase) {
8486 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
8489 const nsString* is = nullptr;
8490 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
8491 if (aOptions.IsElementCreationOptions()) {
8492 const ElementCreationOptions& options =
8493 aOptions.GetAsElementCreationOptions();
8495 if (options.mIs.WasPassed()) {
8496 is = &options.mIs.Value();
8499 // Check 'pseudo' and throw an exception if it's not one allowed
8500 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
8501 if (options.mPseudo.WasPassed()) {
8502 Maybe<PseudoStyleType> type =
8503 nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
8504 if (!type || *type == PseudoStyleType::NotPseudo ||
8505 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
8506 rv.ThrowNotSupportedError("Invalid pseudo-element");
8507 return nullptr;
8509 pseudoType = *type;
8513 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
8514 nullptr, mDefaultElementType, is);
8516 if (pseudoType != PseudoStyleType::NotPseudo) {
8517 elem->SetPseudoElementType(pseudoType);
8520 return elem.forget();
8523 already_AddRefed<Element> Document::CreateElementNS(
8524 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8525 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
8526 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8527 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8528 mNodeInfoManager, ELEMENT_NODE,
8529 getter_AddRefs(nodeInfo));
8530 if (rv.Failed()) {
8531 return nullptr;
8534 const nsString* is = nullptr;
8535 if (aOptions.IsElementCreationOptions()) {
8536 const ElementCreationOptions& options =
8537 aOptions.GetAsElementCreationOptions();
8538 if (options.mIs.WasPassed()) {
8539 is = &options.mIs.Value();
8543 nsCOMPtr<Element> element;
8544 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
8545 NOT_FROM_PARSER, is);
8546 if (rv.Failed()) {
8547 return nullptr;
8550 return element.forget();
8553 already_AddRefed<Element> Document::CreateXULElement(
8554 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8555 ErrorResult& aRv) {
8556 aRv = nsContentUtils::CheckQName(aTagName, false);
8557 if (aRv.Failed()) {
8558 return nullptr;
8561 const nsString* is = nullptr;
8562 if (aOptions.IsElementCreationOptions()) {
8563 const ElementCreationOptions& options =
8564 aOptions.GetAsElementCreationOptions();
8565 if (options.mIs.WasPassed()) {
8566 is = &options.mIs.Value();
8570 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
8571 if (!elem) {
8572 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
8573 return nullptr;
8575 return elem.forget();
8578 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
8579 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8580 return text.forget();
8583 already_AddRefed<nsTextNode> Document::CreateTextNode(
8584 const nsAString& aData) const {
8585 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8586 // Don't notify; this node is still being created.
8587 text->SetText(aData, false);
8588 return text.forget();
8591 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
8592 RefPtr<DocumentFragment> frag =
8593 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
8594 return frag.forget();
8597 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
8598 already_AddRefed<dom::Comment> Document::CreateComment(
8599 const nsAString& aData) const {
8600 RefPtr<dom::Comment> comment =
8601 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
8603 // Don't notify; this node is still being created.
8604 comment->SetText(aData, false);
8605 return comment.forget();
8608 already_AddRefed<CDATASection> Document::CreateCDATASection(
8609 const nsAString& aData, ErrorResult& rv) {
8610 if (IsHTMLDocument()) {
8611 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8612 return nullptr;
8615 if (FindInReadable(u"]]>"_ns, aData)) {
8616 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8617 return nullptr;
8620 RefPtr<CDATASection> cdata =
8621 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
8623 // Don't notify; this node is still being created.
8624 cdata->SetText(aData, false);
8626 return cdata.forget();
8629 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
8630 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
8631 nsresult res = nsContentUtils::CheckQName(aTarget, false);
8632 if (NS_FAILED(res)) {
8633 rv.Throw(res);
8634 return nullptr;
8637 if (FindInReadable(u"?>"_ns, aData)) {
8638 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8639 return nullptr;
8642 RefPtr<ProcessingInstruction> pi =
8643 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
8645 return pi.forget();
8648 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
8649 ErrorResult& rv) {
8650 if (!mNodeInfoManager) {
8651 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8652 return nullptr;
8655 nsresult res = nsContentUtils::CheckQName(aName, false);
8656 if (NS_FAILED(res)) {
8657 rv.Throw(res);
8658 return nullptr;
8661 nsAutoString name;
8662 if (IsHTMLDocument()) {
8663 nsContentUtils::ASCIIToLower(aName, name);
8664 } else {
8665 name = aName;
8668 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8669 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
8670 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
8671 if (NS_FAILED(res)) {
8672 rv.Throw(res);
8673 return nullptr;
8676 RefPtr<Attr> attribute =
8677 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8678 return attribute.forget();
8681 already_AddRefed<Attr> Document::CreateAttributeNS(
8682 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8683 ErrorResult& rv) {
8684 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8685 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8686 mNodeInfoManager, ATTRIBUTE_NODE,
8687 getter_AddRefs(nodeInfo));
8688 if (rv.Failed()) {
8689 return nullptr;
8692 RefPtr<Attr> attribute =
8693 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8694 return attribute.forget();
8697 void Document::ScheduleForPresAttrEvaluation(Element* aElement) {
8698 MOZ_ASSERT(aElement->IsInComposedDoc());
8699 DebugOnly<bool> inserted = mLazyPresElements.EnsureInserted(aElement);
8700 MOZ_ASSERT(inserted);
8701 if (aElement->HasServoData()) {
8702 // TODO(emilio): RESTYLE_SELF is too strong, there should be no need to
8703 // re-selector-match, but right now this is needed to pick up the new mapped
8704 // attributes. We need something like RESTYLE_STYLE_ATTRIBUTE but for mapped
8705 // attributes.
8706 nsLayoutUtils::PostRestyleEvent(aElement, RestyleHint::RESTYLE_SELF,
8707 nsChangeHint(0));
8711 void Document::UnscheduleForPresAttrEvaluation(Element* aElement) {
8712 mLazyPresElements.Remove(aElement);
8715 void Document::DoResolveScheduledPresAttrs() {
8716 MOZ_ASSERT(!mLazyPresElements.IsEmpty());
8717 for (Element* el : mLazyPresElements) {
8718 MOZ_ASSERT(el->IsInComposedDoc(),
8719 "Un-schedule when removing from the document");
8720 MOZ_ASSERT(el->IsPendingMappedAttributeEvaluation());
8721 if (auto* svg = SVGElement::FromNode(el)) {
8722 // SVG does its own (very similar) thing, for now at least.
8723 svg->UpdateMappedDeclarationBlock();
8724 } else {
8725 MappedDeclarationsBuilder builder(*el, *this,
8726 el->GetMappedAttributeStyle());
8727 auto function = el->GetAttributeMappingFunction();
8728 function(builder);
8729 el->SetMappedDeclarationBlock(builder.TakeDeclarationBlock());
8731 MOZ_ASSERT(!el->IsPendingMappedAttributeEvaluation());
8733 mLazyPresElements.Clear();
8736 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
8737 const {
8738 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
8740 const nsTArray<nsWeakPtr> blockedNodes = mBlockedNodesByClassifier.Clone();
8742 for (unsigned long i = 0; i < blockedNodes.Length(); i++) {
8743 nsWeakPtr weakNode = blockedNodes[i];
8744 nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode);
8745 // Consider only nodes to which we have managed to get strong references.
8746 // Coping with nullptrs since it's expected for nodes to disappear when
8747 // nobody else is referring to them.
8748 if (node) {
8749 list->AppendElement(node);
8753 return list.forget();
8756 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
8757 aSheetSet.Truncate();
8759 // Look through our sheets, find the selected set title
8760 size_t count = SheetCount();
8761 nsAutoString title;
8762 for (size_t index = 0; index < count; index++) {
8763 StyleSheet* sheet = SheetAt(index);
8764 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8766 if (sheet->Disabled()) {
8767 // Disabled sheets don't affect the currently selected set
8768 continue;
8771 sheet->GetTitle(title);
8773 if (aSheetSet.IsEmpty()) {
8774 aSheetSet = title;
8775 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
8776 // Sheets from multiple sets enabled; return null string, per spec.
8777 SetDOMStringToNull(aSheetSet);
8778 return;
8783 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
8784 if (DOMStringIsNull(aSheetSet)) {
8785 return;
8788 // Must update mLastStyleSheetSet before doing anything else with stylesheets
8789 // or CSSLoaders.
8790 mLastStyleSheetSet = aSheetSet;
8791 EnableStyleSheetsForSetInternal(aSheetSet, true);
8794 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
8795 mPreferredStyleSheetSet = aSheetSet;
8796 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
8797 // spec.
8798 if (DOMStringIsNull(mLastStyleSheetSet)) {
8799 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
8800 // per spec. The idea here is that we're changing our preferred set and
8801 // that shouldn't change the value of lastStyleSheetSet. Also, we're
8802 // using the Internal version so we can update the CSSLoader and not have
8803 // to worry about null strings.
8804 EnableStyleSheetsForSetInternal(aSheetSet, true);
8808 DOMStringList* Document::StyleSheetSets() {
8809 if (!mStyleSheetSetList) {
8810 mStyleSheetSetList = new DOMStyleSheetSetList(this);
8812 return mStyleSheetSetList;
8815 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
8816 // Per spec, passing in null is a no-op.
8817 if (!DOMStringIsNull(aSheetSet)) {
8818 // Note: must make sure to not change the CSSLoader's preferred sheet --
8819 // that value should be equal to either our lastStyleSheetSet (if that's
8820 // non-null) or to our preferredStyleSheetSet. And this method doesn't
8821 // change either of those.
8822 EnableStyleSheetsForSetInternal(aSheetSet, false);
8826 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
8827 bool aUpdateCSSLoader) {
8828 size_t count = SheetCount();
8829 nsAutoString title;
8830 for (size_t index = 0; index < count; index++) {
8831 StyleSheet* sheet = SheetAt(index);
8832 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8834 sheet->GetTitle(title);
8835 if (!title.IsEmpty()) {
8836 sheet->SetEnabled(title.Equals(aSheetSet));
8839 if (aUpdateCSSLoader) {
8840 CSSLoader()->DocumentStyleSheetSetChanged();
8842 if (mStyleSet->StyleSheetsHaveChanged()) {
8843 ApplicableStylesChanged();
8847 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
8848 nsAutoCString charset;
8849 GetDocumentCharacterSet()->Name(charset);
8850 CopyASCIItoUTF16(charset, aCharacterSet);
8853 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
8854 ErrorResult& rv) const {
8855 nsINode* imported = &aNode;
8857 switch (imported->NodeType()) {
8858 case DOCUMENT_NODE: {
8859 break;
8861 case DOCUMENT_FRAGMENT_NODE:
8862 case ATTRIBUTE_NODE:
8863 case ELEMENT_NODE:
8864 case PROCESSING_INSTRUCTION_NODE:
8865 case TEXT_NODE:
8866 case CDATA_SECTION_NODE:
8867 case COMMENT_NODE:
8868 case DOCUMENT_TYPE_NODE: {
8869 return imported->Clone(aDeep, mNodeInfoManager, rv);
8871 default: {
8872 NS_WARNING("Don't know how to clone this nodetype for importNode.");
8876 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8877 return nullptr;
8880 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
8881 return nsRange::Create(this, 0, this, 0, rv);
8884 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
8885 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
8886 ErrorResult& rv) const {
8887 RefPtr<NodeIterator> iterator =
8888 new NodeIterator(&aRoot, aWhatToShow, aFilter);
8889 return iterator.forget();
8892 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
8893 uint32_t aWhatToShow,
8894 NodeFilter* aFilter,
8895 ErrorResult& rv) const {
8896 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
8897 return walker.forget();
8900 already_AddRefed<Location> Document::GetLocation() const {
8901 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
8903 if (!w) {
8904 return nullptr;
8907 return do_AddRef(w->Location());
8910 already_AddRefed<nsIURI> Document::GetDomainURI() {
8911 nsIPrincipal* principal = NodePrincipal();
8913 nsCOMPtr<nsIURI> uri;
8914 principal->GetDomain(getter_AddRefs(uri));
8915 if (uri) {
8916 return uri.forget();
8918 auto* basePrin = BasePrincipal::Cast(principal);
8919 basePrin->GetURI(getter_AddRefs(uri));
8920 return uri.forget();
8923 void Document::GetDomain(nsAString& aDomain) {
8924 nsCOMPtr<nsIURI> uri = GetDomainURI();
8926 if (!uri) {
8927 aDomain.Truncate();
8928 return;
8931 nsAutoCString hostName;
8932 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
8933 if (NS_SUCCEEDED(rv)) {
8934 CopyUTF8toUTF16(hostName, aDomain);
8935 } else {
8936 // If we can't get the host from the URI (e.g. about:, javascript:,
8937 // etc), just return an empty string.
8938 aDomain.Truncate();
8942 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
8943 if (!GetBrowsingContext()) {
8944 // If our browsing context is null; disallow setting domain
8945 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8946 return;
8949 if (mSandboxFlags & SANDBOXED_DOMAIN) {
8950 // We're sandboxed; disallow setting domain
8951 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8952 return;
8955 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
8956 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8957 return;
8960 if (aDomain.IsEmpty()) {
8961 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8962 return;
8965 nsCOMPtr<nsIURI> uri = GetDomainURI();
8966 if (!uri) {
8967 rv.Throw(NS_ERROR_FAILURE);
8968 return;
8971 // Check new domain - must be a superdomain of the current host
8972 // For example, a page from foo.bar.com may set domain to bar.com,
8973 // but not to ar.com, baz.com, or fi.foo.bar.com.
8975 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
8976 if (!newURI) {
8977 // Error: illegal domain
8978 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8979 return;
8982 if (GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated()) {
8983 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
8984 return;
8987 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
8988 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
8989 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
8990 wgc->SendSetDocumentDomain(newURI);
8994 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
8995 const nsACString& aHostString) {
8996 if (aHostString.IsEmpty()) {
8997 return nullptr;
9000 // Create new URI
9001 nsCOMPtr<nsIURI> uri = GetDomainURI();
9002 if (!uri) {
9003 return nullptr;
9006 nsresult rv;
9007 rv = NS_MutateURI(uri)
9008 .SetUserPass(""_ns)
9009 .SetPort(-1) // we want to reset the port number if needed.
9010 .SetHostPort(aHostString)
9011 .Finalize(uri);
9012 if (NS_FAILED(rv)) {
9013 return nullptr;
9016 return uri.forget();
9019 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
9020 const nsAString& aNewDomain, nsIURI* aOrigHost) {
9021 if (NS_WARN_IF(!aOrigHost)) {
9022 return nullptr;
9025 nsCOMPtr<nsIURI> newURI =
9026 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
9027 if (!newURI) {
9028 // Error: failed to parse input domain
9029 return nullptr;
9032 if (!IsValidDomain(aOrigHost, newURI)) {
9033 // Error: illegal domain
9034 return nullptr;
9037 nsAutoCString domain;
9038 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
9039 return nullptr;
9042 return CreateInheritingURIForHost(domain);
9045 /* static */
9046 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
9047 // Check new domain - must be a superdomain of the current host
9048 // For example, a page from foo.bar.com may set domain to bar.com,
9049 // but not to ar.com, baz.com, or fi.foo.bar.com.
9050 nsAutoCString current;
9051 nsAutoCString domain;
9052 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
9053 current.Truncate();
9055 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
9056 domain.Truncate();
9059 bool ok = current.Equals(domain);
9060 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
9061 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
9062 // We're golden if the new domain is the current page's base domain or a
9063 // subdomain of it.
9064 nsCOMPtr<nsIEffectiveTLDService> tldService =
9065 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
9066 if (!tldService) {
9067 return false;
9070 nsAutoCString currentBaseDomain;
9071 ok = NS_SUCCEEDED(
9072 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
9073 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
9074 (domain.Length() >= currentBaseDomain.Length()),
9075 "uh-oh! slight optimization wasn't valid somehow!");
9076 ok = ok && domain.Length() >= currentBaseDomain.Length();
9079 return ok;
9082 Element* Document::GetHtmlElement() const {
9083 Element* rootElement = GetRootElement();
9084 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
9085 return rootElement;
9086 return nullptr;
9089 Element* Document::GetHtmlChildElement(nsAtom* aTag) {
9090 Element* html = GetHtmlElement();
9091 if (!html) return nullptr;
9093 // Look for the element with aTag inside html. This needs to run
9094 // forwards to find the first such element.
9095 for (nsIContent* child = html->GetFirstChild(); child;
9096 child = child->GetNextSibling()) {
9097 if (child->IsHTMLElement(aTag)) return child->AsElement();
9099 return nullptr;
9102 nsGenericHTMLElement* Document::GetBody() {
9103 Element* html = GetHtmlElement();
9104 if (!html) {
9105 return nullptr;
9108 for (nsIContent* child = html->GetFirstChild(); child;
9109 child = child->GetNextSibling()) {
9110 if (child->IsHTMLElement(nsGkAtoms::body) ||
9111 child->IsHTMLElement(nsGkAtoms::frameset)) {
9112 return static_cast<nsGenericHTMLElement*>(child);
9116 return nullptr;
9119 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
9120 nsCOMPtr<Element> root = GetRootElement();
9122 // The body element must be either a body tag or a frameset tag. And we must
9123 // have a root element to be able to add kids to it.
9124 if (!newBody ||
9125 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
9126 rv.ThrowHierarchyRequestError(
9127 "The new body must be either a body tag or frameset tag.");
9128 return;
9131 if (!root) {
9132 rv.ThrowHierarchyRequestError("No root element.");
9133 return;
9136 // Use DOM methods so that we pass through the appropriate security checks.
9137 nsCOMPtr<Element> currentBody = GetBody();
9138 if (currentBody) {
9139 root->ReplaceChild(*newBody, *currentBody, rv);
9140 } else {
9141 root->AppendChild(*newBody, rv);
9145 HTMLSharedElement* Document::GetHead() {
9146 return static_cast<HTMLSharedElement*>(GetHeadElement());
9149 Element* Document::GetTitleElement() {
9150 // mMayHaveTitleElement will have been set to true if any HTML or SVG
9151 // <title> element has been bound to this document. So if it's false,
9152 // we know there is nothing to do here. This avoids us having to search
9153 // the whole DOM if someone calls document.title on a large document
9154 // without a title.
9155 if (!mMayHaveTitleElement) {
9156 return nullptr;
9159 Element* root = GetRootElement();
9160 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
9161 // In SVG, the document's title must be a child
9162 for (nsIContent* child = root->GetFirstChild(); child;
9163 child = child->GetNextSibling()) {
9164 if (child->IsSVGElement(nsGkAtoms::title)) {
9165 return child->AsElement();
9168 return nullptr;
9171 // We check the HTML namespace even for non-HTML documents, except SVG. This
9172 // matches the spec and the behavior of all tested browsers.
9173 for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
9174 if (node->IsHTMLElement(nsGkAtoms::title)) {
9175 return node->AsElement();
9178 return nullptr;
9181 void Document::GetTitle(nsAString& aTitle) {
9182 aTitle.Truncate();
9184 Element* rootElement = GetRootElement();
9185 if (!rootElement) {
9186 return;
9189 if (rootElement->IsXULElement()) {
9190 rootElement->GetAttr(nsGkAtoms::title, aTitle);
9191 } else if (Element* title = GetTitleElement()) {
9192 nsContentUtils::GetNodeTextContent(title, false, aTitle);
9193 } else {
9194 return;
9197 aTitle.CompressWhitespace();
9200 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
9201 Element* rootElement = GetRootElement();
9202 if (!rootElement) {
9203 return;
9206 if (rootElement->IsXULElement()) {
9207 aRv =
9208 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
9209 return;
9212 Maybe<mozAutoDocUpdate> updateBatch;
9213 nsCOMPtr<Element> title = GetTitleElement();
9214 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
9215 if (!title) {
9216 // Batch updates so that mutation events don't change "the title
9217 // element" under us
9218 updateBatch.emplace(this, true);
9219 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
9220 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
9221 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
9222 NOT_FROM_PARSER);
9223 if (!title) {
9224 return;
9226 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
9227 IgnoreErrors());
9229 } else if (rootElement->IsHTMLElement()) {
9230 if (!title) {
9231 // Batch updates so that mutation events don't change "the title
9232 // element" under us
9233 updateBatch.emplace(this, true);
9234 Element* head = GetHeadElement();
9235 if (!head) {
9236 return;
9239 RefPtr<mozilla::dom::NodeInfo> titleInfo;
9240 titleInfo = mNodeInfoManager->GetNodeInfo(
9241 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
9242 title = NS_NewHTMLTitleElement(titleInfo.forget());
9243 if (!title) {
9244 return;
9247 head->AppendChildTo(title, true, IgnoreErrors());
9249 } else {
9250 return;
9253 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
9256 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
9257 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
9258 "Setting a title while unlinking or destroying the element?");
9259 if (mInUnlinkOrDeletion) {
9260 return;
9263 if (aBoundTitleElement) {
9264 mMayHaveTitleElement = true;
9266 if (mPendingTitleChangeEvent.IsPending()) {
9267 return;
9270 MOZ_RELEASE_ASSERT(NS_IsMainThread());
9271 RefPtr<nsRunnableMethod<Document, void, false>> event =
9272 NewNonOwningRunnableMethod("Document::DoNotifyPossibleTitleChange", this,
9273 &Document::DoNotifyPossibleTitleChange);
9274 if (NS_WARN_IF(NS_FAILED(Dispatch(TaskCategory::Other, do_AddRef(event))))) {
9275 return;
9277 mPendingTitleChangeEvent = std::move(event);
9280 void Document::DoNotifyPossibleTitleChange() {
9281 if (!mPendingTitleChangeEvent.IsPending()) {
9282 return;
9284 // Make sure the pending runnable method is cleared.
9285 mPendingTitleChangeEvent.Revoke();
9286 mHaveFiredTitleChange = true;
9288 nsAutoString title;
9289 GetTitle(title);
9291 if (RefPtr<PresShell> presShell = GetPresShell()) {
9292 nsCOMPtr<nsISupports> container =
9293 presShell->GetPresContext()->GetContainerWeak();
9294 if (container) {
9295 if (nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container)) {
9296 docShellWin->SetTitle(title);
9301 if (WindowGlobalChild* child = GetWindowGlobalChild()) {
9302 child->SendUpdateDocumentTitle(title);
9305 // Fire a DOM event for the title change.
9306 nsContentUtils::DispatchChromeEvent(this, this, u"DOMTitleChanged"_ns,
9307 CanBubble::eYes, Cancelable::eYes);
9309 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
9310 if (obs) {
9311 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
9315 already_AddRefed<MediaQueryList> Document::MatchMedia(
9316 const nsACString& aMediaQueryList, CallerType aCallerType) {
9317 RefPtr<MediaQueryList> result =
9318 new MediaQueryList(this, aMediaQueryList, aCallerType);
9320 mDOMMediaQueryLists.insertBack(result);
9322 return result.forget();
9325 void Document::SetMayStartLayout(bool aMayStartLayout) {
9326 mMayStartLayout = aMayStartLayout;
9327 if (MayStartLayout()) {
9328 // Before starting layout, check whether we're a toplevel chrome
9329 // window. If we are, setup some state so that we don't have to restyle
9330 // the whole tree after StartLayout.
9331 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
9332 // We're the chrome document!
9333 win->BeforeStartLayout();
9335 ReadyState state = GetReadyStateEnum();
9336 if (state >= READYSTATE_INTERACTIVE) {
9337 // DOMContentLoaded has fired already.
9338 MaybeResolveReadyForIdle();
9342 MaybeEditingStateChanged();
9345 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
9346 mInitializableFrameLoaders.RemoveElement(aLoader);
9347 // Don't even try to initialize.
9348 if (mInDestructor) {
9349 NS_WARNING(
9350 "Trying to initialize a frame loader while"
9351 "document is being deleted");
9352 return NS_ERROR_FAILURE;
9355 mInitializableFrameLoaders.AppendElement(aLoader);
9356 if (!mFrameLoaderRunner) {
9357 mFrameLoaderRunner =
9358 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9359 &Document::MaybeInitializeFinalizeFrameLoaders);
9360 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9361 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9363 return NS_OK;
9366 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
9367 nsIRunnable* aFinalizer) {
9368 mInitializableFrameLoaders.RemoveElement(aLoader);
9369 if (mInDestructor) {
9370 return NS_ERROR_FAILURE;
9373 LogRunnable::LogDispatch(aFinalizer);
9374 mFrameLoaderFinalizers.AppendElement(aFinalizer);
9375 if (!mFrameLoaderRunner) {
9376 mFrameLoaderRunner =
9377 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9378 &Document::MaybeInitializeFinalizeFrameLoaders);
9379 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9380 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9382 return NS_OK;
9385 void Document::MaybeInitializeFinalizeFrameLoaders() {
9386 if (mDelayFrameLoaderInitialization) {
9387 // This method will be recalled when !mDelayFrameLoaderInitialization.
9388 mFrameLoaderRunner = nullptr;
9389 return;
9392 // We're not in an update, but it is not safe to run scripts, so
9393 // postpone frameloader initialization and finalization.
9394 if (!nsContentUtils::IsSafeToRunScript()) {
9395 if (!mInDestructor && !mFrameLoaderRunner &&
9396 (mInitializableFrameLoaders.Length() ||
9397 mFrameLoaderFinalizers.Length())) {
9398 mFrameLoaderRunner = NewRunnableMethod(
9399 "Document::MaybeInitializeFinalizeFrameLoaders", this,
9400 &Document::MaybeInitializeFinalizeFrameLoaders);
9401 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9403 return;
9405 mFrameLoaderRunner = nullptr;
9407 // Don't use a temporary array for mInitializableFrameLoaders, because
9408 // loading a frame may cause some other frameloader to be removed from the
9409 // array. But be careful to keep the loader alive when starting the load!
9410 while (mInitializableFrameLoaders.Length()) {
9411 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
9412 mInitializableFrameLoaders.RemoveElementAt(0);
9413 NS_ASSERTION(loader, "null frameloader in the array?");
9414 loader->ReallyStartLoading();
9417 uint32_t length = mFrameLoaderFinalizers.Length();
9418 if (length > 0) {
9419 nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
9420 std::move(mFrameLoaderFinalizers);
9421 for (uint32_t i = 0; i < length; ++i) {
9422 LogRunnable::Run run(finalizers[i]);
9423 finalizers[i]->Run();
9428 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
9429 uint32_t length = mInitializableFrameLoaders.Length();
9430 for (uint32_t i = 0; i < length; ++i) {
9431 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
9432 mInitializableFrameLoaders.RemoveElementAt(i);
9433 return;
9438 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
9439 mPrototypeDocument = aPrototype;
9440 mSynchronousDOMContentLoaded = true;
9443 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
9444 return GetPermissionDelegateHandler();
9447 Document* Document::RequestExternalResource(
9448 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
9449 ExternalResourceLoad** aPendingLoad) {
9450 MOZ_ASSERT(aURI, "Must have a URI");
9451 MOZ_ASSERT(aRequestingNode, "Must have a node");
9452 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
9453 if (mDisplayDocument) {
9454 return mDisplayDocument->RequestExternalResource(
9455 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
9458 return mExternalResourceMap.RequestResource(
9459 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
9462 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
9463 mExternalResourceMap.EnumerateResources(aCallback);
9466 SMILAnimationController* Document::GetAnimationController() {
9467 // We create the animation controller lazily because most documents won't want
9468 // one and only SVG documents and the like will call this
9469 if (mAnimationController) return mAnimationController;
9470 // Refuse to create an Animation Controller for data documents.
9471 if (mLoadedAsData) return nullptr;
9473 mAnimationController = new SMILAnimationController(this);
9475 // If there's a presContext then check the animation mode and pause if
9476 // necessary.
9477 nsPresContext* context = GetPresContext();
9478 if (mAnimationController && context &&
9479 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
9480 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
9483 // If we're hidden (or being hidden), notify the newly-created animation
9484 // controller. (Skip this check for SVG-as-an-image documents, though,
9485 // because they don't get OnPageShow / OnPageHide calls).
9486 if (!mIsShowing && !mIsBeingUsedAsImage) {
9487 mAnimationController->OnPageHide();
9490 return mAnimationController;
9493 PendingAnimationTracker* Document::GetOrCreatePendingAnimationTracker() {
9494 if (!mPendingAnimationTracker) {
9495 mPendingAnimationTracker = new PendingAnimationTracker(this);
9498 return mPendingAnimationTracker;
9501 ScrollTimelineAnimationTracker*
9502 Document::GetOrCreateScrollTimelineAnimationTracker() {
9503 if (!mScrollTimelineAnimationTracker) {
9504 mScrollTimelineAnimationTracker = new ScrollTimelineAnimationTracker(this);
9507 return mScrollTimelineAnimationTracker;
9511 * Retrieve the "direction" property of the document.
9513 * @lina 01/09/2001
9515 void Document::GetDir(nsAString& aDirection) const {
9516 aDirection.Truncate();
9517 Element* rootElement = GetHtmlElement();
9518 if (rootElement) {
9519 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
9524 * Set the "direction" property of the document.
9526 * @lina 01/09/2001
9528 void Document::SetDir(const nsAString& aDirection) {
9529 Element* rootElement = GetHtmlElement();
9530 if (rootElement) {
9531 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
9535 nsIHTMLCollection* Document::Images() {
9536 if (!mImages) {
9537 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
9538 nsGkAtoms::img);
9540 return mImages;
9543 nsIHTMLCollection* Document::Embeds() {
9544 if (!mEmbeds) {
9545 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
9546 nsGkAtoms::embed);
9548 return mEmbeds;
9551 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9552 void* aData) {
9553 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
9554 aElement->HasAttr(nsGkAtoms::href);
9557 nsIHTMLCollection* Document::Links() {
9558 if (!mLinks) {
9559 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
9561 return mLinks;
9564 nsIHTMLCollection* Document::Forms() {
9565 if (!mForms) {
9566 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
9567 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
9568 nsGkAtoms::form);
9571 return mForms;
9574 nsIHTMLCollection* Document::Scripts() {
9575 if (!mScripts) {
9576 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
9577 nsGkAtoms::script);
9579 return mScripts;
9582 nsIHTMLCollection* Document::Applets() {
9583 if (!mApplets) {
9584 mApplets = new nsEmptyContentList(this);
9586 return mApplets;
9589 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9590 void* aData) {
9591 return aElement->IsHTMLElement(nsGkAtoms::a) &&
9592 aElement->HasAttr(nsGkAtoms::name);
9595 nsIHTMLCollection* Document::Anchors() {
9596 if (!mAnchors) {
9597 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
9599 return mAnchors;
9602 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
9603 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
9604 ErrorResult& rv) {
9605 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9606 "XOW should have caught this!");
9608 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
9609 if (!window) {
9610 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
9611 return nullptr;
9613 nsCOMPtr<nsPIDOMWindowOuter> outer =
9614 nsPIDOMWindowOuter::GetFromCurrentInner(window);
9615 if (!outer) {
9616 rv.Throw(NS_ERROR_NOT_INITIALIZED);
9617 return nullptr;
9619 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
9620 RefPtr<BrowsingContext> newBC;
9621 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
9622 if (!newBC) {
9623 return nullptr;
9625 return WindowProxyHolder(std::move(newBC));
9628 Document* Document::Open(const Optional<nsAString>& /* unused */,
9629 const Optional<nsAString>& /* unused */,
9630 ErrorResult& aError) {
9631 // Implements
9632 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
9634 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9635 "XOW should have caught this!");
9637 // Step 1 -- throw if we're an XML document.
9638 if (!IsHTMLDocument() || mDisableDocWrite) {
9639 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9640 return nullptr;
9643 // Step 2 -- throw if dynamic markup insertion should throw.
9644 if (ShouldThrowOnDynamicMarkupInsertion()) {
9645 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9646 return nullptr;
9649 // Step 3 -- get the entry document, so we can use it for security checks.
9650 nsCOMPtr<Document> callerDoc = GetEntryDocument();
9651 if (!callerDoc) {
9652 // If we're called from C++ or in some other way without an originating
9653 // document we can't do a document.open w/o changing the principal of the
9654 // document to something like about:blank (as that's the only sane thing to
9655 // do when we don't know the origin of this call), and since we can't
9656 // change the principals of a document for security reasons we'll have to
9657 // refuse to go ahead with this call.
9659 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9660 return nullptr;
9663 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
9664 // the entry document.
9665 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
9666 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9667 return nullptr;
9670 // Step 5 -- if we have an active parser with a nonzero script nesting level,
9671 // just no-op.
9672 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
9673 return this;
9676 // Step 6 -- check for open() during unload. Per spec, this is just a check
9677 // of the ignore-opens-during-unload counter, but our unload event code
9678 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
9679 // do), so we check for unload directly.
9680 if (ShouldIgnoreOpens()) {
9681 return this;
9684 RefPtr<nsDocShell> shell(mDocumentContainer);
9685 if (shell) {
9686 bool inUnload;
9687 shell->GetIsInUnload(&inUnload);
9688 if (inUnload) {
9689 return this;
9693 // document.open() inherits the CSP from the opening document.
9694 // Please create an actual copy of the CSP (do not share the same
9695 // reference) otherwise appending a new policy within the opened
9696 // document will be incorrectly propagated to the opening doc.
9697 nsCOMPtr<nsIContentSecurityPolicy> csp = callerDoc->GetCsp();
9698 if (csp) {
9699 RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
9700 cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
9701 mCSP = cspToInherit;
9704 // At this point we know this is a valid-enough document.open() call
9705 // and not a no-op. Increment our use counter.
9706 SetUseCounter(eUseCounter_custom_DocumentOpen);
9708 // Step 7 -- stop existing navigation of our browsing context (and all other
9709 // loads it's doing) if we're the active document of our browsing context.
9710 // Note that we do not want to stop anything if there is no existing
9711 // navigation.
9712 if (shell && IsCurrentActiveDocument() &&
9713 shell->GetIsAttemptingToNavigate()) {
9714 shell->Stop(nsIWebNavigation::STOP_NETWORK);
9716 // The Stop call may have cancelled the onload blocker request or
9717 // prevented it from getting added, so we need to make sure it gets added
9718 // to the document again otherwise the document could have a non-zero
9719 // onload block count without the onload blocker request being in the
9720 // loadgroup.
9721 EnsureOnloadBlocker();
9724 // Step 8 -- clear event listeners out of our DOM tree
9725 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
9726 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
9727 elm->RemoveAllListeners();
9731 // Step 9 -- clear event listeners from our window, if we have one.
9733 // Note that we explicitly want the inner window, and only if we're its
9734 // document. We want to do this (per spec) even when we're not the "active
9735 // document", so we can't go through GetWindow(), because it might forward to
9736 // the wrong inner.
9737 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
9738 if (win->GetExtantDoc() == this) {
9739 if (EventListenerManager* elm =
9740 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
9741 elm->RemoveAllListeners();
9746 // If we have a parser that has a zero script nesting level, we need to
9747 // properly terminate it. We do that after we've removed all the event
9748 // listeners (so termination won't trigger event listeners if it does
9749 // something to the DOM), but before we remove all elements from the document
9750 // (so if termination does modify the DOM in some way we will just blow it
9751 // away immediately. See the similar code in WriteCommon that handles the
9752 // !IsInsertionPointDefined() case and should stay in sync with this code.
9753 if (mParser) {
9754 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
9755 "Why didn't we take the early return?");
9756 // Make sure we don't re-enter.
9757 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9758 mParser->Terminate();
9759 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9762 // Step 10 -- remove all our DOM kids without firing any mutation events.
9764 // We want to ignore any recursive calls to Open() that happen while
9765 // disconnecting the node tree. The spec doesn't say to do this, but the
9766 // spec also doesn't envision unload events on subframes firing while we do
9767 // this, while all browsers fire them in practice. See
9768 // <https://github.com/whatwg/html/issues/4611>.
9769 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9770 DisconnectNodeTree();
9773 // Step 11 -- if we're the current document in our docshell, do the
9774 // equivalent of pushState() with the new URL we should have.
9775 if (shell && IsCurrentActiveDocument()) {
9776 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
9777 if (callerDoc != this) {
9778 nsCOMPtr<nsIURI> noFragmentURI;
9779 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
9780 if (NS_WARN_IF(NS_FAILED(rv))) {
9781 aError.Throw(rv);
9782 return nullptr;
9784 newURI = std::move(noFragmentURI);
9787 // UpdateURLAndHistory might do various member-setting, so make sure we're
9788 // holding strong refs to all the refcounted args on the stack. We can
9789 // assume that our caller is holding on to "this" already.
9790 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
9791 bool equalURIs;
9792 nsresult rv = currentURI->Equals(newURI, &equalURIs);
9793 if (NS_WARN_IF(NS_FAILED(rv))) {
9794 aError.Throw(rv);
9795 return nullptr;
9797 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
9798 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
9799 /* aReplace = */ true, currentURI,
9800 equalURIs);
9801 if (NS_WARN_IF(NS_FAILED(rv))) {
9802 aError.Throw(rv);
9803 return nullptr;
9806 // And use the security info of the caller document as well, since
9807 // it's the thing providing our data.
9808 mSecurityInfo = callerDoc->GetSecurityInfo();
9810 // This is not mentioned in the spec, but I think that's a spec bug. See
9811 // <https://github.com/whatwg/html/issues/4299>. In any case, since our
9812 // URL may be changing away from about:blank here, we really want to unset
9813 // this flag no matter what, since only about:blank can be an initial
9814 // document.
9815 SetIsInitialDocument(false);
9817 // And let our docloader know that it will need to track our load event.
9818 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
9821 // Per spec nothing happens with our URI in other cases, though note
9822 // <https://github.com/whatwg/html/issues/4286>.
9824 // Note that we don't need to do anything here with base URIs per spec.
9825 // That said, this might be assuming that we implement
9826 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
9827 // correctly, which we don't right now for the about:blank case.
9829 // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
9830 mSkipLoadEventAfterClose = mLoadEventFiring;
9832 // Preliminary to steps 13-16. Set our ready state to uninitialized before
9833 // we do anything else, so we can then proceed to later ready state levels.
9834 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
9835 /* updateTimingInformation = */ false);
9836 // Reset a flag that affects readyState behavior.
9837 mSetCompleteAfterDOMContentLoaded = false;
9839 // Step 13 -- set our compat mode to standards.
9840 SetCompatibilityMode(eCompatibility_FullStandards);
9842 // Step 14 -- create a new parser associated with document. This also does
9843 // step 16 implicitly.
9844 mParserAborted = false;
9845 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
9846 mParser = parser;
9847 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
9848 nsresult rv = parser->StartExecutor();
9849 if (NS_WARN_IF(NS_FAILED(rv))) {
9850 aError.Throw(rv);
9851 return nullptr;
9854 // Clear out our form control state, because the state of controls
9855 // in the pre-open() document should not affect the state of
9856 // controls that are now going to be written.
9857 mLayoutHistoryState = nullptr;
9859 if (shell) {
9860 // Prepare the docshell and the document viewer for the impending
9861 // out-of-band document.write()
9862 shell->PrepareForNewContentModel();
9864 nsCOMPtr<nsIContentViewer> cv;
9865 shell->GetContentViewer(getter_AddRefs(cv));
9866 if (cv) {
9867 cv->LoadStart(this);
9871 // Step 15.
9872 SetReadyStateInternal(Document::READYSTATE_LOADING,
9873 /* updateTimingInformation = */ false);
9875 // Step 16 happened with step 14 above.
9877 // Step 17.
9878 return this;
9881 void Document::Close(ErrorResult& rv) {
9882 if (!IsHTMLDocument()) {
9883 // No calling document.close() on XHTML!
9885 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9886 return;
9889 if (ShouldThrowOnDynamicMarkupInsertion()) {
9890 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9891 return;
9894 if (!mParser || !mParser->IsScriptCreated()) {
9895 return;
9898 ++mWriteLevel;
9899 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
9900 ->Parse(u""_ns, nullptr, true);
9901 --mWriteLevel;
9904 void Document::WriteCommon(const Sequence<nsString>& aText,
9905 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
9906 // Fast path the common case
9907 if (aText.Length() == 1) {
9908 WriteCommon(aText[0], aNewlineTerminate, rv);
9909 } else {
9910 // XXXbz it would be nice if we could pass all the strings to the parser
9911 // without having to do all this copying and then ask it to start
9912 // parsing....
9913 nsString text;
9914 for (size_t i = 0; i < aText.Length(); ++i) {
9915 text.Append(aText[i]);
9917 WriteCommon(text, aNewlineTerminate, rv);
9921 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
9922 ErrorResult& aRv) {
9923 #ifdef DEBUG
9925 // Assert that we do not use or accidentally introduce doc.write()
9926 // in system privileged context or in any of our about: pages.
9927 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
9928 bool isAboutOrPrivContext = principal->IsSystemPrincipal();
9929 if (!isAboutOrPrivContext) {
9930 if (principal->SchemeIs("about")) {
9931 // about:blank inherits the security contetext and this assertion
9932 // is only meant for actual about: pages.
9933 nsAutoCString host;
9934 principal->GetHost(host);
9935 isAboutOrPrivContext = !host.EqualsLiteral("blank");
9938 // Some automated tests use an empty string to kick off some parsing
9939 // mechansims, but they do not do any harm since they use an empty string.
9940 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
9941 "do not use doc.write in privileged context!");
9943 #endif
9945 mTooDeepWriteRecursion =
9946 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
9947 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
9948 aRv.Throw(NS_ERROR_UNEXPECTED);
9949 return;
9952 if (!IsHTMLDocument() || mDisableDocWrite) {
9953 // No calling document.write*() on XHTML!
9955 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9956 return;
9959 if (ShouldThrowOnDynamicMarkupInsertion()) {
9960 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9961 return;
9964 if (mParserAborted) {
9965 // Hixie says aborting the parser doesn't undefine the insertion point.
9966 // However, since we null out mParser in that case, we track the
9967 // theoretically defined insertion point using mParserAborted.
9968 return;
9971 // Implement Step 4.1 of:
9972 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
9973 if (ShouldIgnoreOpens()) {
9974 return;
9977 void* key = GenerateParserKey();
9978 if (mParser && !mParser->IsInsertionPointDefined()) {
9979 if (mIgnoreDestructiveWritesCounter) {
9980 // Instead of implying a call to document.open(), ignore the call.
9981 nsContentUtils::ReportToConsole(
9982 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9983 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9984 return;
9986 // The spec doesn't tell us to ignore opens from here, but we need to
9987 // ensure opens are ignored here. See similar code in Open() that handles
9988 // the case of an existing parser which is not currently running script and
9989 // should stay in sync with this code.
9990 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9991 mParser->Terminate();
9992 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9995 if (!mParser) {
9996 if (mIgnoreDestructiveWritesCounter) {
9997 // Instead of implying a call to document.open(), ignore the call.
9998 nsContentUtils::ReportToConsole(
9999 nsIScriptError::warningFlag, "DOM Events"_ns, this,
10000 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
10001 return;
10004 Open({}, {}, aRv);
10006 // If Open() fails, or if it didn't create a parser (as it won't
10007 // if the user chose to not discard the current document through
10008 // onbeforeunload), don't write anything.
10009 if (aRv.Failed() || !mParser) {
10010 return;
10014 static constexpr auto new_line = u"\n"_ns;
10016 ++mWriteLevel;
10018 // This could be done with less code, but for performance reasons it
10019 // makes sense to have the code for two separate Parse() calls here
10020 // since the concatenation of strings costs more than we like. And
10021 // why pay that price when we don't need to?
10022 if (aNewlineTerminate) {
10023 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
10024 ->Parse(aText + new_line, key, false);
10025 } else {
10026 aRv =
10027 (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
10030 --mWriteLevel;
10032 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
10035 void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
10036 WriteCommon(aText, false, rv);
10039 void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
10040 WriteCommon(aText, true, rv);
10043 void* Document::GenerateParserKey(void) {
10044 if (!mScriptLoader) {
10045 // If we don't have a script loader, then the parser probably isn't parsing
10046 // anything anyway, so just return null.
10047 return nullptr;
10050 // The script loader provides us with the currently executing script element,
10051 // which is guaranteed to be unique per script.
10052 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
10053 if (script && mParser && mParser->IsScriptCreated()) {
10054 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
10055 if (creatorParser != mParser) {
10056 // Make scripts that aren't inserted by the active parser of this document
10057 // participate in the context of the script that document.open()ed
10058 // this document.
10059 return nullptr;
10062 return script;
10065 /* static */
10066 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
10067 nsAtom* aAtom, void* aData) {
10068 MOZ_ASSERT(aElement, "Must have element to work with!");
10070 if (!aElement->HasName()) {
10071 return false;
10074 nsString* elementName = static_cast<nsString*>(aData);
10075 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
10076 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
10077 eCaseMatters);
10080 /* static */
10081 void* Document::UseExistingNameString(nsINode* aRootNode,
10082 const nsString* aName) {
10083 return const_cast<nsString*>(aName);
10086 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
10087 if (mDocumentURI) {
10088 nsAutoCString uri;
10089 nsresult rv = mDocumentURI->GetSpec(uri);
10090 NS_ENSURE_SUCCESS(rv, rv);
10092 CopyUTF8toUTF16(uri, aDocumentURI);
10093 } else {
10094 aDocumentURI.Truncate();
10097 return NS_OK;
10100 // Alias of above
10101 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
10103 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
10104 CallerType aCallerType,
10105 ErrorResult& aRv) const {
10106 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
10107 aRv = GetDocumentURI(aDocumentURI);
10108 return;
10111 nsAutoCString uri;
10112 nsresult res = mChromeXHRDocURI->GetSpec(uri);
10113 if (NS_FAILED(res)) {
10114 aRv.Throw(res);
10115 return;
10117 CopyUTF8toUTF16(uri, aDocumentURI);
10120 nsIURI* Document::GetDocumentURIObject() const {
10121 if (!mChromeXHRDocURI) {
10122 return GetDocumentURI();
10125 return mChromeXHRDocURI;
10128 void Document::GetCompatMode(nsString& aCompatMode) const {
10129 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
10130 mCompatMode == eCompatibility_AlmostStandards ||
10131 mCompatMode == eCompatibility_FullStandards,
10132 "mCompatMode is neither quirks nor strict for this document");
10134 if (mCompatMode == eCompatibility_NavQuirks) {
10135 aCompatMode.AssignLiteral("BackCompat");
10136 } else {
10137 aCompatMode.AssignLiteral("CSS1Compat");
10141 } // namespace dom
10142 } // namespace mozilla
10144 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
10145 if (Element* element = Element::FromNode(aNode)) {
10146 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
10147 while (true) {
10148 RefPtr<Attr> attr;
10150 // Use an iterator to get an arbitrary attribute from the
10151 // cache. The iterator must be destroyed before any other
10152 // operations on mAttributeCache, to avoid hash table
10153 // assertions.
10154 auto iter = map->mAttributeCache.ConstIter();
10155 if (iter.Done()) {
10156 break;
10158 attr = iter.UserData();
10161 BlastSubtreeToPieces(attr);
10163 mozilla::DebugOnly<nsresult> rv =
10164 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
10165 attr->NodeInfo()->NameAtom(), false);
10167 // XXX Should we abort here?
10168 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
10172 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
10173 BlastSubtreeToPieces(shadow);
10174 element->UnattachShadow();
10178 while (aNode->HasChildren()) {
10179 nsIContent* node = aNode->GetFirstChild();
10180 BlastSubtreeToPieces(node);
10181 aNode->RemoveChildNode(node, false);
10185 namespace mozilla::dom {
10187 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv,
10188 bool aAcceptShadowRoot) {
10189 OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
10190 if (adoptedNode->IsShadowRoot() && !aAcceptShadowRoot) {
10191 rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
10192 return nullptr;
10195 // Scope firing mutation events so that we don't carry any state that
10196 // might be stale
10198 if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
10199 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
10203 nsAutoScriptBlocker scriptBlocker;
10205 switch (adoptedNode->NodeType()) {
10206 case ATTRIBUTE_NODE: {
10207 // Remove from ownerElement.
10208 OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
10210 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement();
10211 if (rv.Failed()) {
10212 return nullptr;
10215 if (ownerElement) {
10216 OwningNonNull<Attr> newAttr =
10217 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
10218 if (rv.Failed()) {
10219 return nullptr;
10223 break;
10225 case DOCUMENT_FRAGMENT_NODE:
10226 case ELEMENT_NODE:
10227 case PROCESSING_INSTRUCTION_NODE:
10228 case TEXT_NODE:
10229 case CDATA_SECTION_NODE:
10230 case COMMENT_NODE:
10231 case DOCUMENT_TYPE_NODE: {
10232 // Don't allow adopting a node's anonymous subtree out from under it.
10233 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
10234 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10235 return nullptr;
10238 // We don't want to adopt an element into its own contentDocument or into
10239 // a descendant contentDocument, so we check if the frameElement of this
10240 // document or any of its parents is the adopted node or one of its
10241 // descendants.
10242 RefPtr<BrowsingContext> bc = GetBrowsingContext();
10243 while (bc) {
10244 nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
10245 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
10246 rv.ThrowHierarchyRequestError(
10247 "Trying to adopt a node into its own contentDocument or a "
10248 "descendant contentDocument.");
10249 return nullptr;
10252 if (XRE_IsParentProcess()) {
10253 bc = bc->Canonical()->GetParentCrossChromeBoundary();
10254 } else {
10255 bc = bc->GetParent();
10259 // Remove from parent.
10260 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
10261 if (parent) {
10262 parent->RemoveChildNode(adoptedNode->AsContent(), true);
10263 } else {
10264 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
10267 break;
10269 case DOCUMENT_NODE: {
10270 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10271 return nullptr;
10273 default: {
10274 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
10276 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10277 return nullptr;
10281 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
10282 bool sameDocument = oldDocument == this;
10284 AutoJSContext cx;
10285 JS::Rooted<JSObject*> newScope(cx, nullptr);
10286 if (!sameDocument) {
10287 newScope = GetWrapper();
10288 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
10289 // Make sure cx is in a semi-sane compartment before we call WrapNative.
10290 // It's kind of irrelevant, given that we're passing aAllowWrapping =
10291 // false, and documents should always insist on being wrapped in an
10292 // canonical scope. But we try to pass something sane anyway.
10293 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
10294 JSAutoRealm ar(cx, globalObject);
10295 JS::Rooted<JS::Value> v(cx);
10296 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
10297 /* aAllowWrapping = */ false);
10298 if (rv.Failed()) return nullptr;
10299 newScope = &v.toObject();
10303 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
10304 if (rv.Failed()) {
10305 // Disconnect all nodes from their parents, since some have the old document
10306 // as their ownerDocument and some have this as their ownerDocument.
10307 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
10308 return nullptr;
10311 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
10312 "Should still be in the document we just got adopted into");
10314 return adoptedNode;
10317 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
10319 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
10320 const nsString& aScaleString) {
10321 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
10322 if (aScaleString.EqualsLiteral("device-width") ||
10323 aScaleString.EqualsLiteral("device-height")) {
10324 return Some(LayoutDeviceToScreenScale(10.0f));
10325 } else if (aScaleString.EqualsLiteral("yes")) {
10326 return Some(LayoutDeviceToScreenScale(1.0f));
10327 } else if (aScaleString.EqualsLiteral("no")) {
10328 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10329 } else if (aScaleString.IsEmpty()) {
10330 return Nothing();
10333 nsresult scaleErrorCode;
10334 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
10335 if (NS_FAILED(scaleErrorCode)) {
10336 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10339 if (scale < 0) {
10340 return Nothing();
10342 return Some(clamped(LayoutDeviceToScreenScale(scale), ViewportMinScale(),
10343 ViewportMaxScale()));
10346 void Document::ParseScalesInViewportMetaData(
10347 const ViewportMetaData& aViewportMetaData) {
10348 Maybe<LayoutDeviceToScreenScale> scale;
10350 scale = ParseScaleString(aViewportMetaData.mInitialScale);
10351 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
10352 mValidScaleFloat = scale.isSome();
10354 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
10355 // Chrome uses '5' for the fallback value of maximum-scale, we might
10356 // consider matching it in future.
10357 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
10358 mScaleMaxFloat = scale.valueOr(ViewportMaxScale());
10359 mValidMaxScale = scale.isSome();
10361 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
10362 mScaleMinFloat = scale.valueOr(ViewportMinScale());
10363 mValidMinScale = scale.isSome();
10365 // Resolve min-zoom and max-zoom values.
10366 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
10367 if (mValidMaxScale && mValidMinScale) {
10368 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
10372 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
10373 const nsAString& aHeightString,
10374 bool aHasValidScale) {
10375 // The width and height properties
10376 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
10378 // The width and height viewport <META> properties are translated into width
10379 // and height descriptors, setting the min-width/min-height value to
10380 // extend-to-zoom and the max-width/max-height value to the length from the
10381 // viewport <META> property as follows:
10383 // 1. Non-negative number values are translated to pixel lengths, clamped to
10384 // the range: [1px, 10000px]
10385 // 2. Negative number values are dropped
10386 // 3. device-width and device-height translate to 100vw and 100vh respectively
10387 // 4. Other keywords and unknown values are also dropped
10388 mMinWidth = nsViewportInfo::kAuto;
10389 mMaxWidth = nsViewportInfo::kAuto;
10390 if (!aWidthString.IsEmpty()) {
10391 mMinWidth = nsViewportInfo::kExtendToZoom;
10392 if (aWidthString.EqualsLiteral("device-width")) {
10393 mMaxWidth = nsViewportInfo::kDeviceSize;
10394 } else {
10395 nsresult widthErrorCode;
10396 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
10397 if (NS_FAILED(widthErrorCode)) {
10398 mMaxWidth = nsViewportInfo::kAuto;
10399 } else if (mMaxWidth >= 0.0f) {
10400 mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
10401 } else {
10402 mMaxWidth = nsViewportInfo::kAuto;
10405 } else if (aHasValidScale) {
10406 if (aHeightString.IsEmpty()) {
10407 mMinWidth = nsViewportInfo::kExtendToZoom;
10408 mMaxWidth = nsViewportInfo::kExtendToZoom;
10410 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
10411 mMinWidth = nsViewportInfo::kExtendToZoom;
10412 mMaxWidth = nsViewportInfo::kDeviceSize;
10415 mMinHeight = nsViewportInfo::kAuto;
10416 mMaxHeight = nsViewportInfo::kAuto;
10417 if (!aHeightString.IsEmpty()) {
10418 mMinHeight = nsViewportInfo::kExtendToZoom;
10419 if (aHeightString.EqualsLiteral("device-height")) {
10420 mMaxHeight = nsViewportInfo::kDeviceSize;
10421 } else {
10422 nsresult heightErrorCode;
10423 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
10424 if (NS_FAILED(heightErrorCode)) {
10425 mMaxHeight = nsViewportInfo::kAuto;
10426 } else if (mMaxHeight >= 0.0f) {
10427 mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
10428 } else {
10429 mMaxHeight = nsViewportInfo::kAuto;
10435 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
10436 MOZ_ASSERT(mPresShell);
10438 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
10439 // widget scale and the full zoom.
10440 nsPresContext* context = mPresShell->GetPresContext();
10441 // When querying the full zoom, get it from the device context rather than
10442 // directly from the pres context, because the device context's value can
10443 // include an adjustment necessary to keep the number of app units per device
10444 // pixel an integer, and we want the adjusted value.
10445 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
10446 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
10447 CSSToLayoutDeviceScale layoutDeviceScale =
10448 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
10450 CSSToScreenScale defaultScale =
10451 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
10453 // Special behaviour for desktop mode, provided we are not on an about: page,
10454 // or fullscreen.
10455 const bool fullscreen = Fullscreen();
10456 auto* bc = GetBrowsingContext();
10457 if (bc && bc->ForceDesktopViewport() && !IsAboutPage() && !fullscreen) {
10458 CSSCoord viewportWidth =
10459 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
10460 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
10461 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
10462 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
10463 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
10464 return nsViewportInfo(fakeDesktopSize, scaleToFit,
10465 nsViewportInfo::ZoomFlag::AllowZoom,
10466 nsViewportInfo::ZoomBehaviour::Mobile,
10467 nsViewportInfo::AutoScaleFlag::AutoScale);
10470 // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
10471 if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
10472 return nsViewportInfo(aDisplaySize, defaultScale,
10473 nsLayoutUtils::AllowZoomingForDocument(this)
10474 ? nsViewportInfo::ZoomFlag::AllowZoom
10475 : nsViewportInfo::ZoomFlag::DisallowZoom,
10476 StaticPrefs::apz_allow_zooming_out()
10477 ? nsViewportInfo::ZoomBehaviour::Mobile
10478 : nsViewportInfo::ZoomBehaviour::Desktop);
10481 // In cases where the width of the CSS viewport is less than or equal to the
10482 // width of the display (i.e. width <= device-width) then we disable
10483 // double-tap-to-zoom behaviour. See bug 941995 for details.
10485 switch (mViewportType) {
10486 case DisplayWidthHeight:
10487 return nsViewportInfo(aDisplaySize, defaultScale,
10488 nsViewportInfo::ZoomFlag::AllowZoom,
10489 nsViewportInfo::ZoomBehaviour::Mobile);
10490 case Unknown: {
10491 // We might early exit if the viewport is empty. Even if we don't,
10492 // at the end of this case we'll note that it was empty. Later, when
10493 // we're using the cached values, this will trigger alternate code paths.
10494 if (!mLastModifiedViewportMetaData) {
10495 // If the docType specifies that we are on a site optimized for mobile,
10496 // then we want to return specially crafted defaults for the viewport
10497 // info.
10498 if (RefPtr<DocumentType> docType = GetDoctype()) {
10499 nsAutoString docId;
10500 docType->GetPublicId(docId);
10501 if ((docId.Find(u"WAP") != -1) || (docId.Find(u"Mobile") != -1) ||
10502 (docId.Find(u"WML") != -1)) {
10503 // We're making an assumption that the docType can't change here
10504 mViewportType = DisplayWidthHeight;
10505 return nsViewportInfo(aDisplaySize, defaultScale,
10506 nsViewportInfo::ZoomFlag::AllowZoom,
10507 nsViewportInfo::ZoomBehaviour::Mobile);
10511 nsAutoString handheldFriendly;
10512 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
10513 if (handheldFriendly.EqualsLiteral("true")) {
10514 mViewportType = DisplayWidthHeight;
10515 return nsViewportInfo(aDisplaySize, defaultScale,
10516 nsViewportInfo::ZoomFlag::AllowZoom,
10517 nsViewportInfo::ZoomBehaviour::Mobile);
10521 ViewportMetaData metaData = GetViewportMetaData();
10523 // Parse initial-scale, minimum-scale and maximum-scale.
10524 ParseScalesInViewportMetaData(metaData);
10526 // Parse width and height properties
10527 // This function sets m{Min,Max}{Width,Height}.
10528 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
10529 mValidScaleFloat);
10531 mAllowZoom = true;
10532 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
10533 (metaData.mUserScalable.EqualsLiteral("no")) ||
10534 (metaData.mUserScalable.EqualsLiteral("false"))) {
10535 mAllowZoom = false;
10538 // Resolve viewport-fit value.
10539 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
10540 mViewportFit = ViewportFitType::Auto;
10541 if (!metaData.mViewportFit.IsEmpty()) {
10542 if (metaData.mViewportFit.EqualsLiteral("contain")) {
10543 mViewportFit = ViewportFitType::Contain;
10544 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
10545 mViewportFit = ViewportFitType::Cover;
10549 mWidthStrEmpty = metaData.mWidth.IsEmpty();
10551 mViewportType = Specified;
10552 [[fallthrough]];
10554 case Specified:
10555 default:
10556 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
10557 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
10558 bool effectiveValidMaxScale = mValidMaxScale;
10560 nsViewportInfo::ZoomFlag effectiveZoomFlag =
10561 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
10562 : nsViewportInfo::ZoomFlag::DisallowZoom;
10563 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
10564 // If the pref to force user-scalable is enabled, we ignore the values
10565 // from the meta-viewport tag for these properties and just assume they
10566 // allow the page to be scalable. Note in particular that this code is
10567 // in the "Specified" branch of the enclosing switch statement, so that
10568 // calls to GetViewportInfo always use the latest value of the
10569 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
10570 // return nsViewportInfo instances are all consistent with
10571 // browser_ui_zoom_force_user_scalable() already.
10572 effectiveMinScale = ViewportMinScale();
10573 effectiveMaxScale = ViewportMaxScale();
10574 effectiveValidMaxScale = true;
10575 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
10578 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
10579 auto ComputeExtendZoom = [&]() -> float {
10580 if (mValidScaleFloat && effectiveValidMaxScale) {
10581 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
10583 if (mValidScaleFloat) {
10584 return mScaleFloat.scale;
10586 if (effectiveValidMaxScale) {
10587 return effectiveMaxScale.scale;
10589 return nsViewportInfo::kAuto;
10592 // Resolving 'extend-to-zoom'
10593 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
10594 float extendZoom = ComputeExtendZoom();
10596 CSSCoord minWidth = mMinWidth;
10597 CSSCoord maxWidth = mMaxWidth;
10598 CSSCoord minHeight = mMinHeight;
10599 CSSCoord maxHeight = mMaxHeight;
10601 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
10602 // viewport size. We need to use this scaled size for any clamping of
10603 // width or height.
10604 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
10606 // Our min and max width and height values are mostly as specified by
10607 // the viewport declaration, but we make an exception for max width.
10608 // Max width, if auto, and if there's no initial-scale, will be set
10609 // to a default size. This is to support legacy site design with no
10610 // viewport declaration, and to do that using the same scheme as
10611 // Chrome does, in order to maintain web compatibility. Since the
10612 // default size has a complicated calculation, we fixup the maxWidth
10613 // value after setting it, above.
10614 if (maxWidth == nsViewportInfo::kAuto && !mValidScaleFloat) {
10615 if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
10616 bc->InRDMPane()) {
10617 // If RDM and touch simulation are active, then use the simulated
10618 // screen width to accommodate for cases where the screen width is
10619 // larger than the desktop viewport default.
10620 maxWidth = nsViewportInfo::Max(
10621 displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
10622 } else {
10623 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
10625 // Divide by fullZoom to stretch CSS pixel size of viewport in order
10626 // to keep device pixel size unchanged after full zoom applied.
10627 // See bug 974242.
10628 maxWidth /= fullZoom;
10630 // We set minWidth to ExtendToZoom, which will cause our later width
10631 // calculation to expand to maxWidth, if scale restrictions allow it.
10632 minWidth = nsViewportInfo::kExtendToZoom;
10635 // Resolve device-width and device-height first.
10636 if (maxWidth == nsViewportInfo::kDeviceSize) {
10637 maxWidth = displaySize.width;
10639 if (maxHeight == nsViewportInfo::kDeviceSize) {
10640 maxHeight = displaySize.height;
10642 if (extendZoom == nsViewportInfo::kAuto) {
10643 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10644 maxWidth = nsViewportInfo::kAuto;
10646 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10647 maxHeight = nsViewportInfo::kAuto;
10649 if (minWidth == nsViewportInfo::kExtendToZoom) {
10650 minWidth = maxWidth;
10652 if (minHeight == nsViewportInfo::kExtendToZoom) {
10653 minHeight = maxHeight;
10655 } else {
10656 CSSSize extendSize = displaySize / extendZoom;
10657 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10658 maxWidth = extendSize.width;
10660 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10661 maxHeight = extendSize.height;
10663 if (minWidth == nsViewportInfo::kExtendToZoom) {
10664 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
10666 if (minHeight == nsViewportInfo::kExtendToZoom) {
10667 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
10671 // Resolve initial width and height from min/max descriptors
10672 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
10673 CSSCoord width = nsViewportInfo::kAuto;
10674 if (minWidth != nsViewportInfo::kAuto ||
10675 maxWidth != nsViewportInfo::kAuto) {
10676 width = nsViewportInfo::Max(
10677 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
10679 CSSCoord height = nsViewportInfo::kAuto;
10680 if (minHeight != nsViewportInfo::kAuto ||
10681 maxHeight != nsViewportInfo::kAuto) {
10682 height = nsViewportInfo::Max(
10683 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
10686 // Resolve width value
10687 // https://drafts.csswg.org/css-device-adapt/#resolve-width
10688 if (width == nsViewportInfo::kAuto) {
10689 if (height == nsViewportInfo::kAuto || aDisplaySize.height == 0) {
10690 width = displaySize.width;
10691 } else {
10692 width = height * aDisplaySize.width / aDisplaySize.height;
10696 // Resolve height value
10697 // https://drafts.csswg.org/css-device-adapt/#resolve-height
10698 if (height == nsViewportInfo::kAuto) {
10699 if (aDisplaySize.width == 0) {
10700 height = displaySize.height;
10701 } else {
10702 height = width * aDisplaySize.height / aDisplaySize.width;
10705 MOZ_ASSERT(width != nsViewportInfo::kAuto &&
10706 height != nsViewportInfo::kAuto);
10708 CSSSize size(width, height);
10710 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
10711 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
10712 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
10714 nsViewportInfo::AutoSizeFlag sizeFlag =
10715 nsViewportInfo::AutoSizeFlag::FixedSize;
10716 if (mMaxWidth == nsViewportInfo::kDeviceSize ||
10717 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::kDeviceSize ||
10718 mScaleFloat.scale == 1.0f)) ||
10719 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::kAuto &&
10720 mMaxHeight < 0)) {
10721 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
10724 // FIXME: Resolving width and height should be done above 'Resolve width
10725 // value' and 'Resolve height value'.
10726 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
10727 size = displaySize;
10730 // The purpose of clamping the viewport width to a minimum size is to
10731 // prevent page authors from setting it to a ridiculously small value.
10732 // If the page is actually being rendered in a very small area (as might
10733 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
10734 // prevent the viewport from taking on that size.
10735 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
10737 size.width = clamped(size.width, effectiveMinSize.width,
10738 float(kViewportMaxSize.width));
10740 // Also recalculate the default zoom, if it wasn't specified in the
10741 // metadata, and the width is specified.
10742 if (!mValidScaleFloat && !mWidthStrEmpty) {
10743 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
10744 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
10747 size.height = clamped(size.height, effectiveMinSize.height,
10748 float(kViewportMaxSize.height));
10750 // In cases of user-scalable=no, if we have a positive scale, clamp it to
10751 // min and max, and then use the clamped value for the scale, the min, and
10752 // the max. If we don't have a positive scale, assert that we are setting
10753 // the auto scale flag.
10754 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
10755 scaleFloat > CSSToScreenScale(0.0f)) {
10756 scaleFloat = scaleMinFloat = scaleMaxFloat =
10757 clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
10759 MOZ_ASSERT(
10760 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
10761 "If we don't have a positive scale, we should be using auto scale.");
10763 // We need to perform a conversion, but only if the initial or maximum
10764 // scale were set explicitly by the user.
10765 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
10766 scaleFloat <= scaleMaxFloat) {
10767 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
10768 size.width = std::max(size.width, displaySize.width);
10769 size.height = std::max(size.height, displaySize.height);
10770 } else if (effectiveValidMaxScale) {
10771 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
10772 size.width = std::max(size.width, displaySize.width);
10773 size.height = std::max(size.height, displaySize.height);
10776 return nsViewportInfo(
10777 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
10778 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
10779 : nsViewportInfo::AutoScaleFlag::AutoScale,
10780 effectiveZoomFlag, mViewportFit);
10784 ViewportMetaData Document::GetViewportMetaData() const {
10785 return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
10786 : ViewportMetaData();
10789 void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
10790 mLastModifiedViewportMetaData = std::move(aData);
10791 // Trigger recomputation of the nsViewportInfo the next time it's queried.
10792 mViewportType = Unknown;
10794 AsyncEventDispatcher::RunDOMEventWhenSafe(
10795 *this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes,
10796 ChromeOnlyDispatch::eYes);
10799 EventListenerManager* Document::GetOrCreateListenerManager() {
10800 if (!mListenerManager) {
10801 mListenerManager =
10802 new EventListenerManager(static_cast<EventTarget*>(this));
10803 SetFlags(NODE_HAS_LISTENERMANAGER);
10806 return mListenerManager;
10809 EventListenerManager* Document::GetExistingListenerManager() const {
10810 return mListenerManager;
10813 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
10814 aVisitor.mCanHandle = true;
10815 // FIXME! This is a hack to make middle mouse paste working also in Editor.
10816 // Bug 329119
10817 aVisitor.mForceContentDispatch = true;
10819 // Load events must not propagate to |window| object, see bug 335251.
10820 if (aVisitor.mEvent->mMessage != eLoad) {
10821 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
10822 aVisitor.SetParentTarget(
10823 window ? window->GetTargetForEventTargetChain() : nullptr, false);
10827 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
10828 CallerType aCallerType,
10829 ErrorResult& rv) const {
10830 nsPresContext* presContext = GetPresContext();
10832 // Create event even without presContext.
10833 RefPtr<Event> ev =
10834 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
10835 nullptr, aEventType, aCallerType);
10836 if (!ev) {
10837 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10838 return nullptr;
10840 WidgetEvent* e = ev->WidgetEventPtr();
10841 e->mFlags.mBubbles = false;
10842 e->mFlags.mCancelable = false;
10843 return ev.forget();
10846 void Document::FlushPendingNotifications(FlushType aType) {
10847 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
10848 FlushPendingNotifications(flush);
10851 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
10852 FlushType flushType = aFlush.mFlushType;
10854 RefPtr<Document> documentOnStack = this;
10856 // We need to flush the sink for non-HTML documents (because the XML
10857 // parser still does insertion with deferred notifications). We
10858 // also need to flush the sink if this is a layout-related flush, to
10859 // make sure that layout is started as needed. But we can skip that
10860 // part if we have no presshell or if it's already done an initial
10861 // reflow.
10862 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
10863 mPresShell && !mPresShell->DidInitialize())) &&
10864 (mParser || mWeakSink)) {
10865 nsCOMPtr<nsIContentSink> sink;
10866 if (mParser) {
10867 sink = mParser->GetContentSink();
10868 } else {
10869 sink = do_QueryReferent(mWeakSink);
10870 if (!sink) {
10871 mWeakSink = nullptr;
10874 // Determine if it is safe to flush the sink notifications
10875 // by determining if it safe to flush all the presshells.
10876 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
10877 sink->FlushPendingNotifications(flushType);
10881 // Should we be flushing pending binding constructors in here?
10883 if (flushType <= FlushType::ContentAndNotify) {
10884 // Nothing to do here
10885 return;
10888 // If we have a parent we must flush the parent too to ensure that our
10889 // container is reflowed if its size was changed.
10891 // We do it only if the subdocument and the parent can observe each other
10892 // synchronously (that is, if we're not cross-origin), to avoid work that is
10893 // not observable, and if the parent document has finished loading all its
10894 // render-blocking stylesheets and may start laying out the document, to avoid
10895 // unnecessary flashes of unstyled content on the parent document. Note that
10896 // this last bit means that size-dependent media queries in this document may
10897 // produce incorrect results temporarily.
10899 // But if it's not safe to flush ourselves, then don't flush the parent, since
10900 // that can cause things like resizes of our frame's widget, which we can't
10901 // handle while flushing is unsafe.
10902 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
10903 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
10904 ChangesToFlush parentFlush = aFlush;
10905 if (flushType >= FlushType::Style) {
10906 // Since media queries mean that a size change of our container can affect
10907 // style, we need to promote a style flush on ourself to a layout flush on
10908 // our parent, since we need our container to be the correct size to
10909 // determine the correct style.
10910 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
10912 mParentDocument->FlushPendingNotifications(parentFlush);
10915 if (RefPtr<PresShell> presShell = GetPresShell()) {
10916 presShell->FlushPendingNotifications(aFlush);
10920 void Document::FlushExternalResources(FlushType aType) {
10921 NS_ASSERTION(
10922 aType >= FlushType::Style,
10923 "should only need to flush for style or higher in external resources");
10924 if (GetDisplayDocument()) {
10925 return;
10928 auto flush = [aType](Document& aDoc) {
10929 aDoc.FlushPendingNotifications(aType);
10930 return CallState::Continue;
10933 EnumerateExternalResources(flush);
10936 void Document::SetXMLDeclaration(const char16_t* aVersion,
10937 const char16_t* aEncoding,
10938 const int32_t aStandalone) {
10939 if (!aVersion || *aVersion == '\0') {
10940 mXMLDeclarationBits = 0;
10941 return;
10944 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
10946 if (aEncoding && *aEncoding != '\0') {
10947 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
10950 if (aStandalone == 1) {
10951 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
10952 XML_DECLARATION_BITS_STANDALONE_YES;
10953 } else if (aStandalone == 0) {
10954 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
10958 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
10959 nsAString& aStandalone) {
10960 aVersion.Truncate();
10961 aEncoding.Truncate();
10962 aStandalone.Truncate();
10964 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
10965 return;
10968 // always until we start supporting 1.1 etc.
10969 aVersion.AssignLiteral("1.0");
10971 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
10972 // This is what we have stored, not necessarily what was written
10973 // in the original
10974 GetCharacterSet(aEncoding);
10977 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
10978 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
10979 aStandalone.AssignLiteral("yes");
10980 } else {
10981 aStandalone.AssignLiteral("no");
10986 void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
10987 mColorSchemeMetaTags.Insert(aMeta);
10988 RecomputeColorScheme();
10991 void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
10992 mColorSchemeMetaTags.RemoveElement(aMeta);
10993 RecomputeColorScheme();
10996 void Document::RecomputeColorScheme() {
10997 auto oldColorScheme = mColorSchemeBits;
10998 mColorSchemeBits = 0;
10999 const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
11000 for (const HTMLMetaElement* el : elements) {
11001 nsAutoString content;
11002 if (!el->GetAttr(nsGkAtoms::content, content)) {
11003 continue;
11006 NS_ConvertUTF16toUTF8 contentU8(content);
11007 if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
11008 break;
11012 if (mColorSchemeBits == oldColorScheme) {
11013 return;
11016 if (nsPresContext* pc = GetPresContext()) {
11017 // This affects system colors, which are inherited, so we need to recascade.
11018 pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
11022 bool Document::IsScriptEnabled() const {
11023 // If this document is sandboxed without 'allow-scripts'
11024 // script is not enabled
11025 if (HasScriptsBlockedBySandbox()) {
11026 return false;
11029 nsCOMPtr<nsIScriptGlobalObject> globalObject =
11030 do_QueryInterface(GetInnerWindow());
11031 if (!globalObject || !globalObject->HasJSGlobal()) {
11032 return false;
11035 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
11036 .Allowed();
11039 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
11040 PRTime modDate = 0;
11041 nsresult rv;
11043 nsCOMPtr<nsIHttpChannel> httpChannel;
11044 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
11045 if (NS_WARN_IF(NS_FAILED(rv))) {
11046 return;
11049 if (httpChannel) {
11050 nsAutoCString tmp;
11051 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
11053 if (NS_SUCCEEDED(rv)) {
11054 PRTime time;
11055 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
11056 if (st == PR_SUCCESS) {
11057 modDate = time;
11061 static const char* const headers[] = {
11062 "default-style", "content-style-type", "content-language",
11063 "content-disposition", "refresh", "x-dns-prefetch-control",
11064 "x-frame-options", "origin-trial",
11065 // add more http headers if you need
11066 // XXXbz don't add content-location support without reading bug
11067 // 238654 and its dependencies/dups first.
11070 nsAutoCString headerVal;
11071 const char* const* name = headers;
11072 while (*name) {
11073 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
11074 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
11075 RefPtr<nsAtom> key = NS_Atomize(*name);
11076 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
11078 ++name;
11080 } else {
11081 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
11082 if (fileChannel) {
11083 nsCOMPtr<nsIFile> file;
11084 fileChannel->GetFile(getter_AddRefs(file));
11085 if (file) {
11086 PRTime msecs;
11087 rv = file->GetLastModifiedTime(&msecs);
11089 if (NS_SUCCEEDED(rv)) {
11090 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
11093 } else {
11094 nsAutoCString contentDisp;
11095 rv = aChannel->GetContentDispositionHeader(contentDisp);
11096 if (NS_SUCCEEDED(rv)) {
11097 SetHeaderData(nsGkAtoms::headerContentDisposition,
11098 NS_ConvertASCIItoUTF16(contentDisp));
11103 mLastModified.Truncate();
11104 if (modDate != 0) {
11105 GetFormattedTimeString(modDate, mLastModified);
11109 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
11110 // set any HTTP-EQUIV data into document's header data as well as url
11111 nsAutoString header;
11112 aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
11113 if (!header.IsEmpty()) {
11114 // Ignore META REFRESH when document is sandboxed from automatic features.
11115 nsContentUtils::ASCIIToLower(header);
11116 if (nsGkAtoms::refresh->Equals(header) &&
11117 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
11118 return;
11121 nsAutoString result;
11122 aMetaElement->GetAttr(nsGkAtoms::content, result);
11123 if (!result.IsEmpty()) {
11124 RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
11125 SetHeaderData(fieldAtom, result);
11129 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
11130 nsGkAtoms::handheldFriendly, eIgnoreCase)) {
11131 nsAutoString result;
11132 aMetaElement->GetAttr(nsGkAtoms::content, result);
11133 if (!result.IsEmpty()) {
11134 nsContentUtils::ASCIIToLower(result);
11135 SetHeaderData(nsGkAtoms::handheldFriendly, result);
11140 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
11141 nsAtom* aPrefix,
11142 int32_t aNamespaceID,
11143 const nsAString* aIs) {
11144 #ifdef DEBUG
11145 nsAutoString qName;
11146 if (aPrefix) {
11147 aPrefix->ToString(qName);
11148 qName.Append(':');
11150 qName.Append(aName);
11152 // Note: "a:b:c" is a valid name in non-namespaces XML, and
11153 // Document::CreateElement can call us with such a name and no prefix,
11154 // which would cause an error if we just used true here.
11155 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
11156 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
11157 "Don't pass invalid prefixes to Document::CreateElem, "
11158 "check caller.");
11159 #endif
11161 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
11162 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
11163 getter_AddRefs(nodeInfo));
11164 NS_ENSURE_TRUE(nodeInfo, nullptr);
11166 nsCOMPtr<Element> element;
11167 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
11168 NOT_FROM_PARSER, aIs);
11169 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
11172 bool Document::IsSafeToFlush() const {
11173 PresShell* presShell = GetPresShell();
11174 if (!presShell) {
11175 return true;
11177 return presShell->IsSafeToFlush();
11180 void Document::Sanitize() {
11181 // Sanitize the document by resetting all (current and former) password fields
11182 // and any form fields with autocomplete=off to their default values. We do
11183 // this now, instead of when the presentation is restored, to offer some
11184 // protection in case there is ever an exploit that allows a cached document
11185 // to be accessed from a different document.
11187 // First locate all input elements, regardless of whether they are
11188 // in a form, and reset the password and autocomplete=off elements.
11190 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
11192 nsAutoString value;
11194 uint32_t length = nodes->Length(true);
11195 for (uint32_t i = 0; i < length; ++i) {
11196 NS_ASSERTION(nodes->Item(i), "null item in node list!");
11198 RefPtr<HTMLInputElement> input =
11199 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
11200 if (!input) continue;
11202 input->GetAttr(nsGkAtoms::autocomplete, value);
11203 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
11204 input->Reset();
11208 // Now locate all _form_ elements that have autocomplete=off and reset them
11209 nodes = GetElementsByTagName(u"form"_ns);
11211 length = nodes->Length(true);
11212 for (uint32_t i = 0; i < length; ++i) {
11213 // Reset() may change the list dynamically.
11214 RefPtr<HTMLFormElement> form =
11215 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
11216 if (!form) continue;
11218 form->GetAttr(nsGkAtoms::autocomplete, value);
11219 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
11223 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
11224 if (!mSubDocuments) {
11225 return;
11228 // PLDHashTable::Iterator can't handle modifications while iterating so we
11229 // copy all entries to an array first before calling any callbacks.
11230 AutoTArray<RefPtr<Document>, 8> subdocs;
11231 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11232 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11233 if (Document* subdoc = entry->mSubDocument) {
11234 subdocs.AppendElement(subdoc);
11237 for (auto& subdoc : subdocs) {
11238 if (aCallback(*subdoc) == CallState::Stop) {
11239 break;
11244 void Document::CollectDescendantDocuments(
11245 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
11246 if (!mSubDocuments) {
11247 return;
11250 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11251 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11252 const Document* subdoc = entry->mSubDocument;
11253 if (subdoc) {
11254 if (aCallback(subdoc)) {
11255 aDescendants.AppendElement(entry->mSubDocument);
11257 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
11262 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
11263 uint32_t& aBFCacheCombo,
11264 bool aIncludeSubdocuments,
11265 bool aAllowUnloadListeners) {
11266 bool ret = true;
11268 if (!IsBFCachingAllowed()) {
11269 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
11270 ret = false;
11273 nsAutoCString uri;
11274 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11275 if (mDocumentURI) {
11276 mDocumentURI->GetSpec(uri);
11280 if (EventHandlingSuppressed()) {
11281 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11282 ("Save of %s blocked on event handling suppression", uri.get()));
11283 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
11284 ret = false;
11287 // Do not allow suspended windows to be placed in the
11288 // bfcache. This method is also used to verify a document
11289 // coming out of the bfcache is ok to restore, though. So
11290 // we only want to block suspend windows that aren't also
11291 // frozen.
11292 nsPIDOMWindowInner* win = GetInnerWindow();
11293 if (win && win->IsSuspended() && !win->IsFrozen()) {
11294 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11295 ("Save of %s blocked on suspended Window", uri.get()));
11296 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
11297 ret = false;
11300 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
11301 bool thirdParty = false;
11302 // Currently some other mobile browsers seem to bfcache only cross-domain
11303 // pages, but bfcache those also when there are unload event listeners, so
11304 // this is trying to match that behavior as much as possible.
11305 bool allowUnloadListeners =
11306 aAllowUnloadListeners &&
11307 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
11308 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
11309 channel, &thirdParty)) &&
11310 thirdParty));
11312 // Check our event listener manager for unload/beforeunload listeners.
11313 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
11314 if (!allowUnloadListeners && piTarget) {
11315 EventListenerManager* manager = piTarget->GetExistingListenerManager();
11316 if (manager) {
11317 if (manager->HasUnloadListeners()) {
11318 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11319 ("Save of %s blocked due to unload handlers", uri.get()));
11320 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
11321 ret = false;
11323 if (manager->HasBeforeUnloadListeners()) {
11324 if (!mozilla::SessionHistoryInParent() ||
11325 !StaticPrefs::
11326 docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
11327 MOZ_LOG(
11328 gPageCacheLog, mozilla::LogLevel::Verbose,
11329 ("Save of %s blocked due to beforeUnload handlers", uri.get()));
11330 aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
11331 ret = false;
11337 // Check if we have pending network requests
11338 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11339 if (loadGroup) {
11340 nsCOMPtr<nsISimpleEnumerator> requests;
11341 loadGroup->GetRequests(getter_AddRefs(requests));
11343 bool hasMore = false;
11345 // We want to bail out if we have any requests other than aNewRequest (or
11346 // in the case when aNewRequest is a part of a multipart response the base
11347 // channel the multipart response is coming in on).
11348 nsCOMPtr<nsIChannel> baseChannel;
11349 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
11350 if (part) {
11351 part->GetBaseChannel(getter_AddRefs(baseChannel));
11354 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11355 nsCOMPtr<nsISupports> elem;
11356 requests->GetNext(getter_AddRefs(elem));
11358 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11359 if (request && request != aNewRequest && request != baseChannel) {
11360 // Favicon loads don't need to block caching.
11361 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
11362 if (channel) {
11363 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
11364 if (li->InternalContentPolicyType() ==
11365 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
11366 continue;
11370 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11371 nsAutoCString requestName;
11372 request->GetName(requestName);
11373 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
11374 ("Save of %s blocked because document has request %s",
11375 uri.get(), requestName.get()));
11377 aBFCacheCombo |= BFCacheStatus::REQUEST;
11378 ret = false;
11383 // Check if we have active GetUserMedia use
11384 if (MediaManager::Exists() && win &&
11385 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
11386 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11387 ("Save of %s blocked due to GetUserMedia", uri.get()));
11388 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
11389 ret = false;
11392 #ifdef MOZ_WEBRTC
11393 // Check if we have active PeerConnections
11394 if (win && win->HasActivePeerConnections()) {
11395 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11396 ("Save of %s blocked due to PeerConnection", uri.get()));
11397 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
11398 ret = false;
11400 #endif // MOZ_WEBRTC
11402 // Don't save presentations for documents containing EME content, so that
11403 // CDMs reliably shutdown upon user navigation.
11404 if (ContainsEMEContent()) {
11405 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
11406 ret = false;
11409 // Don't save presentations for documents containing MSE content, to
11410 // reduce memory usage.
11411 if (ContainsMSEContent()) {
11412 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11413 ("Save of %s blocked due to MSE use", uri.get()));
11414 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
11415 ret = false;
11418 if (aIncludeSubdocuments && mSubDocuments) {
11419 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11420 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11421 Document* subdoc = entry->mSubDocument;
11423 uint32_t subDocBFCacheCombo = 0;
11424 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
11425 bool canCache =
11426 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
11427 true, allowUnloadListeners)
11428 : false;
11429 if (!canCache) {
11430 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11431 ("Save of %s blocked due to subdocument blocked", uri.get()));
11432 aBFCacheCombo |= subDocBFCacheCombo;
11433 ret = false;
11438 if (!mozilla::BFCacheInParent()) {
11439 // BFCache is currently not compatible with remote subframes (bug 1609324)
11440 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
11441 for (auto& child : browsingContext->Children()) {
11442 if (!child->IsInProcess()) {
11443 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
11444 ret = false;
11445 break;
11451 if (win) {
11452 auto* globalWindow = nsGlobalWindowInner::Cast(win);
11453 #ifdef MOZ_WEBSPEECH
11454 if (globalWindow->HasActiveSpeechSynthesis()) {
11455 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11456 ("Save of %s blocked due to Speech use", uri.get()));
11457 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
11458 ret = false;
11460 #endif
11461 if (globalWindow->HasUsedVR()) {
11462 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11463 ("Save of %s blocked due to having used VR", uri.get()));
11464 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
11465 ret = false;
11468 if (win->HasActiveLocks()) {
11469 MOZ_LOG(
11470 gPageCacheLog, mozilla::LogLevel::Verbose,
11471 ("Save of %s blocked due to having active lock requests", uri.get()));
11472 aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
11473 ret = false;
11476 if (win->HasActiveWebTransports()) {
11477 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11478 ("Save of %s blocked due to WebTransport", uri.get()));
11479 aBFCacheCombo |= BFCacheStatus::ACTIVE_WEBTRANSPORT;
11480 ret = false;
11484 return ret;
11487 void Document::Destroy() {
11488 // The ContentViewer wants to release the document now. So, tell our content
11489 // to drop any references to the document so that it can be destroyed.
11490 if (mIsGoingAway) {
11491 return;
11494 ReportDocumentUseCounters();
11495 SetDevToolsWatchingDOMMutations(false);
11497 mIsGoingAway = true;
11499 ScriptLoader()->Destroy();
11500 SetScriptGlobalObject(nullptr);
11501 RemovedFromDocShell();
11503 bool oldVal = mInUnlinkOrDeletion;
11504 mInUnlinkOrDeletion = true;
11506 #ifdef DEBUG
11507 uint32_t oldChildCount = GetChildCount();
11508 #endif
11510 for (nsIContent* child = GetFirstChild(); child;
11511 child = child->GetNextSibling()) {
11512 child->DestroyContent();
11513 MOZ_ASSERT(child->GetParentNode() == this);
11515 MOZ_ASSERT(oldChildCount == GetChildCount());
11516 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
11518 mInUnlinkOrDeletion = oldVal;
11520 mLayoutHistoryState = nullptr;
11522 if (mOriginalDocument) {
11523 mOriginalDocument->mLatestStaticClone = nullptr;
11526 if (IsStaticDocument()) {
11527 RemoveProperty(nsGkAtoms::printisfocuseddoc);
11528 RemoveProperty(nsGkAtoms::printselectionranges);
11531 // Shut down our external resource map. We might not need this for
11532 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
11533 // tearing down all those frame trees right now is the right thing to do.
11534 mExternalResourceMap.Shutdown();
11536 // Manually break cycles via promise's global object pointer.
11537 mReadyForIdle = nullptr;
11538 mOrientationPendingPromise = nullptr;
11540 // To break cycles.
11541 mPreloadService.ClearAllPreloads();
11543 if (mDocumentL10n) {
11544 mDocumentL10n->Destroy();
11548 void Document::RemovedFromDocShell() {
11549 mEditingState = EditingState::eOff;
11551 if (mRemovedFromDocShell) return;
11553 mRemovedFromDocShell = true;
11554 NotifyActivityChanged();
11556 for (nsIContent* child = GetFirstChild(); child;
11557 child = child->GetNextSibling()) {
11558 child->SaveSubtreeState();
11561 nsIDocShell* docShell = GetDocShell();
11562 if (docShell) {
11563 docShell->SynchronizeLayoutHistoryState();
11567 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
11568 const {
11569 nsCOMPtr<nsILayoutHistoryState> state;
11570 if (!mScriptGlobalObject) {
11571 state = mLayoutHistoryState;
11572 } else {
11573 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
11574 if (docShell) {
11575 docShell->GetLayoutHistoryState(getter_AddRefs(state));
11579 return state.forget();
11582 void Document::EnsureOnloadBlocker() {
11583 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11584 // -- it's not ours.
11585 if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
11586 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11587 if (loadGroup) {
11588 // Check first to see if mOnloadBlocker is in the loadgroup.
11589 nsCOMPtr<nsISimpleEnumerator> requests;
11590 loadGroup->GetRequests(getter_AddRefs(requests));
11592 bool hasMore = false;
11593 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11594 nsCOMPtr<nsISupports> elem;
11595 requests->GetNext(getter_AddRefs(elem));
11596 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11597 if (request && request == mOnloadBlocker) {
11598 return;
11602 // Not in the loadgroup, so add it.
11603 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11608 void Document::BlockOnload() {
11609 if (mDisplayDocument) {
11610 mDisplayDocument->BlockOnload();
11611 return;
11614 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11615 // -- it's not ours.
11616 if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
11617 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11618 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11621 ++mOnloadBlockCount;
11624 void Document::UnblockOnload(bool aFireSync) {
11625 if (mDisplayDocument) {
11626 mDisplayDocument->UnblockOnload(aFireSync);
11627 return;
11630 --mOnloadBlockCount;
11632 if (mOnloadBlockCount == 0) {
11633 if (mScriptGlobalObject) {
11634 // Only manipulate the loadgroup in this case, because if
11635 // mScriptGlobalObject is null, it's not ours.
11636 if (aFireSync) {
11637 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
11638 ++mOnloadBlockCount;
11639 DoUnblockOnload();
11640 } else {
11641 PostUnblockOnloadEvent();
11643 } else if (mIsBeingUsedAsImage) {
11644 // To correctly unblock onload for a document that contains an SVG
11645 // image, we need to know when all of the SVG document's resources are
11646 // done loading, in a way comparable to |window.onload|. We fire this
11647 // event to indicate that the SVG should be considered fully loaded.
11648 // Because scripting is disabled on SVG-as-image documents, this event
11649 // is not accessible to content authors. (See bug 837315.)
11650 RefPtr<AsyncEventDispatcher> asyncDispatcher =
11651 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
11652 CanBubble::eNo, ChromeOnlyDispatch::eNo);
11653 asyncDispatcher->PostDOMEvent();
11658 class nsUnblockOnloadEvent : public Runnable {
11659 public:
11660 explicit nsUnblockOnloadEvent(Document* aDoc)
11661 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
11662 NS_IMETHOD Run() override {
11663 mDoc->DoUnblockOnload();
11664 return NS_OK;
11667 private:
11668 RefPtr<Document> mDoc;
11671 void Document::PostUnblockOnloadEvent() {
11672 MOZ_RELEASE_ASSERT(NS_IsMainThread());
11673 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
11674 nsresult rv = Dispatch(TaskCategory::Other, evt.forget());
11675 if (NS_SUCCEEDED(rv)) {
11676 // Stabilize block count so we don't post more events while this one is up
11677 ++mOnloadBlockCount;
11678 } else {
11679 NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
11683 void Document::DoUnblockOnload() {
11684 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
11685 MOZ_ASSERT(mOnloadBlockCount != 0,
11686 "Shouldn't have a count of zero here, since we stabilized in "
11687 "PostUnblockOnloadEvent");
11689 --mOnloadBlockCount;
11691 if (mOnloadBlockCount != 0) {
11692 // We blocked again after the last unblock. Nothing to do here. We'll
11693 // post a new event when we unblock again.
11694 return;
11697 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11698 // -- it's not ours.
11699 if (mScriptGlobalObject) {
11700 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11701 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
11706 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
11707 for (nsIFrame* f = aFrame; f;
11708 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
11709 nsIContent* content = f->GetContent();
11710 if (!content) {
11711 continue;
11714 if (content->OwnerDoc() == this) {
11715 return content;
11717 // We must be in a subdocument so jump directly to the root frame.
11718 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
11719 // the containing document.
11720 f = f->PresContext()->GetPresShell()->GetRootFrame();
11723 return nullptr;
11726 void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
11727 const nsAString& aType, bool aInFrameSwap,
11728 bool aPersisted, bool aOnlySystemGroup) {
11729 if (!aDispatchTarget) {
11730 return;
11733 PageTransitionEventInit init;
11734 init.mBubbles = true;
11735 init.mCancelable = true;
11736 init.mPersisted = aPersisted;
11737 init.mInFrameSwap = aInFrameSwap;
11739 RefPtr<PageTransitionEvent> event =
11740 PageTransitionEvent::Constructor(this, aType, init);
11742 event->SetTrusted(true);
11743 event->SetTarget(this);
11744 if (aOnlySystemGroup) {
11745 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
11747 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
11748 nullptr);
11751 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
11752 bool aOnlySystemGroup) {
11753 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11754 nsCString uri;
11755 if (GetDocumentURI()) {
11756 uri = GetDocumentURI()->GetSpecOrDefault();
11758 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11759 ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
11762 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11763 MOZ_DIAGNOSTIC_ASSERT(
11764 inFrameLoaderSwap ==
11765 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11767 Element* root = GetRootElement();
11768 if (aPersisted && root) {
11769 // Send out notifications that our <link> elements are attached.
11770 RefPtr<nsContentList> links =
11771 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11773 uint32_t linkCount = links->Length(true);
11774 for (uint32_t i = 0; i < linkCount; ++i) {
11775 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
11779 // See Document
11780 if (!inFrameLoaderSwap) {
11781 if (aPersisted) {
11782 ImageTracker()->SetAnimatingState(true);
11785 // Set mIsShowing before firing events, in case those event handlers
11786 // move us around.
11787 mIsShowing = true;
11788 mVisible = true;
11790 UpdateVisibilityState();
11793 NotifyActivityChanged();
11795 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11796 aExternalResource.OnPageShow(aPersisted, nullptr);
11797 return CallState::Continue;
11799 EnumerateExternalResources(notifyExternal);
11801 if (mAnimationController) {
11802 mAnimationController->OnPageShow();
11805 if (!mIsBeingUsedAsImage) {
11806 // Dispatch observer notification to notify observers page is shown.
11807 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11808 if (os) {
11809 nsIPrincipal* principal = NodePrincipal();
11810 os->NotifyObservers(ToSupports(this),
11811 principal->IsSystemPrincipal() ? "chrome-page-shown"
11812 : "content-page-shown",
11813 nullptr);
11816 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11817 if (!target) {
11818 target = do_QueryInterface(GetWindow());
11820 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
11821 aPersisted, aOnlySystemGroup);
11825 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
11826 if (nsPresContext* presContext = aDocument.GetPresContext()) {
11827 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
11828 FullscreenEventType::Change, &aDocument, aTarget);
11829 presContext->RefreshDriver()->ScheduleFullscreenEvent(
11830 std::move(pendingEvent));
11834 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
11835 bool aOnlySystemGroup) {
11836 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11837 nsCString uri;
11838 if (GetDocumentURI()) {
11839 uri = GetDocumentURI()->GetSpecOrDefault();
11841 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11842 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
11845 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11846 MOZ_DIAGNOSTIC_ASSERT(
11847 inFrameLoaderSwap ==
11848 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11850 // Send out notifications that our <link> elements are detached,
11851 // but only if this is not a full unload.
11852 Element* root = GetRootElement();
11853 if (aPersisted && root) {
11854 RefPtr<nsContentList> links =
11855 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11857 uint32_t linkCount = links->Length(true);
11858 for (uint32_t i = 0; i < linkCount; ++i) {
11859 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved();
11863 if (mAnimationController) {
11864 mAnimationController->OnPageHide();
11867 if (!inFrameLoaderSwap) {
11868 if (aPersisted) {
11869 // We do not stop the animations (bug 1024343) when the page is refreshing
11870 // while being dragged out.
11871 ImageTracker()->SetAnimatingState(false);
11874 // Set mIsShowing before firing events, in case those event handlers
11875 // move us around.
11876 mIsShowing = false;
11877 mVisible = false;
11880 ExitPointerLock();
11882 if (!mIsBeingUsedAsImage) {
11883 // Dispatch observer notification to notify observers page is hidden.
11884 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11885 if (os) {
11886 nsIPrincipal* principal = NodePrincipal();
11887 os->NotifyObservers(ToSupports(this),
11888 principal->IsSystemPrincipal()
11889 ? "chrome-page-hidden"
11890 : "content-page-hidden",
11891 nullptr);
11894 // Now send out a PageHide event.
11895 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11896 if (!target) {
11897 target = do_QueryInterface(GetWindow());
11900 PageUnloadingEventTimeStamp timeStamp(this);
11901 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
11902 aPersisted, aOnlySystemGroup);
11906 if (!inFrameLoaderSwap) {
11907 UpdateVisibilityState();
11910 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11911 aExternalResource.OnPageHide(aPersisted, nullptr);
11912 return CallState::Continue;
11914 EnumerateExternalResources(notifyExternal);
11915 NotifyActivityChanged();
11917 ClearPendingFullscreenRequests(this);
11918 if (Fullscreen()) {
11919 // If this document was fullscreen, we should exit fullscreen in this
11920 // doctree branch. This ensures that if the user navigates while in
11921 // fullscreen mode we don't leave its still visible ancestor documents
11922 // in fullscreen mode. So exit fullscreen in the document's fullscreen
11923 // root document, as this will exit fullscreen in all the root's
11924 // descendant documents. Note that documents are removed from the
11925 // doctree by the time OnPageHide() is called, so we must store a
11926 // reference to the root (in Document::mFullscreenRoot) since we can't
11927 // just traverse the doctree to get the root.
11928 Document::ExitFullscreenInDocTree(this);
11930 // Since the document is removed from the doctree before OnPageHide() is
11931 // called, ExitFullscreen() can't traverse from the root down to *this*
11932 // document, so we must manually call CleanupFullscreenState() below too.
11933 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
11934 // so we *must* call it after ExitFullscreen(), not before.
11935 // OnPageHide() is called in every hidden (i.e. descendant) document,
11936 // so calling CleanupFullscreenState() here will ensure all hidden
11937 // documents have their fullscreen state reset.
11938 CleanupFullscreenState();
11940 // The fullscreenchange event is to be queued in the refresh driver,
11941 // however a hidden page wouldn't trigger that again, so it makes no
11942 // sense to dispatch such event here.
11946 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
11947 NS_ASSERTION(
11948 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
11949 "mSubtreeModifiedTargets not cleared after dispatching?");
11950 ++mSubtreeModifiedDepth;
11951 if (aTarget) {
11952 // MayDispatchMutationEvent is often called just before this method,
11953 // so it has already appended the node to mSubtreeModifiedTargets.
11954 int32_t count = mSubtreeModifiedTargets.Count();
11955 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
11956 mSubtreeModifiedTargets.AppendObject(aTarget);
11961 void Document::MutationEventDispatched(nsINode* aTarget) {
11962 if (--mSubtreeModifiedDepth) {
11963 return;
11966 int32_t count = mSubtreeModifiedTargets.Count();
11967 if (!count) {
11968 return;
11971 nsPIDOMWindowInner* window = GetInnerWindow();
11972 if (window &&
11973 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
11974 mSubtreeModifiedTargets.Clear();
11975 return;
11978 nsCOMArray<nsINode> realTargets;
11979 for (nsINode* possibleTarget : mSubtreeModifiedTargets) {
11980 if (possibleTarget->ChromeOnlyAccess()) {
11981 continue;
11984 nsINode* commonAncestor = nullptr;
11985 int32_t realTargetCount = realTargets.Count();
11986 for (int32_t j = 0; j < realTargetCount; ++j) {
11987 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
11988 possibleTarget, realTargets[j]);
11989 if (commonAncestor) {
11990 realTargets.ReplaceObjectAt(commonAncestor, j);
11991 break;
11994 if (!commonAncestor) {
11995 realTargets.AppendObject(possibleTarget);
11999 mSubtreeModifiedTargets.Clear();
12001 for (const nsCOMPtr<nsINode>& target : realTargets) {
12002 InternalMutationEvent mutation(true, eLegacySubtreeModified);
12003 // MOZ_KnownLive due to bug 1620312
12004 AsyncEventDispatcher::RunDOMEventWhenSafe(MOZ_KnownLive(*target), mutation);
12008 void Document::DestroyElementMaps() {
12009 #ifdef DEBUG
12010 mStyledLinksCleared = true;
12011 #endif
12012 mStyledLinks.Clear();
12013 // Notify ID change listeners before clearing the identifier map.
12014 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
12015 iter.Get()->ClearAndNotify();
12017 mIdentifierMap.Clear();
12018 mComposedShadowRoots.Clear();
12019 mResponsiveContent.Clear();
12020 IncrementExpandoGeneration(*this);
12023 void Document::RefreshLinkHrefs() {
12024 // Get a list of all links we know about. We will reset them, which will
12025 // remove them from the document, so we need a copy of what is in the
12026 // hashtable.
12027 const nsTArray<Link*> linksToNotify = ToArray(mStyledLinks);
12029 // Reset all of our styled links.
12030 nsAutoScriptBlocker scriptBlocker;
12031 for (Link* link : linksToNotify) {
12032 link->ResetLinkState(true);
12036 nsresult Document::CloneDocHelper(Document* clone) const {
12037 clone->mIsStaticDocument = mCreatingStaticClone;
12039 // Init document
12040 nsresult rv = clone->Init(NodePrincipal(), mPartitionedPrincipal);
12041 NS_ENSURE_SUCCESS(rv, rv);
12043 if (mCreatingStaticClone) {
12044 if (mOriginalDocument) {
12045 clone->mOriginalDocument = mOriginalDocument;
12046 } else {
12047 clone->mOriginalDocument = const_cast<Document*>(this);
12049 clone->mOriginalDocument->mLatestStaticClone = clone;
12050 clone->mOriginalDocument->mStaticCloneCount++;
12052 nsCOMPtr<nsILoadGroup> loadGroup;
12054 // |mDocumentContainer| is the container of the document that is being
12055 // created and not the original container. See CreateStaticClone function().
12056 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
12057 if (docLoader) {
12058 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
12060 nsCOMPtr<nsIChannel> channel = GetChannel();
12061 nsCOMPtr<nsIURI> uri;
12062 if (channel) {
12063 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
12064 } else {
12065 uri = Document::GetDocumentURI();
12067 clone->mChannel = channel;
12068 clone->mShouldResistFingerprinting = mShouldResistFingerprinting;
12069 if (uri) {
12070 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
12073 clone->mIsSrcdocDocument = mIsSrcdocDocument;
12074 clone->SetContainer(mDocumentContainer);
12076 // Setup the navigation time. This will be needed by any animations in the
12077 // document, even if they are only paused.
12078 MOZ_ASSERT(!clone->GetNavigationTiming(),
12079 "Navigation time was already set?");
12080 if (mTiming) {
12081 RefPtr<nsDOMNavigationTiming> timing =
12082 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
12083 clone->SetNavigationTiming(timing);
12085 clone->SetCsp(mCSP);
12088 // Now ensure that our clone has the same URI, base URI, and principal as us.
12089 // We do this after the mCreatingStaticClone block above, because that block
12090 // can set the base URI to an incorrect value in cases when base URI
12091 // information came from the channel. So we override explicitly, and do it
12092 // for all these properties, in case ResetToURI messes with any of the rest of
12093 // them.
12094 clone->SetDocumentURI(Document::GetDocumentURI());
12095 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
12096 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
12097 clone->mActiveCookiePrincipal = mActiveCookiePrincipal;
12098 // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
12099 // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
12100 // when printed standalone via window.print() (where there won't be a parent
12101 // document to grab the URI from).
12102 clone->mDocumentBaseURI = GetDocBaseURI();
12103 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
12104 clone->mReferrerInfo =
12105 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
12106 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
12108 bool hasHadScriptObject = true;
12109 nsIScriptGlobalObject* scriptObject =
12110 GetScriptHandlingObject(hasHadScriptObject);
12111 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
12112 if (mCreatingStaticClone) {
12113 // If we're doing a static clone (print, print preview), then we're going to
12114 // be setting a scope object after the clone. It's better to set it only
12115 // once, so we don't do that here. However, we do want to act as if there is
12116 // a script handling object. So we set mHasHadScriptHandlingObject.
12117 clone->mHasHadScriptHandlingObject = true;
12118 } else if (scriptObject) {
12119 clone->SetScriptHandlingObject(scriptObject);
12120 } else {
12121 clone->SetScopeObject(GetScopeObject());
12123 // Make the clone a data document
12124 clone->SetLoadedAsData(
12125 true,
12126 /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
12128 // Misc state
12130 // State from Document
12131 clone->mCharacterSet = mCharacterSet;
12132 clone->mCharacterSetSource = mCharacterSetSource;
12133 clone->SetCompatibilityMode(mCompatMode);
12134 clone->mBidiOptions = mBidiOptions;
12135 clone->mContentLanguage = mContentLanguage;
12136 clone->SetContentType(GetContentTypeInternal());
12137 clone->mSecurityInfo = mSecurityInfo;
12139 // State from Document
12140 clone->mType = mType;
12141 clone->mXMLDeclarationBits = mXMLDeclarationBits;
12142 clone->mBaseTarget = mBaseTarget;
12144 return NS_OK;
12147 void Document::NotifyLoading(bool aNewParentIsLoading,
12148 const ReadyState& aCurrentState,
12149 ReadyState aNewState) {
12150 // Mirror the top-level loading state down to all subdocuments
12151 bool was_loading = mAncestorIsLoading ||
12152 aCurrentState == READYSTATE_LOADING ||
12153 aCurrentState == READYSTATE_INTERACTIVE;
12154 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
12155 aNewState == READYSTATE_INTERACTIVE; // new value for state
12156 bool set_load_state = was_loading != is_loading;
12158 MOZ_LOG(
12159 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12160 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
12161 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
12162 "set_load_state: %d",
12163 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
12164 (int)aNewState, was_loading, is_loading, set_load_state));
12166 mAncestorIsLoading = aNewParentIsLoading;
12167 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
12168 // Tell our innerwindow (and thus TimeoutManager)
12169 nsPIDOMWindowInner* inner = GetInnerWindow();
12170 if (inner) {
12171 inner->SetActiveLoadingState(is_loading);
12173 BrowsingContext* context = GetBrowsingContext();
12174 if (context) {
12175 // Don't use PreOrderWalk to mirror this down; go down one level as a
12176 // time so we can set mAncestorIsLoading and take into account the
12177 // readystates of the subdocument. In the child process it will call
12178 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
12179 // iterate it's children
12180 for (auto& child : context->Children()) {
12181 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12182 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
12183 // Setting ancestor loading on a discarded browsing context has no
12184 // effect.
12185 Unused << child->SetAncestorLoading(is_loading);
12191 void Document::SetReadyStateInternal(ReadyState aReadyState,
12192 bool aUpdateTimingInformation) {
12193 if (aReadyState == READYSTATE_UNINITIALIZED) {
12194 // Transition back to uninitialized happens only to keep assertions happy
12195 // right before readyState transitions to something else. Make this
12196 // transition undetectable by Web content.
12197 mReadyState = aReadyState;
12198 return;
12201 if (IsTopLevelContentDocument()) {
12202 if (aReadyState == READYSTATE_LOADING) {
12203 AddToplevelLoadingDocument(this);
12204 } else if (aReadyState == READYSTATE_COMPLETE) {
12205 RemoveToplevelLoadingDocument(this);
12209 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
12210 SetLoadingOrRestoredFromBFCacheTimeStampToNow();
12212 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
12213 mReadyState = aReadyState;
12214 if (aUpdateTimingInformation && mTiming) {
12215 switch (aReadyState) {
12216 case READYSTATE_LOADING:
12217 mTiming->NotifyDOMLoading(GetDocumentURI());
12218 break;
12219 case READYSTATE_INTERACTIVE:
12220 mTiming->NotifyDOMInteractive(GetDocumentURI());
12221 break;
12222 case READYSTATE_COMPLETE:
12223 mTiming->NotifyDOMComplete(GetDocumentURI());
12224 break;
12225 default:
12226 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
12227 break;
12230 // At the time of loading start, we don't have timing object, record time.
12232 if (READYSTATE_INTERACTIVE == aReadyState &&
12233 NodePrincipal()->IsSystemPrincipal()) {
12234 if (!mXULPersist) {
12235 mXULPersist = new XULPersist(this);
12236 mXULPersist->Init();
12238 if (!mChromeObserver) {
12239 mChromeObserver = new ChromeObserver(this);
12240 mChromeObserver->Init();
12244 if (aUpdateTimingInformation) {
12245 RecordNavigationTiming(aReadyState);
12248 AsyncEventDispatcher::RunDOMEventWhenSafe(
12249 *this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
12252 void Document::GetReadyState(nsAString& aReadyState) const {
12253 switch (mReadyState) {
12254 case READYSTATE_LOADING:
12255 aReadyState.AssignLiteral(u"loading");
12256 break;
12257 case READYSTATE_INTERACTIVE:
12258 aReadyState.AssignLiteral(u"interactive");
12259 break;
12260 case READYSTATE_COMPLETE:
12261 aReadyState.AssignLiteral(u"complete");
12262 break;
12263 default:
12264 aReadyState.AssignLiteral(u"uninitialized");
12268 void Document::SuppressEventHandling(uint32_t aIncrease) {
12269 mEventsSuppressed += aIncrease;
12270 if (mEventsSuppressed == aIncrease) {
12271 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
12272 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12275 UpdateFrameRequestCallbackSchedulingState();
12276 for (uint32_t i = 0; i < aIncrease; ++i) {
12277 ScriptLoader()->AddExecuteBlocker();
12280 auto suppressInSubDoc = [aIncrease](Document& aSubDoc) {
12281 aSubDoc.SuppressEventHandling(aIncrease);
12282 return CallState::Continue;
12285 EnumerateSubDocuments(suppressInSubDoc);
12288 void Document::NotifyAbortedLoad() {
12289 // If we still have outstanding work blocking DOMContentLoaded,
12290 // then don't try to change the readystate now, but wait until
12291 // they finish and then do so.
12292 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
12293 mSetCompleteAfterDOMContentLoaded = true;
12294 return;
12297 // Otherwise we're fully done at this point, so set the
12298 // readystate to complete.
12299 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
12300 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
12304 MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents(
12305 nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) {
12306 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
12307 if (MOZ_UNLIKELY(!fm)) {
12308 return;
12311 nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments);
12312 for (uint32_t i = 0; i < documents.Length(); ++i) {
12313 nsCOMPtr<Document> document = std::move(documents[i]);
12314 // NB: Don't bother trying to fire delayed events on documents that were
12315 // closed before this event ran.
12316 if (!document->EventHandlingSuppressed()) {
12317 fm->FireDelayedEvents(document);
12318 RefPtr<PresShell> presShell = document->GetPresShell();
12319 if (presShell) {
12320 // Only fire events for active documents.
12321 bool fire = aFireEvents && document->GetInnerWindow() &&
12322 document->GetInnerWindow()->IsCurrentInnerWindow();
12323 presShell->FireOrClearDelayedEvents(fire);
12325 document->FireOrClearPostMessageEvents(aFireEvents);
12330 void Document::PreloadPictureClosed() {
12331 MOZ_ASSERT(mPreloadPictureDepth > 0);
12332 mPreloadPictureDepth--;
12333 if (mPreloadPictureDepth == 0) {
12334 mPreloadPictureFoundSource.SetIsVoid(true);
12338 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
12339 const nsAString& aSizesAttr,
12340 const nsAString& aTypeAttr,
12341 const nsAString& aMediaAttr) {
12342 // Nested pictures are not valid syntax, so while we'll eventually load them,
12343 // it's not worth tracking sources mixed between nesting levels to preload
12344 // them effectively.
12345 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
12346 // <picture> selects the first matching source, so if this returns a URI we
12347 // needn't consider new sources until a new <picture> is encountered.
12348 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
12349 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
12350 aMediaAttr, mPreloadPictureFoundSource);
12351 if (found && mPreloadPictureFoundSource.IsVoid()) {
12352 // Found an empty source, which counts
12353 mPreloadPictureFoundSource.SetIsVoid(false);
12358 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
12359 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
12360 const nsAString& aSizesAttr, bool* aIsImgSet) {
12361 nsString sourceURL;
12362 bool isImgSet;
12363 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
12364 // We're in a <picture> element and found a URI from a source previous to
12365 // this image, use it.
12366 sourceURL = mPreloadPictureFoundSource;
12367 isImgSet = true;
12368 } else {
12369 // Otherwise try to use this <img> as a source
12370 HTMLImageElement::SelectSourceForTagWithAttrs(
12371 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
12372 VoidString(), sourceURL);
12373 isImgSet = !aSrcsetAttr.IsEmpty();
12376 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
12377 if (sourceURL.IsEmpty()) {
12378 return nullptr;
12381 // Construct into URI using passed baseURI (the parser may know of base URI
12382 // changes that have not reached us)
12383 nsresult rv;
12384 nsCOMPtr<nsIURI> uri;
12385 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
12386 this, aBaseURI);
12387 if (NS_FAILED(rv)) {
12388 return nullptr;
12391 *aIsImgSet = isImgSet;
12393 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
12394 // this this <picture> share the same <sources> (though this is not valid per
12395 // spec)
12396 return uri.forget();
12399 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
12400 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
12401 bool aLinkPreload, uint64_t aEarlyHintPreloaderId) {
12402 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
12403 nsContentUtils::CORSModeToLoadImageFlags(
12404 Element::StringToCORSMode(aCrossOriginAttr));
12406 nsContentPolicyType policyType =
12407 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
12408 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
12410 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12411 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12413 RefPtr<imgRequestProxy> request;
12415 nsLiteralString initiator = aEarlyHintPreloaderId
12416 ? u"early-hints"_ns
12417 : (aLinkPreload ? u"link"_ns : u"img"_ns);
12419 nsresult rv = nsContentUtils::LoadImage(
12420 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
12421 nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request),
12422 policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId);
12424 // Pin image-reference to avoid evicting it from the img-cache before
12425 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
12426 // unlink
12427 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
12428 mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
12432 void Document::MaybePreLoadImage(nsIURI* aUri,
12433 const nsAString& aCrossOriginAttr,
12434 ReferrerPolicyEnum aReferrerPolicy,
12435 bool aIsImgSet, bool aLinkPreload) {
12436 const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr);
12437 if (aLinkPreload) {
12438 // Check if the image was already preloaded in this document to avoid
12439 // duplicate preloading.
12440 PreloadHashKey key =
12441 PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode);
12442 if (!mPreloadService.PreloadExists(key)) {
12443 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12444 aLinkPreload, 0);
12446 return;
12449 // Early exit if the img is already present in the img-cache
12450 // which indicates that the "real" load has already started and
12451 // that we shouldn't preload it.
12452 if (nsContentUtils::IsImageAvailable(aUri, NodePrincipal(), corsMode, this)) {
12453 return;
12456 // Image not in cache - trigger preload
12457 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload,
12461 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
12462 if (!StaticPrefs::network_preconnect()) {
12463 return;
12466 NS_MutateURI mutator(aOrigURI);
12467 if (NS_FAILED(mutator.GetStatus())) {
12468 return;
12471 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
12472 // which ignores the path and uses only the origin. The other is for the
12473 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
12474 // non-Anonymous preconnects create different connections on the wire and
12475 // therefore should not be considred duplicates of each other and we
12476 // normalize the path before putting it in the hash to accomplish that.
12478 if (aCORSMode == CORS_ANONYMOUS) {
12479 mutator.SetPathQueryRef("/anonymous"_ns);
12480 } else {
12481 mutator.SetPathQueryRef("/"_ns);
12484 nsCOMPtr<nsIURI> uri;
12485 nsresult rv = mutator.Finalize(uri);
12486 if (NS_FAILED(rv)) {
12487 return;
12490 const bool existingEntryFound =
12491 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
12492 if (entry) {
12493 return true;
12495 entry.Insert(true);
12496 return false;
12498 if (existingEntryFound) {
12499 return;
12502 nsCOMPtr<nsISpeculativeConnect> speculator =
12503 mozilla::components::IO::Service();
12504 if (!speculator) {
12505 return;
12508 OriginAttributes oa;
12509 StoragePrincipalHelper::GetOriginAttributesForNetworkState(this, oa);
12510 speculator->SpeculativeConnectWithOriginAttributesNative(
12511 uri, std::move(oa), nullptr, aCORSMode == CORS_ANONYMOUS);
12514 void Document::ForgetImagePreload(nsIURI* aURI) {
12515 // Checking count is faster than hashing the URI in the common
12516 // case of empty table.
12517 if (mPreloadingImages.Count() != 0) {
12518 nsCOMPtr<imgIRequest> req;
12519 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
12520 if (req) {
12521 // Make sure to cancel the request so imagelib knows it's gone.
12522 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
12527 void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates,
12528 bool aNotify) {
12529 const DocumentState oldStates = mDocumentState;
12530 if (aMaybeChangedStates.HasAtLeastOneOfStates(
12531 DocumentState::ALL_LOCALEDIR_BITS)) {
12532 mDocumentState &= ~DocumentState::ALL_LOCALEDIR_BITS;
12533 if (IsDocumentRightToLeft()) {
12534 mDocumentState |= DocumentState::RTL_LOCALE;
12535 } else {
12536 mDocumentState |= DocumentState::LTR_LOCALE;
12540 if (aMaybeChangedStates.HasAtLeastOneOfStates(DocumentState::LWTHEME)) {
12541 if (ComputeDocumentLWTheme()) {
12542 mDocumentState |= DocumentState::LWTHEME;
12543 } else {
12544 mDocumentState &= ~DocumentState::LWTHEME;
12548 if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) {
12549 if (IsTopLevelWindowInactive()) {
12550 mDocumentState |= DocumentState::WINDOW_INACTIVE;
12551 } else {
12552 mDocumentState &= ~DocumentState::WINDOW_INACTIVE;
12556 const DocumentState changedStates = oldStates ^ mDocumentState;
12557 if (aNotify && !changedStates.IsEmpty()) {
12558 if (PresShell* ps = GetObservingPresShell()) {
12559 ps->DocumentStatesChanged(changedStates);
12564 namespace {
12567 * Stub for LoadSheet(), since all we want is to get the sheet into
12568 * the CSSLoader's style cache
12570 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
12571 ~StubCSSLoaderObserver() = default;
12573 public:
12574 NS_IMETHOD
12575 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
12576 NS_DECL_ISUPPORTS
12578 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
12580 } // namespace
12582 SheetPreloadStatus Document::PreloadStyle(
12583 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
12584 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aNonce,
12585 const nsAString& aIntegrity, css::StylePreloadKind aKind,
12586 uint64_t aEarlyHintPreloaderId) {
12587 MOZ_ASSERT(aKind != css::StylePreloadKind::None);
12589 // The CSSLoader will retain this object after we return.
12590 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
12592 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12593 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12595 // Charset names are always ASCII.
12596 auto result = CSSLoader()->LoadSheet(
12597 uri, aKind, aEncoding, referrerInfo, obs, aEarlyHintPreloaderId,
12598 Element::StringToCORSMode(aCrossOriginAttr), aNonce, aIntegrity);
12599 if (result.isErr()) {
12600 return SheetPreloadStatus::Errored;
12602 RefPtr<StyleSheet> sheet = result.unwrap();
12603 if (sheet->IsComplete()) {
12604 return SheetPreloadStatus::AlreadyComplete;
12606 return SheetPreloadStatus::InProgress;
12609 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
12610 return CSSLoader()
12611 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
12612 .unwrapOr(nullptr);
12615 void Document::ResetDocumentDirection() {
12616 if (!nsContentUtils::IsChromeDoc(this)) {
12617 return;
12619 UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true);
12622 bool Document::IsDocumentRightToLeft() {
12623 if (!nsContentUtils::IsChromeDoc(this)) {
12624 return false;
12626 // setting the localedir attribute on the root element forces a
12627 // specific direction for the document.
12628 Element* element = GetRootElement();
12629 if (element) {
12630 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
12631 nullptr};
12632 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
12633 strings, eCaseMatters)) {
12634 case 0:
12635 return false;
12636 case 1:
12637 return true;
12638 default:
12639 break; // otherwise, not a valid value, so fall through
12643 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
12644 !mDocumentURI->SchemeIs("resource")) {
12645 return false;
12648 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
12651 class nsDelayedEventDispatcher : public Runnable {
12652 public:
12653 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
12654 : mozilla::Runnable("nsDelayedEventDispatcher"),
12655 mDocuments(std::move(aDocuments)) {}
12656 virtual ~nsDelayedEventDispatcher() = default;
12658 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
12659 // bug 1535398.
12660 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
12661 FireOrClearDelayedEvents(std::move(mDocuments), true);
12662 return NS_OK;
12665 private:
12666 nsTArray<nsCOMPtr<Document>> mDocuments;
12669 static void GetAndUnsuppressSubDocuments(
12670 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
12671 if (aDocument.EventHandlingSuppressed() > 0) {
12672 aDocument.DecreaseEventSuppression();
12673 aDocument.ScriptLoader()->RemoveExecuteBlocker();
12675 aDocuments.AppendElement(&aDocument);
12676 auto recurse = [&aDocuments](Document& aSubDoc) {
12677 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
12678 return CallState::Continue;
12680 aDocument.EnumerateSubDocuments(recurse);
12683 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
12684 nsTArray<nsCOMPtr<Document>> documents;
12685 GetAndUnsuppressSubDocuments(*this, documents);
12687 for (nsCOMPtr<Document>& doc : documents) {
12688 if (!doc->EventHandlingSuppressed()) {
12689 if (WindowGlobalChild* wgc = doc->GetWindowGlobalChild()) {
12690 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12693 MOZ_ASSERT(NS_IsMainThread());
12694 nsTArray<RefPtr<net::ChannelEventQueue>> queues =
12695 std::move(doc->mSuspendedQueues);
12696 for (net::ChannelEventQueue* queue : queues) {
12697 queue->Resume();
12700 // If there have been any events driven by the refresh driver which were
12701 // delayed due to events being suppressed in this document, make sure
12702 // there is a refresh scheduled soon so the events will run.
12703 if (doc->mHasDelayedRefreshEvent) {
12704 doc->mHasDelayedRefreshEvent = false;
12706 if (doc->mPresShell) {
12707 nsRefreshDriver* rd =
12708 doc->mPresShell->GetPresContext()->RefreshDriver();
12709 rd->RunDelayedEventsSoon();
12715 if (aFireEvents) {
12716 MOZ_RELEASE_ASSERT(NS_IsMainThread());
12717 nsCOMPtr<nsIRunnable> ded =
12718 new nsDelayedEventDispatcher(std::move(documents));
12719 Dispatch(TaskCategory::Other, ded.forget());
12720 } else {
12721 FireOrClearDelayedEvents(std::move(documents), false);
12725 bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
12726 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
12729 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
12730 MOZ_ASSERT(NS_IsMainThread());
12731 MOZ_ASSERT(EventHandlingSuppressed());
12732 mSuspendedQueues.AppendElement(aQueue);
12735 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
12736 MOZ_ASSERT(NS_IsMainThread());
12738 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
12739 mSuspendedPostMessageEvents.AppendElement(aEvent);
12740 return true;
12742 return false;
12745 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
12746 nsTArray<RefPtr<PostMessageEvent>> events =
12747 std::move(mSuspendedPostMessageEvents);
12749 if (aFireEvents) {
12750 for (PostMessageEvent* event : events) {
12751 event->Run();
12756 void Document::SetSuppressedEventListener(EventListener* aListener) {
12757 mSuppressedEventListener = aListener;
12758 auto setOnSubDocs = [&](Document& aDocument) {
12759 aDocument.SetSuppressedEventListener(aListener);
12760 return CallState::Continue;
12762 EnumerateSubDocuments(setOnSubDocs);
12765 bool Document::IsActive() const {
12766 return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
12767 !GetBrowsingContext()->IsInBFCache();
12770 nsISupports* Document::GetCurrentContentSink() {
12771 return mParser ? mParser->GetContentSink() : nullptr;
12774 Document* Document::GetTemplateContentsOwner() {
12775 if (!mTemplateContentsOwner) {
12776 bool hasHadScriptObject = true;
12777 nsIScriptGlobalObject* scriptObject =
12778 GetScriptHandlingObject(hasHadScriptObject);
12780 nsCOMPtr<Document> document;
12781 nsresult rv = NS_NewDOMDocument(
12782 getter_AddRefs(document),
12783 u""_ns, // aNamespaceURI
12784 u""_ns, // aQualifiedName
12785 nullptr, // aDoctype
12786 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
12787 true, // aLoadedAsData
12788 scriptObject, // aEventObject
12789 IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
12790 NS_ENSURE_SUCCESS(rv, nullptr);
12792 mTemplateContentsOwner = document;
12793 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
12795 if (!scriptObject) {
12796 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
12799 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
12801 // Set |mTemplateContentsOwner| as the template contents owner of itself so
12802 // that it is the template contents owner of nested template elements.
12803 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
12806 MOZ_ASSERT(mTemplateContentsOwner->IsTemplateContentsOwner());
12807 return mTemplateContentsOwner;
12810 // https://html.spec.whatwg.org/#the-autofocus-attribute
12811 void Document::ElementWithAutoFocusInserted(Element* aAutoFocusCandidate) {
12812 BrowsingContext* bc = GetBrowsingContext();
12813 if (!bc) {
12814 return;
12817 // If target is not fully active, then return.
12818 if (!IsCurrentActiveDocument()) {
12819 return;
12822 // If target's active sandboxing flag set has the sandboxed automatic features
12823 // browsing context flag, then return.
12824 if (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES) {
12825 return;
12828 // For each ancestorBC of target's browsing context's ancestor browsing
12829 // contexts: if ancestorBC's active document's origin is not same origin with
12830 // target's origin, then return.
12831 while (bc) {
12832 BrowsingContext* parent = bc->GetParent();
12833 if (!parent) {
12834 break;
12836 // AncestorBC is not the same site
12837 if (!parent->IsInProcess()) {
12838 return;
12841 Document* currentDocument = bc->GetDocument();
12842 if (!currentDocument) {
12843 return;
12846 Document* parentDocument = parent->GetDocument();
12847 if (!parentDocument) {
12848 return;
12851 // Not same origin
12852 if (!currentDocument->NodePrincipal()->Equals(
12853 parentDocument->NodePrincipal())) {
12854 return;
12857 bc = parent;
12859 MOZ_ASSERT(bc->IsTop());
12861 Document* topDocument = bc->GetDocument();
12862 MOZ_ASSERT(topDocument);
12863 topDocument->AppendAutoFocusCandidateToTopDocument(aAutoFocusCandidate);
12866 void Document::ScheduleFlushAutoFocusCandidates() {
12867 MOZ_ASSERT(mPresShell && mPresShell->DidInitialize());
12868 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12869 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
12870 rd->ScheduleAutoFocusFlush(this);
12874 void Document::AppendAutoFocusCandidateToTopDocument(
12875 Element* aAutoFocusCandidate) {
12876 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12877 if (mAutoFocusFired) {
12878 return;
12881 if (!HasAutoFocusCandidates()) {
12882 // PresShell may be initialized later
12883 if (mPresShell && mPresShell->DidInitialize()) {
12884 ScheduleFlushAutoFocusCandidates();
12888 nsWeakPtr element = do_GetWeakReference(aAutoFocusCandidate);
12889 mAutoFocusCandidates.RemoveElement(element);
12890 mAutoFocusCandidates.AppendElement(element);
12893 void Document::SetAutoFocusFired() {
12894 mAutoFocusCandidates.Clear();
12895 mAutoFocusFired = true;
12898 // https://html.spec.whatwg.org/#flush-autofocus-candidates
12899 void Document::FlushAutoFocusCandidates() {
12900 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12901 if (mAutoFocusFired) {
12902 return;
12905 if (!mPresShell) {
12906 return;
12909 MOZ_ASSERT(HasAutoFocusCandidates());
12910 MOZ_ASSERT(mPresShell->DidInitialize());
12912 nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetWindow();
12913 // We should be the top document
12914 if (!topWindow) {
12915 return;
12918 #ifdef DEBUG
12920 // Trying to find the top window (equivalent to window.top).
12921 nsCOMPtr<nsPIDOMWindowOuter> top = topWindow->GetInProcessTop();
12922 MOZ_ASSERT(topWindow == top);
12924 #endif
12926 // Don't steal the focus from the user
12927 if (topWindow->GetFocusedElement()) {
12928 SetAutoFocusFired();
12929 return;
12932 MOZ_ASSERT(mDocumentURI);
12933 nsAutoCString ref;
12934 // GetRef never fails
12935 nsresult rv = mDocumentURI->GetRef(ref);
12936 if (NS_SUCCEEDED(rv) &&
12937 nsContentUtils::GetTargetElement(this, NS_ConvertUTF8toUTF16(ref))) {
12938 SetAutoFocusFired();
12939 return;
12942 nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mAutoFocusCandidates);
12943 while (iter.HasMore()) {
12944 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(iter.GetNext());
12945 if (!autoFocusElement) {
12946 continue;
12948 RefPtr<Document> autoFocusElementDoc = autoFocusElement->OwnerDoc();
12949 // Get the latest info about the frame and allow scripts
12950 // to run which might affect the focusability of this element.
12951 autoFocusElementDoc->FlushPendingNotifications(FlushType::Frames);
12953 // Above layout flush may cause the PresShell to disappear.
12954 if (!mPresShell) {
12955 return;
12958 // Re-get the element because the ownerDoc() might have changed
12959 autoFocusElementDoc = autoFocusElement->OwnerDoc();
12960 BrowsingContext* bc = autoFocusElementDoc->GetBrowsingContext();
12961 if (!bc) {
12962 continue;
12965 // If doc is not fully active, then remove element from candidates, and
12966 // continue.
12967 if (!autoFocusElementDoc->IsCurrentActiveDocument()) {
12968 iter.Remove();
12969 continue;
12972 nsCOMPtr<nsIContentSink> sink =
12973 do_QueryInterface(autoFocusElementDoc->GetCurrentContentSink());
12974 if (sink) {
12975 nsHtml5TreeOpExecutor* executor =
12976 static_cast<nsHtml5TreeOpExecutor*>(sink->AsExecutor());
12977 if (executor) {
12978 // This is a HTML5 document
12979 MOZ_ASSERT(autoFocusElementDoc->IsHTMLDocument());
12980 // If doc's script-blocking style sheet counter is greater than 0, th
12981 // return.
12982 if (executor->WaitForPendingSheets()) {
12983 // In this case, element is the currently-best candidate, but doc is
12984 // not ready for autofocusing. We'll try again next time flush
12985 // autofocus candidates is called.
12986 ScheduleFlushAutoFocusCandidates();
12987 return;
12992 // The autofocus element could be moved to a different
12993 // top level BC.
12994 if (bc->Top()->GetDocument() != this) {
12995 continue;
12998 iter.Remove();
13000 // Let inclusiveAncestorDocuments be a list consisting of doc, plus the
13001 // active documents of each of doc's browsing context's ancestor browsing
13002 // contexts.
13003 // If any Document in inclusiveAncestorDocuments has non-null target
13004 // element, then continue.
13005 bool shouldFocus = true;
13006 while (bc) {
13007 Document* doc = bc->GetDocument();
13008 if (!doc) {
13009 shouldFocus = false;
13010 break;
13013 nsIURI* uri = doc->GetDocumentURI();
13014 if (!uri) {
13015 shouldFocus = false;
13016 break;
13019 nsAutoCString ref;
13020 nsresult rv = uri->GetRef(ref);
13021 // If there is an element in the document tree that has an ID equal to
13022 // fragment
13023 if (NS_SUCCEEDED(rv) &&
13024 nsContentUtils::GetTargetElement(doc, NS_ConvertUTF8toUTF16(ref))) {
13025 shouldFocus = false;
13026 break;
13028 bc = bc->GetParent();
13031 if (!shouldFocus) {
13032 continue;
13035 MOZ_ASSERT(topWindow);
13036 if (TryAutoFocusCandidate(*autoFocusElement)) {
13037 // We've successfully autofocused an element, don't
13038 // need to try to focus the rest.
13039 SetAutoFocusFired();
13040 break;
13044 if (HasAutoFocusCandidates()) {
13045 ScheduleFlushAutoFocusCandidates();
13049 bool Document::TryAutoFocusCandidate(Element& aElement) {
13050 const FocusOptions options;
13051 if (RefPtr<Element> target = nsFocusManager::GetTheFocusableArea(
13052 &aElement, nsFocusManager::ProgrammaticFocusFlags(options))) {
13053 target->Focus(options, CallerType::NonSystem, IgnoreErrors());
13054 return true;
13057 return false;
13060 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
13061 if (!aDocumentURI) {
13062 return;
13065 nsAutoCString ref;
13067 // Since all URI's that pass through here aren't URL's we can't
13068 // rely on the nsIURI implementation for providing a way for
13069 // finding the 'ref' part of the URI, we'll haveto revert to
13070 // string routines for finding the data past '#'
13072 nsresult rv = aDocumentURI->GetSpec(ref);
13073 if (NS_FAILED(rv)) {
13074 Unused << aDocumentURI->GetRef(mScrollToRef);
13075 return;
13078 nsReadingIterator<char> start, end;
13080 ref.BeginReading(start);
13081 ref.EndReading(end);
13083 if (FindCharInReadable('#', start, end)) {
13084 ++start; // Skip over the '#'
13086 mScrollToRef = Substring(start, end);
13090 // https://html.spec.whatwg.org/#scrolling-to-a-fragment
13091 void Document::ScrollToRef() {
13092 if (mScrolledToRefAlready) {
13093 RefPtr<PresShell> presShell = GetPresShell();
13094 if (presShell) {
13095 presShell->ScrollToAnchor();
13097 return;
13100 // 2. If fragment is the empty string, then return the special value top of
13101 // the document.
13102 if (mScrollToRef.IsEmpty()) {
13103 return;
13106 RefPtr<PresShell> presShell = GetPresShell();
13107 if (!presShell) {
13108 return;
13111 // 3. Let potentialIndicatedElement be the result of finding a potential
13112 // indicated element given document and fragment.
13113 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
13114 auto rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
13116 // 4. If potentialIndicatedElement is not null, then return
13117 // potentialIndicatedElement.
13118 if (NS_SUCCEEDED(rv)) {
13119 mScrolledToRefAlready = true;
13120 return;
13123 // 5. Let fragmentBytes be the result of percent-decoding fragment.
13124 nsAutoCString fragmentBytes;
13125 const bool unescaped =
13126 NS_UnescapeURL(mScrollToRef.Data(), mScrollToRef.Length(),
13127 /* aFlags = */ 0, fragmentBytes);
13129 if (!unescaped || fragmentBytes.IsEmpty()) {
13130 // Another attempt is only necessary if characters were unescaped.
13131 return;
13134 // 6. Let decodedFragment be the result of running UTF-8 decode without BOM on
13135 // fragmentBytes.
13136 nsAutoString decodedFragment;
13137 rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(fragmentBytes, decodedFragment);
13138 NS_ENSURE_SUCCESS_VOID(rv);
13140 // 7. Set potentialIndicatedElement to the result of finding a potential
13141 // indicated element given document and decodedFragment.
13142 rv = presShell->GoToAnchor(decodedFragment,
13143 mChangeScrollPosWhenScrollingToRef);
13144 if (NS_SUCCEEDED(rv)) {
13145 mScrolledToRefAlready = true;
13149 void Document::RegisterActivityObserver(nsISupports* aSupports) {
13150 if (!mActivityObservers) {
13151 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
13153 mActivityObservers->Insert(aSupports);
13156 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
13157 if (!mActivityObservers) {
13158 return false;
13160 return mActivityObservers->EnsureRemoved(aSupports);
13163 void Document::EnumerateActivityObservers(
13164 ActivityObserverEnumerator aEnumerator) {
13165 if (!mActivityObservers) {
13166 return;
13169 const auto keyArray =
13170 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
13171 for (auto& observer : keyArray) {
13172 aEnumerator(observer.get());
13176 void Document::RegisterPendingLinkUpdate(Link* aLink) {
13177 if (aLink->HasPendingLinkUpdate()) {
13178 return;
13181 aLink->SetHasPendingLinkUpdate();
13183 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
13184 nsCOMPtr<nsIRunnable> event =
13185 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
13186 &Document::FlushPendingLinkUpdates);
13187 // Do this work in a second in the worst case.
13188 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
13189 EventQueuePriority::Idle);
13190 if (NS_FAILED(rv)) {
13191 // If during shutdown posting a runnable doesn't succeed, we probably
13192 // don't need to update link states.
13193 return;
13195 mHasLinksToUpdateRunnable = true;
13198 mLinksToUpdate.InfallibleAppend(aLink);
13201 void Document::FlushPendingLinkUpdates() {
13202 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
13203 MOZ_ASSERT(mHasLinksToUpdateRunnable);
13204 mHasLinksToUpdateRunnable = false;
13206 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
13207 mFlushingPendingLinkUpdates = true;
13209 while (!mLinksToUpdate.IsEmpty()) {
13210 LinksToUpdateList links(std::move(mLinksToUpdate));
13211 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
13212 Link* link = iter.Get();
13213 Element* element = link->GetElement();
13214 if (element->OwnerDoc() == this) {
13215 link->ClearHasPendingLinkUpdate();
13216 if (element->IsInComposedDoc()) {
13217 link->TriggerLinkUpdate(/* aNotify = */ true);
13225 * Retrieves the node in a static-clone document that corresponds to aOrigNode,
13226 * which is a node in the original document from which aStaticClone was cloned.
13228 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
13229 Document& aStaticClone) {
13230 MOZ_ASSERT(aOrigNode);
13232 // Selections in anonymous subtrees aren't supported.
13233 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
13234 return nullptr;
13237 // If the node is disconnected, this is a bug in the selection code, but it
13238 // can happen with shadow DOM so handle it.
13239 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
13240 return nullptr;
13243 AutoTArray<Maybe<uint32_t>, 32> indexArray;
13244 const nsINode* current = aOrigNode;
13245 while (const nsINode* parent = current->GetParentNode()) {
13246 Maybe<uint32_t> index = parent->ComputeIndexOf(current);
13247 NS_ENSURE_TRUE(index.isSome(), nullptr);
13248 indexArray.AppendElement(std::move(index));
13249 current = parent;
13251 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
13252 nsINode* correspondingNode = [&]() -> nsINode* {
13253 if (current->IsDocument()) {
13254 return &aStaticClone;
13256 const auto* shadow = ShadowRoot::FromNode(*current);
13257 if (!shadow) {
13258 return nullptr;
13260 nsINode* correspondingHost =
13261 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
13262 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
13263 return nullptr;
13265 return correspondingHost->AsElement()->GetShadowRoot();
13266 }();
13268 if (NS_WARN_IF(!correspondingNode)) {
13269 return nullptr;
13271 for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
13272 correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
13273 NS_ENSURE_TRUE(correspondingNode, nullptr);
13275 return correspondingNode;
13279 * Caches the selection ranges from the source document onto the static clone in
13280 * case the "Print Selection Only" functionality is invoked.
13282 * Note that we cannot use the selection obtained from GetOriginalDocument()
13283 * since that selection may have mutated after the print was invoked.
13285 * Note also that because nsRange objects point into a specific document's
13286 * nodes, we cannot reuse an array of nsRange objects across multiple static
13287 * clone documents. For that reason we cache a new array of ranges on each
13288 * static clone that we create.
13290 * TODO(emilio): This can be simplified once we don't re-clone from static
13291 * documents.
13293 * @param aSourceDoc the document from which we are caching selection ranges
13294 * @param aStaticClone the document that will hold the cache
13295 * @return true if a selection range was cached
13297 static void CachePrintSelectionRanges(const Document& aSourceDoc,
13298 Document& aStaticClone) {
13299 MOZ_ASSERT(aStaticClone.IsStaticDocument());
13300 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc));
13301 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
13303 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
13305 // When the user opts to "Print Selection Only", the print code prefers any
13306 // selection in the static clone corresponding to the focused frame. If this
13307 // is that static clone, flag it for the printing code:
13308 const bool isFocusedDoc = [&] {
13309 if (sourceDocIsStatic) {
13310 return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc));
13312 nsPIDOMWindowOuter* window = aSourceDoc.GetWindow();
13313 if (!window) {
13314 return false;
13316 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
13317 if (!rootWindow) {
13318 return false;
13320 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
13321 nsFocusManager::GetFocusedDescendant(rootWindow,
13322 nsFocusManager::eIncludeAllDescendants,
13323 getter_AddRefs(focusedWindow));
13324 return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc;
13325 }();
13326 if (isFocusedDoc) {
13327 aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc,
13328 reinterpret_cast<void*>(true));
13331 const Selection* origSelection = nullptr;
13332 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
13334 if (sourceDocIsStatic) {
13335 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
13336 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
13337 } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
13338 origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
13341 if (!origSelection && !origRanges) {
13342 return;
13345 const uint32_t rangeCount =
13346 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
13347 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
13349 for (const uint32_t i : IntegerRange(rangeCount)) {
13350 MOZ_ASSERT_IF(!sourceDocIsStatic,
13351 origSelection->RangeCount() == rangeCount);
13352 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
13353 : origSelection->GetRangeAt(i);
13354 MOZ_ASSERT(range);
13355 nsINode* startContainer = range->GetStartContainer();
13356 nsINode* endContainer = range->GetEndContainer();
13358 if (!startContainer || !endContainer) {
13359 continue;
13362 nsINode* startNode =
13363 GetCorrespondingNodeInDocument(startContainer, aStaticClone);
13364 nsINode* endNode =
13365 GetCorrespondingNodeInDocument(endContainer, aStaticClone);
13367 if (NS_WARN_IF(!startNode || !endNode)) {
13368 continue;
13371 RefPtr<nsRange> clonedRange =
13372 nsRange::Create(startNode, range->StartOffset(), endNode,
13373 range->EndOffset(), IgnoreErrors());
13374 if (clonedRange && !clonedRange->Collapsed()) {
13375 printRanges->AppendElement(std::move(clonedRange));
13379 if (printRanges->IsEmpty()) {
13380 return;
13383 aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
13384 printRanges.release(),
13385 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
13388 already_AddRefed<Document> Document::CreateStaticClone(
13389 nsIDocShell* aCloneContainer, nsIContentViewer* aViewer,
13390 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
13391 MOZ_ASSERT(!mCreatingStaticClone);
13392 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
13393 MOZ_DIAGNOSTIC_ASSERT(aViewer);
13395 mCreatingStaticClone = true;
13396 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
13397 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
13399 auto raii = MakeScopeExit([&] {
13400 RemoveProperty(nsGkAtoms::adoptedsheetclones);
13401 mCreatingStaticClone = false;
13404 // Make document use different container during cloning.
13406 // FIXME(emilio): Why is this needed?
13407 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
13408 SetContainer(nsDocShell::Cast(aCloneContainer));
13409 IgnoredErrorResult rv;
13410 nsCOMPtr<nsINode> clonedNode = CloneNode(true, rv);
13411 SetContainer(originalShell);
13412 if (rv.Failed()) {
13413 return nullptr;
13416 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
13417 if (!clonedDoc) {
13418 return nullptr;
13421 size_t sheetsCount = SheetCount();
13422 for (size_t i = 0; i < sheetsCount; ++i) {
13423 RefPtr<StyleSheet> sheet = SheetAt(i);
13424 if (sheet) {
13425 if (sheet->IsApplicable()) {
13426 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13427 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13428 if (clonedSheet) {
13429 clonedDoc->AddStyleSheet(clonedSheet);
13434 clonedDoc->CloneAdoptedSheetsFrom(*this);
13436 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
13437 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
13438 for (StyleSheet* sheet : sheets) {
13439 if (sheet->IsApplicable()) {
13440 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13441 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13442 if (clonedSheet) {
13443 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
13444 clonedSheet);
13450 // Font faces created with the JS API will not be reflected in the
13451 // stylesheets and need to be copied over to the cloned document.
13452 if (const FontFaceSet* set = GetFonts()) {
13453 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
13456 clonedDoc->mReferrerInfo =
13457 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
13458 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
13459 CachePrintSelectionRanges(*this, *clonedDoc);
13461 // We're done with the clone, embed ourselves into the document viewer and
13462 // clone our children. The order here is pretty important, because our
13463 // document our document needs to have an owner global before we can create
13464 // the frame loaders for subdocuments.
13465 aViewer->SetDocument(clonedDoc);
13467 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
13469 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
13470 for (const auto& clone : pendingClones) {
13471 RefPtr<Element> element = do_QueryObject(clone.mElement);
13472 RefPtr<nsFrameLoader> frameLoader =
13473 nsFrameLoader::Create(element, /* aNetworkCreated */ false);
13475 if (NS_WARN_IF(!frameLoader)) {
13476 continue;
13479 clone.mElement->SetFrameLoader(frameLoader);
13481 nsresult rv = frameLoader->FinishStaticClone(
13482 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
13483 Unused << NS_WARN_IF(NS_FAILED(rv));
13486 return clonedDoc.forget();
13489 void Document::UnlinkOriginalDocumentIfStatic() {
13490 if (IsStaticDocument() && mOriginalDocument) {
13491 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
13492 mOriginalDocument->mStaticCloneCount--;
13493 mOriginalDocument = nullptr;
13495 MOZ_ASSERT(!mOriginalDocument);
13498 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
13499 int32_t* aHandle) {
13500 nsresult rv = mFrameRequestManager.Schedule(aCallback, aHandle);
13501 if (NS_FAILED(rv)) {
13502 return rv;
13505 UpdateFrameRequestCallbackSchedulingState();
13506 return NS_OK;
13509 void Document::CancelFrameRequestCallback(int32_t aHandle) {
13510 if (mFrameRequestManager.Cancel(aHandle)) {
13511 UpdateFrameRequestCallbackSchedulingState();
13515 bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
13516 return mFrameRequestManager.IsCanceled(aHandle);
13519 nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
13520 // Get the document's current state object. This is the object backing both
13521 // history.state and popStateEvent.state.
13523 // mStateObjectContainer may be null; this just means that there's no
13524 // current state object.
13526 if (!mCachedStateObjectValid) {
13527 if (mStateObjectContainer) {
13528 AutoJSAPI jsapi;
13529 // Init with null is "OK" in the sense that it will just fail.
13530 if (!jsapi.Init(GetScopeObject())) {
13531 return NS_ERROR_UNEXPECTED;
13533 JS::Rooted<JS::Value> value(jsapi.cx());
13534 nsresult rv =
13535 mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value);
13536 NS_ENSURE_SUCCESS(rv, rv);
13538 mCachedStateObject = value;
13539 if (!value.isNullOrUndefined()) {
13540 mozilla::HoldJSObjects(this);
13542 } else {
13543 mCachedStateObject = JS::NullValue();
13545 mCachedStateObjectValid = true;
13548 aState.set(mCachedStateObject);
13549 return NS_OK;
13552 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
13553 mTiming = aTiming;
13554 if (!mLoadingOrRestoredFromBFCacheTimeStamp.IsNull() && mTiming) {
13555 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(),
13556 mLoadingOrRestoredFromBFCacheTimeStamp);
13559 // If there's already the DocumentTimeline instance, tell it since the
13560 // DocumentTimeline is based on both the navigation start time stamp and the
13561 // refresh driver timestamp.
13562 if (mDocumentTimeline) {
13563 mDocumentTimeline->UpdateLastRefreshDriverTime();
13567 nsContentList* Document::ImageMapList() {
13568 if (!mImageMaps) {
13569 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
13570 nsGkAtoms::map);
13573 return mImageMaps;
13576 #define DEPRECATED_OPERATION(_op) #_op "Warning",
13577 static const char* kDeprecationWarnings[] = {
13578 #include "nsDeprecatedOperationList.h"
13579 nullptr};
13580 #undef DEPRECATED_OPERATION
13582 #define DOCUMENT_WARNING(_op) #_op "Warning",
13583 static const char* kDocumentWarnings[] = {
13584 #include "nsDocumentWarningList.h"
13585 nullptr};
13586 #undef DOCUMENT_WARNING
13588 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
13589 switch (aOperation) {
13590 #define DEPRECATED_OPERATION(_op) \
13591 case DeprecatedOperations::e##_op: \
13592 return eUseCounter_##_op;
13593 #include "nsDeprecatedOperationList.h"
13594 #undef DEPRECATED_OPERATION
13595 default:
13596 MOZ_CRASH();
13600 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
13601 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
13604 void Document::WarnOnceAbout(
13605 DeprecatedOperations aOperation, bool asError /* = false */,
13606 const nsTArray<nsString>& aParams /* = empty array */) const {
13607 MOZ_ASSERT(NS_IsMainThread());
13608 if (HasWarnedAbout(aOperation)) {
13609 return;
13611 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
13612 // Don't count deprecated operations for about pages since those pages
13613 // are almost in our control, and we always need to remove uses there
13614 // before we remove the operation itself anyway.
13615 if (!IsAboutPage()) {
13616 const_cast<Document*>(this)->SetUseCounter(
13617 OperationToUseCounter(aOperation));
13619 uint32_t flags =
13620 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13621 nsContentUtils::ReportToConsole(
13622 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
13623 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
13626 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
13627 return mDocWarningWarnedAbout[aWarning];
13630 void Document::WarnOnceAbout(
13631 DocumentWarnings aWarning, bool asError /* = false */,
13632 const nsTArray<nsString>& aParams /* = empty array */) const {
13633 MOZ_ASSERT(NS_IsMainThread());
13634 if (HasWarnedAbout(aWarning)) {
13635 return;
13637 mDocWarningWarnedAbout[aWarning] = true;
13638 uint32_t flags =
13639 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13640 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
13641 nsContentUtils::eDOM_PROPERTIES,
13642 kDocumentWarnings[aWarning], aParams);
13645 mozilla::dom::ImageTracker* Document::ImageTracker() {
13646 if (!mImageTracker) {
13647 mImageTracker = new mozilla::dom::ImageTracker;
13649 return mImageTracker;
13652 void Document::ScheduleSVGUseElementShadowTreeUpdate(
13653 SVGUseElement& aUseElement) {
13654 MOZ_ASSERT(aUseElement.IsInComposedDoc());
13656 if (MOZ_UNLIKELY(mIsStaticDocument)) {
13657 // Printing doesn't deal well with dynamic DOM mutations.
13658 return;
13661 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
13663 if (PresShell* presShell = GetPresShell()) {
13664 presShell->EnsureStyleFlush();
13668 void Document::DoUpdateSVGUseElementShadowTrees() {
13669 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13671 MOZ_ASSERT(!mCloningForSVGUse);
13672 mCloningForSVGUse = true;
13674 do {
13675 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
13676 mSVGUseElementsNeedingShadowTreeUpdate);
13677 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
13679 for (const auto& useElement : useElementsToUpdate) {
13680 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
13681 // The element was in another <use> shadow tree which we processed
13682 // already and also needed an update, and is removed from the document
13683 // now, so nothing to do here.
13684 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
13685 continue;
13687 useElement->UpdateShadowTree();
13689 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13691 mCloningForSVGUse = false;
13694 void Document::NotifyMediaFeatureValuesChanged() {
13695 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
13696 imageElement->MediaFeatureValuesChanged();
13700 already_AddRefed<Touch> Document::CreateTouch(
13701 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
13702 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
13703 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
13704 float aRotationAngle, float aForce) {
13705 RefPtr<Touch> touch =
13706 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
13707 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
13708 return touch.forget();
13711 already_AddRefed<TouchList> Document::CreateTouchList() {
13712 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13713 return retval.forget();
13716 already_AddRefed<TouchList> Document::CreateTouchList(
13717 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
13718 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13719 retval->Append(&aTouch);
13720 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13721 retval->Append(aTouches[i].get());
13723 return retval.forget();
13726 already_AddRefed<TouchList> Document::CreateTouchList(
13727 const Sequence<OwningNonNull<Touch>>& aTouches) {
13728 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13729 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13730 retval->Append(aTouches[i].get());
13732 return retval.forget();
13735 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
13736 float aX, float aY) {
13737 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
13739 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
13740 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
13741 nsPoint pt(x, y);
13743 FlushPendingNotifications(FlushType::Layout);
13745 PresShell* presShell = GetPresShell();
13746 if (!presShell) {
13747 return nullptr;
13750 nsIFrame* rootFrame = presShell->GetRootFrame();
13752 // XUL docs, unlike HTML, have no frame tree until everything's done loading
13753 if (!rootFrame) {
13754 return nullptr;
13757 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
13758 RelativeTo{rootFrame}, pt,
13759 {{FrameForPointOption::IgnorePaintSuppression,
13760 FrameForPointOption::IgnoreCrossDoc}});
13761 if (!ptFrame) {
13762 return nullptr;
13765 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
13766 nsPoint adjustedPoint = pt;
13767 if (nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
13768 adjustedPoint) !=
13769 nsLayoutUtils::TRANSFORM_SUCCEEDED) {
13770 return nullptr;
13773 nsIFrame::ContentOffsets offsets =
13774 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
13776 nsCOMPtr<nsIContent> node = offsets.content;
13777 uint32_t offset = offsets.offset;
13778 nsCOMPtr<nsIContent> anonNode = node;
13779 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
13780 if (nodeIsAnonymous) {
13781 node = ptFrame->GetContent();
13782 nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
13783 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
13784 nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
13785 if (textFrame) {
13786 // If the anonymous content node has a child, then we need to make sure
13787 // that we get the appropriate child, as otherwise the offset may not be
13788 // correct when we construct a range for it.
13789 nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
13790 if (firstChild) {
13791 anonNode = firstChild;
13794 if (textArea) {
13795 offset =
13796 nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
13799 node = nonanon;
13800 } else {
13801 node = nullptr;
13802 offset = 0;
13806 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
13807 if (nodeIsAnonymous) {
13808 aCaretPos->SetAnonymousContentNode(anonNode);
13810 return aCaretPos.forget();
13813 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
13814 // We rely on correct frame information here, so need to flush frames.
13815 FlushPendingNotifications(FlushType::Frames);
13817 // An element that is the HTML body element is potentially scrollable if all
13818 // of the following conditions are true:
13820 // The element has an associated CSS layout box.
13821 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
13822 if (!bodyFrame) {
13823 return false;
13826 // The element's parent element's computed value of the overflow-x and
13827 // overflow-y properties are visible.
13828 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
13829 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
13830 if (parentFrame &&
13831 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
13832 return false;
13835 // The element's computed value of the overflow-x or overflow-y properties is
13836 // not visible.
13837 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
13840 Element* Document::GetScrollingElement() {
13841 // Keep this in sync with IsScrollingElement.
13842 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
13843 RefPtr<HTMLBodyElement> body = GetBodyElement();
13844 if (body && !IsPotentiallyScrollable(body)) {
13845 return body;
13848 return nullptr;
13851 return GetRootElement();
13854 bool Document::IsScrollingElement(Element* aElement) {
13855 // Keep this in sync with GetScrollingElement.
13856 MOZ_ASSERT(aElement);
13858 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
13859 return aElement == GetRootElement();
13862 // In the common case when aElement != body, avoid refcounting.
13863 HTMLBodyElement* body = GetBodyElement();
13864 if (aElement != body) {
13865 return false;
13868 // Now we know body is non-null, since aElement is not null. It's the
13869 // scrolling element for the document if it itself is not potentially
13870 // scrollable.
13871 RefPtr<HTMLBodyElement> strongBody(body);
13872 return !IsPotentiallyScrollable(strongBody);
13875 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
13876 public:
13877 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
13878 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
13880 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
13881 const BlockParsingOptions& aOptions)
13882 : mPromise(aPromise) {
13883 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
13884 if (parser &&
13885 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
13886 parser->BlockParser();
13887 mParser = do_GetWeakReference(parser);
13888 mDocument = aDocument;
13889 mDocument->BlockOnload();
13890 mDocument->BlockDOMContentLoaded();
13894 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13895 ErrorResult& aRv) override {
13896 MaybeUnblockParser();
13898 mPromise->MaybeResolve(aValue);
13901 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13902 ErrorResult& aRv) override {
13903 MaybeUnblockParser();
13905 mPromise->MaybeReject(aValue);
13908 protected:
13909 virtual ~UnblockParsingPromiseHandler() {
13910 // If we're being cleaned up by the cycle collector, our mDocument reference
13911 // may have been unlinked while our mParser weak reference is still alive.
13912 if (mDocument) {
13913 MaybeUnblockParser();
13917 private:
13918 void MaybeUnblockParser() {
13919 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
13920 if (parser) {
13921 MOZ_DIAGNOSTIC_ASSERT(mDocument);
13922 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
13923 if (parser == docParser) {
13924 parser->UnblockParser();
13925 parser->ContinueInterruptedParsingAsync();
13928 if (mDocument) {
13929 // We blocked DOMContentLoaded and load events on this document. Unblock
13930 // them. Note that we want to do that no matter what's going on with the
13931 // parser state for this document. Maybe someone caused it to stop being
13932 // parsed, so CreatorParserOrNull() is returning null, but we still want
13933 // to unblock these.
13934 mDocument->UnblockDOMContentLoaded();
13935 mDocument->UnblockOnload(false);
13937 mParser = nullptr;
13938 mDocument = nullptr;
13941 nsWeakPtr mParser;
13942 RefPtr<Promise> mPromise;
13943 RefPtr<Document> mDocument;
13946 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
13948 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
13949 NS_INTERFACE_MAP_ENTRY(nsISupports)
13950 NS_INTERFACE_MAP_END
13952 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
13953 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
13955 already_AddRefed<Promise> Document::BlockParsing(
13956 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
13957 RefPtr<Promise> resultPromise =
13958 Promise::Create(aPromise.GetParentObject(), aRv);
13959 if (aRv.Failed()) {
13960 return nullptr;
13963 RefPtr<PromiseNativeHandler> promiseHandler =
13964 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
13965 aPromise.AppendNativeHandler(promiseHandler);
13967 return resultPromise.forget();
13970 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
13971 if (mFailedChannel) {
13972 nsCOMPtr<nsIURI> failedURI;
13973 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
13974 return failedURI.forget();
13978 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
13979 if (!uri) {
13980 return nullptr;
13983 return uri.forget();
13986 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
13987 if (mIsGoingAway) {
13988 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13989 return nullptr;
13992 if (!mReadyForIdle) {
13993 nsIGlobalObject* global = GetScopeObject();
13994 if (!global) {
13995 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13996 return nullptr;
13999 mReadyForIdle = Promise::Create(global, aRv);
14000 if (aRv.Failed()) {
14001 return nullptr;
14005 return mReadyForIdle;
14008 void Document::MaybeResolveReadyForIdle() {
14009 IgnoredErrorResult rv;
14010 Promise* readyPromise = GetDocumentReadyForIdle(rv);
14011 if (readyPromise) {
14012 readyPromise->MaybeResolveWithUndefined();
14016 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
14017 // The policy is created when the document is initialized. We _must_ have a
14018 // policy here even if the featurePolicy pref is off. If this assertion fails,
14019 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
14020 MOZ_ASSERT(mFeaturePolicy);
14021 return mFeaturePolicy;
14024 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
14025 // Only chrome documents are allowed to use command dispatcher.
14026 if (!nsContentUtils::IsChromeDoc(this)) {
14027 return nullptr;
14029 if (!mCommandDispatcher) {
14030 // Create our command dispatcher and hook it up.
14031 mCommandDispatcher = new nsXULCommandDispatcher(this);
14033 return mCommandDispatcher;
14036 void Document::InitializeXULBroadcastManager() {
14037 if (mXULBroadcastManager) {
14038 return;
14040 mXULBroadcastManager = new XULBroadcastManager(this);
14043 namespace {
14045 class DevToolsMutationObserver final : public nsStubMutationObserver {
14046 NS_DECL_ISUPPORTS
14047 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
14048 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
14049 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
14051 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
14052 // relies on the event firing _before_ the removal happens.
14053 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
14055 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
14056 // data changes right now (maybe intentionally?).
14057 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
14059 DevToolsMutationObserver() = default;
14061 private:
14062 void FireEvent(nsINode* aTarget, const nsAString& aType);
14064 ~DevToolsMutationObserver() = default;
14067 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
14069 void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
14070 const nsAString& aType) {
14071 AsyncEventDispatcher::RunDOMEventWhenSafe(*aTarget, aType, CanBubble::eNo,
14072 ChromeOnlyDispatch::eYes,
14073 Composed::eYes);
14076 void DevToolsMutationObserver::AttributeChanged(Element* aElement,
14077 int32_t aNamespaceID,
14078 nsAtom* aAttribute,
14079 int32_t aModType,
14080 const nsAttrValue* aOldValue) {
14081 FireEvent(aElement, u"devtoolsattrmodified"_ns);
14084 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
14085 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
14086 ContentInserted(c);
14090 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
14091 FireEvent(aChild, u"devtoolschildinserted"_ns);
14094 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
14096 } // namespace
14098 void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
14099 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
14100 return;
14102 mDevToolsWatchingDOMMutations = aValue;
14103 if (aValue) {
14104 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
14105 sDevToolsMutationObserver = new DevToolsMutationObserver();
14106 ClearOnShutdown(&sDevToolsMutationObserver);
14108 AddMutationObserver(sDevToolsMutationObserver);
14109 } else if (sDevToolsMutationObserver) {
14110 RemoveMutationObserver(sDevToolsMutationObserver);
14114 void EvaluateMediaQueryLists(nsTArray<RefPtr<MediaQueryList>>& aListsToNotify,
14115 Document& aDocument, bool aRecurse) {
14116 if (nsPresContext* pc = aDocument.GetPresContext()) {
14117 pc->FlushPendingMediaFeatureValuesChanged();
14120 for (MediaQueryList* mql : aDocument.MediaQueryLists()) {
14121 if (mql->EvaluateOnRenderingUpdate()) {
14122 aListsToNotify.AppendElement(mql);
14125 if (!aRecurse) {
14126 return;
14128 auto recurse = [&](Document& aSubDoc) {
14129 EvaluateMediaQueryLists(aListsToNotify, aSubDoc, true);
14130 return CallState::Continue;
14132 aDocument.EnumerateSubDocuments(recurse);
14135 void Document::EvaluateMediaQueriesAndReportChanges(bool aRecurse) {
14136 AutoTArray<RefPtr<MediaQueryList>, 32> mqls;
14137 EvaluateMediaQueryLists(mqls, *this, aRecurse);
14138 for (auto& mql : mqls) {
14139 mql->FireChangeEvent();
14143 void Document::MaybeWarnAboutZoom() {
14144 if (mHasWarnedAboutZoom) {
14145 return;
14147 const bool usedZoom = Servo_IsPropertyIdRecordedInUseCounter(
14148 mStyleUseCounters.get(), eCSSProperty_zoom);
14149 if (!usedZoom) {
14150 return;
14153 mHasWarnedAboutZoom = true;
14154 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
14155 this, nsContentUtils::eLAYOUT_PROPERTIES,
14156 "ZoomPropertyWarning");
14159 nsIHTMLCollection* Document::Children() {
14160 if (!mChildrenCollection) {
14161 mChildrenCollection =
14162 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
14163 nsGkAtoms::_asterisk, false);
14166 return mChildrenCollection;
14169 uint32_t Document::ChildElementCount() { return Children()->Length(); }
14171 // Singleton class to manage the list of fullscreen documents which are the
14172 // root of a branch which contains fullscreen documents. We maintain this list
14173 // so that we can easily exit all windows from fullscreen when the user
14174 // presses the escape key.
14175 class FullscreenRoots {
14176 public:
14177 // Adds the root of given document to the manager. Calling this method
14178 // with a document whose root is already contained has no effect.
14179 static void Add(Document* aDoc);
14181 // Iterates over every root in the root list, and calls aFunction, passing
14182 // each root once to aFunction. It is safe to call Add() and Remove() while
14183 // iterating over the list (i.e. in aFunction). Documents that are removed
14184 // from the manager during traversal are not traversed, and documents that
14185 // are added to the manager during traversal are also not traversed.
14186 static void ForEach(void (*aFunction)(Document* aDoc));
14188 // Removes the root of a specific document from the manager.
14189 static void Remove(Document* aDoc);
14191 // Returns true if all roots added to the list have been removed.
14192 static bool IsEmpty();
14194 private:
14195 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
14196 MOZ_COUNTED_DTOR(FullscreenRoots)
14198 enum : uint32_t { NotFound = uint32_t(-1) };
14199 // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
14200 static uint32_t Find(Document* aRoot);
14202 // Returns true if aRoot is in the list of fullscreen roots.
14203 static bool Contains(Document* aRoot);
14205 // Singleton instance of the FullscreenRoots. This is instantiated when a
14206 // root is added, and it is deleted when the last root is removed.
14207 static FullscreenRoots* sInstance;
14209 // List of weak pointers to roots.
14210 nsTArray<nsWeakPtr> mRoots;
14213 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
14215 /* static */
14216 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
14217 if (!sInstance) {
14218 return;
14220 // Create a copy of the roots array, and iterate over the copy. This is so
14221 // that if an element is removed from mRoots we don't mess up our iteration.
14222 nsTArray<nsWeakPtr> roots(sInstance->mRoots.Clone());
14223 // Call aFunction on all entries.
14224 for (uint32_t i = 0; i < roots.Length(); i++) {
14225 nsCOMPtr<Document> root = do_QueryReferent(roots[i]);
14226 // Check that the root isn't in the manager. This is so that new additions
14227 // while we were running don't get traversed.
14228 if (root && FullscreenRoots::Contains(root)) {
14229 aFunction(root);
14234 /* static */
14235 bool FullscreenRoots::Contains(Document* aRoot) {
14236 return FullscreenRoots::Find(aRoot) != NotFound;
14239 /* static */
14240 void FullscreenRoots::Add(Document* aDoc) {
14241 nsCOMPtr<Document> root =
14242 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14243 if (!FullscreenRoots::Contains(root)) {
14244 if (!sInstance) {
14245 sInstance = new FullscreenRoots();
14247 sInstance->mRoots.AppendElement(do_GetWeakReference(root));
14251 /* static */
14252 uint32_t FullscreenRoots::Find(Document* aRoot) {
14253 if (!sInstance) {
14254 return NotFound;
14256 nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
14257 for (uint32_t i = 0; i < roots.Length(); i++) {
14258 nsCOMPtr<Document> otherRoot(do_QueryReferent(roots[i]));
14259 if (otherRoot == aRoot) {
14260 return i;
14263 return NotFound;
14266 /* static */
14267 void FullscreenRoots::Remove(Document* aDoc) {
14268 nsCOMPtr<Document> root =
14269 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14270 uint32_t index = Find(root);
14271 NS_ASSERTION(index != NotFound,
14272 "Should only try to remove roots which are still added!");
14273 if (index == NotFound || !sInstance) {
14274 return;
14276 sInstance->mRoots.RemoveElementAt(index);
14277 if (sInstance->mRoots.IsEmpty()) {
14278 delete sInstance;
14279 sInstance = nullptr;
14283 /* static */
14284 bool FullscreenRoots::IsEmpty() { return !sInstance; }
14286 // Any fullscreen change waiting for the widget to finish transition
14287 // is queued here. This is declared static instead of a member of
14288 // Document because in the majority of time, there would be at most
14289 // one document requesting or exiting fullscreen. We shouldn't waste
14290 // the space to hold for it in every document.
14291 class PendingFullscreenChangeList {
14292 public:
14293 PendingFullscreenChangeList() = delete;
14295 template <typename T>
14296 static void Add(UniquePtr<T> aChange) {
14297 sList.insertBack(aChange.release());
14300 static const FullscreenChange* GetLast() { return sList.getLast(); }
14302 enum IteratorOption {
14303 // When we are committing fullscreen changes or preparing for
14304 // that, we generally want to iterate all requests in the same
14305 // window with eDocumentsWithSameRoot option.
14306 eDocumentsWithSameRoot,
14307 // If we are removing a document from the tree, we would only
14308 // want to remove the requests from the given document and its
14309 // descendants. For that case, use eInclusiveDescendants.
14310 eInclusiveDescendants
14313 template <typename T>
14314 class Iterator {
14315 public:
14316 explicit Iterator(Document* aDoc, IteratorOption aOption)
14317 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
14318 if (mCurrent) {
14319 if (aDoc->GetBrowsingContext()) {
14320 mRootBCForIteration = aDoc->GetBrowsingContext();
14321 if (aOption == eDocumentsWithSameRoot) {
14322 RefPtr<BrowsingContext> bc =
14323 GetParentIgnoreChromeBoundary(mRootBCForIteration);
14324 while (bc) {
14325 mRootBCForIteration = bc;
14326 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
14330 SkipToNextMatch();
14334 UniquePtr<T> TakeAndNext() {
14335 auto thisChange = TakeAndNextInternal();
14336 SkipToNextMatch();
14337 return thisChange;
14339 bool AtEnd() const { return mCurrent == nullptr; }
14341 private:
14342 already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
14343 BrowsingContext* aBC) {
14344 // Chrome BrowsingContexts are only available in the parent process, so if
14345 // we're in a content process, we only worry about the context tree.
14346 if (XRE_IsParentProcess()) {
14347 return aBC->Canonical()->GetParentCrossChromeBoundary();
14349 return do_AddRef(aBC->GetParent());
14352 UniquePtr<T> TakeAndNextInternal() {
14353 FullscreenChange* thisChange = mCurrent;
14354 MOZ_ASSERT(thisChange->Type() == T::kType);
14355 mCurrent = mCurrent->removeAndGetNext();
14356 return WrapUnique(static_cast<T*>(thisChange));
14358 void SkipToNextMatch() {
14359 while (mCurrent) {
14360 if (mCurrent->Type() == T::kType) {
14361 RefPtr<BrowsingContext> bc =
14362 mCurrent->Document()->GetBrowsingContext();
14363 if (!bc) {
14364 // Always automatically drop fullscreen changes which are
14365 // from a document detached from the doc shell.
14366 UniquePtr<T> change = TakeAndNextInternal();
14367 change->MayRejectPromise("Document is not active");
14368 continue;
14370 while (bc && bc != mRootBCForIteration) {
14371 bc = GetParentIgnoreChromeBoundary(bc);
14373 if (bc) {
14374 break;
14377 // The current one either don't have matched type, or isn't
14378 // inside the given subtree, so skip this item.
14379 mCurrent = mCurrent->getNext();
14383 FullscreenChange* mCurrent;
14384 RefPtr<BrowsingContext> mRootBCForIteration;
14387 private:
14388 static LinkedList<FullscreenChange> sList;
14391 /* static */
14392 LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
14394 Document* Document::GetFullscreenRoot() {
14395 nsCOMPtr<Document> root = do_QueryReferent(mFullscreenRoot);
14396 return root;
14399 size_t Document::CountFullscreenElements() const {
14400 size_t count = 0;
14401 for (const nsWeakPtr& ptr : mTopLayer) {
14402 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14403 if (elem->State().HasState(ElementState::FULLSCREEN)) {
14404 count++;
14408 return count;
14411 void Document::SetFullscreenRoot(Document* aRoot) {
14412 mFullscreenRoot = do_GetWeakReference(aRoot);
14415 // https://github.com/whatwg/html/issues/9143
14416 // We need to consider the precedence between active modal dialog, topmost auto
14417 // popover and fullscreen element once it's specified.
14418 void Document::HandleEscKey() {
14419 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14420 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14421 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14422 dialog->QueueCancelDialog();
14423 break;
14425 if (RefPtr<nsGenericHTMLElement> popoverHTMLEl =
14426 nsGenericHTMLElement::FromNodeOrNull(element)) {
14427 if (element->IsAutoPopover() && element->IsPopoverOpen()) {
14428 popoverHTMLEl->HidePopover(IgnoreErrors());
14429 break;
14435 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
14436 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
14437 RefPtr<Promise> promise = exit->GetPromise();
14438 RestorePreviousFullscreenState(std::move(exit));
14439 return promise.forget();
14442 static void AskWindowToExitFullscreen(Document* aDoc) {
14443 if (XRE_GetProcessType() == GeckoProcessType_Content) {
14444 nsContentUtils::DispatchEventOnlyToChrome(
14445 aDoc, aDoc, u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
14446 Cancelable::eNo, /* DefaultAction */ nullptr);
14447 } else {
14448 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
14449 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
14454 class nsCallExitFullscreen : public Runnable {
14455 public:
14456 explicit nsCallExitFullscreen(Document* aDoc)
14457 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
14459 NS_IMETHOD Run() final {
14460 if (!mDoc) {
14461 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
14462 } else {
14463 AskWindowToExitFullscreen(mDoc);
14465 return NS_OK;
14468 private:
14469 nsCOMPtr<Document> mDoc;
14472 /* static */
14473 void Document::AsyncExitFullscreen(Document* aDoc) {
14474 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14475 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
14476 if (aDoc) {
14477 aDoc->Dispatch(TaskCategory::Other, exit.forget());
14478 } else {
14479 NS_DispatchToCurrentThread(exit.forget());
14483 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
14484 uint32_t count = 0;
14485 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
14486 auto subDoc = [&count](Document& aSubDoc) {
14487 if (aSubDoc.Fullscreen()) {
14488 count++;
14490 return CallState::Continue;
14492 aDoc.EnumerateSubDocuments(subDoc);
14493 return count;
14496 bool Document::IsFullscreenLeaf() {
14497 // A fullscreen leaf document is fullscreen, and has no fullscreen
14498 // subdocuments.
14500 // FIXME(emilio): This doesn't seem to account for fission iframes, is that
14501 // ok?
14502 return Fullscreen() && CountFullscreenSubDocuments(*this) == 0;
14505 static Document* GetFullscreenLeaf(Document& aDoc) {
14506 if (aDoc.IsFullscreenLeaf()) {
14507 return &aDoc;
14509 if (!aDoc.Fullscreen()) {
14510 return nullptr;
14512 Document* leaf = nullptr;
14513 auto recurse = [&leaf](Document& aSubDoc) {
14514 leaf = GetFullscreenLeaf(aSubDoc);
14515 return leaf ? CallState::Stop : CallState::Continue;
14517 aDoc.EnumerateSubDocuments(recurse);
14518 return leaf;
14521 static Document* GetFullscreenLeaf(Document* aDoc) {
14522 if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
14523 return leaf;
14525 // Otherwise we could be either in a non-fullscreen doc tree, or we're
14526 // below the fullscreen doc. Start the search from the root.
14527 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14528 return GetFullscreenLeaf(*root);
14531 static CallState ResetFullscreen(Document& aDocument) {
14532 if (Element* fsElement = aDocument.GetUnretargetedFullscreenElement()) {
14533 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
14534 "Should have at most 1 fullscreen subdocument.");
14535 aDocument.CleanupFullscreenState();
14536 NS_ASSERTION(!aDocument.Fullscreen(), "Should reset fullscreen");
14537 DispatchFullscreenChange(aDocument, fsElement);
14538 aDocument.EnumerateSubDocuments(ResetFullscreen);
14540 return CallState::Continue;
14543 // Since Document::ExitFullscreenInDocTree() could be called from
14544 // Element::UnbindFromTree() where it is not safe to synchronously run
14545 // script. This runnable is the script part of that function.
14546 class ExitFullscreenScriptRunnable : public Runnable {
14547 public:
14548 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
14549 : mozilla::Runnable("ExitFullscreenScriptRunnable"),
14550 mRoot(aRoot),
14551 mLeaf(aLeaf) {}
14553 NS_IMETHOD Run() override {
14554 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
14555 // document since we want this event to follow the same path that
14556 // MozDOMFullscreen:Entered was dispatched.
14557 nsContentUtils::DispatchEventOnlyToChrome(
14558 mLeaf, mLeaf, u"MozDOMFullscreen:Exited"_ns, CanBubble::eYes,
14559 Cancelable::eNo, /* DefaultAction */ nullptr);
14560 // Ensure the window exits fullscreen, as long as we don't have
14561 // pending fullscreen requests.
14562 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
14563 if (!mRoot->HasPendingFullscreenRequests()) {
14564 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
14565 false);
14568 return NS_OK;
14571 private:
14572 nsCOMPtr<Document> mRoot;
14573 nsCOMPtr<Document> mLeaf;
14576 /* static */
14577 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
14578 MOZ_ASSERT(aMaybeNotARootDoc);
14580 // Unlock the pointer
14581 PointerLockManager::Unlock();
14583 // Resolve all promises which waiting for exit fullscreen.
14584 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
14585 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14586 while (!iter.AtEnd()) {
14587 UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
14588 exit->MayResolvePromise();
14591 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
14592 if (!root || !root->Fullscreen()) {
14593 // If a document was detached before exiting from fullscreen, it is
14594 // possible that the root had left fullscreen state. In this case,
14595 // we would not get anything from the ResetFullscreen() call. Root's
14596 // not being a fullscreen doc also means the widget should have
14597 // exited fullscreen state. It means even if we do not return here,
14598 // we would actually do nothing below except crashing ourselves via
14599 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
14600 // document.
14601 return;
14604 // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
14605 // See ExitFullscreenScriptRunnable::Run for details. We have to
14606 // record it here because we don't have such information after we
14607 // reset the fullscreen state below.
14608 Document* fullscreenLeaf = GetFullscreenLeaf(root);
14610 // Walk the tree of fullscreen documents, and reset their fullscreen state.
14611 ResetFullscreen(*root);
14613 NS_ASSERTION(!root->Fullscreen(),
14614 "Fullscreen root should no longer be a fullscreen doc...");
14616 // Move the top-level window out of fullscreen mode.
14617 FullscreenRoots::Remove(root);
14619 nsContentUtils::AddScriptRunner(
14620 new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
14623 static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
14624 RefPtr<AsyncEventDispatcher> asyncDispatcher =
14625 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
14626 CanBubble::eYes, ChromeOnlyDispatch::eYes);
14627 asyncDispatcher->PostDOMEvent();
14630 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
14631 NS_ASSERTION(!Fullscreen() || !FullscreenRoots::IsEmpty(),
14632 "Should have at least 1 fullscreen root when fullscreen!");
14634 if (!GetWindow()) {
14635 aExit->MayRejectPromise("No active window");
14636 return;
14638 if (!Fullscreen() || FullscreenRoots::IsEmpty()) {
14639 aExit->MayRejectPromise("Not in fullscreen mode");
14640 return;
14643 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
14644 AutoTArray<Element*, 8> exitElements;
14646 Document* doc = fullScreenDoc;
14647 // Collect all subdocuments.
14648 for (; doc != this; doc = doc->GetInProcessParentDocument()) {
14649 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14650 MOZ_ASSERT(fsElement,
14651 "Parent document of "
14652 "a fullscreen document without fullscreen element?");
14653 exitElements.AppendElement(fsElement);
14655 MOZ_ASSERT(doc == this, "Must have reached this doc");
14656 // Collect all ancestor documents which we are going to change.
14657 for (; doc; doc = doc->GetInProcessParentDocument()) {
14658 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14659 MOZ_ASSERT(fsElement,
14660 "Ancestor of fullscreen document must also be in fullscreen");
14661 if (doc != this) {
14662 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
14663 if (iframe->FullscreenFlag()) {
14664 // If this is an iframe, and it explicitly requested
14665 // fullscreen, don't rollback it automatically.
14666 break;
14670 exitElements.AppendElement(fsElement);
14671 if (doc->CountFullscreenElements() > 1) {
14672 break;
14676 Document* lastDoc = exitElements.LastElement()->OwnerDoc();
14677 size_t fullscreenCount = lastDoc->CountFullscreenElements();
14678 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
14679 // If we are fully exiting fullscreen, don't touch anything here,
14680 // just wait for the window to get out from fullscreen first.
14681 PendingFullscreenChangeList::Add(std::move(aExit));
14682 AskWindowToExitFullscreen(this);
14683 return;
14686 // If fullscreen mode is updated the pointer should be unlocked
14687 PointerLockManager::Unlock();
14688 // All documents listed in the array except the last one are going to
14689 // completely exit from the fullscreen state.
14690 for (auto i : IntegerRange(exitElements.Length() - 1)) {
14691 exitElements[i]->OwnerDoc()->CleanupFullscreenState();
14693 // The last document will either rollback one fullscreen element, or
14694 // completely exit from the fullscreen state as well.
14695 Document* newFullscreenDoc;
14696 if (fullscreenCount > 1) {
14697 DebugOnly<bool> removedFullscreenElement = lastDoc->PopFullscreenElement();
14698 MOZ_ASSERT(removedFullscreenElement);
14699 newFullscreenDoc = lastDoc;
14700 } else {
14701 lastDoc->CleanupFullscreenState();
14702 newFullscreenDoc = lastDoc->GetInProcessParentDocument();
14704 // Dispatch the fullscreenchange event to all document listed. Note
14705 // that the loop order is reversed so that events are dispatched in
14706 // the tree order as indicated in the spec.
14707 for (Element* e : Reversed(exitElements)) {
14708 DispatchFullscreenChange(*e->OwnerDoc(), e);
14710 aExit->MayResolvePromise();
14712 MOZ_ASSERT(newFullscreenDoc,
14713 "If we were going to exit from fullscreen on "
14714 "all documents in this doctree, we should've asked the window to "
14715 "exit first instead of reaching here.");
14716 if (fullScreenDoc != newFullscreenDoc &&
14717 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
14718 // We've popped so enough off the stack that we've rolled back to
14719 // a fullscreen element in a parent document. If this document is
14720 // cross origin, dispatch an event to chrome so it knows to show
14721 // the warning UI.
14722 DispatchFullscreenNewOriginEvent(newFullscreenDoc);
14726 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
14727 if (nsPresContext* presContext = aDoc->GetPresContext()) {
14728 presContext->UpdateViewportScrollStylesOverride();
14732 static void NotifyFullScreenChangedForMediaElement(Element& aElement) {
14733 // When a media element enters the fullscreen, we would like to notify that
14734 // to the media controller in order to update its status.
14735 if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) {
14736 mediaElem->NotifyFullScreenChanged();
14740 void Document::CleanupFullscreenState() {
14741 while (PopFullscreenElement(UpdateViewport::No)) {
14742 // Remove the next one if appropriate
14745 UpdateViewportScrollbarOverrideForFullscreen(this);
14746 mFullscreenRoot = nullptr;
14748 // Restore the zoom level that was in place prior to entering fullscreen.
14749 if (PresShell* presShell = GetPresShell()) {
14750 if (presShell->GetMobileViewportManager()) {
14751 presShell->SetResolutionAndScaleTo(
14752 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
14757 bool Document::PopFullscreenElement(UpdateViewport aUpdateViewport) {
14758 Element* removedElement = TopLayerPop([](Element* element) -> bool {
14759 return element->State().HasState(ElementState::FULLSCREEN);
14762 if (!removedElement) {
14763 return false;
14766 MOZ_ASSERT(removedElement->State().HasState(ElementState::FULLSCREEN));
14767 removedElement->RemoveStates(ElementState::FULLSCREEN | ElementState::MODAL);
14768 NotifyFullScreenChangedForMediaElement(*removedElement);
14769 // Reset iframe fullscreen flag.
14770 if (auto* iframe = HTMLIFrameElement::FromNode(removedElement)) {
14771 iframe->SetFullscreenFlag(false);
14773 if (aUpdateViewport == UpdateViewport::Yes) {
14774 UpdateViewportScrollbarOverrideForFullscreen(this);
14776 return true;
14779 void Document::SetFullscreenElement(Element& aElement) {
14780 ElementState statesToAdd = ElementState::FULLSCREEN;
14781 if (!IsInChromeDocShell()) {
14782 // Don't make the document modal in chrome documents, since we don't want
14783 // the browser UI like the context menu / etc to be inert.
14784 statesToAdd |= ElementState::MODAL;
14786 aElement.AddStates(statesToAdd);
14787 TopLayerPush(aElement);
14788 NotifyFullScreenChangedForMediaElement(aElement);
14789 UpdateViewportScrollbarOverrideForFullscreen(this);
14792 void Document::TopLayerPush(Element& aElement) {
14793 const bool modal = aElement.State().HasState(ElementState::MODAL);
14795 TopLayerPop(aElement);
14796 mTopLayer.AppendElement(do_GetWeakReference(&aElement));
14797 NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
14799 if (modal) {
14800 aElement.AddStates(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 != &aElement &&
14806 element->State().HasState(ElementState::TOPMOST_MODAL)) {
14807 element->RemoveStates(ElementState::TOPMOST_MODAL);
14808 foundExistingModalElement = true;
14809 break;
14813 if (!foundExistingModalElement) {
14814 Element* root = GetRootElement();
14815 MOZ_RELEASE_ASSERT(root, "top layer element outside of document?");
14816 if (&aElement != root) {
14817 // Add inert to the root element so that the inertness is applied to the
14818 // entire document.
14819 root->AddStates(ElementState::INERT);
14825 void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
14826 aDialogElement.AddStates(ElementState::MODAL);
14827 TopLayerPush(aDialogElement);
14830 void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
14831 DebugOnly<Element*> removedElement = TopLayerPop(aDialogElement);
14832 MOZ_ASSERT(removedElement == &aDialogElement);
14833 aDialogElement.RemoveStates(ElementState::MODAL);
14836 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
14837 if (mTopLayer.IsEmpty()) {
14838 return nullptr;
14841 // Remove the topmost element that qualifies aPredicate; This
14842 // is required is because the top layer contains not only
14843 // fullscreen elements, but also dialog elements.
14844 Element* removedElement = nullptr;
14845 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
14846 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
14847 if (element && aPredicate(element)) {
14848 removedElement = element;
14849 mTopLayer.RemoveElementAt(i);
14850 break;
14854 // Pop from the stack null elements (references to elements which have
14855 // been GC'd since they were added to the stack) and elements which are
14856 // no longer in this document.
14858 // FIXME(emilio): If this loop does something, it'd violate the assertions
14859 // from GetTopLayerTop()... What gives?
14860 while (!mTopLayer.IsEmpty()) {
14861 Element* element = GetTopLayerTop();
14862 if (!element || element->GetComposedDoc() != this) {
14863 mTopLayer.RemoveLastElement();
14864 } else {
14865 // The top element of the stack is now an in-doc element. Return here.
14866 break;
14870 if (!removedElement) {
14871 return nullptr;
14874 const bool modal = removedElement->State().HasState(ElementState::MODAL);
14876 if (modal) {
14877 removedElement->RemoveStates(ElementState::TOPMOST_MODAL);
14878 bool foundExistingModalElement = false;
14879 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14880 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14881 if (element && element->State().HasState(ElementState::MODAL)) {
14882 element->AddStates(ElementState::TOPMOST_MODAL);
14883 foundExistingModalElement = true;
14884 break;
14887 // No more modal elements, make the document not inert anymore.
14888 if (!foundExistingModalElement) {
14889 Element* root = GetRootElement();
14890 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
14891 root->RemoveStates(ElementState::INERT);
14896 return removedElement;
14899 Element* Document::TopLayerPop(Element& aElement) {
14900 auto predictFunc = [&aElement](Element* element) {
14901 return element == &aElement;
14903 return TopLayerPop(predictFunc);
14906 void Document::GetWireframe(bool aIncludeNodes,
14907 Nullable<Wireframe>& aWireframe) {
14908 FlushPendingNotifications(FlushType::Layout);
14909 GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
14912 void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
14913 Nullable<Wireframe>& aWireframe) {
14914 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
14915 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
14917 PresShell* shell = GetPresShell();
14918 if (!shell) {
14919 return;
14922 nsPresContext* pc = shell->GetPresContext();
14923 if (!pc) {
14924 return;
14927 nsIFrame* rootFrame = shell->GetRootFrame();
14928 if (!rootFrame) {
14929 return;
14932 auto& wireframe = aWireframe.SetValue();
14933 wireframe.mCanvasBackground = shell->ComputeCanvasBackground().mViewportColor;
14935 FrameForPointOptions options;
14936 options.mBits += FrameForPointOption::IgnoreCrossDoc;
14937 options.mBits += FrameForPointOption::IgnorePaintSuppression;
14938 options.mBits += FrameForPointOption::OnlyVisible;
14940 AutoTArray<nsIFrame*, 32> frames;
14941 const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
14942 nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
14943 options);
14945 // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
14946 // something perhaps, but seems hard / like it'd involve at least some extra
14947 // copying around, since they don't outlive GetFramesForArea.
14948 auto& rects = wireframe.mRects.Construct();
14949 if (!rects.SetCapacity(frames.Length(), fallible)) {
14950 return;
14952 for (nsIFrame* frame : Reversed(frames)) {
14953 auto [rectColor,
14954 rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> {
14955 if (frame->IsTextFrame()) {
14956 return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
14957 WireframeRectType::Text};
14959 if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
14960 return {0, WireframeRectType::Image};
14962 if (frame->IsThemed()) {
14963 return {0, WireframeRectType::Background};
14965 bool drawImage = false;
14966 bool drawColor = false;
14967 if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) {
14968 const nscolor color = nsCSSRendering::DetermineBackgroundColor(
14969 pc, bgStyle, frame, drawImage, drawColor);
14970 if (drawImage &&
14971 !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
14972 return {color, WireframeRectType::Image};
14974 if (drawColor && !frame->IsCanvasFrame()) {
14975 // Canvas frame background already accounted for in mCanvasBackground.
14976 return {color, WireframeRectType::Background};
14979 return {0, WireframeRectType::Unknown};
14980 }();
14982 if (rectType == WireframeRectType::Unknown) {
14983 continue;
14986 const auto r =
14987 CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
14988 frame, frame->GetRectRelativeToSelf(), relativeTo));
14989 if ((uint32_t)r.Area() <
14990 StaticPrefs::browser_history_wireframeAreaThreshold()) {
14991 continue;
14994 // Can't really fail because SetCapacity succeeded.
14995 auto& taggedRect = *rects.AppendElement(fallible);
14997 if (aIncludeNodes) {
14998 if (nsIContent* c = frame->GetContent()) {
14999 taggedRect.mNode.Construct(c);
15002 taggedRect.mX = r.x;
15003 taggedRect.mY = r.y;
15004 taggedRect.mWidth = r.width;
15005 taggedRect.mHeight = r.height;
15006 taggedRect.mColor = rectColor;
15007 taggedRect.mType.Construct(rectType);
15011 Element* Document::GetTopLayerTop() {
15012 if (mTopLayer.IsEmpty()) {
15013 return nullptr;
15015 uint32_t last = mTopLayer.Length() - 1;
15016 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
15017 NS_ASSERTION(element, "Should have a top layer element!");
15018 NS_ASSERTION(element->IsInComposedDoc(),
15019 "Top layer element should be in doc");
15020 NS_ASSERTION(element->OwnerDoc() == this,
15021 "Top layer element should be in this doc");
15022 return element;
15025 Element* Document::GetUnretargetedFullscreenElement() const {
15026 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15027 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15028 // Per spec, the fullscreen element is the topmost element in the document’s
15029 // top layer whose fullscreen flag is set, if any, and null otherwise.
15030 if (element && element->State().HasState(ElementState::FULLSCREEN)) {
15031 return element;
15034 return nullptr;
15037 nsTArray<Element*> Document::GetTopLayer() const {
15038 nsTArray<Element*> elements;
15039 for (const nsWeakPtr& ptr : mTopLayer) {
15040 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
15041 elements.AppendElement(elem);
15044 return elements;
15047 bool Document::TopLayerContains(Element& aElement) const {
15048 if (mTopLayer.IsEmpty()) {
15049 return false;
15051 nsWeakPtr weakElement = do_GetWeakReference(&aElement);
15052 return mTopLayer.Contains(weakElement);
15055 void Document::HideAllPopoversUntil(nsINode& aEndpoint,
15056 bool aFocusPreviousElement,
15057 bool aFireEvents) {
15058 auto closeAllOpenPopovers = [&aFocusPreviousElement, &aFireEvents,
15059 this]() MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
15060 while (RefPtr<Element> topmost = GetTopmostAutoPopover()) {
15061 HidePopover(*topmost, aFocusPreviousElement, aFireEvents, IgnoreErrors());
15065 if (&aEndpoint == this) {
15066 closeAllOpenPopovers();
15067 return;
15070 // https://github.com/whatwg/html/pull/9198
15071 auto needRepeatingHide = [&]() {
15072 auto autoList = AutoPopoverList();
15073 return autoList.Contains(&aEndpoint) &&
15074 &aEndpoint != autoList.LastElement();
15077 MOZ_ASSERT((&aEndpoint)->IsElement() &&
15078 (&aEndpoint)->AsElement()->IsAutoPopover());
15079 bool repeatingHide = false;
15080 bool fireEvents = aFireEvents;
15081 do {
15082 RefPtr<const Element> lastToHide = nullptr;
15083 bool foundEndpoint = false;
15084 for (const Element* popover : AutoPopoverList()) {
15085 if (popover == &aEndpoint) {
15086 foundEndpoint = true;
15087 } else if (foundEndpoint) {
15088 lastToHide = popover;
15089 break;
15093 if (!foundEndpoint) {
15094 closeAllOpenPopovers();
15095 return;
15098 while (lastToHide && lastToHide->IsPopoverOpen()) {
15099 RefPtr<Element> topmost = GetTopmostAutoPopover();
15100 if (!topmost) {
15101 break;
15103 HidePopover(*topmost, aFocusPreviousElement, fireEvents, IgnoreErrors());
15106 repeatingHide = needRepeatingHide();
15107 if (repeatingHide) {
15108 fireEvents = false;
15110 } while (repeatingHide);
15113 MOZ_CAN_RUN_SCRIPT_BOUNDARY void
15114 Document::HideAllPopoversWithoutRunningScript() {
15115 return HideAllPopoversUntil(*this, false, false);
15118 void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement,
15119 bool aFireEvents, ErrorResult& aRv) {
15120 RefPtr<nsGenericHTMLElement> popoverHTMLEl =
15121 nsGenericHTMLElement::FromNode(aPopover);
15122 NS_ASSERTION(popoverHTMLEl, "Not a HTML element");
15124 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15125 nullptr, aRv)) {
15126 return;
15129 bool wasShowingOrHiding =
15130 popoverHTMLEl->GetPopoverData()->IsShowingOrHiding();
15131 popoverHTMLEl->GetPopoverData()->SetIsShowingOrHiding(true);
15132 const bool fireEvents = aFireEvents && !wasShowingOrHiding;
15133 auto cleanupHidingFlag = MakeScopeExit([&]() {
15134 if (auto* popoverData = popoverHTMLEl->GetPopoverData()) {
15135 popoverData->SetIsShowingOrHiding(wasShowingOrHiding);
15139 if (popoverHTMLEl->IsAutoPopover()) {
15140 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, fireEvents);
15141 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15142 nullptr, aRv)) {
15143 return;
15145 // TODO: we can't always guarantee:
15146 // The last item in document's auto popover list is popoverHTMLEl.
15147 // See, https://github.com/whatwg/html/issues/9197
15148 // If popoverHTMLEl is not on top, hide popovers again without firing
15149 // events.
15150 if (NS_WARN_IF(GetTopmostAutoPopover() != popoverHTMLEl)) {
15151 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
15152 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15153 nullptr, aRv)) {
15154 return;
15156 MOZ_ASSERT(GetTopmostAutoPopover() == popoverHTMLEl,
15157 "popoverHTMLEl should be on top of auto popover list");
15161 auto* data = popoverHTMLEl->GetPopoverData();
15162 MOZ_ASSERT(data, "Should have popover data");
15163 data->SetInvoker(nullptr);
15165 // Fire beforetoggle event and re-check popover validity.
15166 if (fireEvents) {
15167 // Intentionally ignore the return value here as only on open event for
15168 // beforetoggle the cancelable attribute is initialized to true.
15169 popoverHTMLEl->FireToggleEvent(PopoverVisibilityState::Showing,
15170 PopoverVisibilityState::Hidden,
15171 u"beforetoggle"_ns);
15172 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15173 nullptr, aRv)) {
15174 return;
15178 RemovePopoverFromTopLayer(aPopover);
15180 popoverHTMLEl->PopoverPseudoStateUpdate(false, true);
15181 popoverHTMLEl->GetPopoverData()->SetPopoverVisibilityState(
15182 PopoverVisibilityState::Hidden);
15184 // Queue popover toggle event task.
15185 if (fireEvents) {
15186 popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing);
15189 if (aFocusPreviousElement) {
15190 popoverHTMLEl->FocusPreviousElementAfterHidingPopover();
15191 } else {
15192 popoverHTMLEl->ForgetPreviouslyFocusedElementAfterHidingPopover();
15196 nsTArray<Element*> Document::AutoPopoverList() const {
15197 nsTArray<Element*> elements;
15198 for (const nsWeakPtr& ptr : mTopLayer) {
15199 if (nsCOMPtr<Element> element = do_QueryReferent(ptr)) {
15200 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15201 elements.AppendElement(element);
15205 return elements;
15208 Element* Document::GetTopmostAutoPopover() const {
15209 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15210 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15211 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15212 return element;
15215 return nullptr;
15218 void Document::AddToAutoPopoverList(Element& aElement) {
15219 MOZ_ASSERT(aElement.IsAutoPopover());
15220 TopLayerPush(aElement);
15223 void Document::RemoveFromAutoPopoverList(Element& aElement) {
15224 MOZ_ASSERT(aElement.IsAutoPopover());
15225 TopLayerPop(aElement);
15228 void Document::AddPopoverToTopLayer(Element& aElement) {
15229 MOZ_ASSERT(aElement.GetPopoverData());
15230 TopLayerPush(aElement);
15233 void Document::RemovePopoverFromTopLayer(Element& aElement) {
15234 MOZ_ASSERT(aElement.GetPopoverData());
15235 TopLayerPop(aElement);
15238 // Returns true if aDoc browsing context is focused.
15239 bool IsInFocusedTab(Document* aDoc) {
15240 BrowsingContext* bc = aDoc->GetBrowsingContext();
15241 if (!bc) {
15242 return false;
15245 nsFocusManager* fm = nsFocusManager::GetFocusManager();
15246 if (!fm) {
15247 return false;
15250 if (XRE_IsParentProcess()) {
15251 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
15252 // by retaining the old code path for the parent process.
15253 nsIDocShell* docshell = aDoc->GetDocShell();
15254 if (!docshell) {
15255 return false;
15257 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15258 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15259 if (!rootItem) {
15260 return false;
15262 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
15263 if (!rootWin) {
15264 return false;
15267 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
15268 activeWindow = fm->GetActiveWindow();
15269 if (!activeWindow) {
15270 return false;
15273 return activeWindow == rootWin;
15276 return fm->GetActiveBrowsingContext() == bc->Top();
15279 // Returns true if aDoc browsing context is focused and is also active.
15280 bool IsInActiveTab(Document* aDoc) {
15281 if (!IsInFocusedTab(aDoc)) {
15282 return false;
15285 BrowsingContext* bc = aDoc->GetBrowsingContext();
15286 MOZ_ASSERT(bc, "With no BrowsingContext, we should have failed earlier.");
15287 return bc->IsActive();
15290 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
15291 // Ensure the frame element is the fullscreen element in this document.
15292 // If the frame element is already the fullscreen element in this document,
15293 // this has no effect.
15294 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
15295 RequestFullscreen(std::move(request), XRE_IsContentProcess());
15298 void Document::RemoteFrameFullscreenReverted() {
15299 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
15300 RestorePreviousFullscreenState(std::move(exit));
15303 static bool HasFullscreenSubDocument(Document& aDoc) {
15304 uint32_t count = CountFullscreenSubDocuments(aDoc);
15305 NS_ASSERTION(count <= 1,
15306 "Fullscreen docs should have at most 1 fullscreen child!");
15307 return count >= 1;
15310 // Returns nullptr if a request for Fullscreen API is currently enabled
15311 // in the given document. Returns a static string indicates the reason
15312 // why it is not enabled otherwise.
15313 const char* Document::GetFullscreenError(CallerType aCallerType) {
15314 if (!StaticPrefs::full_screen_api_enabled()) {
15315 return "FullscreenDeniedDisabled";
15318 if (aCallerType == CallerType::System) {
15319 // Chrome code can always use the fullscreen API, provided it's not
15320 // explicitly disabled.
15321 return nullptr;
15324 if (!IsVisible()) {
15325 return "FullscreenDeniedHidden";
15328 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
15329 return "FullscreenDeniedFeaturePolicy";
15332 // Ensure that all containing elements are <iframe> and have allowfullscreen
15333 // attribute set.
15334 BrowsingContext* bc = GetBrowsingContext();
15335 if (!bc || !bc->FullscreenAllowed()) {
15336 return "FullscreenDeniedContainerNotAllowed";
15339 return nullptr;
15342 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
15343 Element* elem = aRequest.Element();
15344 // Strictly speaking, this isn't part of the fullscreen element ready
15345 // check in the spec, but per steps in the spec, when an element which
15346 // is already the fullscreen element requests fullscreen, nothing
15347 // should change and no event should be dispatched, but we still need
15348 // to resolve the returned promise.
15349 Element* fullscreenElement = GetUnretargetedFullscreenElement();
15350 if (elem == fullscreenElement) {
15351 aRequest.MayResolvePromise();
15352 return false;
15354 if (!elem->IsInComposedDoc()) {
15355 aRequest.Reject("FullscreenDeniedNotInDocument");
15356 return false;
15358 if (elem->IsPopoverOpen()) {
15359 aRequest.Reject("FullscreenDeniedPopoverOpen");
15360 return false;
15362 if (elem->OwnerDoc() != this) {
15363 aRequest.Reject("FullscreenDeniedMovedDocument");
15364 return false;
15366 if (!GetWindow()) {
15367 aRequest.Reject("FullscreenDeniedLostWindow");
15368 return false;
15370 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
15371 aRequest.Reject(msg);
15372 return false;
15374 if (HasFullscreenSubDocument(*this)) {
15375 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
15376 return false;
15378 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
15379 aRequest.Reject("FullscreenDeniedHTMLDialog");
15380 return false;
15382 if (!nsContentUtils::IsChromeDoc(this) && !IsInFocusedTab(this)) {
15383 aRequest.Reject("FullscreenDeniedNotFocusedTab");
15384 return false;
15386 return true;
15389 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
15390 MOZ_ASSERT(XRE_IsParentProcess());
15391 nsIDocShell* docShell = aDoc->GetDocShell();
15392 if (!docShell) {
15393 return nullptr;
15395 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15396 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15397 return rootItem ? rootItem->GetWindow() : nullptr;
15400 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
15401 nsPIDOMWindowOuter* aRootWin) {
15402 MOZ_ASSERT(XRE_IsParentProcess());
15403 // If we are in the chrome process, and the window has not been in
15404 // fullscreen, we certainly need to make that fullscreen first.
15405 if (!aRootWin->GetFullScreen()) {
15406 return false;
15408 // The iterator not being at end indicates there is still some
15409 // pending fullscreen request relates to this document. We have to
15410 // push the request to the pending queue so requests are handled
15411 // in the correct order.
15412 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15413 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15414 if (!iter.AtEnd()) {
15415 return false;
15418 // Same thing for exits. If we have any pending, we have to push
15419 // to the pending queue.
15420 PendingFullscreenChangeList::Iterator<FullscreenExit> iterExit(
15421 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15422 if (!iterExit.AtEnd()) {
15423 return false;
15426 // We have to apply the fullscreen state directly in this case,
15427 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
15428 // if it is already in fullscreen. If we do not apply the state but
15429 // instead add it to the queue and wait for the window as normal,
15430 // we would get stuck.
15431 return true;
15434 static bool CheckFullscreenAllowedElementType(const Element* elem) {
15435 // Per spec only HTML, <svg>, and <math> should be allowed, but
15436 // we also need to allow XUL elements right now.
15437 return elem->IsHTMLElement() || elem->IsXULElement() ||
15438 elem->IsSVGElement(nsGkAtoms::svg) ||
15439 elem->IsMathMLElement(nsGkAtoms::math);
15442 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
15443 bool aApplyFullscreenDirectly) {
15444 if (XRE_IsContentProcess()) {
15445 RequestFullscreenInContentProcess(std::move(aRequest),
15446 aApplyFullscreenDirectly);
15447 } else {
15448 RequestFullscreenInParentProcess(std::move(aRequest),
15449 aApplyFullscreenDirectly);
15453 void Document::RequestFullscreenInContentProcess(
15454 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15455 MOZ_ASSERT(XRE_IsContentProcess());
15457 // If we are in the content process, we can apply the fullscreen
15458 // state directly only if we have been in DOM fullscreen, because
15459 // otherwise we always need to notify the chrome.
15460 if (aApplyFullscreenDirectly ||
15461 nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) {
15462 ApplyFullscreen(std::move(aRequest));
15463 return;
15466 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15467 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15468 return;
15471 // We don't need to check element ready before this point, because
15472 // if we called ApplyFullscreen, it would check that for us.
15473 if (!FullscreenElementReadyCheck(*aRequest)) {
15474 return;
15477 PendingFullscreenChangeList::Add(std::move(aRequest));
15478 // If we are not the top level process, dispatch an event to make
15479 // our parent process go fullscreen first.
15480 Dispatch(
15481 TaskCategory::Other,
15482 NS_NewRunnableFunction(
15483 "Document::RequestFullscreenInContentProcess", [self = RefPtr{this}] {
15484 if (!self->HasPendingFullscreenRequests()) {
15485 return;
15487 nsContentUtils::DispatchEventOnlyToChrome(
15488 self, self, u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
15489 Cancelable::eNo, /* DefaultAction */ nullptr);
15490 }));
15493 void Document::RequestFullscreenInParentProcess(
15494 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15495 MOZ_ASSERT(XRE_IsParentProcess());
15496 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
15497 if (!rootWin) {
15498 aRequest->MayRejectPromise("No active window");
15499 return;
15502 if (aApplyFullscreenDirectly ||
15503 ShouldApplyFullscreenDirectly(this, rootWin)) {
15504 ApplyFullscreen(std::move(aRequest));
15505 return;
15508 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15509 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15510 return;
15513 // See if we're waiting on an exit. If so, just make this one pending.
15514 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
15515 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15516 if (!iter.AtEnd()) {
15517 PendingFullscreenChangeList::Add(std::move(aRequest));
15518 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15519 return;
15522 // We don't need to check element ready before this point, because
15523 // if we called ApplyFullscreen, it would check that for us.
15524 if (!FullscreenElementReadyCheck(*aRequest)) {
15525 return;
15528 PendingFullscreenChangeList::Add(std::move(aRequest));
15529 // Make the window fullscreen.
15530 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15533 /* static */
15534 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
15535 bool handled = false;
15536 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15537 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15538 while (!iter.AtEnd()) {
15539 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15540 Document* doc = request->Document();
15541 if (doc->ApplyFullscreen(std::move(request))) {
15542 handled = true;
15545 return handled;
15548 /* static */
15549 void Document::ClearPendingFullscreenRequests(Document* aDoc) {
15550 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15551 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
15552 while (!iter.AtEnd()) {
15553 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15554 request->MayRejectPromise("Fullscreen request aborted");
15558 bool Document::HasPendingFullscreenRequests() {
15559 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15560 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15561 return !iter.AtEnd();
15564 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
15565 if (!FullscreenElementReadyCheck(*aRequest)) {
15566 return false;
15569 RefPtr<Document> doc = aRequest->Document();
15570 doc->HideAllPopoversWithoutRunningScript();
15572 // Stash a reference to any existing fullscreen doc, we'll use this later
15573 // to detect if the origin which is fullscreen has changed.
15574 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
15576 // Stores a list of documents which we must dispatch "fullscreenchange"
15577 // too. We're required by the spec to dispatch the events in root-to-leaf
15578 // order, but we traverse the doctree in a leaf-to-root order, so we save
15579 // references to the documents we must dispatch to so that we get the order
15580 // as specified.
15581 AutoTArray<Document*, 8> changed;
15583 // Remember the root document, so that if a fullscreen document is hidden
15584 // we can reset fullscreen state in the remaining visible fullscreen
15585 // documents.
15586 Document* fullScreenRootDoc =
15587 nsContentUtils::GetInProcessSubtreeRootDocument(this);
15589 // If a document is already in fullscreen, then unlock the mouse pointer
15590 // before setting a new document to fullscreen
15591 PointerLockManager::Unlock();
15593 // Set the fullscreen element. This sets the fullscreen style on the
15594 // element, and the fullscreen-ancestor styles on ancestors of the element
15595 // in this document.
15596 Element* elem = aRequest->Element();
15597 SetFullscreenElement(*elem);
15598 // Set the iframe fullscreen flag.
15599 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
15600 iframe->SetFullscreenFlag(true);
15602 changed.AppendElement(this);
15604 // Propagate up the document hierarchy, setting the fullscreen element as
15605 // the element's container in ancestor documents. This also sets the
15606 // appropriate css styles as well. Note we don't propagate down the
15607 // document hierarchy, the fullscreen element (or its container) is not
15608 // visible there. Stop when we reach the root document.
15609 Document* child = this;
15610 while (true) {
15611 child->SetFullscreenRoot(fullScreenRootDoc);
15613 // When entering fullscreen, reset the RCD's resolution to the intrinsic
15614 // resolution, otherwise the fullscreen content could be sized larger than
15615 // the screen (since fullscreen is implemented using position:fixed and
15616 // fixed elements are sized to the layout viewport).
15617 // This also ensures that things like video controls aren't zoomed in
15618 // when in fullscreen mode.
15619 if (PresShell* presShell = child->GetPresShell()) {
15620 if (RefPtr<MobileViewportManager> manager =
15621 presShell->GetMobileViewportManager()) {
15622 // Save the previous resolution so it can be restored.
15623 child->mSavedResolution = presShell->GetResolution();
15624 presShell->SetResolutionAndScaleTo(
15625 manager->ComputeIntrinsicResolution(),
15626 ResolutionChangeOrigin::MainThreadRestore);
15630 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
15631 "Fullscreen root should be set!");
15632 if (child == fullScreenRootDoc) {
15633 break;
15636 Element* element = child->GetEmbedderElement();
15637 if (!element) {
15638 // We've reached the root.No more changes need to be made
15639 // to the top layer stacks of documents further up the tree.
15640 break;
15643 Document* parent = child->GetInProcessParentDocument();
15644 parent->SetFullscreenElement(*element);
15645 changed.AppendElement(parent);
15646 child = parent;
15649 FullscreenRoots::Add(this);
15651 // If it is the first entry of the fullscreen, trigger an event so
15652 // that the UI can response to this change, e.g. hide chrome, or
15653 // notifying parent process to enter fullscreen. Note that chrome
15654 // code may also want to listen to MozDOMFullscreen:NewOrigin event
15655 // to pop up warning UI.
15656 if (!previousFullscreenDoc) {
15657 nsContentUtils::DispatchEventOnlyToChrome(
15658 this, elem, u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
15659 Cancelable::eNo, /* DefaultAction */ nullptr);
15662 // The origin which is fullscreen gets changed. Trigger an event so
15663 // that the chrome knows to pop up a warning UI. Note that
15664 // previousFullscreenDoc == nullptr upon first entry, we show the warning UI
15665 // directly as soon as chrome document goes into fullscreen state. Also note
15666 // that, in a multi-process browser, the code in content process is
15667 // responsible for sending message with the origin to its parent, and the
15668 // parent shouldn't rely on this event itself.
15669 if (aRequest->mShouldNotifyNewOrigin && previousFullscreenDoc &&
15670 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
15671 DispatchFullscreenNewOriginEvent(this);
15674 // Dispatch "fullscreenchange" events. Note that the loop order is
15675 // reversed so that events are dispatched in the tree order as
15676 // indicated in the spec.
15677 for (Document* d : Reversed(changed)) {
15678 DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement());
15680 aRequest->MayResolvePromise();
15681 return true;
15684 void Document::ClearOrientationPendingPromise() {
15685 mOrientationPendingPromise = nullptr;
15688 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
15689 if (mIsGoingAway) {
15690 return false;
15693 mOrientationPendingPromise = aPromise;
15694 return true;
15697 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
15698 dom::VisibilityState oldState = mVisibilityState;
15699 mVisibilityState = ComputeVisibilityState();
15700 if (oldState != mVisibilityState) {
15701 if (aDispatchEvent == DispatchVisibilityChange::Yes) {
15702 nsContentUtils::DispatchTrustedEvent(this, this, u"visibilitychange"_ns,
15703 CanBubble::eYes, Cancelable::eNo);
15705 NotifyActivityChanged();
15706 if (mVisibilityState == dom::VisibilityState::Visible) {
15707 MaybeActiveMediaComponents();
15710 bool visible = !Hidden();
15711 for (auto* listener : mWorkerListeners) {
15712 listener->OnVisible(visible);
15717 void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
15718 mWorkerListeners.Insert(aListener);
15719 aListener->OnVisible(!Hidden());
15722 void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
15723 mWorkerListeners.Remove(aListener);
15726 VisibilityState Document::ComputeVisibilityState() const {
15727 // We have to check a few pieces of information here:
15728 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
15729 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
15730 // want to use GetWindow here because it does weird groveling for windows
15731 // in some cases.
15732 // 3) Is our outer window background? If so, we're hidden.
15733 // Otherwise, we're visible.
15734 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
15735 mWindow->GetOuterWindow()->IsBackground()) {
15736 return dom::VisibilityState::Hidden;
15739 return dom::VisibilityState::Visible;
15742 void Document::PostVisibilityUpdateEvent() {
15743 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
15744 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
15745 DispatchVisibilityChange::Yes);
15746 Dispatch(TaskCategory::Other, event.forget());
15749 void Document::MaybeActiveMediaComponents() {
15750 auto* window = GetWindow();
15751 if (!window || !window->ShouldDelayMediaFromStart()) {
15752 return;
15754 window->ActivateMediaComponents();
15757 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
15758 nsINode::AddSizeOfExcludingThis(aWindowSizes,
15759 &aWindowSizes.mDOMSizes.mDOMOtherSize);
15761 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
15762 AddSizeOfNodeTree(*kid, aWindowSizes);
15765 // IMPORTANT: for our ComputedValues measurements, we want to measure
15766 // ComputedValues accessible from DOM elements before ComputedValues not
15767 // accessible from DOM elements (i.e. accessible only from the frame tree).
15769 // Therefore, the measurement of the Document superclass must happen after
15770 // the measurement of DOM nodes (above), because Document contains the
15771 // PresShell, which contains the frame tree.
15772 if (mPresShell) {
15773 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
15776 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
15778 aWindowSizes.mPropertyTablesSize +=
15779 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15781 if (EventListenerManager* elm = GetExistingListenerManager()) {
15782 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15785 if (mNodeInfoManager) {
15786 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
15789 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15790 mDOMMediaQueryLists.sizeOfExcludingThis(
15791 aWindowSizes.mState.mMallocSizeOf);
15793 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
15794 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15795 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15798 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
15800 for (auto& sheetArray : mAdditionalSheets) {
15801 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
15803 // Lumping in the loader with the style-sheets size is not ideal,
15804 // but most of the things in there are in fact stylesheets, so it
15805 // doesn't seem worthwhile to separate it out.
15806 // This can be null if we've already been unlinked.
15807 if (mCSSLoader) {
15808 aWindowSizes.mLayoutStyleSheetsSize +=
15809 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
15812 if (mResizeObserverController) {
15813 mResizeObserverController->AddSizeOfIncludingThis(aWindowSizes);
15816 if (mAttributeStyles) {
15817 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15818 mAttributeStyles->DOMSizeOfIncludingThis(
15819 aWindowSizes.mState.mMallocSizeOf);
15822 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15823 mStyledLinks.ShallowSizeOfExcludingThis(
15824 aWindowSizes.mState.mMallocSizeOf);
15826 // Measurement of the following members may be added later if DMD finds it
15827 // is worthwhile:
15828 // - mMidasCommandManager
15829 // - many!
15832 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
15833 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15834 aWindowSizes.mState.mMallocSizeOf(this);
15835 DocAddSizeOfExcludingThis(aWindowSizes);
15838 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
15839 size_t* aNodeSize) const {
15840 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
15841 // nsDocuments can only appear at the top of the DOM tree, and we use the
15842 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
15843 // be called.
15844 MOZ_CRASH();
15847 /* static */
15848 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
15849 size_t nodeSize = 0;
15850 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
15852 // This is where we transfer the nodeSize obtained from
15853 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
15854 switch (aNode.NodeType()) {
15855 case nsINode::ELEMENT_NODE:
15856 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
15857 break;
15858 case nsINode::TEXT_NODE:
15859 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
15860 break;
15861 case nsINode::CDATA_SECTION_NODE:
15862 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
15863 break;
15864 case nsINode::COMMENT_NODE:
15865 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
15866 break;
15867 default:
15868 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
15869 break;
15872 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
15873 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15876 if (aNode.IsContent()) {
15877 nsTArray<nsIContent*> anonKids;
15878 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
15879 nsIContent::eAllChildren);
15880 for (nsIContent* anonKid : anonKids) {
15881 AddSizeOfNodeTree(*anonKid, aWindowSizes);
15884 if (auto* element = Element::FromNode(aNode)) {
15885 if (ShadowRoot* shadow = element->GetShadowRoot()) {
15886 AddSizeOfNodeTree(*shadow, aWindowSizes);
15891 // NOTE(emilio): If you feel smart and want to change this function to use
15892 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
15893 // sane way, and kids of <content> won't point to the parent, so we'd never
15894 // find the root node where we should stop at.
15895 for (nsIContent* kid = aNode.GetFirstChild(); kid;
15896 kid = kid->GetNextSibling()) {
15897 AddSizeOfNodeTree(*kid, aWindowSizes);
15901 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
15902 ErrorResult& rv) {
15903 nsCOMPtr<nsIScriptGlobalObject> global =
15904 do_QueryInterface(aGlobal.GetAsSupports());
15905 if (!global) {
15906 rv.Throw(NS_ERROR_UNEXPECTED);
15907 return nullptr;
15910 nsCOMPtr<nsIScriptObjectPrincipal> prin =
15911 do_QueryInterface(aGlobal.GetAsSupports());
15912 if (!prin) {
15913 rv.Throw(NS_ERROR_UNEXPECTED);
15914 return nullptr;
15917 nsCOMPtr<nsIURI> uri;
15918 NS_NewURI(getter_AddRefs(uri), "about:blank");
15919 if (!uri) {
15920 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
15921 return nullptr;
15924 nsCOMPtr<Document> doc;
15925 nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
15926 nullptr, uri, uri, prin->GetPrincipal(),
15927 true, global, DocumentFlavorPlain);
15928 if (NS_FAILED(res)) {
15929 rv.Throw(res);
15930 return nullptr;
15933 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
15935 return doc.forget();
15938 XPathExpression* Document::CreateExpression(const nsAString& aExpression,
15939 XPathNSResolver* aResolver,
15940 ErrorResult& rv) {
15941 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
15944 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
15945 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
15948 already_AddRefed<XPathResult> Document::Evaluate(
15949 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
15950 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
15951 ErrorResult& rv) {
15952 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
15953 aType, aResult, rv);
15956 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
15957 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
15958 if (!item) {
15959 return nullptr;
15961 nsCOMPtr<nsIDocShellTreeOwner> owner;
15962 item->GetTreeOwner(getter_AddRefs(owner));
15963 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
15964 if (!appWin) {
15965 return nullptr;
15967 nsCOMPtr<nsIDocShell> appWinShell;
15968 appWin->GetDocShell(getter_AddRefs(appWinShell));
15969 if (!SameCOMIdentity(appWinShell, item)) {
15970 return nullptr;
15972 return appWin.forget();
15975 WindowContext* Document::GetTopLevelWindowContext() const {
15976 WindowContext* windowContext = GetWindowContext();
15977 return windowContext ? windowContext->TopWindowContext() : nullptr;
15980 Document* Document::GetTopLevelContentDocumentIfSameProcess() {
15981 Document* parent;
15983 if (!mLoadedAsData) {
15984 parent = this;
15985 } else {
15986 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15987 if (!window) {
15988 return nullptr;
15991 parent = window->GetExtantDoc();
15992 if (!parent) {
15993 return nullptr;
15997 do {
15998 if (parent->IsTopLevelContentDocument()) {
15999 break;
16002 // If we ever have a non-content parent before we hit a toplevel content
16003 // parent, then we're never going to find one. Just bail.
16004 if (!parent->IsContentDocument()) {
16005 return nullptr;
16008 parent = parent->GetInProcessParentDocument();
16009 } while (parent);
16011 return parent;
16014 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
16015 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
16018 void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
16019 MOZ_ASSERT(IsBeingUsedAsImage());
16020 MOZ_ASSERT(aReferencingDocument);
16022 if (!aReferencingDocument->mShouldReportUseCounters) {
16023 // No need to propagate use counters to a document that itself won't report
16024 // use counters.
16025 return;
16028 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16029 ("PropagateImageUseCounters from %s to %s",
16030 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
16031 nsContentUtils::TruncatedURLForDisplay(
16032 aReferencingDocument->mDocumentURI)
16033 .get()));
16035 if (aReferencingDocument->IsBeingUsedAsImage()) {
16036 NS_WARNING(
16037 "Page use counters from nested image documents may not "
16038 "propagate to the top-level document (bug 1657805)");
16041 SetCssUseCounterBits();
16042 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
16043 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
16046 bool Document::HasScriptsBlockedBySandbox() const {
16047 return mSandboxFlags & SANDBOXED_SCRIPTS;
16050 // Some use-counter sanity-checking.
16051 static_assert(size_t(eUseCounter_EndCSSProperties) -
16052 size_t(eUseCounter_FirstCSSProperty) ==
16053 size_t(eCSSProperty_COUNT_with_aliases),
16054 "We should have the right amount of CSS property use counters");
16055 static_assert(size_t(eUseCounter_Count) -
16056 size_t(eUseCounter_FirstCountedUnknownProperty) ==
16057 size_t(CountedUnknownProperty::Count),
16058 "We should have the right amount of counted unknown properties"
16059 " use counters");
16060 static_assert(size_t(eUseCounter_Count) * 2 ==
16061 size_t(Telemetry::HistogramUseCounterCount),
16062 "There should be two histograms (document and page)"
16063 " for each use counter");
16065 #define ASSERT_CSS_COUNTER(id_, method_) \
16066 static_assert(size_t(eUseCounter_property_##method_) - \
16067 size_t(eUseCounter_FirstCSSProperty) == \
16068 size_t(id_), \
16069 "Order for CSS counters and CSS property id should match");
16070 #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
16071 #define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
16072 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
16073 #define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
16074 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
16075 #define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
16076 ASSERT_CSS_COUNTER(eCSSPropertyAlias_##aliasid_, method_)
16077 #include "mozilla/ServoCSSPropList.h"
16078 #undef CSS_PROP_ALIAS
16079 #undef CSS_PROP_SHORTHAND
16080 #undef CSS_PROP_LONGHAND
16081 #undef CSS_PROP_PUBLIC_OR_PRIVATE
16082 #undef ASSERT_CSS_COUNTER
16084 void Document::SetCssUseCounterBits() {
16085 if (StaticPrefs::layout_css_use_counters_enabled()) {
16086 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
16087 auto id = nsCSSPropertyID(i);
16088 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
16089 SetUseCounter(nsCSSProps::UseCounterFor(id));
16094 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
16095 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
16096 auto id = CountedUnknownProperty(i);
16097 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
16098 id)) {
16099 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
16105 void Document::InitUseCounters() {
16106 // We can be called more than once, e.g. when session history navigation shows
16107 // us a second time.
16108 if (mUseCountersInitialized) {
16109 return;
16111 mUseCountersInitialized = true;
16113 static_assert(Telemetry::HistogramUseCounterCount > 0);
16115 if (!ShouldIncludeInTelemetry(/* aAllowExtensionURIs = */ true)) {
16116 return;
16119 // Now we know for sure that we should report use counters from this document.
16120 mShouldReportUseCounters = true;
16122 WindowContext* top = GetWindowContextForPageUseCounters();
16123 if (!top) {
16124 // This is the case for SVG image documents. They are not displayed in a
16125 // window, but we still do want to record document use counters for them.
16127 // Page use counter propagation is handled in PropagateImageUseCounters,
16128 // so there is no need to use the cross-process machinery to send them.
16129 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16130 ("InitUseCounters for a non-displayed document [%s]",
16131 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16132 return;
16135 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16136 if (!wgc) {
16137 return;
16140 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16141 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
16142 " [from %s]",
16143 wgc->InnerWindowId(), top->Id(),
16144 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16146 // Inform the parent process that we will send it page use counters later on.
16147 wgc->SendExpectPageUseCounters(top);
16148 mShouldSendPageUseCounters = true;
16151 // We keep separate counts for individual documents and top-level
16152 // pages to more accurately track how many web pages might break if
16153 // certain features were removed. Consider the case of a single
16154 // HTML document with several SVG images and/or iframes with
16155 // sub-documents of their own. If we maintained a single set of use
16156 // counters and all the sub-documents use a particular feature, then
16157 // telemetry would indicate that we would be breaking N documents if
16158 // that feature were removed. Whereas with a document/top-level
16159 // page split, we can see that N documents would be affected, but
16160 // only a single web page would be affected.
16162 // The difference between the values of these two histograms and the
16163 // related use counters below tell us how many pages did *not* use
16164 // the feature in question. For instance, if we see that a given
16165 // session has destroyed 30 content documents, but a particular use
16166 // counter shows only a count of 5, we can infer that the use
16167 // counter was *not* used in 25 of those 30 documents.
16169 // We do things this way, rather than accumulating a boolean flag
16170 // for each use counter, to avoid sending histograms for features
16171 // that don't get widely used. Doing things in this fashion means
16172 // smaller telemetry payloads and faster processing on the server
16173 // side.
16174 void Document::ReportDocumentUseCounters() {
16175 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
16176 return;
16179 mReportedDocumentUseCounters = true;
16181 // Note that a document is being destroyed. See the comment above for how
16182 // use counter histograms are interpreted relative to this measurement.
16183 // TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED is recorded in
16184 // WindowGlobalParent::FinishAccumulatingPageUseCounters.
16185 Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
16187 // Ask all of our resource documents to report their own document use
16188 // counters.
16189 EnumerateExternalResources([](Document& aDoc) {
16190 aDoc.ReportDocumentUseCounters();
16191 return CallState::Continue;
16194 // Copy StyleUseCounters into our document use counters.
16195 SetCssUseCounterBits();
16197 Maybe<nsCString> urlForLogging;
16198 const bool dumpCounters = StaticPrefs::dom_use_counters_dump_document();
16199 if (dumpCounters) {
16200 urlForLogging.emplace(
16201 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()));
16204 // Report our per-document use counters.
16205 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
16206 auto uc = static_cast<UseCounter>(c);
16207 if (!mUseCounters[uc]) {
16208 continue;
16211 auto id = static_cast<Telemetry::HistogramID>(
16212 Telemetry::HistogramFirstUseCounter + uc * 2);
16213 if (dumpCounters) {
16214 printf_stderr("USE_COUNTER_DOCUMENT: %s - %s\n",
16215 Telemetry::GetHistogramName(id), urlForLogging->get());
16217 Telemetry::Accumulate(id, 1);
16221 void Document::SendPageUseCounters() {
16222 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
16223 return;
16226 // Ask all of our resource documents to send their own document use
16227 // counters to the parent process to be counted as page use counters.
16228 EnumerateExternalResources([](Document& aDoc) {
16229 aDoc.SendPageUseCounters();
16230 return CallState::Continue;
16233 // Send our use counters to the parent process to accumulate them towards the
16234 // page use counters for the top-level document.
16236 // We take our own document use counters (those in mUseCounters) and any child
16237 // document use counters (those in mChildDocumentUseCounters) that have been
16238 // explicitly propagated up to us, which includes resource documents, static
16239 // clones, and SVG images.
16240 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16241 if (!wgc) {
16242 MOZ_ASSERT_UNREACHABLE(
16243 "SendPageUseCounters should be called while we still have access "
16244 "to our WindowContext");
16245 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16246 (" > too late to send page use counters"));
16247 return;
16250 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16251 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
16252 wgc->WindowContext()->Id(),
16253 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
16255 // Copy StyleUseCounters into our document use counters.
16256 SetCssUseCounterBits();
16258 UseCounters counters = mUseCounters | mChildDocumentUseCounters;
16259 wgc->SendAccumulatePageUseCounters(counters);
16262 bool Document::RecomputeResistFingerprinting() {
16263 const bool previous = mShouldResistFingerprinting;
16265 RefPtr<BrowsingContext> opener =
16266 GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
16267 // If we have a parent or opener document, defer to it only when we have a
16268 // null principal (e.g. a sandboxed iframe or a data: uri) or when the
16269 // document's principal matches. This means we will defer about:blank,
16270 // about:srcdoc, blob and same-origin iframes/popups to the parent/opener,
16271 // but not cross-origin ones. Cross-origin iframes/popups may inherit a
16272 // CookieJarSettings.mShouldRFP = false bit however, which will be respected.
16273 auto shouldInheritFrom = [this](Document* aDoc) {
16274 return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
16275 this->NodePrincipal()->GetIsNullPrincipal());
16278 if (shouldInheritFrom(mParentDocument)) {
16279 MOZ_LOG(
16280 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16281 ("Inside RecomputeResistFingerprinting with URI %s and deferring "
16282 "to parent document %s",
16283 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16284 mParentDocument->GetDocumentURI()->GetSpecOrDefault().get()));
16285 mShouldResistFingerprinting = mParentDocument->ShouldResistFingerprinting(
16286 RFPTarget::IsAlwaysEnabledForPrecompute);
16287 } else if (opener && shouldInheritFrom(opener->GetDocument())) {
16288 MOZ_LOG(
16289 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16290 ("Inside RecomputeResistFingerprinting with URI %s and deferring to "
16291 "opener document %s",
16292 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16293 opener->GetDocument()->GetDocumentURI()->GetSpecOrDefault().get()));
16294 mShouldResistFingerprinting =
16295 opener->GetDocument()->ShouldResistFingerprinting(
16296 RFPTarget::IsAlwaysEnabledForPrecompute);
16297 } else {
16298 bool chromeDoc = nsContentUtils::IsChromeDoc(this);
16299 MOZ_LOG(
16300 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16301 ("Inside RecomputeResistFingerprinting with URI %s ChromeDoc:%x",
16302 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16303 chromeDoc));
16304 mShouldResistFingerprinting =
16305 !chromeDoc && nsContentUtils::ShouldResistFingerprinting(
16306 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16309 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16310 ("Finished RecomputeResistFingerprinting with result %x",
16311 mShouldResistFingerprinting));
16313 return previous != mShouldResistFingerprinting;
16316 bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const {
16317 return mShouldResistFingerprinting && nsRFPService::IsRFPEnabledFor(aTarget);
16320 WindowContext* Document::GetWindowContextForPageUseCounters() const {
16321 if (mDisplayDocument) {
16322 // If we are a resource document, then go through it to find the
16323 // top-level document.
16324 return mDisplayDocument->GetWindowContextForPageUseCounters();
16327 if (mOriginalDocument) {
16328 // For static clones (print preview documents), contribute page use counters
16329 // towards the original document.
16330 return mOriginalDocument->GetWindowContextForPageUseCounters();
16333 WindowContext* wc = GetTopLevelWindowContext();
16334 if (!wc || !wc->GetBrowsingContext()->IsContent()) {
16335 return nullptr;
16338 return wc;
16341 void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
16342 if (mIntersectionObservers.IsEmpty()) {
16343 return;
16346 DOMHighResTimeStamp time = 0;
16347 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
16348 if (Performance* perf = win->GetPerformance()) {
16349 time = perf->TimeStampToDOMHighResForRendering(aNowTime);
16353 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16354 mIntersectionObservers);
16355 for (const auto& observer : observers) {
16356 if (observer) {
16357 observer->Update(*this, time);
16362 void Document::ScheduleIntersectionObserverNotification() {
16363 if (mIntersectionObservers.IsEmpty()) {
16364 return;
16366 MOZ_RELEASE_ASSERT(NS_IsMainThread());
16367 nsCOMPtr<nsIRunnable> notification =
16368 NewRunnableMethod("Document::NotifyIntersectionObservers", this,
16369 &Document::NotifyIntersectionObservers);
16370 Dispatch(TaskCategory::Other, notification.forget());
16373 void Document::NotifyIntersectionObservers() {
16374 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16375 mIntersectionObservers);
16376 for (const auto& observer : observers) {
16377 if (observer) {
16378 // MOZ_KnownLive because the 'observers' array guarantees to keep it
16379 // alive.
16380 MOZ_KnownLive(observer)->Notify();
16385 DOMIntersectionObserver& Document::EnsureLazyLoadImageObserver() {
16386 if (!mLazyLoadImageObserver) {
16387 mLazyLoadImageObserver =
16388 DOMIntersectionObserver::CreateLazyLoadObserver(*this);
16390 return *mLazyLoadImageObserver;
16393 DOMIntersectionObserver& Document::EnsureContentVisibilityObserver() {
16394 if (!mContentVisibilityObserver) {
16395 mContentVisibilityObserver =
16396 DOMIntersectionObserver::CreateContentVisibilityObserver(*this);
16398 return *mContentVisibilityObserver;
16401 void Document::ObserveForContentVisibility(Element& aElement) {
16402 EnsureContentVisibilityObserver().Observe(aElement);
16405 void Document::UnobserveForContentVisibility(Element& aElement) {
16406 if (mContentVisibilityObserver) {
16407 mContentVisibilityObserver->Unobserve(aElement);
16411 ResizeObserver& Document::EnsureLastRememberedSizeObserver() {
16412 if (!mLastRememberedSizeObserver) {
16413 mLastRememberedSizeObserver =
16414 ResizeObserver::CreateLastRememberedSizeObserver(*this);
16416 return *mLastRememberedSizeObserver;
16419 void Document::ObserveForLastRememberedSize(Element& aElement) {
16420 if (NS_WARN_IF(!IsActive())) {
16421 return;
16423 // Options are initialized with ResizeObserverBoxOptions::Content_box by
16424 // default, which is what we want.
16425 static ResizeObserverOptions options;
16426 EnsureLastRememberedSizeObserver().Observe(aElement, options);
16429 void Document::UnobserveForLastRememberedSize(Element& aElement) {
16430 if (mLastRememberedSizeObserver) {
16431 mLastRememberedSizeObserver->Unobserve(aElement);
16435 void Document::NotifyLayerManagerRecreated() {
16436 NotifyActivityChanged();
16437 EnumerateSubDocuments([](Document& aSubDoc) {
16438 aSubDoc.NotifyLayerManagerRecreated();
16439 return CallState::Continue;
16443 XPathEvaluator* Document::XPathEvaluator() {
16444 if (!mXPathEvaluator) {
16445 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
16447 return mXPathEvaluator.get();
16450 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
16451 return mCachedEncoder.forget();
16454 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
16455 mCachedEncoder = aEncoder;
16458 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
16460 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
16462 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
16463 mStateObjectContainer = scContainer;
16464 mCachedStateObject = JS::UndefinedValue();
16465 mCachedStateObjectValid = false;
16468 bool Document::ComputeDocumentLWTheme() const {
16469 if (!NodePrincipal()->IsSystemPrincipal()) {
16470 return false;
16473 Element* element = GetRootElement();
16474 return element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
16475 nsGkAtoms::_true, eCaseMatters);
16478 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
16479 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
16480 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
16481 ELEMENT_NODE);
16482 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
16484 nsCOMPtr<Element> element;
16485 DebugOnly<nsresult> rv =
16486 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
16487 mozilla::dom::NOT_FROM_PARSER);
16489 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
16490 return element.forget();
16493 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
16494 BrowsingContext* aContext) {
16495 aContext->PreOrderWalk([&](BrowsingContext* aBC) {
16496 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
16497 if (RefPtr<Document> doc = win->GetExtantDoc()) {
16498 SuppressDocument(doc);
16499 mDocuments.AppendElement(doc);
16505 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
16506 BrowsingContextGroup* aGroup) {
16507 for (const auto& bc : aGroup->Toplevels()) {
16508 SuppressBrowsingContext(bc);
16512 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
16513 SyncOperationBehavior aSyncBehavior)
16514 : mSyncBehavior(aSyncBehavior) {
16515 mMicroTaskLevel = 0;
16516 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16517 mMicroTaskLevel = ccjs->MicroTaskLevel();
16518 ccjs->SetMicroTaskLevel(0);
16520 if (aDoc) {
16521 mBrowsingContext = aDoc->GetBrowsingContext();
16522 if (InputTaskManager::CanSuspendInputEvent()) {
16523 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
16524 SuppressBrowsingContextGroup(bcg);
16526 } else if (mBrowsingContext) {
16527 SuppressBrowsingContext(mBrowsingContext->Top());
16529 if (mBrowsingContext &&
16530 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16531 InputTaskManager::CanSuspendInputEvent()) {
16532 mBrowsingContext->Group()->IncInputEventSuspensionLevel();
16537 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
16538 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16539 win->TimeoutManager().BeginSyncOperation();
16541 aDoc->SetIsInSyncOperation(true);
16544 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
16545 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16546 win->TimeoutManager().EndSyncOperation();
16548 aDoc->SetIsInSyncOperation(false);
16551 nsAutoSyncOperation::~nsAutoSyncOperation() {
16552 UnsuppressDocuments();
16553 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
16554 if (ccjs) {
16555 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
16557 if (mBrowsingContext &&
16558 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16559 InputTaskManager::CanSuspendInputEvent()) {
16560 mBrowsingContext->Group()->DecInputEventSuspensionLevel();
16564 void Document::SetIsInSyncOperation(bool aSync) {
16565 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16566 ccjs->UpdateMicroTaskSuppressionGeneration();
16569 if (aSync) {
16570 ++mInSyncOperationCount;
16571 } else {
16572 --mInSyncOperationCount;
16576 gfxUserFontSet* Document::GetUserFontSet() {
16577 if (!mFontFaceSet) {
16578 return nullptr;
16581 return mFontFaceSet->GetImpl();
16584 void Document::FlushUserFontSet() {
16585 if (!mFontFaceSetDirty) {
16586 return;
16589 mFontFaceSetDirty = false;
16591 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
16592 nsTArray<nsFontFaceRuleContainer> rules;
16593 RefPtr<PresShell> presShell = GetPresShell();
16594 if (presShell) {
16595 MOZ_ASSERT(mStyleSetFilled);
16596 mStyleSet->AppendFontFaceRules(rules);
16599 if (!mFontFaceSet && !rules.IsEmpty()) {
16600 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16603 bool changed = false;
16604 if (mFontFaceSet) {
16605 changed = mFontFaceSet->UpdateRules(rules);
16608 // We need to enqueue a style change reflow (for later) to
16609 // reflect that we're modifying @font-face rules. (However,
16610 // without a reflow, nothing will happen to start any downloads
16611 // that are needed.)
16612 if (changed && presShell) {
16613 if (nsPresContext* presContext = presShell->GetPresContext()) {
16614 presContext->UserFontSetUpdated();
16620 void Document::MarkUserFontSetDirty() {
16621 if (mFontFaceSetDirty) {
16622 return;
16624 mFontFaceSetDirty = true;
16625 if (PresShell* presShell = GetPresShell()) {
16626 presShell->EnsureStyleFlush();
16630 FontFaceSet* Document::Fonts() {
16631 if (!mFontFaceSet) {
16632 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16633 FlushUserFontSet();
16635 return mFontFaceSet;
16638 void Document::ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp) {
16639 MOZ_ASSERT(!aTimeStamp.IsNull());
16641 if (!mLastScrollLinkedEffectDetectionTime.IsNull() &&
16642 mLastScrollLinkedEffectDetectionTime >= aTimeStamp) {
16643 return;
16646 if (mLastScrollLinkedEffectDetectionTime.IsNull()) {
16647 // Report to console just once.
16648 nsContentUtils::ReportToConsole(
16649 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
16650 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
16653 mLastScrollLinkedEffectDetectionTime = aTimeStamp;
16656 bool Document::HasScrollLinkedEffect() const {
16657 if (nsPresContext* pc = GetPresContext()) {
16658 return mLastScrollLinkedEffectDetectionTime ==
16659 pc->RefreshDriver()->MostRecentRefresh();
16662 return false;
16665 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
16666 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16667 // Setting has user interction on a discarded browsing context has
16668 // no effect.
16669 Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
16673 bool Document::GetSHEntryHasUserInteraction() {
16674 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16675 return topWc->GetSHEntryHasUserInteraction();
16677 return false;
16680 void Document::SetUserHasInteracted() {
16681 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
16682 ("Document %p has been interacted by user.", this));
16684 // We maybe need to update the user-interaction permission.
16685 MaybeStoreUserInteractionAsPermission();
16687 // For purposes of reducing irrelevant session history entries on
16688 // the back button, we annotate entries with whether they had user
16689 // interaction. This is gated on its own flag on the WindowContext
16690 // (instead of mUserHasInteracted) to account for the fact that multiple
16691 // top-level SH entries can be associated with the same document.
16692 // Thus, whenever we create a new SH entry for this document,
16693 // this flag is reset.
16694 if (!GetSHEntryHasUserInteraction()) {
16695 nsIDocShell* docShell = GetDocShell();
16696 if (docShell) {
16697 nsCOMPtr<nsISHEntry> currentEntry;
16698 bool oshe;
16699 nsresult rv =
16700 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
16701 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
16702 currentEntry->SetHasUserInteraction(true);
16705 SetSHEntryHasUserInteraction(true);
16708 if (mUserHasInteracted) {
16709 return;
16712 mUserHasInteracted = true;
16714 if (mChannel) {
16715 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16716 loadInfo->SetDocumentHasUserInteracted(true);
16718 // Tell the parent process about user interaction
16719 if (auto* wgc = GetWindowGlobalChild()) {
16720 wgc->SendUpdateDocumentHasUserInteracted(true);
16723 MaybeAllowStorageForOpenerAfterUserInteraction();
16726 BrowsingContext* Document::GetBrowsingContext() const {
16727 nsCOMPtr<nsIDocShell> docshell(mDocumentContainer);
16728 return docshell ? docshell->GetBrowsingContext() : nullptr;
16731 void Document::NotifyUserGestureActivation() {
16732 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16733 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16734 WindowContext* windowContext = aBC->GetCurrentWindowContext();
16735 if (!windowContext) {
16736 return;
16739 nsIDocShell* docShell = aBC->GetDocShell();
16740 if (!docShell) {
16741 return;
16744 Document* document = docShell->GetDocument();
16745 if (!document) {
16746 return;
16749 // XXXedgar we probably could just check `IsInProcess()` after fission
16750 // enable.
16751 if (NodePrincipal()->Equals(document->NodePrincipal())) {
16752 windowContext->NotifyUserGestureActivation();
16756 for (bc = bc->GetParent(); bc; bc = bc->GetParent()) {
16757 if (WindowContext* windowContext = bc->GetCurrentWindowContext()) {
16758 windowContext->NotifyUserGestureActivation();
16764 bool Document::HasBeenUserGestureActivated() {
16765 RefPtr<WindowContext> wc = GetWindowContext();
16766 return wc && wc->HasBeenUserGestureActivated();
16769 DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
16770 if (RefPtr<WindowContext> wc = GetWindowContext()) {
16771 if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) {
16772 if (Performance* perf = innerWindow->GetPerformance()) {
16773 return perf->GetDOMTiming()->TimeStampToDOMHighRes(
16774 wc->GetUserGestureStart());
16779 NS_WARNING(
16780 "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
16781 return 0;
16784 void Document::ClearUserGestureActivation() {
16785 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16786 bc = bc->Top();
16787 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16788 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
16789 windowContext->NotifyResetUserGestureActivation();
16795 bool Document::HasValidTransientUserGestureActivation() const {
16796 RefPtr<WindowContext> wc = GetWindowContext();
16797 return wc && wc->HasValidTransientUserGestureActivation();
16800 bool Document::ConsumeTransientUserGestureActivation() {
16801 RefPtr<WindowContext> wc = GetWindowContext();
16802 return wc && wc->ConsumeTransientUserGestureActivation();
16805 void Document::SetDocTreeHadMedia() {
16806 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
16807 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
16808 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
16812 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
16813 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
16814 return;
16817 // This will probably change for project fission, but currently this document
16818 // and the opener are on the same process. In the future, we should make this
16819 // part async.
16820 nsPIDOMWindowInner* inner = GetInnerWindow();
16821 if (NS_WARN_IF(!inner)) {
16822 return;
16825 // We care about first-party tracking resources only.
16826 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
16827 return;
16830 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16831 if (NS_WARN_IF(!outer)) {
16832 return;
16835 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
16836 if (!openerBC) {
16837 // No opener.
16838 return;
16841 // We want to ensure the following check works for both fission mode and
16842 // non-fission mode:
16843 // "If the opener is not a 3rd party and if this window is not a 3rd party
16844 // with respect to the opener, we should not continue."
16846 // In non-fission mode, the opener and the opened window are in the same
16847 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
16848 // In fission mode, if this window is not a 3rd party with respect to the
16849 // opener, they must be in the same process, so we can still use
16850 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
16851 // party.
16852 if (openerBC->IsInProcess()) {
16853 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
16854 if (NS_WARN_IF(!outerOpener)) {
16855 return;
16858 nsCOMPtr<nsPIDOMWindowInner> openerInner =
16859 outerOpener->GetCurrentInnerWindow();
16860 if (NS_WARN_IF(!openerInner)) {
16861 return;
16864 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
16865 if (NS_WARN_IF(!openerDocument)) {
16866 return;
16869 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
16870 if (NS_WARN_IF(!openerURI)) {
16871 return;
16874 // If the opener is not a 3rd party and if this window is not
16875 // a 3rd party with respect to the opener, we should not continue.
16876 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
16877 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
16878 return;
16882 // We don't care when the asynchronous work finishes here.
16883 Unused << StorageAccessAPIHelper::AllowAccessFor(
16884 NodePrincipal(), openerBC,
16885 ContentBlockingNotifier::eOpenerAfterUserInteraction);
16888 namespace {
16890 // Documents can stay alive for days. We don't want to update the permission
16891 // value at any user-interaction, and, using a timer triggered any X seconds
16892 // should be good enough. 'X' is taken from
16893 // privacy.userInteraction.document.interval pref.
16894 // We also want to store the user-interaction before shutting down, and, for
16895 // this reason, this class implements nsIAsyncShutdownBlocker interface.
16896 class UserInteractionTimer final : public Runnable,
16897 public nsITimerCallback,
16898 public nsIAsyncShutdownBlocker {
16899 public:
16900 NS_DECL_ISUPPORTS_INHERITED
16902 explicit UserInteractionTimer(Document* aDocument)
16903 : Runnable("UserInteractionTimer"),
16904 mPrincipal(aDocument->NodePrincipal()),
16905 mDocument(do_GetWeakReference(aDocument)) {
16906 static int32_t userInteractionTimerId = 0;
16907 // Blocker names must be unique. Let's create it now because when needed,
16908 // the document could be already gone.
16909 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
16910 ++userInteractionTimerId, aDocument);
16913 // Runnable interface
16915 NS_IMETHOD
16916 Run() override {
16917 uint32_t interval =
16918 StaticPrefs::privacy_userInteraction_document_interval();
16919 if (!interval) {
16920 return NS_OK;
16923 RefPtr<UserInteractionTimer> self = this;
16924 auto raii =
16925 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
16927 nsresult rv = NS_NewTimerWithCallback(
16928 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
16929 NS_ENSURE_SUCCESS(rv, NS_OK);
16931 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16932 NS_ENSURE_TRUE(!!phase, NS_OK);
16934 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
16935 __LINE__, u"UserInteractionTimer shutdown"_ns);
16936 NS_ENSURE_SUCCESS(rv, NS_OK);
16938 raii.release();
16939 return NS_OK;
16942 // nsITimerCallback interface
16944 NS_IMETHOD
16945 Notify(nsITimer* aTimer) override {
16946 StoreUserInteraction();
16947 return NS_OK;
16950 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
16951 using nsINamed::GetName;
16952 #endif
16954 // nsIAsyncShutdownBlocker interface
16956 NS_IMETHOD
16957 GetName(nsAString& aName) override {
16958 aName = mBlockerName;
16959 return NS_OK;
16962 NS_IMETHOD
16963 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
16964 CancelTimerAndStoreUserInteraction();
16965 return NS_OK;
16968 NS_IMETHOD
16969 GetState(nsIPropertyBag**) override { return NS_OK; }
16971 private:
16972 ~UserInteractionTimer() = default;
16974 void StoreUserInteraction() {
16975 // Remove the shutting down blocker
16976 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16977 if (phase) {
16978 phase->RemoveBlocker(this);
16981 // If the document is not gone, let's reset its timer flag.
16982 nsCOMPtr<Document> document = do_QueryReferent(mDocument);
16983 if (document) {
16984 ContentBlockingUserInteraction::Observe(mPrincipal);
16985 document->ResetUserInteractionTimer();
16989 void CancelTimerAndStoreUserInteraction() {
16990 if (mTimer) {
16991 mTimer->Cancel();
16992 mTimer = nullptr;
16995 StoreUserInteraction();
16998 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
16999 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
17000 NS_ENSURE_TRUE(!!svc, nullptr);
17002 nsCOMPtr<nsIAsyncShutdownClient> phase;
17003 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
17004 NS_ENSURE_SUCCESS(rv, nullptr);
17006 return phase.forget();
17009 nsCOMPtr<nsIPrincipal> mPrincipal;
17010 nsWeakPtr mDocument;
17012 nsCOMPtr<nsITimer> mTimer;
17014 nsString mBlockerName;
17017 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
17018 nsIAsyncShutdownBlocker)
17020 } // namespace
17022 void Document::MaybeStoreUserInteractionAsPermission() {
17023 // We care about user-interaction stored only for top-level documents
17024 // and documents with access to the Storage Access API
17025 if (!IsTopLevelContentDocument()) {
17026 bool hasSA;
17027 nsresult rv = HasStorageAccessSync(hasSA);
17028 if (NS_FAILED(rv) || !hasSA) {
17029 return;
17033 if (!mUserHasInteracted) {
17034 // First interaction, let's store this info now.
17035 ContentBlockingUserInteraction::Observe(NodePrincipal());
17036 return;
17039 if (mHasUserInteractionTimerScheduled) {
17040 return;
17043 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
17044 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
17045 EventQueuePriority::Idle);
17046 if (NS_WARN_IF(NS_FAILED(rv))) {
17047 return;
17050 // This value will be reset by the timer.
17051 mHasUserInteractionTimerScheduled = true;
17054 void Document::ResetUserInteractionTimer() {
17055 mHasUserInteractionTimerScheduled = false;
17058 bool Document::IsExtensionPage() const {
17059 return BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
17062 void Document::AddResizeObserver(ResizeObserver& aObserver) {
17063 if (!mResizeObserverController) {
17064 mResizeObserverController = MakeUnique<ResizeObserverController>(this);
17066 mResizeObserverController->AddResizeObserver(aObserver);
17069 void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
17070 MOZ_DIAGNOSTIC_ASSERT(mResizeObserverController, "No controller?");
17071 if (MOZ_UNLIKELY(!mResizeObserverController)) {
17072 return;
17074 mResizeObserverController->RemoveResizeObserver(aObserver);
17077 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
17078 if (!mPermissionDelegateHandler) {
17079 mPermissionDelegateHandler =
17080 mozilla::MakeAndAddRef<PermissionDelegateHandler>(this);
17083 if (!mPermissionDelegateHandler->Initialize()) {
17084 mPermissionDelegateHandler = nullptr;
17087 return mPermissionDelegateHandler;
17090 void Document::ScheduleResizeObserversNotification() const {
17091 if (!mResizeObserverController) {
17092 return;
17095 mResizeObserverController->ScheduleNotification();
17098 void Document::ClearStaleServoData() {
17099 DocumentStyleRootIterator iter(this);
17100 while (Element* root = iter.GetNextStyleRoot()) {
17101 RestyleManager::ClearServoDataFromSubtree(root);
17105 Selection* Document::GetSelection(ErrorResult& aRv) {
17106 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
17107 if (!window) {
17108 return nullptr;
17111 if (!window->IsCurrentInnerWindow()) {
17112 return nullptr;
17115 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
17118 void Document::MakeBrowsingContextNonSynthetic() {
17119 if (nsContentUtils::ShouldHideObjectOrEmbedImageDocument()) {
17120 if (BrowsingContext* bc = GetBrowsingContext()) {
17121 if (bc->GetSyntheticDocumentContainer()) {
17122 Unused << bc->SetSyntheticDocumentContainer(false);
17128 nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
17129 // Step 1: check if cookie permissions are available or denied to this
17130 // document's principal
17131 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17132 if (!inner) {
17133 aHasStorageAccess = false;
17134 return NS_OK;
17136 Maybe<bool> resultBecauseCookiesApproved =
17137 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17138 CookieJarSettings(), NodePrincipal());
17139 if (resultBecauseCookiesApproved.isSome()) {
17140 if (resultBecauseCookiesApproved.value()) {
17141 aHasStorageAccess = true;
17142 return NS_OK;
17143 } else {
17144 aHasStorageAccess = false;
17145 return NS_OK;
17149 // Step 2: Check if the browser settings determine whether or not this
17150 // document has access to its unpartitioned cookies.
17151 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17152 bool isOnThirdPartySkipList = false;
17153 if (mChannel) {
17154 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17155 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17156 nsILoadInfo::StoragePermissionAllowListed;
17158 bool isThirdPartyTracker =
17159 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17160 Maybe<bool> resultBecauseBrowserSettings =
17161 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17162 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
17163 isThirdPartyTracker);
17164 if (resultBecauseBrowserSettings.isSome()) {
17165 if (resultBecauseBrowserSettings.value()) {
17166 aHasStorageAccess = true;
17167 return NS_OK;
17168 } else {
17169 aHasStorageAccess = false;
17170 return NS_OK;
17174 // Step 3: Check if the location of this call (embedded, top level, same-site)
17175 // determines if cookies are permitted or not.
17176 Maybe<bool> resultBecauseCallContext =
17177 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17178 false);
17179 if (resultBecauseCallContext.isSome()) {
17180 if (resultBecauseCallContext.value()) {
17181 aHasStorageAccess = true;
17182 return NS_OK;
17183 } else {
17184 aHasStorageAccess = false;
17185 return NS_OK;
17189 // Step 4: Check if the permissions for this document determine if if has
17190 // access or is denied cookies.
17191 Maybe<bool> resultBecausePreviousPermission =
17192 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17193 this, false);
17194 if (resultBecausePreviousPermission.isSome()) {
17195 if (resultBecausePreviousPermission.value()) {
17196 aHasStorageAccess = true;
17197 return NS_OK;
17198 } else {
17199 aHasStorageAccess = false;
17200 return NS_OK;
17203 // If you get here, we default to not giving you permission.
17204 aHasStorageAccess = false;
17205 return NS_OK;
17208 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
17209 mozilla::ErrorResult& aRv) {
17210 nsIGlobalObject* global = GetScopeObject();
17211 if (!global) {
17212 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17213 return nullptr;
17216 RefPtr<Promise> promise =
17217 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
17218 if (aRv.Failed()) {
17219 return nullptr;
17222 if (!IsCurrentActiveDocument()) {
17223 promise->MaybeRejectWithInvalidStateError(
17224 "hasStorageAccess requires an active document");
17225 return promise.forget();
17228 bool hasStorageAccess;
17229 nsresult rv = HasStorageAccessSync(hasStorageAccess);
17230 if (NS_FAILED(rv)) {
17231 promise->MaybeRejectWithUndefined();
17232 } else {
17233 promise->MaybeResolve(hasStorageAccess);
17236 return promise.forget();
17239 RefPtr<Document::GetContentBlockingEventsPromise>
17240 Document::GetContentBlockingEvents() {
17241 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
17242 if (!wgc) {
17243 return nullptr;
17246 return wgc->SendGetContentBlockingEvents()->Then(
17247 GetCurrentSerialEventTarget(), __func__,
17248 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
17249 ResolveOrRejectValue& aValue) {
17250 if (aValue.IsResolve()) {
17251 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
17252 aValue.ResolveValue(), __func__);
17255 return Document::GetContentBlockingEventsPromise::CreateAndReject(
17256 false, __func__);
17260 StorageAccessAPIHelper::PerformPermissionGrant
17261 Document::CreatePermissionGrantPromise(
17262 nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal,
17263 bool aHasUserInteraction, bool aRequireUserInteraction,
17264 const Maybe<nsCString>& aTopLevelBaseDomain, bool aFrameOnly) {
17265 MOZ_ASSERT(aInnerWindow);
17266 MOZ_ASSERT(aPrincipal);
17267 RefPtr<Document> self(this);
17268 RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
17269 RefPtr<nsIPrincipal> principal(aPrincipal);
17271 return [inner, self, principal, aHasUserInteraction, aRequireUserInteraction,
17272 aTopLevelBaseDomain, aFrameOnly]() {
17273 RefPtr<StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::Private>
17274 p = new StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
17275 Private(__func__);
17277 RefPtr<PWindowGlobalChild::HasStorageAccessPermissionPromise> promise;
17278 // Test the permission
17279 MOZ_ASSERT(XRE_IsContentProcess());
17281 WindowGlobalChild* wgc = inner->GetWindowGlobalChild();
17282 MOZ_ASSERT(wgc);
17284 promise = wgc->SendHasStorageAccessPermission();
17285 MOZ_ASSERT(promise);
17286 promise->Then(
17287 GetCurrentSerialEventTarget(), __func__,
17288 [self, p, inner, principal, aHasUserInteraction,
17289 aRequireUserInteraction, aTopLevelBaseDomain,
17290 aFrameOnly](bool aGranted) {
17291 if (aGranted) {
17292 p->Resolve(true, __func__);
17293 return;
17296 // We require user activation before conducting a permission request
17297 // See
17298 // https://privacycg.github.io/storage-access/#dom-document-requeststorageaccess
17299 // where we "If has transient activation is false: ..." immediately
17300 // before we "Let permissionState be the result of requesting
17301 // permission to use "storage-access"" from in parallel.
17302 if (!aHasUserInteraction && aRequireUserInteraction) {
17303 // Report an error to the console for this case
17304 nsContentUtils::ReportToConsole(
17305 nsIScriptError::errorFlag,
17306 nsLiteralCString("requestStorageAccess"), self,
17307 nsContentUtils::eDOM_PROPERTIES,
17308 "RequestStorageAccessUserGesture");
17309 p->Reject(false, __func__);
17310 return;
17313 // Create the user prompt
17314 RefPtr<StorageAccessPermissionRequest> sapr =
17315 StorageAccessPermissionRequest::Create(
17316 inner, principal, aTopLevelBaseDomain, aFrameOnly,
17317 // Allow
17318 [p] {
17319 Telemetry::AccumulateCategorical(
17320 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
17321 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17323 // Block
17324 [p] {
17325 Telemetry::AccumulateCategorical(
17326 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
17327 p->Reject(false, __func__);
17330 using PromptResult = ContentPermissionRequestBase::PromptResult;
17331 PromptResult pr = sapr->CheckPromptPrefs();
17333 if (pr == PromptResult::Pending) {
17334 // We're about to show a prompt, record the request attempt
17335 Telemetry::AccumulateCategorical(
17336 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
17339 // Try to auto-grant the storage access so the user doesn't see the
17340 // prompt.
17341 self->AutomaticStorageAccessPermissionCanBeGranted(
17342 aHasUserInteraction)
17343 ->Then(
17344 GetCurrentSerialEventTarget(), __func__,
17345 // If the autogrant check didn't fail, call this function
17346 [p, pr, sapr,
17347 inner](const Document::
17348 AutomaticStorageAccessPermissionGrantPromise::
17349 ResolveOrRejectValue& aValue) -> void {
17350 // Make a copy because we can't modified copy-captured
17351 // lambda variables.
17352 PromptResult pr2 = pr;
17354 // If the user didn't already click "allow" and we can
17355 // autogrant, do that!
17356 bool storageAccessCanBeGrantedAutomatically =
17357 aValue.IsResolve() && aValue.ResolveValue();
17358 bool autoGrant = false;
17359 if (pr2 == PromptResult::Pending &&
17360 storageAccessCanBeGrantedAutomatically) {
17361 pr2 = PromptResult::Granted;
17362 autoGrant = true;
17364 Telemetry::AccumulateCategorical(
17365 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
17366 AllowAutomatically);
17369 // If we can complete the permission request, do so.
17370 if (pr2 != PromptResult::Pending) {
17371 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
17372 pr2 == PromptResult::Denied);
17373 if (pr2 == PromptResult::Granted) {
17374 StorageAccessAPIHelper::StorageAccessPromptChoices
17375 choice = StorageAccessAPIHelper::eAllow;
17376 if (autoGrant) {
17377 choice = StorageAccessAPIHelper::eAllowAutoGrant;
17379 if (!autoGrant) {
17380 p->Resolve(choice, __func__);
17381 } else {
17382 // We capture sapr here to prevent it from destructing
17383 // before the callbacks complete.
17384 sapr->MaybeDelayAutomaticGrants()->Then(
17385 GetCurrentSerialEventTarget(), __func__,
17386 [p, sapr, choice] {
17387 p->Resolve(choice, __func__);
17389 [p, sapr] { p->Reject(false, __func__); });
17391 return;
17393 p->Reject(false, __func__);
17394 return;
17397 // If we get here, the auto-decision failed and we need to
17398 // wait for the user prompt to complete.
17399 sapr->RequestDelayedTask(
17400 inner->EventTargetFor(TaskCategory::Other),
17401 ContentPermissionRequestBase::DelayedTaskType::Request);
17404 [p](mozilla::ipc::ResponseRejectReason aError) {
17405 p->Reject(false, __func__);
17406 return p;
17409 return p;
17413 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
17414 mozilla::ErrorResult& aRv) {
17415 nsIGlobalObject* global = GetScopeObject();
17416 if (!global) {
17417 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17418 return nullptr;
17421 RefPtr<Promise> promise = Promise::Create(global, aRv);
17422 if (aRv.Failed()) {
17423 return nullptr;
17426 if (!IsCurrentActiveDocument()) {
17427 promise->MaybeRejectWithInvalidStateError(
17428 "requestStorageAccess requires an active document");
17429 return promise.forget();
17432 // Get a pointer to the inner window- We need this for convenience sake
17433 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17434 if (!inner) {
17435 ConsumeTransientUserGestureActivation();
17436 promise->MaybeRejectWithNotAllowedError(
17437 "requestStorageAccess not allowed"_ns);
17438 return promise.forget();
17441 // Step 1: Check if the principal calling this has a permission that lets
17442 // them use cookies or forbids them from using cookies.
17443 // This is outside of the spec of the StorageAccess API, but makes the return
17444 // values to have proper semantics.
17445 Maybe<bool> resultBecauseCookiesApproved =
17446 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17447 CookieJarSettings(), NodePrincipal());
17448 if (resultBecauseCookiesApproved.isSome()) {
17449 if (resultBecauseCookiesApproved.value()) {
17450 promise->MaybeResolveWithUndefined();
17451 return promise.forget();
17452 } else {
17453 ConsumeTransientUserGestureActivation();
17454 promise->MaybeRejectWithNotAllowedError(
17455 "requestStorageAccess not allowed"_ns);
17456 return promise.forget();
17460 // Step 2: Check if the browser settings always allow or deny cookies.
17461 // We should always return a resolved promise if the cookieBehavior is ACCEPT.
17462 // This is outside of the spec of the StorageAccess API, but makes the return
17463 // values to have proper semantics.
17464 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17465 bool isOnThirdPartySkipList = false;
17466 if (mChannel) {
17467 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17468 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17469 nsILoadInfo::StoragePermissionAllowListed;
17471 bool isThirdPartyTracker =
17472 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17473 Maybe<bool> resultBecauseBrowserSettings =
17474 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17475 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
17476 isThirdPartyTracker);
17477 if (resultBecauseBrowserSettings.isSome()) {
17478 if (resultBecauseBrowserSettings.value()) {
17479 promise->MaybeResolveWithUndefined();
17480 return promise.forget();
17481 } else {
17482 ConsumeTransientUserGestureActivation();
17483 promise->MaybeRejectWithNotAllowedError(
17484 "requestStorageAccess not allowed"_ns);
17485 return promise.forget();
17489 // Step 3: Check if the Document calling requestStorageAccess has anything to
17490 // gain from storage access. It should be embedded, non-null, etc.
17491 Maybe<bool> resultBecauseCallContext =
17492 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17493 true);
17494 if (resultBecauseCallContext.isSome()) {
17495 if (resultBecauseCallContext.value()) {
17496 promise->MaybeResolveWithUndefined();
17497 return promise.forget();
17498 } else {
17499 ConsumeTransientUserGestureActivation();
17500 promise->MaybeRejectWithNotAllowedError(
17501 "requestStorageAccess not allowed"_ns);
17502 return promise.forget();
17506 // Step 4: Check if we already allowed or denied storage access for this
17507 // document's storage key.
17508 Maybe<bool> resultBecausePreviousPermission =
17509 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17510 this, true);
17511 if (resultBecausePreviousPermission.isSome()) {
17512 if (resultBecausePreviousPermission.value()) {
17513 promise->MaybeResolveWithUndefined();
17514 return promise.forget();
17515 } else {
17516 ConsumeTransientUserGestureActivation();
17517 promise->MaybeRejectWithNotAllowedError(
17518 "requestStorageAccess not allowed"_ns);
17519 return promise.forget();
17523 // Get pointers to some objects that will be used in the async portion
17524 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17525 RefPtr<nsGlobalWindowOuter> outer =
17526 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17527 if (!outer) {
17528 ConsumeTransientUserGestureActivation();
17529 promise->MaybeRejectWithNotAllowedError(
17530 "requestStorageAccess not allowed"_ns);
17531 return promise.forget();
17533 RefPtr<Document> self(this);
17535 // Step 5. Start an async call to request storage access. This will either
17536 // perform an automatic decision or notify the user, then perform some follow
17537 // on work changing state to reflect the result of the API. If it resolves,
17538 // the request was granted. If it rejects it was denied.
17539 StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17540 this, inner, bc, NodePrincipal(),
17541 self->HasValidTransientUserGestureActivation(), true, true,
17542 ContentBlockingNotifier::eStorageAccessAPI, true)
17543 ->Then(
17544 GetCurrentSerialEventTarget(), __func__,
17545 [inner, promise] {
17546 inner->SaveStorageAccessPermissionGranted();
17547 promise->MaybeResolveWithUndefined();
17549 [self, promise] {
17550 self->ConsumeTransientUserGestureActivation();
17551 promise->MaybeRejectWithNotAllowedError(
17552 "requestStorageAccess not allowed"_ns);
17555 return promise.forget();
17558 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
17559 const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
17560 mozilla::ErrorResult& aRv) {
17561 nsIGlobalObject* global = GetScopeObject();
17562 if (!global) {
17563 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17564 return nullptr;
17566 RefPtr<Promise> promise = Promise::Create(global, aRv);
17567 if (aRv.Failed()) {
17568 return nullptr;
17571 // Step 0: Check that we have user activation before proceeding to prevent
17572 // rapid calls to the API to leak information.
17573 if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) {
17574 // Report an error to the console for this case
17575 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17576 nsLiteralCString("requestStorageAccess"),
17577 this, nsContentUtils::eDOM_PROPERTIES,
17578 "RequestStorageAccessUserGesture");
17579 ConsumeTransientUserGestureActivation();
17580 promise->MaybeRejectWithNotAllowedError(
17581 "requestStorageAccess not allowed"_ns);
17582 return promise.forget();
17585 // Step 1: Check if the provided URI is different-site to this Document
17586 nsCOMPtr<nsIURI> thirdPartyURI;
17587 nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
17588 if (NS_WARN_IF(NS_FAILED(rv))) {
17589 aRv.Throw(rv);
17590 return nullptr;
17592 bool isThirdPartyDocument;
17593 rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument);
17594 if (NS_WARN_IF(NS_FAILED(rv))) {
17595 aRv.Throw(rv);
17596 return nullptr;
17598 Maybe<bool> resultBecauseBrowserSettings =
17599 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17600 CookieJarSettings(), isThirdPartyDocument, false, true);
17601 if (resultBecauseBrowserSettings.isSome()) {
17602 if (resultBecauseBrowserSettings.value()) {
17603 promise->MaybeResolveWithUndefined();
17604 return promise.forget();
17606 ConsumeTransientUserGestureActivation();
17607 promise->MaybeRejectWithNotAllowedError(
17608 "requestStorageAccess not allowed"_ns);
17609 return promise.forget();
17612 // Step 2: Check that this Document is same-site to the top, and check that
17613 // we have user activation if we require it.
17614 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17615 CheckSameSiteCallingContextDecidesStorageAccessAPI(
17616 this, aRequireUserActivation);
17617 if (resultBecauseCallContext.isSome()) {
17618 if (resultBecauseCallContext.value()) {
17619 promise->MaybeResolveWithUndefined();
17620 return promise.forget();
17622 ConsumeTransientUserGestureActivation();
17623 promise->MaybeRejectWithNotAllowedError(
17624 "requestStorageAccess not allowed"_ns);
17625 return promise.forget();
17628 // Step 3: Get some useful variables that can be captured by the lambda for
17629 // the asynchronous portion
17630 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17631 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17632 if (!inner) {
17633 ConsumeTransientUserGestureActivation();
17634 promise->MaybeRejectWithNotAllowedError(
17635 "requestStorageAccess not allowed"_ns);
17636 return promise.forget();
17638 RefPtr<nsGlobalWindowOuter> outer =
17639 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17640 if (!outer) {
17641 ConsumeTransientUserGestureActivation();
17642 promise->MaybeRejectWithNotAllowedError(
17643 "requestStorageAccess not allowed"_ns);
17644 return promise.forget();
17646 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17647 thirdPartyURI, NodePrincipal()->OriginAttributesRef());
17648 if (!principal) {
17649 ConsumeTransientUserGestureActivation();
17650 promise->MaybeRejectWithNotAllowedError(
17651 "requestStorageAccess not allowed"_ns);
17652 return promise.forget();
17655 RefPtr<Document> self(this);
17656 bool hasUserActivation = HasValidTransientUserGestureActivation();
17658 // Consume user activation before entering the async part of this method.
17659 // This prevents usage of other transient activation-gated APIs.
17660 ConsumeTransientUserGestureActivation();
17662 // Step 4a: Start the async part of this function. Check the cookie
17663 // permission, but this can't be done in this process. We needs the cookie
17664 // permission of the URL as if it were embedded on this page, so we need to
17665 // make this check in the ContentParent.
17666 StorageAccessAPIHelper::AsyncCheckCookiesPermittedDecidesStorageAccessAPI(
17667 GetBrowsingContext(), principal)
17668 ->Then(
17669 GetCurrentSerialEventTarget(), __func__,
17670 [inner, thirdPartyURI, bc, principal, hasUserActivation,
17671 aRequireUserActivation, self, promise](Maybe<bool> cookieResult) {
17672 // Handle the result of the cookie permission check that took place
17673 // in the ContentParent.
17674 if (cookieResult.isSome()) {
17675 if (cookieResult.value()) {
17676 return MozPromise<int, bool, true>::CreateAndResolve(true,
17677 __func__);
17679 return MozPromise<int, bool, true>::CreateAndReject(false,
17680 __func__);
17683 // Step 4b: Check for the existing storage access permission
17684 nsAutoCString type;
17685 bool ok =
17686 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
17687 if (!ok) {
17688 return MozPromise<int, bool, true>::CreateAndReject(false,
17689 __func__);
17691 if (AntiTrackingUtils::CheckStoragePermission(
17692 self->NodePrincipal(), type,
17693 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
17694 return MozPromise<int, bool, true>::CreateAndResolve(true,
17695 __func__);
17698 // Step 4c: Try to request storage access, either automatically or
17699 // with a user-prompt. This is the part that is async in the
17700 // typical requestStorageAccess function.
17701 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17702 self, inner, bc, principal, hasUserActivation,
17703 aRequireUserActivation, false,
17704 ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI,
17705 true);
17707 // If the IPC rejects, we should reject our promise here which will
17708 // cause a rejection of the promise we already returned
17709 [promise]() {
17710 return MozPromise<int, bool, true>::CreateAndReject(false,
17711 __func__);
17713 ->Then(
17714 GetCurrentSerialEventTarget(), __func__,
17715 // If the previous handlers resolved, we should reinstate user
17716 // activation and resolve the promise we returned in Step 5.
17717 [self, inner, promise] {
17718 inner->SaveStorageAccessPermissionGranted();
17719 self->NotifyUserGestureActivation();
17720 promise->MaybeResolveWithUndefined();
17722 // If the previous handler rejected, we should reject the promise
17723 // returned by this function.
17724 [promise] {
17725 promise->MaybeRejectWithNotAllowedError(
17726 "requestStorageAccess not allowed"_ns);
17729 // Step 5: While the async stuff is happening, we should return the promise so
17730 // our caller can continue executing.
17731 return promise.forget();
17734 already_AddRefed<Promise> Document::RequestStorageAccessUnderSite(
17735 const nsAString& aSerializedSite, ErrorResult& aRv) {
17736 nsIGlobalObject* global = GetScopeObject();
17737 if (!global) {
17738 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17739 return nullptr;
17741 RefPtr<Promise> promise = Promise::Create(global, aRv);
17742 if (aRv.Failed()) {
17743 return nullptr;
17746 // Check that we have user activation before proceeding to prevent
17747 // rapid calls to the API to leak information.
17748 if (!ConsumeTransientUserGestureActivation()) {
17749 // Report an error to the console for this case
17750 nsContentUtils::ReportToConsole(
17751 nsIScriptError::errorFlag, "requestStorageAccess"_ns, this,
17752 nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture");
17753 promise->MaybeRejectWithUndefined();
17754 return promise.forget();
17757 // Check if the provided URI is different-site to this Document
17758 nsCOMPtr<nsIURI> siteURI;
17759 nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite);
17760 if (NS_WARN_IF(NS_FAILED(rv))) {
17761 promise->MaybeRejectWithUndefined();
17762 return promise.forget();
17764 bool isCrossSiteArgument;
17765 rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument);
17766 if (NS_WARN_IF(NS_FAILED(rv))) {
17767 aRv.Throw(rv);
17768 return nullptr;
17770 if (!isCrossSiteArgument) {
17771 promise->MaybeRejectWithUndefined();
17772 return promise.forget();
17775 // Check if this party has broad cookie permissions.
17776 Maybe<bool> resultBecauseCookiesApproved =
17777 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17778 CookieJarSettings(), NodePrincipal());
17779 if (resultBecauseCookiesApproved.isSome()) {
17780 if (resultBecauseCookiesApproved.value()) {
17781 promise->MaybeResolveWithUndefined();
17782 return promise.forget();
17784 promise->MaybeRejectWithUndefined();
17785 return promise.forget();
17788 // Check if browser settings preclude this document getting storage
17789 // access under the provided site
17790 Maybe<bool> resultBecauseBrowserSettings =
17791 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17792 CookieJarSettings(), true, false, true);
17793 if (resultBecauseBrowserSettings.isSome()) {
17794 if (resultBecauseBrowserSettings.value()) {
17795 promise->MaybeResolveWithUndefined();
17796 return promise.forget();
17798 promise->MaybeRejectWithUndefined();
17799 return promise.forget();
17802 // Check that this Document is same-site to the top
17803 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17804 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
17805 if (resultBecauseCallContext.isSome()) {
17806 if (resultBecauseCallContext.value()) {
17807 promise->MaybeResolveWithUndefined();
17808 return promise.forget();
17810 promise->MaybeRejectWithUndefined();
17811 return promise.forget();
17814 nsCOMPtr<nsIPrincipal> principal(NodePrincipal());
17816 // Test if the permission this is requesting is already set
17817 nsCOMPtr<nsIPrincipal> argumentPrincipal =
17818 BasePrincipal::CreateContentPrincipal(
17819 siteURI, NodePrincipal()->OriginAttributesRef());
17820 if (!argumentPrincipal) {
17821 ConsumeTransientUserGestureActivation();
17822 promise->MaybeRejectWithUndefined();
17823 return promise.forget();
17825 nsCString originNoSuffix;
17826 rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix);
17827 if (NS_WARN_IF(NS_FAILED(rv))) {
17828 promise->MaybeRejectWithUndefined();
17829 return promise.forget();
17832 ContentChild* cc = ContentChild::GetSingleton();
17833 MOZ_ASSERT(cc);
17834 RefPtr<Document> self(this);
17835 cc->SendTestStorageAccessPermission(argumentPrincipal, originNoSuffix)
17836 ->Then(
17837 GetCurrentSerialEventTarget(), __func__,
17838 [promise, siteURI,
17839 self](const ContentChild::TestStorageAccessPermissionPromise::
17840 ResolveValueType& aResult) {
17841 if (aResult) {
17842 return StorageAccessAPIHelper::
17843 StorageAccessPermissionGrantPromise::CreateAndResolve(
17844 StorageAccessAPIHelper::eAllow, __func__);
17846 // Get a grant for the storage access permission that will be set
17847 // when this is completed in the embedding context
17848 nsCString serializedSite;
17849 RefPtr<nsEffectiveTLDService> etld =
17850 nsEffectiveTLDService::GetInstance();
17851 if (!etld) {
17852 return StorageAccessAPIHelper::
17853 StorageAccessPermissionGrantPromise::CreateAndReject(
17854 false, __func__);
17856 nsresult rv = etld->GetSite(siteURI, serializedSite);
17857 if (NS_FAILED(rv)) {
17858 return StorageAccessAPIHelper::
17859 StorageAccessPermissionGrantPromise::CreateAndReject(
17860 false, __func__);
17862 return self->CreatePermissionGrantPromise(
17863 self->GetInnerWindow(), self->NodePrincipal(), true, true,
17864 Some(serializedSite), false)();
17866 [](const ContentChild::TestStorageAccessPermissionPromise::
17867 RejectValueType& aResult) {
17868 return StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
17869 CreateAndReject(false, __func__);
17871 ->Then(
17872 GetCurrentSerialEventTarget(), __func__,
17873 [promise, principal, siteURI](int result) {
17874 ContentChild* cc = ContentChild::GetSingleton();
17875 if (!cc) {
17876 // TODO(bug 1778561): Make this work in non-content processes.
17877 promise->MaybeRejectWithUndefined();
17878 return;
17880 // Set a permission in the parent process that this document wants
17881 // storage access under the argument's site, resolving our returned
17882 // promise on success
17883 cc->SendSetAllowStorageAccessRequestFlag(principal, siteURI)
17884 ->Then(
17885 GetCurrentSerialEventTarget(), __func__,
17886 [promise](bool success) {
17887 if (success) {
17888 promise->MaybeResolveWithUndefined();
17889 } else {
17890 promise->MaybeRejectWithUndefined();
17893 [promise](mozilla::ipc::ResponseRejectReason reason) {
17894 promise->MaybeRejectWithUndefined();
17897 [promise](bool result) { promise->MaybeRejectWithUndefined(); });
17899 // Return the promise that is resolved in the async handler above
17900 return promise.forget();
17903 already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite(
17904 const nsAString& aSerializedOrigin, ErrorResult& aRv) {
17905 nsIGlobalObject* global = GetScopeObject();
17906 if (!global) {
17907 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17908 return nullptr;
17910 RefPtr<Promise> promise = Promise::Create(global, aRv);
17911 if (aRv.Failed()) {
17912 return nullptr;
17915 // Check that the provided URI is different-site to this Document
17916 nsCOMPtr<nsIURI> argumentURI;
17917 nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin);
17918 if (NS_WARN_IF(NS_FAILED(rv))) {
17919 promise->MaybeRejectWithUndefined();
17920 return promise.forget();
17922 bool isCrossSiteArgument;
17923 rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument);
17924 if (NS_WARN_IF(NS_FAILED(rv))) {
17925 aRv.Throw(rv);
17926 return nullptr;
17928 if (!isCrossSiteArgument) {
17929 promise->MaybeRejectWithUndefined();
17930 return promise.forget();
17933 // Check if browser settings preclude this document getting storage
17934 // access under the provided site
17935 Maybe<bool> resultBecauseBrowserSettings =
17936 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17937 CookieJarSettings(), true, false, true);
17938 if (resultBecauseBrowserSettings.isSome()) {
17939 if (resultBecauseBrowserSettings.value()) {
17940 promise->MaybeResolveWithUndefined();
17941 return promise.forget();
17943 promise->MaybeRejectWithUndefined();
17944 return promise.forget();
17947 // Check that this Document is same-site to the top
17948 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17949 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
17950 if (resultBecauseCallContext.isSome()) {
17951 if (resultBecauseCallContext.value()) {
17952 promise->MaybeResolveWithUndefined();
17953 return promise.forget();
17955 promise->MaybeRejectWithUndefined();
17956 return promise.forget();
17959 // Create principal of the embedded site requesting storage access
17960 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17961 argumentURI, NodePrincipal()->OriginAttributesRef());
17962 if (!principal) {
17963 promise->MaybeRejectWithUndefined();
17964 return promise.forget();
17967 // Get versions of these objects that we can use in lambdas for callbacks
17968 RefPtr<Document> self(this);
17969 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17970 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17972 // Test that the permission was set by a call to RequestStorageAccessUnderSite
17973 // from a top level document that is same-site with the argument
17974 ContentChild* cc = ContentChild::GetSingleton();
17975 if (!cc) {
17976 // TODO(bug 1778561): Make this work in non-content processes.
17977 promise->MaybeRejectWithUndefined();
17978 return promise.forget();
17980 cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI)
17981 ->Then(
17982 GetCurrentSerialEventTarget(), __func__,
17983 [inner, bc, self, principal](bool success) {
17984 if (success) {
17985 // If that resolved with true, check that we don't already have a
17986 // permission that gives cookie access.
17987 return StorageAccessAPIHelper::
17988 AsyncCheckCookiesPermittedDecidesStorageAccessAPI(bc,
17989 principal);
17991 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
17992 NS_ERROR_FAILURE, __func__);
17994 [](mozilla::ipc::ResponseRejectReason reason) {
17995 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
17996 NS_ERROR_FAILURE, __func__);
17998 ->Then(
17999 GetCurrentSerialEventTarget(), __func__,
18000 [inner, bc, principal, self, promise](Maybe<bool> cookieResult) {
18001 // Handle the result of the cookie permission check that took place
18002 // in the ContentParent.
18003 if (cookieResult.isSome()) {
18004 if (cookieResult.value()) {
18005 return StorageAccessAPIHelper::
18006 StorageAccessPermissionGrantPromise::CreateAndResolve(
18007 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18009 return StorageAccessAPIHelper::
18010 StorageAccessPermissionGrantPromise::CreateAndReject(
18011 false, __func__);
18014 // Check for the existing storage access permission
18015 nsAutoCString type;
18016 bool ok =
18017 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
18018 if (!ok) {
18019 return StorageAccessAPIHelper::
18020 StorageAccessPermissionGrantPromise::CreateAndReject(
18021 false, __func__);
18023 if (AntiTrackingUtils::CheckStoragePermission(
18024 self->NodePrincipal(), type,
18025 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
18026 return StorageAccessAPIHelper::
18027 StorageAccessPermissionGrantPromise::CreateAndResolve(
18028 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18031 // Try to request storage access, ignoring the final checks.
18032 // We ignore the final checks because this is where the "grant"
18033 // either by prompt doorhanger or autogrant takes place. We already
18034 // gathered an equivalent grant in requestStorageAccessUnderSite.
18035 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
18036 self, inner, bc, principal, true, true, false,
18037 ContentBlockingNotifier::eStorageAccessAPI, false);
18039 // If the IPC rejects, we should reject our promise here which will
18040 // cause a rejection of the promise we already returned
18041 [promise]() {
18042 return MozPromise<int, bool, true>::CreateAndReject(false,
18043 __func__);
18045 ->Then(
18046 GetCurrentSerialEventTarget(), __func__,
18047 // If the previous handlers resolved, we should reinstate user
18048 // activation and resolve the promise we returned in Step 5.
18049 [self, inner, promise] {
18050 inner->SaveStorageAccessPermissionGranted();
18051 promise->MaybeResolveWithUndefined();
18053 // If the previous handler rejected, we should reject the promise
18054 // returned by this function.
18055 [promise] { promise->MaybeRejectWithUndefined(); });
18057 return promise.forget();
18060 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
18061 Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
18062 // requestStorageAccessForOrigin may not require user activation. If we don't
18063 // have user activation at this point we should always show the prompt.
18064 if (!hasUserActivation ||
18065 !StaticPrefs::privacy_antitracking_enableWebcompat()) {
18066 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18067 false, __func__);
18069 if (XRE_IsContentProcess()) {
18070 // In the content process, we need to ask the parent process to compute
18071 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
18072 // isn't accessible in the content process.
18073 ContentChild* cc = ContentChild::GetSingleton();
18074 MOZ_ASSERT(cc);
18076 return cc->SendAutomaticStorageAccessPermissionCanBeGranted(NodePrincipal())
18077 ->Then(GetCurrentSerialEventTarget(), __func__,
18078 [](const ContentChild::
18079 AutomaticStorageAccessPermissionCanBeGrantedPromise::
18080 ResolveOrRejectValue& aValue) {
18081 if (aValue.IsResolve()) {
18082 return AutomaticStorageAccessPermissionGrantPromise::
18083 CreateAndResolve(aValue.ResolveValue(), __func__);
18086 return AutomaticStorageAccessPermissionGrantPromise::
18087 CreateAndReject(false, __func__);
18091 if (XRE_IsParentProcess()) {
18092 // In the parent process, we can directly compute this.
18093 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18094 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
18095 __func__);
18098 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
18099 false, __func__);
18102 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
18103 nsIPrincipal* aPrincipal) {
18104 if (!StaticPrefs::dom_storage_access_auto_grants()) {
18105 return false;
18108 if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
18109 return false;
18112 nsCOMPtr<nsIBrowserUsage> bu = do_ImportESModule(
18113 "resource:///modules/BrowserUsageTelemetry.sys.mjs", fallible);
18114 if (NS_WARN_IF(!bu)) {
18115 return false;
18118 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
18119 nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours(
18120 &uniqueDomainsVisitedInPast24Hours);
18121 if (NS_WARN_IF(NS_FAILED(rv))) {
18122 return false;
18125 Maybe<size_t> maybeOriginsThirdPartyHasAccessTo =
18126 AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal);
18127 if (maybeOriginsThirdPartyHasAccessTo.isNothing()) {
18128 return false;
18130 size_t originsThirdPartyHasAccessTo =
18131 maybeOriginsThirdPartyHasAccessTo.value();
18133 // one percent of the number of top-levels origins visited in the current
18134 // session (but not to exceed 24 hours), or the value of the
18135 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
18136 // higher.
18137 size_t maxConcurrentAutomaticGrants = std::max(
18138 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
18139 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
18142 return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
18145 void Document::RecordNavigationTiming(ReadyState aReadyState) {
18146 if (!XRE_IsContentProcess()) {
18147 return;
18149 if (!IsTopLevelContentDocument()) {
18150 return;
18152 // If we dont have the timing yet (mostly because the doc is still loading),
18153 // get it from docshell.
18154 RefPtr<nsDOMNavigationTiming> timing = mTiming;
18155 if (!timing) {
18156 if (!mDocumentContainer) {
18157 return;
18159 timing = mDocumentContainer->GetNavigationTiming();
18160 if (!timing) {
18161 return;
18164 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
18165 switch (aReadyState) {
18166 case READYSTATE_LOADING:
18167 if (!mDOMLoadingSet) {
18168 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
18169 startTime);
18170 mDOMLoadingSet = true;
18172 break;
18173 case READYSTATE_INTERACTIVE:
18174 if (!mDOMInteractiveSet) {
18175 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
18176 startTime);
18177 mDOMInteractiveSet = true;
18179 break;
18180 case READYSTATE_COMPLETE:
18181 if (!mDOMCompleteSet) {
18182 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
18183 startTime);
18184 mDOMCompleteSet = true;
18186 break;
18187 default:
18188 NS_WARNING("Unexpected ReadyState value");
18189 break;
18193 bool Document::ImportMapsEnabled() const {
18194 return nsContentUtils::IsChromeDoc(this) ||
18195 StaticPrefs::dom_importMaps_enabled();
18198 void Document::ReportShadowDOMUsage() {
18199 nsPIDOMWindowInner* inner = GetInnerWindow();
18200 if (NS_WARN_IF(!inner)) {
18201 return;
18204 WindowContext* wc = inner->GetWindowContext();
18205 if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
18206 return;
18209 WindowContext* topWc = wc->TopWindowContext();
18210 if (topWc->GetHasReportedShadowDOMUsage()) {
18211 return;
18214 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
18217 // static
18218 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
18219 return StaticPrefs::dom_storage_access_enabled() &&
18220 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
18223 bool Document::StorageAccessSandboxed() const {
18224 return Document::StorageAccessSandboxed(GetSandboxFlags());
18227 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
18228 if (mCachedTabSizeGeneration == 0 ||
18229 GetGeneration() != mCachedTabSizeGeneration) {
18230 return false;
18232 aSizes->mDom += mCachedTabSizes.mDom;
18233 aSizes->mStyle += mCachedTabSizes.mStyle;
18234 aSizes->mOther += mCachedTabSizes.mOther;
18235 return true;
18238 void Document::SetCachedSizes(nsTabSizes* aSizes) {
18239 mCachedTabSizes.mDom = aSizes->mDom;
18240 mCachedTabSizes.mStyle = aSizes->mStyle;
18241 mCachedTabSizes.mOther = aSizes->mOther;
18242 mCachedTabSizeGeneration = GetGeneration();
18245 already_AddRefed<nsAtom> Document::GetContentLanguageAsAtomForStyle() const {
18246 nsAutoString contentLang;
18247 GetContentLanguage(contentLang);
18248 contentLang.StripWhitespace();
18250 // Content-Language may be a comma-separated list of language codes,
18251 // in which case the HTML5 spec says to treat it as unknown
18252 if (!contentLang.IsEmpty() && !contentLang.Contains(char16_t(','))) {
18253 return NS_Atomize(contentLang);
18256 return nullptr;
18259 already_AddRefed<nsAtom> Document::GetLanguageForStyle() const {
18260 RefPtr<nsAtom> lang = GetContentLanguageAsAtomForStyle();
18261 if (!lang) {
18262 lang = mLanguageFromCharset;
18264 return lang.forget();
18267 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
18268 nsAtom* aLanguage, bool* aNeedsToCache) const {
18269 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
18270 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
18273 void Document::DoCacheAllKnownLangPrefs() {
18274 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
18275 RefPtr<nsAtom> lang = GetLanguageForStyle();
18276 StaticPresData* data = StaticPresData::Get();
18277 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
18278 data->GetFontPrefsForLang(nsGkAtoms::x_math);
18279 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
18280 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
18281 for (const auto& key : mLanguagesUsed) {
18282 data->GetFontPrefsForLang(key);
18284 mMayNeedFontPrefsUpdate = false;
18287 void Document::RecomputeLanguageFromCharset() {
18288 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
18289 RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
18290 if (language == nsGkAtoms::Unicode) {
18291 language = service->GetLocaleLanguage();
18294 if (language == mLanguageFromCharset) {
18295 return;
18298 mMayNeedFontPrefsUpdate = true;
18299 mLanguageFromCharset = std::move(language);
18302 nsICookieJarSettings* Document::CookieJarSettings() {
18303 // If we are here, this is probably a javascript: URL document. In any case,
18304 // we must have a nsCookieJarSettings. Let's create it.
18305 if (!mCookieJarSettings) {
18306 Document* inProcessParent = GetInProcessParentDocument();
18308 if (inProcessParent) {
18309 mCookieJarSettings = net::CookieJarSettings::Create(
18310 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
18311 mozilla::net::CookieJarSettings::Cast(
18312 inProcessParent->CookieJarSettings())
18313 ->GetPartitionKey(),
18314 inProcessParent->CookieJarSettings()->GetIsFirstPartyIsolated(),
18315 inProcessParent->CookieJarSettings()
18316 ->GetIsOnContentBlockingAllowList(),
18317 inProcessParent->CookieJarSettings()
18318 ->GetShouldResistFingerprinting());
18320 // Inherit the fingerprinting random key from the parent.
18321 nsTArray<uint8_t> randomKey;
18322 nsresult rv = inProcessParent->CookieJarSettings()
18323 ->GetFingerprintingRandomizationKey(randomKey);
18325 if (NS_SUCCEEDED(rv)) {
18326 net::CookieJarSettings::Cast(mCookieJarSettings)
18327 ->SetFingerprintingRandomizationKey(randomKey);
18329 } else {
18330 mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
18333 if (auto* wgc = GetWindowGlobalChild()) {
18334 net::CookieJarSettingsArgs csArgs;
18335 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
18336 // Update cookie settings in the parent process
18337 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
18338 NS_WARNING(
18339 "Failed to update document's cookie jar settings on the "
18340 "WindowGlobalParent");
18345 return mCookieJarSettings;
18348 bool Document::UsingStorageAccess() {
18349 if (WindowContext* wc = GetWindowContext()) {
18350 return wc->GetUsingStorageAccess();
18353 // If we don't yet have a window context, we have to use the decision
18354 // from the Document's Channel's LoadInfo directly.
18355 if (!mChannel) {
18356 return false;
18359 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18360 return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
18363 bool Document::HasStorageAccessPermissionGrantedByAllowList() {
18364 // We only care about if the document gets the storage permission via the
18365 // allow list here. So we don't check the storage access cache in the inner
18366 // window.
18368 if (!mChannel) {
18369 return false;
18372 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18373 return loadInfo->GetStoragePermission() ==
18374 nsILoadInfo::StoragePermissionAllowListed;
18377 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
18378 if (!StaticPrefs::
18379 privacy_partition_always_partition_third_party_non_cookie_storage()) {
18380 return EffectiveCookiePrincipal();
18383 nsPIDOMWindowInner* inner = GetInnerWindow();
18384 if (!inner) {
18385 return NodePrincipal();
18388 // Return our cached storage principal if one exists.
18389 if (mActiveStoragePrincipal) {
18390 return mActiveStoragePrincipal;
18393 // Calling StorageAllowedForDocument will notify the ContentBlockLog. This
18394 // loads TrackingDBService.jsm, which in turn pulls in osfile.jsm, making us
18395 // fail // browser/base/content/test/performance/browser_startup.js. To avoid
18396 // that, we short-circuit the check here by allowing storage access to system
18397 // and addon principles, avoiding the test-failure.
18398 nsIPrincipal* principal = NodePrincipal();
18399 if (principal && (principal->IsSystemPrincipal() ||
18400 principal->GetIsAddonOrExpandedAddonPrincipal())) {
18401 return mActiveStoragePrincipal = NodePrincipal();
18404 auto cookieJarSettings = const_cast<Document*>(this)->CookieJarSettings();
18405 if (cookieJarSettings->GetIsOnContentBlockingAllowList()) {
18406 return mActiveStoragePrincipal = NodePrincipal();
18409 StorageAccess storageAccess = StorageAllowedForDocument(this);
18410 if (!ShouldPartitionStorage(storageAccess) ||
18411 !StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
18412 return mActiveStoragePrincipal = NodePrincipal();
18415 Unused << NS_WARN_IF(NS_FAILED(StoragePrincipalHelper::GetPrincipal(
18416 nsGlobalWindowInner::Cast(inner),
18417 StoragePrincipalHelper::eForeignPartitionedPrincipal,
18418 getter_AddRefs(mActiveStoragePrincipal))));
18419 return mActiveStoragePrincipal;
18422 nsIPrincipal* Document::EffectiveCookiePrincipal() const {
18423 nsPIDOMWindowInner* inner = GetInnerWindow();
18424 if (!inner) {
18425 return NodePrincipal();
18428 // Return our cached storage principal if one exists.
18429 if (mActiveCookiePrincipal) {
18430 return mActiveCookiePrincipal;
18433 // We use the lower-level ContentBlocking API here to ensure this
18434 // check doesn't send notifications.
18435 uint32_t rejectedReason = 0;
18436 if (ShouldAllowAccessFor(inner, GetDocumentURI(), &rejectedReason)) {
18437 return mActiveCookiePrincipal = NodePrincipal();
18440 // Let's use the storage principal only if we need to partition the cookie
18441 // jar. When the permission is granted, access will be different and the
18442 // normal principal will be used.
18443 if (ShouldPartitionStorage(rejectedReason) &&
18444 !StoragePartitioningEnabled(
18445 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
18446 return mActiveCookiePrincipal = NodePrincipal();
18449 return mActiveCookiePrincipal = mPartitionedPrincipal;
18452 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
18453 // If the document is sandboxed document or data: document, we should
18454 // get URI of the parent document.
18455 for (const Document* document = this;
18456 document && document->IsContentDocument();
18457 document = document->GetInProcessParentDocument()) {
18458 // The document URI may be about:blank even if it comes from actual web
18459 // site. Therefore, we need to check the URI of its principal.
18460 nsIPrincipal* principal = document->NodePrincipal();
18461 if (principal->GetIsNullPrincipal()) {
18462 continue;
18464 return principal;
18466 return nullptr;
18469 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
18470 mIsInitialDocumentInWindow = aIsInitialDocument;
18472 // Asynchronously tell the parent process that we are, or are no longer, the
18473 // initial document. This happens async.
18474 if (auto* wgc = GetWindowGlobalChild()) {
18475 wgc->SendSetIsInitialDocument(aIsInitialDocument);
18479 // static
18480 void Document::AddToplevelLoadingDocument(Document* aDoc) {
18481 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18482 // Currently we're interested in foreground documents only, so bail out early.
18483 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
18484 return;
18487 if (!sLoadingForegroundTopLevelContentDocument) {
18488 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
18489 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18490 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18491 if (idleScheduler) {
18492 idleScheduler->SendRunningPrioritizedOperation();
18495 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
18496 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
18500 // static
18501 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
18502 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18503 if (sLoadingForegroundTopLevelContentDocument) {
18504 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
18505 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
18506 delete sLoadingForegroundTopLevelContentDocument;
18507 sLoadingForegroundTopLevelContentDocument = nullptr;
18509 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18510 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18511 if (idleScheduler) {
18512 idleScheduler->SendPrioritizedOperationDone();
18518 ColorScheme Document::DefaultColorScheme() const {
18519 return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
18522 ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
18523 if (ShouldResistFingerprinting(RFPTarget::CSSPrefersColorScheme) &&
18524 aIgnoreRFP == IgnoreRFP::No) {
18525 return ColorScheme::Light;
18528 if (nsPresContext* pc = GetPresContext()) {
18529 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
18530 return *scheme;
18534 // NOTE(emilio): We use IsInChromeDocShell rather than IsChromeDoc
18535 // intentionally, to make chrome documents in content docshells (like about
18536 // pages) use the content color scheme.
18537 if (IsInChromeDocShell()) {
18538 return LookAndFeel::ColorSchemeForChrome();
18540 return LookAndFeel::PreferredColorSchemeForContent();
18543 bool Document::HasRecentlyStartedForegroundLoads() {
18544 if (!sLoadingForegroundTopLevelContentDocument) {
18545 return false;
18548 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
18549 ++i) {
18550 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
18551 // A page loaded in foreground could be in background now.
18552 if (!doc->IsInBackgroundWindow()) {
18553 nsPIDOMWindowInner* win = doc->GetInnerWindow();
18554 if (win) {
18555 Performance* perf = win->GetPerformance();
18556 if (perf &&
18557 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
18558 return true;
18564 // Didn't find any loading foreground documents, just clear the array.
18565 delete sLoadingForegroundTopLevelContentDocument;
18566 sLoadingForegroundTopLevelContentDocument = nullptr;
18568 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18569 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18570 if (idleScheduler) {
18571 idleScheduler->SendPrioritizedOperationDone();
18573 return false;
18576 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
18577 nsFrameLoader* aStaticCloneOf) {
18578 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
18579 clone->mElement = aElement;
18580 clone->mStaticCloneOf = aStaticCloneOf;
18583 bool Document::ShouldAvoidNativeTheme() const {
18584 return StaticPrefs::widget_non_native_theme_enabled() &&
18585 (!IsInChromeDocShell() || XRE_IsContentProcess());
18588 bool Document::UseRegularPrincipal() const {
18589 return EffectiveStoragePrincipal() == NodePrincipal();
18592 bool Document::HasThirdPartyChannel() {
18593 nsCOMPtr<nsIChannel> channel = GetChannel();
18594 if (channel) {
18595 // We assume that the channel is a third-party by default.
18596 bool thirdParty = true;
18598 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
18599 components::ThirdPartyUtil::Service();
18600 if (!thirdPartyUtil) {
18601 return thirdParty;
18604 // Check that if the channel is a third-party to its parent.
18605 nsresult rv =
18606 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
18607 if (NS_FAILED(rv)) {
18608 // Assume third-party in case of failure
18609 thirdParty = true;
18612 return thirdParty;
18615 if (mParentDocument) {
18616 return mParentDocument->HasThirdPartyChannel();
18619 return false;
18622 bool Document::IsContentInaccessibleAboutBlank() const {
18623 if (!mDocumentURI || !NS_IsAboutBlank(mDocumentURI)) {
18624 return false;
18626 nsIPrincipal* prin = NodePrincipal();
18627 if (!prin->GetIsNullPrincipal()) {
18628 return false;
18630 nsCOMPtr<nsIPrincipal> prec = prin->GetPrecursorPrincipal();
18631 if (prec) {
18632 return false;
18634 // FIXME(emilio): This doesn't account for internal about:blank documents we
18635 // do on cross-process navigations in iframes. That makes our per-document
18636 // telemetry probes not really reliable but doesn't affect the correctness of
18637 // our page probes, so it's not too terrible.
18638 BrowsingContext* bc = GetBrowsingContext();
18639 return bc && bc->IsTop() && bc->Group()->Toplevels().Length() == 1;
18642 bool Document::ShouldIncludeInTelemetry(bool aAllowExtensionURIs) {
18643 if (!IsContentDocument() && !IsResourceDoc()) {
18644 return false;
18647 if (IsContentInaccessibleAboutBlank()) {
18648 return false;
18651 nsIPrincipal* prin = NodePrincipal();
18652 if (!aAllowExtensionURIs && prin->GetIsAddonOrExpandedAddonPrincipal()) {
18653 return false;
18656 // TODO(emilio): Should this use GetIsContentPrincipal() +
18657 // GetPrecursorPrincipal() instead (accounting for add-ons separately)?
18658 if (prin->IsSystemPrincipal() || prin->SchemeIs("about") ||
18659 prin->SchemeIs("chrome") || prin->SchemeIs("resource")) {
18660 return false;
18663 return true;
18666 void Document::GetConnectedShadowRoots(
18667 nsTArray<RefPtr<ShadowRoot>>& aOut) const {
18668 AppendToArray(aOut, mComposedShadowRoots);
18671 bool Document::HasPictureInPictureChildElement() const {
18672 return mPictureInPictureChildElementCount > 0;
18675 void Document::EnableChildElementInPictureInPictureMode() {
18676 mPictureInPictureChildElementCount++;
18677 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
18680 void Document::DisableChildElementInPictureInPictureMode() {
18681 mPictureInPictureChildElementCount--;
18682 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
18685 void Document::AddMediaElementWithMSE() {
18686 if (mMediaElementWithMSECount++ == 0) {
18687 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18688 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18693 void Document::RemoveMediaElementWithMSE() {
18694 MOZ_ASSERT(mMediaElementWithMSECount > 0);
18695 if (--mMediaElementWithMSECount == 0) {
18696 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18697 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18702 void Document::UnregisterFromMemoryReportingForDataDocument() {
18703 if (!mAddedToMemoryReportingAsDataDocument) {
18704 return;
18706 mAddedToMemoryReportingAsDataDocument = false;
18707 nsIGlobalObject* global = GetScopeObject();
18708 if (global) {
18709 if (nsPIDOMWindowInner* win = global->GetAsInnerWindow()) {
18710 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
18711 this);
18715 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
18716 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
18717 mOOPChildrenLoading.AppendElement(aChild);
18718 if (mOOPChildrenLoading.Length() == 1) {
18719 // Let's block unload so that we're blocked from going into the BFCache
18720 // until the child has actually notified us that it has done loading.
18721 BlockOnload();
18725 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
18726 // aChild will not be in the list if nsDocLoader::Stop() was called, since
18727 // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
18728 // so we don't need to call DocLoaderIsEmpty in that case.
18729 if (mOOPChildrenLoading.RemoveElement(aChild)) {
18730 if (mOOPChildrenLoading.IsEmpty()) {
18731 UnblockOnload(false);
18733 RefPtr<nsDocLoader> docLoader(mDocumentContainer);
18734 if (docLoader) {
18735 docLoader->OOPChildrenLoadingIsEmpty();
18740 void Document::ClearOOPChildrenLoading() {
18741 nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
18742 mOOPChildrenLoading.SwapElements(oopChildrenLoading);
18743 if (!oopChildrenLoading.IsEmpty()) {
18744 UnblockOnload(false);
18748 bool Document::MayHaveDOMActivateListeners() const {
18749 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
18750 return inner->HasDOMActivateEventListeners();
18753 // If we can't get information from the window object, default to true.
18754 return true;
18757 HighlightRegistry& Document::HighlightRegistry() {
18758 if (!mHighlightRegistry) {
18759 mHighlightRegistry = MakeRefPtr<class HighlightRegistry>(this);
18761 return *mHighlightRegistry;
18764 } // namespace mozilla::dom