Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / dom / base / Document.cpp
blob263d3fbc23949b61b5fbafd81d03b13983c192f5
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/RejectForeignAllowList.h"
101 #include "mozilla/RelativeTo.h"
102 #include "mozilla/RestyleManager.h"
103 #include "mozilla/ReverseIterator.h"
104 #include "mozilla/ScrollTimelineAnimationTracker.h"
105 #include "mozilla/SMILAnimationController.h"
106 #include "mozilla/SMILTimeContainer.h"
107 #include "mozilla/ScopeExit.h"
108 #include "mozilla/Components.h"
109 #include "mozilla/ServoStyleConsts.h"
110 #include "mozilla/ServoStyleSet.h"
111 #include "mozilla/ServoTypes.h"
112 #include "mozilla/SizeOfState.h"
113 #include "mozilla/Span.h"
114 #include "mozilla/Sprintf.h"
115 #include "mozilla/StaticAnalysisFunctions.h"
116 #include "mozilla/StaticPrefs_apz.h"
117 #include "mozilla/StaticPrefs_browser.h"
118 #include "mozilla/StaticPrefs_docshell.h"
119 #include "mozilla/StaticPrefs_dom.h"
120 #include "mozilla/StaticPrefs_editor.h"
121 #include "mozilla/StaticPrefs_fission.h"
122 #include "mozilla/StaticPrefs_full_screen_api.h"
123 #include "mozilla/StaticPrefs_layout.h"
124 #include "mozilla/StaticPrefs_network.h"
125 #include "mozilla/StaticPrefs_page_load.h"
126 #include "mozilla/StaticPrefs_privacy.h"
127 #include "mozilla/StaticPrefs_security.h"
128 #include "mozilla/StaticPrefs_widget.h"
129 #include "mozilla/StaticPresData.h"
130 #include "mozilla/StorageAccess.h"
131 #include "mozilla/StoragePrincipalHelper.h"
132 #include "mozilla/StyleSheet.h"
133 #include "mozilla/Telemetry.h"
134 #include "mozilla/TelemetryHistogramEnums.h"
135 #include "mozilla/TelemetryScalarEnums.h"
136 #include "mozilla/TextControlElement.h"
137 #include "mozilla/TextEditor.h"
138 #include "mozilla/TimelineConsumers.h"
139 #include "mozilla/TypedEnumBits.h"
140 #include "mozilla/URLDecorationStripper.h"
141 #include "mozilla/URLExtraData.h"
142 #include "mozilla/Unused.h"
143 #include "mozilla/css/ImageLoader.h"
144 #include "mozilla/css/Loader.h"
145 #include "mozilla/css/Rule.h"
146 #include "mozilla/css/SheetParsingMode.h"
147 #include "mozilla/dom/AnonymousContent.h"
148 #include "mozilla/dom/BlobURLProtocolHandler.h"
149 #include "mozilla/dom/BrowserChild.h"
150 #include "mozilla/dom/BrowsingContext.h"
151 #include "mozilla/dom/BrowsingContextGroup.h"
152 #include "mozilla/dom/CDATASection.h"
153 #include "mozilla/dom/CSPDictionariesBinding.h"
154 #include "mozilla/dom/CanonicalBrowsingContext.h"
155 #include "mozilla/dom/ChromeObserver.h"
156 #include "mozilla/dom/ClientInfo.h"
157 #include "mozilla/dom/ClientState.h"
158 #include "mozilla/dom/Comment.h"
159 #include "mozilla/dom/ContentChild.h"
160 #include "mozilla/dom/DOMImplementation.h"
161 #include "mozilla/dom/DOMIntersectionObserver.h"
162 #include "mozilla/dom/DOMStringList.h"
163 #include "mozilla/dom/DocGroup.h"
164 #include "mozilla/dom/DocumentBinding.h"
165 #include "mozilla/dom/DocumentFragment.h"
166 #include "mozilla/dom/DocumentL10n.h"
167 #include "mozilla/dom/DocumentTimeline.h"
168 #include "mozilla/dom/DocumentType.h"
169 #include "mozilla/dom/ElementBinding.h"
170 #include "mozilla/dom/Event.h"
171 #include "mozilla/dom/EventListenerBinding.h"
172 #include "mozilla/dom/FailedCertSecurityInfoBinding.h"
173 #include "mozilla/dom/FeaturePolicy.h"
174 #include "mozilla/dom/FeaturePolicyUtils.h"
175 #include "mozilla/dom/FontFaceSet.h"
176 #include "mozilla/dom/FromParser.h"
177 #include "mozilla/dom/HighlightRegistry.h"
178 #include "mozilla/dom/HTMLAllCollection.h"
179 #include "mozilla/dom/HTMLBodyElement.h"
180 #include "mozilla/dom/HTMLCollectionBinding.h"
181 #include "mozilla/dom/HTMLDialogElement.h"
182 #include "mozilla/dom/HTMLFormElement.h"
183 #include "mozilla/dom/HTMLIFrameElement.h"
184 #include "mozilla/dom/HTMLImageElement.h"
185 #include "mozilla/dom/HTMLInputElement.h"
186 #include "mozilla/dom/HTMLLinkElement.h"
187 #include "mozilla/dom/HTMLMediaElement.h"
188 #include "mozilla/dom/HTMLMetaElement.h"
189 #include "mozilla/dom/HTMLSharedElement.h"
190 #include "mozilla/dom/HTMLTextAreaElement.h"
191 #include "mozilla/dom/ImageTracker.h"
192 #include "mozilla/dom/Link.h"
193 #include "mozilla/dom/MediaQueryList.h"
194 #include "mozilla/dom/MediaSource.h"
195 #include "mozilla/dom/MutationObservers.h"
196 #include "mozilla/dom/NameSpaceConstants.h"
197 #include "mozilla/dom/Navigator.h"
198 #include "mozilla/dom/NetErrorInfoBinding.h"
199 #include "mozilla/dom/NodeInfo.h"
200 #include "mozilla/dom/NodeIterator.h"
201 #include "mozilla/dom/PContentChild.h"
202 #include "mozilla/dom/PWindowGlobalChild.h"
203 #include "mozilla/dom/PageTransitionEvent.h"
204 #include "mozilla/dom/PageTransitionEventBinding.h"
205 #include "mozilla/dom/Performance.h"
206 #include "mozilla/dom/PermissionMessageUtils.h"
207 #include "mozilla/dom/PostMessageEvent.h"
208 #include "mozilla/dom/ProcessingInstruction.h"
209 #include "mozilla/dom/Promise.h"
210 #include "mozilla/dom/PromiseNativeHandler.h"
211 #include "mozilla/dom/ResizeObserverController.h"
212 #include "mozilla/dom/RustTypes.h"
213 #include "mozilla/dom/SVGElement.h"
214 #include "mozilla/dom/SVGDocument.h"
215 #include "mozilla/dom/SVGSVGElement.h"
216 #include "mozilla/dom/SVGUseElement.h"
217 #include "mozilla/dom/ScriptLoader.h"
218 #include "mozilla/dom/ScriptSettings.h"
219 #include "mozilla/dom/Selection.h"
220 #include "mozilla/dom/ServiceWorkerContainer.h"
221 #include "mozilla/dom/ServiceWorkerDescriptor.h"
222 #include "mozilla/dom/ServiceWorkerManager.h"
223 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
224 #include "mozilla/dom/ShadowRoot.h"
225 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
226 #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
227 #include "mozilla/dom/StyleSheetList.h"
228 #include "mozilla/dom/StyleSheetRemovedEvent.h"
229 #include "mozilla/dom/StyleSheetRemovedEventBinding.h"
230 #include "mozilla/dom/TimeoutManager.h"
231 #include "mozilla/dom/ToggleEvent.h"
232 #include "mozilla/dom/Touch.h"
233 #include "mozilla/dom/TouchEvent.h"
234 #include "mozilla/dom/TreeOrderedArrayInlines.h"
235 #include "mozilla/dom/TreeWalker.h"
236 #include "mozilla/dom/URL.h"
237 #include "mozilla/dom/UserActivation.h"
238 #include "mozilla/dom/WindowBinding.h"
239 #include "mozilla/dom/WindowContext.h"
240 #include "mozilla/dom/WindowGlobalChild.h"
241 #include "mozilla/dom/WindowProxyHolder.h"
242 #include "mozilla/dom/WorkerDocumentListener.h"
243 #include "mozilla/dom/XPathEvaluator.h"
244 #include "mozilla/dom/nsCSPContext.h"
245 #include "mozilla/dom/nsCSPUtils.h"
246 #include "mozilla/extensions/WebExtensionPolicy.h"
247 #include "mozilla/fallible.h"
248 #include "mozilla/gfx/BaseCoord.h"
249 #include "mozilla/gfx/BaseSize.h"
250 #include "mozilla/gfx/Coord.h"
251 #include "mozilla/gfx/Point.h"
252 #include "mozilla/gfx/ScaleFactor.h"
253 #include "mozilla/glean/GleanMetrics.h"
254 #include "mozilla/intl/LocaleService.h"
255 #include "mozilla/ipc/IdleSchedulerChild.h"
256 #include "mozilla/ipc/MessageChannel.h"
257 #include "mozilla/net/ChannelEventQueue.h"
258 #include "mozilla/net/CookieJarSettings.h"
259 #include "mozilla/net/NeckoChannelParams.h"
260 #include "mozilla/net/RequestContextService.h"
261 #include "nsAboutProtocolUtils.h"
262 #include "nsAlgorithm.h"
263 #include "nsAttrValue.h"
264 #include "nsAttrValueInlines.h"
265 #include "nsBaseHashtable.h"
266 #include "nsBidiUtils.h"
267 #include "nsCRT.h"
268 #include "nsCSSPropertyID.h"
269 #include "nsCSSProps.h"
270 #include "nsCSSPseudoElements.h"
271 #include "nsCSSRendering.h"
272 #include "nsCanvasFrame.h"
273 #include "nsCaseTreatment.h"
274 #include "nsCharsetSource.h"
275 #include "nsCommandManager.h"
276 #include "nsCommandParams.h"
277 #include "nsComponentManagerUtils.h"
278 #include "nsContentCreatorFunctions.h"
279 #include "nsContentList.h"
280 #include "nsContentPermissionHelper.h"
281 #include "nsContentSecurityUtils.h"
282 #include "nsContentUtils.h"
283 #include "nsCoord.h"
284 #include "nsCycleCollectionNoteChild.h"
285 #include "nsCycleCollectionTraversalCallback.h"
286 #include "nsDOMAttributeMap.h"
287 #include "nsDOMCaretPosition.h"
288 #include "nsDOMNavigationTiming.h"
289 #include "nsDOMString.h"
290 #include "nsDeviceContext.h"
291 #include "nsDocShell.h"
292 #include "nsDocShellLoadTypes.h"
293 #include "nsEffectiveTLDService.h"
294 #include "nsError.h"
295 #include "nsEscape.h"
296 #include "nsFocusManager.h"
297 #include "nsFrameLoader.h"
298 #include "nsFrameLoaderOwner.h"
299 #include "nsGenericHTMLElement.h"
300 #include "nsGlobalWindowInner.h"
301 #include "nsGlobalWindowOuter.h"
302 #include "nsHTMLDocument.h"
303 #include "nsHtml5Module.h"
304 #include "nsHtml5Parser.h"
305 #include "nsHtml5TreeOpExecutor.h"
306 #include "nsIAsyncShutdown.h"
307 #include "nsIAuthPrompt.h"
308 #include "nsIAuthPrompt2.h"
309 #include "nsIBFCacheEntry.h"
310 #include "nsIBaseWindow.h"
311 #include "nsIBrowserChild.h"
312 #include "nsIBrowserUsage.h"
313 #include "nsICSSLoaderObserver.h"
314 #include "nsICategoryManager.h"
315 #include "nsICertOverrideService.h"
316 #include "nsIContent.h"
317 #include "nsIContentInlines.h"
318 #include "nsIContentPolicy.h"
319 #include "nsIContentSecurityPolicy.h"
320 #include "nsIContentSink.h"
321 #include "nsICookieJarSettings.h"
322 #include "nsICookieService.h"
323 #include "nsIDOMXULCommandDispatcher.h"
324 #include "nsIDocShell.h"
325 #include "nsIDocShellTreeItem.h"
326 #include "nsIDocumentActivity.h"
327 #include "nsIDocumentEncoder.h"
328 #include "nsIDocumentLoader.h"
329 #include "nsIDocumentLoaderFactory.h"
330 #include "nsIDocumentObserver.h"
331 #include "nsIDNSService.h"
332 #include "nsIEditingSession.h"
333 #include "nsIEditor.h"
334 #include "nsIEffectiveTLDService.h"
335 #include "nsIFile.h"
336 #include "nsIFileChannel.h"
337 #include "nsIFrame.h"
338 #include "nsIGlobalObject.h"
339 #include "nsIHTMLCollection.h"
340 #include "nsIHttpChannel.h"
341 #include "nsIHttpChannelInternal.h"
342 #include "nsIIOService.h"
343 #include "nsIImageLoadingContent.h"
344 #include "nsIInlineSpellChecker.h"
345 #include "nsIInputStreamChannel.h"
346 #include "nsIInterfaceRequestorUtils.h"
347 #include "nsILayoutHistoryState.h"
348 #include "nsIMultiPartChannel.h"
349 #include "nsIMutationObserver.h"
350 #include "nsINSSErrorsService.h"
351 #include "nsINamed.h"
352 #include "nsINodeList.h"
353 #include "nsIObjectLoadingContent.h"
354 #include "nsIObserverService.h"
355 #include "nsIPermission.h"
356 #include "nsIPrompt.h"
357 #include "nsIPropertyBag2.h"
358 #include "nsIPublicKeyPinningService.h"
359 #include "nsIReferrerInfo.h"
360 #include "nsIRefreshURI.h"
361 #include "nsIRequest.h"
362 #include "nsIRequestContext.h"
363 #include "nsIRunnable.h"
364 #include "nsISHEntry.h"
365 #include "nsIScriptElement.h"
366 #include "nsIScriptError.h"
367 #include "nsIScriptGlobalObject.h"
368 #include "nsIScriptSecurityManager.h"
369 #include "nsISecurityConsoleMessage.h"
370 #include "nsISelectionController.h"
371 #include "nsISerialEventTarget.h"
372 #include "nsISimpleEnumerator.h"
373 #include "nsISiteSecurityService.h"
374 #include "nsISocketProvider.h"
375 #include "nsISpeculativeConnect.h"
376 #include "nsIStructuredCloneContainer.h"
377 #include "nsIThread.h"
378 #include "nsITimedChannel.h"
379 #include "nsITimer.h"
380 #include "nsITransportSecurityInfo.h"
381 #include "nsIURIMutator.h"
382 #include "nsIVariant.h"
383 #include "nsIWeakReference.h"
384 #include "nsIWebNavigation.h"
385 #include "nsIWidget.h"
386 #include "nsIX509Cert.h"
387 #include "nsIX509CertValidity.h"
388 #include "nsIXMLContentSink.h"
389 #include "nsIHTMLContentSink.h"
390 #include "nsIXULRuntime.h"
391 #include "nsImageLoadingContent.h"
392 #include "nsImportModule.h"
393 #include "nsLanguageAtomService.h"
394 #include "nsLayoutUtils.h"
395 #include "nsMimeTypes.h"
396 #include "nsNetCID.h"
397 #include "nsNetUtil.h"
398 #include "nsNodeInfoManager.h"
399 #include "nsObjectLoadingContent.h"
400 #include "nsPIDOMWindowInlines.h"
401 #include "nsPIWindowRoot.h"
402 #include "nsPoint.h"
403 #include "nsPointerHashKeys.h"
404 #include "nsPresContext.h"
405 #include "nsQueryFrame.h"
406 #include "nsQueryObject.h"
407 #include "nsRange.h"
408 #include "nsRect.h"
409 #include "nsRefreshDriver.h"
410 #include "nsSandboxFlags.h"
411 #include "nsSerializationHelper.h"
412 #include "nsServiceManagerUtils.h"
413 #include "nsStringFlags.h"
414 #include "nsStyleUtil.h"
415 #include "nsStringIterator.h"
416 #include "nsStyleSheetService.h"
417 #include "nsStyleStruct.h"
418 #include "nsTextNode.h"
419 #include "nsUnicharUtils.h"
420 #include "nsWrapperCache.h"
421 #include "nsWrapperCacheInlines.h"
422 #include "nsXPCOMCID.h"
423 #include "nsXULAppAPI.h"
424 #include "prthread.h"
425 #include "prtime.h"
426 #include "prtypes.h"
427 #include "xpcpublic.h"
429 // XXX Must be included after mozilla/Encoding.h
430 #include "encoding_rs.h"
432 #include "mozilla/dom/XULBroadcastManager.h"
433 #include "mozilla/dom/XULPersist.h"
434 #include "nsIAppWindow.h"
435 #include "nsXULPrototypeDocument.h"
436 #include "nsXULCommandDispatcher.h"
437 #include "nsXULPopupManager.h"
438 #include "nsIDocShellTreeOwner.h"
440 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
441 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
442 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
443 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
445 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
447 mozilla::LazyLogModule gPageCacheLog("PageCache");
448 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
449 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
450 mozilla::LazyLogModule gUseCountersLog("UseCounters");
452 namespace mozilla {
453 namespace dom {
455 class Document::HeaderData {
456 public:
457 HeaderData(nsAtom* aField, const nsAString& aData)
458 : mField(aField), mData(aData) {}
460 ~HeaderData() {
461 // Delete iteratively to avoid blowing up the stack, though it shouldn't
462 // happen in practice.
463 UniquePtr<HeaderData> next = std::move(mNext);
464 while (next) {
465 next = std::move(next->mNext);
469 RefPtr<nsAtom> mField;
470 nsString mData;
471 UniquePtr<HeaderData> mNext;
474 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
475 nullptr;
477 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
478 static LazyLogModule gCspPRLog("CSP");
479 LazyLogModule gUserInteractionPRLog("UserInteraction");
481 static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
482 nsIHttpChannel** aHttpChannel) {
483 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
484 if (httpChannel) {
485 httpChannel.forget(aHttpChannel);
486 return NS_OK;
489 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
490 if (!multipart) {
491 *aHttpChannel = nullptr;
492 return NS_OK;
495 nsCOMPtr<nsIChannel> baseChannel;
496 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
497 if (NS_WARN_IF(NS_FAILED(rv))) {
498 return rv;
501 httpChannel = do_QueryInterface(baseChannel);
502 httpChannel.forget(aHttpChannel);
504 return NS_OK;
507 } // namespace dom
509 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
511 IdentifierMapEntry::IdentifierMapEntry(
512 const IdentifierMapEntry::DependentAtomOrString* aKey)
513 : mKey(aKey ? *aKey : nullptr) {}
515 void IdentifierMapEntry::Traverse(
516 nsCycleCollectionTraversalCallback* aCallback) {
517 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
518 "mIdentifierMap mNameContentList");
519 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
521 if (mImageElement) {
522 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
523 "mIdentifierMap mImageElement element");
524 nsIContent* imageElement = mImageElement;
525 aCallback->NoteXPCOMChild(imageElement);
529 bool IdentifierMapEntry::IsEmpty() {
530 return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
531 !mImageElement;
534 bool IdentifierMapEntry::HasNameElement() const {
535 return mNameContentList && mNameContentList->Length() != 0;
538 void IdentifierMapEntry::AddContentChangeCallback(
539 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
540 if (!mChangeCallbacks) {
541 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
544 ChangeCallback cc = {aCallback, aData, aForImage};
545 mChangeCallbacks->PutEntry(cc);
548 void IdentifierMapEntry::RemoveContentChangeCallback(
549 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
550 if (!mChangeCallbacks) return;
551 ChangeCallback cc = {aCallback, aData, aForImage};
552 mChangeCallbacks->RemoveEntry(cc);
553 if (mChangeCallbacks->Count() == 0) {
554 mChangeCallbacks = nullptr;
558 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
559 Element* aNewElement,
560 bool aImageOnly) {
561 if (!mChangeCallbacks) return;
563 for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) {
564 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
565 // Don't fire image changes for non-image observers, and don't fire element
566 // changes for image observers when an image override is active.
567 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
568 continue;
571 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
572 iter.Remove();
577 void IdentifierMapEntry::AddIdElement(Element* aElement) {
578 MOZ_ASSERT(aElement, "Must have element");
579 MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
581 size_t index = mIdContentList.Insert(*aElement);
582 if (index == 0) {
583 Element* oldElement = mIdContentList->SafeElementAt(1);
584 FireChangeCallbacks(oldElement, aElement);
588 void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
589 MOZ_ASSERT(aElement, "Missing element");
591 // This should only be called while the document is in an update.
592 // Assertions near the call to this method guarantee this.
594 // This could fire in OOM situations
595 // Only assert this in HTML documents for now as XUL does all sorts of weird
596 // crap.
597 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
598 mIdContentList->Contains(aElement),
599 "Removing id entry that doesn't exist");
601 // XXXbz should this ever Compact() I guess when all the content is gone
602 // we'll just get cleaned up in the natural order of things...
603 Element* currentElement = mIdContentList->SafeElementAt(0);
604 mIdContentList.RemoveElement(*aElement);
605 if (currentElement == aElement) {
606 FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
610 void IdentifierMapEntry::SetImageElement(Element* aElement) {
611 Element* oldElement = GetImageIdElement();
612 mImageElement = aElement;
613 Element* newElement = GetImageIdElement();
614 if (oldElement != newElement) {
615 FireChangeCallbacks(oldElement, newElement, true);
619 void IdentifierMapEntry::ClearAndNotify() {
620 Element* currentElement = mIdContentList->SafeElementAt(0);
621 mIdContentList.Clear();
622 if (currentElement) {
623 FireChangeCallbacks(currentElement, nullptr);
625 mNameContentList = nullptr;
626 if (mImageElement) {
627 SetImageElement(nullptr);
629 mChangeCallbacks = nullptr;
632 namespace dom {
634 class SimpleHTMLCollection final : public nsSimpleContentList,
635 public nsIHTMLCollection {
636 public:
637 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
639 NS_DECL_ISUPPORTS_INHERITED
641 virtual nsINode* GetParentObject() override {
642 return nsSimpleContentList::GetParentObject();
644 virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
645 virtual Element* GetElementAt(uint32_t aIndex) override {
646 return mElements.SafeElementAt(aIndex)->AsElement();
649 virtual Element* GetFirstNamedElement(const nsAString& aName,
650 bool& aFound) override {
651 aFound = false;
652 RefPtr<nsAtom> name = NS_Atomize(aName);
653 for (uint32_t i = 0; i < mElements.Length(); i++) {
654 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
655 Element* element = mElements[i]->AsElement();
656 if (element->GetID() == name ||
657 (element->HasName() &&
658 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
659 aFound = true;
660 return element;
663 return nullptr;
666 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
667 AutoTArray<nsAtom*, 8> atoms;
668 for (uint32_t i = 0; i < mElements.Length(); i++) {
669 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
670 Element* element = mElements[i]->AsElement();
672 nsAtom* id = element->GetID();
673 MOZ_ASSERT(id != nsGkAtoms::_empty);
674 if (id && !atoms.Contains(id)) {
675 atoms.AppendElement(id);
678 if (element->HasName()) {
679 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
680 MOZ_ASSERT(name && name != nsGkAtoms::_empty);
681 if (name && !atoms.Contains(name)) {
682 atoms.AppendElement(name);
687 nsString* names = aNames.AppendElements(atoms.Length());
688 for (uint32_t i = 0; i < atoms.Length(); i++) {
689 atoms[i]->ToString(names[i]);
693 virtual JSObject* GetWrapperPreserveColorInternal() override {
694 return nsWrapperCache::GetWrapperPreserveColor();
696 virtual void PreserveWrapperInternal(
697 nsISupports* aScriptObjectHolder) override {
698 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
700 virtual JSObject* WrapObject(JSContext* aCx,
701 JS::Handle<JSObject*> aGivenProto) override {
702 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
705 using nsBaseContentList::Item;
707 private:
708 virtual ~SimpleHTMLCollection() = default;
711 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
712 nsIHTMLCollection)
714 } // namespace dom
716 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
717 if (!mNameContentList) {
718 mNameContentList = new dom::SimpleHTMLCollection(aNode);
721 mNameContentList->AppendElement(aElement);
724 void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
725 if (mNameContentList) {
726 mNameContentList->RemoveElement(aElement);
730 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const {
731 Element* idElement = GetIdElement();
732 return idElement &&
733 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
736 size_t IdentifierMapEntry::SizeOfExcludingThis(
737 MallocSizeOf aMallocSizeOf) const {
738 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
741 // Helper structs for the content->subdoc map
743 class SubDocMapEntry : public PLDHashEntryHdr {
744 public:
745 // Both of these are strong references
746 dom::Element* mKey; // must be first, to look like PLDHashEntryStub
747 dom::Document* mSubDocument;
750 class OnloadBlocker final : public nsIRequest {
751 public:
752 OnloadBlocker() = default;
754 NS_DECL_ISUPPORTS
755 NS_DECL_NSIREQUEST
757 private:
758 ~OnloadBlocker() = default;
761 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
763 NS_IMETHODIMP
764 OnloadBlocker::GetName(nsACString& aResult) {
765 aResult.AssignLiteral("about:document-onload-blocker");
766 return NS_OK;
769 NS_IMETHODIMP
770 OnloadBlocker::IsPending(bool* _retval) {
771 *_retval = true;
772 return NS_OK;
775 NS_IMETHODIMP
776 OnloadBlocker::GetStatus(nsresult* status) {
777 *status = NS_OK;
778 return NS_OK;
781 NS_IMETHODIMP OnloadBlocker::SetCanceledReason(const nsACString& aReason) {
782 return SetCanceledReasonImpl(aReason);
785 NS_IMETHODIMP OnloadBlocker::GetCanceledReason(nsACString& aReason) {
786 return GetCanceledReasonImpl(aReason);
789 NS_IMETHODIMP OnloadBlocker::CancelWithReason(nsresult aStatus,
790 const nsACString& aReason) {
791 return CancelWithReasonImpl(aStatus, aReason);
793 NS_IMETHODIMP
794 OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
795 NS_IMETHODIMP
796 OnloadBlocker::Suspend(void) { return NS_OK; }
797 NS_IMETHODIMP
798 OnloadBlocker::Resume(void) { return NS_OK; }
800 NS_IMETHODIMP
801 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
802 *aLoadGroup = nullptr;
803 return NS_OK;
806 NS_IMETHODIMP
807 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
809 NS_IMETHODIMP
810 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
811 *aLoadFlags = nsIRequest::LOAD_NORMAL;
812 return NS_OK;
815 NS_IMETHODIMP
816 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
817 return GetTRRModeImpl(aTRRMode);
820 NS_IMETHODIMP
821 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
822 return SetTRRModeImpl(aTRRMode);
825 NS_IMETHODIMP
826 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
828 // ==================================================================
830 namespace dom {
832 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
834 Document* ExternalResourceMap::RequestResource(
835 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
836 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
837 // If we ever start allowing non-same-origin loads here, we might need to do
838 // something interesting with aRequestingPrincipal even for the hashtable
839 // gets.
840 MOZ_ASSERT(aURI, "Must have a URI");
841 MOZ_ASSERT(aRequestingNode, "Must have a node");
842 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
843 *aPendingLoad = nullptr;
844 if (mHaveShutDown) {
845 return nullptr;
848 // First, make sure we strip the ref from aURI.
849 nsCOMPtr<nsIURI> clone;
850 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
851 if (NS_FAILED(rv) || !clone) {
852 return nullptr;
855 ExternalResource* resource;
856 mMap.Get(clone, &resource);
857 if (resource) {
858 return resource->mDocument;
861 bool loadStartSucceeded =
862 mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
863 if (!loadEntry) {
864 loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
866 if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
867 aRequestingNode))) {
868 return false;
872 RefPtr<PendingLoad> load(loadEntry.Data());
873 load.forget(aPendingLoad);
874 return true;
876 if (!loadStartSucceeded) {
877 // Make sure we don't thrash things by trying this load again, since
878 // chances are it failed for good reasons (security check, etc).
879 // This must be done outside the WithEntryHandle functor, as it accesses
880 // mPendingLoads.
881 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
884 return nullptr;
887 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
888 nsTArray<RefPtr<Document>> docs(mMap.Count());
889 for (const auto& entry : mMap.Values()) {
890 if (Document* doc = entry->mDocument) {
891 docs.AppendElement(doc);
895 for (auto& doc : docs) {
896 if (aCallback(*doc) == CallState::Stop) {
897 return;
902 void ExternalResourceMap::Traverse(
903 nsCycleCollectionTraversalCallback* aCallback) const {
904 // mPendingLoads will get cleared out as the requests complete, so
905 // no need to worry about those here.
906 for (const auto& entry : mMap) {
907 ExternalResourceMap::ExternalResource* resource = entry.GetWeak();
909 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
910 "mExternalResourceMap.mMap entry"
911 "->mDocument");
912 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
914 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
915 "mExternalResourceMap.mMap entry"
916 "->mViewer");
917 aCallback->NoteXPCOMChild(resource->mViewer);
919 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
920 "mExternalResourceMap.mMap entry"
921 "->mLoadGroup");
922 aCallback->NoteXPCOMChild(resource->mLoadGroup);
926 void ExternalResourceMap::HideViewers() {
927 for (const auto& entry : mMap) {
928 nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
929 if (viewer) {
930 viewer->Hide();
935 void ExternalResourceMap::ShowViewers() {
936 for (const auto& entry : mMap) {
937 nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
938 if (viewer) {
939 viewer->Show();
944 void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
945 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
947 if (aFromDoc->IsShowing()) {
948 aToDoc->OnPageShow(true, nullptr);
952 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
953 nsIContentViewer* aViewer,
954 nsILoadGroup* aLoadGroup,
955 Document* aDisplayDocument) {
956 MOZ_ASSERT(aURI, "Unexpected call");
957 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
958 "Must have both or neither");
960 RefPtr<PendingLoad> load;
961 mPendingLoads.Remove(aURI, getter_AddRefs(load));
963 nsresult rv = NS_OK;
965 nsCOMPtr<Document> doc;
966 if (aViewer) {
967 doc = aViewer->GetDocument();
968 NS_ASSERTION(doc, "Must have a document");
970 doc->SetDisplayDocument(aDisplayDocument);
972 // Make sure that hiding our viewer will tear down its presentation.
973 aViewer->SetSticky(false);
975 rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
976 if (NS_SUCCEEDED(rv)) {
977 rv = aViewer->Open(nullptr, nullptr);
980 if (NS_FAILED(rv)) {
981 doc = nullptr;
982 aViewer = nullptr;
983 aLoadGroup = nullptr;
987 ExternalResource* newResource =
988 mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get();
990 newResource->mDocument = doc;
991 newResource->mViewer = aViewer;
992 newResource->mLoadGroup = aLoadGroup;
993 if (doc) {
994 if (nsPresContext* pc = doc->GetPresContext()) {
995 pc->RecomputeBrowsingContextDependentData();
997 TransferShowingState(aDisplayDocument, doc);
1000 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
1001 for (uint32_t i = 0; i < obs.Length(); ++i) {
1002 obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
1003 nullptr);
1006 return rv;
1009 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
1010 nsIRequestObserver)
1012 NS_IMETHODIMP
1013 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
1014 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
1015 if (map.HaveShutDown()) {
1016 return NS_BINDING_ABORTED;
1019 nsCOMPtr<nsIContentViewer> viewer;
1020 nsCOMPtr<nsILoadGroup> loadGroup;
1021 nsresult rv =
1022 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
1024 // Make sure to do this no matter what
1025 nsresult rv2 =
1026 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
1027 if (NS_FAILED(rv)) {
1028 return rv;
1030 if (NS_FAILED(rv2)) {
1031 mTargetListener = nullptr;
1032 return rv2;
1035 return mTargetListener->OnStartRequest(aRequest);
1038 nsresult ExternalResourceMap::PendingLoad::SetupViewer(
1039 nsIRequest* aRequest, nsIContentViewer** aViewer,
1040 nsILoadGroup** aLoadGroup) {
1041 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
1042 *aViewer = nullptr;
1043 *aLoadGroup = nullptr;
1045 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
1046 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
1048 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
1049 if (httpChannel) {
1050 bool requestSucceeded;
1051 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
1052 !requestSucceeded) {
1053 // Bail out on this load, since it looks like we have an HTTP error page
1054 return NS_BINDING_ABORTED;
1058 nsAutoCString type;
1059 chan->GetContentType(type);
1061 nsCOMPtr<nsILoadGroup> loadGroup;
1062 chan->GetLoadGroup(getter_AddRefs(loadGroup));
1064 // Give this document its own loadgroup
1065 nsCOMPtr<nsILoadGroup> newLoadGroup =
1066 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1067 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
1068 newLoadGroup->SetLoadGroup(loadGroup);
1070 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1071 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1073 nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
1074 new LoadgroupCallbacks(callbacks);
1075 newLoadGroup->SetNotificationCallbacks(newCallbacks);
1077 // This is some serious hackery cribbed from docshell
1078 nsCOMPtr<nsICategoryManager> catMan =
1079 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
1080 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
1081 nsCString contractId;
1082 nsresult rv =
1083 catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
1084 NS_ENSURE_SUCCESS(rv, rv);
1085 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
1086 do_GetService(contractId.get());
1087 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
1089 nsCOMPtr<nsIContentViewer> viewer;
1090 nsCOMPtr<nsIStreamListener> listener;
1091 rv = docLoaderFactory->CreateInstance(
1092 "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
1093 getter_AddRefs(listener), getter_AddRefs(viewer));
1094 NS_ENSURE_SUCCESS(rv, rv);
1095 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
1097 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
1098 if (!parser) {
1099 /// We don't want to deal with the various fake documents yet
1100 return NS_ERROR_NOT_IMPLEMENTED;
1103 // We can't handle HTML and other weird things here yet.
1104 nsIContentSink* sink = parser->GetContentSink();
1105 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
1106 if (!xmlSink) {
1107 return NS_ERROR_NOT_IMPLEMENTED;
1110 listener.swap(mTargetListener);
1111 viewer.forget(aViewer);
1112 newLoadGroup.forget(aLoadGroup);
1113 return NS_OK;
1116 NS_IMETHODIMP
1117 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
1118 nsIInputStream* aStream,
1119 uint64_t aOffset,
1120 uint32_t aCount) {
1121 // mTargetListener might be null if SetupViewer or AddExternalResource failed.
1122 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
1123 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
1124 return NS_BINDING_ABORTED;
1126 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1129 NS_IMETHODIMP
1130 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
1131 nsresult aStatus) {
1132 // mTargetListener might be null if SetupViewer or AddExternalResource failed
1133 if (mTargetListener) {
1134 nsCOMPtr<nsIStreamListener> listener;
1135 mTargetListener.swap(listener);
1136 return listener->OnStopRequest(aRequest, aStatus);
1139 return NS_OK;
1142 nsresult ExternalResourceMap::PendingLoad::StartLoad(
1143 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
1144 MOZ_ASSERT(aURI, "Must have a URI");
1145 MOZ_ASSERT(aRequestingNode, "Must have a node");
1146 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
1148 nsCOMPtr<nsILoadGroup> loadGroup =
1149 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
1151 nsresult rv = NS_OK;
1152 nsCOMPtr<nsIChannel> channel;
1153 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
1154 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1155 nsIContentPolicy::TYPE_OTHER,
1156 nullptr, // aPerformanceStorage
1157 loadGroup);
1158 NS_ENSURE_SUCCESS(rv, rv);
1160 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1161 if (httpChannel) {
1162 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
1163 Unused << NS_WARN_IF(NS_FAILED(rv));
1166 mURI = aURI;
1168 return channel->AsyncOpen(this);
1171 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
1172 nsIInterfaceRequestor)
1174 #define IMPL_SHIM(_i) \
1175 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
1177 IMPL_SHIM(nsILoadContext)
1178 IMPL_SHIM(nsIProgressEventSink)
1179 IMPL_SHIM(nsIChannelEventSink)
1181 #undef IMPL_SHIM
1183 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
1185 #define TRY_SHIM(_i) \
1186 PR_BEGIN_MACRO \
1187 if (IID_IS(_i)) { \
1188 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
1189 if (!real) { \
1190 return NS_NOINTERFACE; \
1192 nsCOMPtr<_i> shim = new _i##Shim(this, real); \
1193 shim.forget(aSink); \
1194 return NS_OK; \
1196 PR_END_MACRO
1198 NS_IMETHODIMP
1199 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
1200 void** aSink) {
1201 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
1202 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
1203 return mCallbacks->GetInterface(aIID, aSink);
1206 *aSink = nullptr;
1208 TRY_SHIM(nsILoadContext);
1209 TRY_SHIM(nsIProgressEventSink);
1210 TRY_SHIM(nsIChannelEventSink);
1212 return NS_NOINTERFACE;
1215 #undef TRY_SHIM
1216 #undef IID_IS
1218 ExternalResourceMap::ExternalResource::~ExternalResource() {
1219 if (mViewer) {
1220 mViewer->Close(nullptr);
1221 mViewer->Destroy();
1225 // ==================================================================
1226 // =
1227 // ==================================================================
1229 // If we ever have an nsIDocumentObserver notification for stylesheet title
1230 // changes we should update the list from that instead of overriding
1231 // EnsureFresh.
1232 class DOMStyleSheetSetList final : public DOMStringList {
1233 public:
1234 explicit DOMStyleSheetSetList(Document* aDocument);
1236 void Disconnect() { mDocument = nullptr; }
1238 virtual void EnsureFresh() override;
1240 protected:
1241 Document* mDocument; // Our document; weak ref. It'll let us know if it
1242 // dies.
1245 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
1246 : mDocument(aDocument) {
1247 NS_ASSERTION(mDocument, "Must have document!");
1250 void DOMStyleSheetSetList::EnsureFresh() {
1251 MOZ_ASSERT(NS_IsMainThread());
1253 mNames.Clear();
1255 if (!mDocument) {
1256 return; // Spec says "no exceptions", and we have no style sets if we have
1257 // no document, for sure
1260 size_t count = mDocument->SheetCount();
1261 nsAutoString title;
1262 for (size_t index = 0; index < count; index++) {
1263 StyleSheet* sheet = mDocument->SheetAt(index);
1264 NS_ASSERTION(sheet, "Null sheet in sheet list!");
1265 sheet->GetTitle(title);
1266 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
1267 return;
1272 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1274 // ==================================================================
1275 // =
1276 // ==================================================================
1278 Document::InternalCommandDataHashtable*
1279 Document::sInternalCommandDataHashtable = nullptr;
1281 // static
1282 void Document::Shutdown() {
1283 if (sInternalCommandDataHashtable) {
1284 sInternalCommandDataHashtable->Clear();
1285 delete sInternalCommandDataHashtable;
1286 sInternalCommandDataHashtable = nullptr;
1290 Document::Document(const char* aContentType)
1291 : nsINode(nullptr),
1292 DocumentOrShadowRoot(this),
1293 mCharacterSet(WINDOWS_1252_ENCODING),
1294 mCharacterSetSource(0),
1295 mParentDocument(nullptr),
1296 mCachedRootElement(nullptr),
1297 mNodeInfoManager(nullptr),
1298 #ifdef DEBUG
1299 mStyledLinksCleared(false),
1300 #endif
1301 mCachedStateObjectValid(false),
1302 mBlockAllMixedContent(false),
1303 mBlockAllMixedContentPreloads(false),
1304 mUpgradeInsecureRequests(false),
1305 mUpgradeInsecurePreloads(false),
1306 mDevToolsWatchingDOMMutations(false),
1307 mBidiEnabled(false),
1308 mMayNeedFontPrefsUpdate(true),
1309 mMathMLEnabled(false),
1310 mIsInitialDocumentInWindow(false),
1311 mIgnoreDocGroupMismatches(false),
1312 mLoadedAsData(false),
1313 mAddedToMemoryReportingAsDataDocument(false),
1314 mMayStartLayout(true),
1315 mHaveFiredTitleChange(false),
1316 mIsShowing(false),
1317 mVisible(true),
1318 mRemovedFromDocShell(false),
1319 // mAllowDNSPrefetch starts true, so that we can always reliably && it
1320 // with various values that might disable it. Since we never prefetch
1321 // unless we get a window, and in that case the docshell value will get
1322 // &&-ed in, this is safe.
1323 mAllowDNSPrefetch(true),
1324 mIsStaticDocument(false),
1325 mCreatingStaticClone(false),
1326 mHasPrintCallbacks(false),
1327 mInUnlinkOrDeletion(false),
1328 mHasHadScriptHandlingObject(false),
1329 mIsBeingUsedAsImage(false),
1330 mChromeRulesEnabled(false),
1331 mInChromeDocShell(false),
1332 mIsDevToolsDocument(false),
1333 mIsSyntheticDocument(false),
1334 mHasLinksToUpdateRunnable(false),
1335 mFlushingPendingLinkUpdates(false),
1336 mMayHaveDOMMutationObservers(false),
1337 mMayHaveAnimationObservers(false),
1338 mHasCSPDeliveredThroughHeader(false),
1339 mBFCacheDisallowed(false),
1340 mHasHadDefaultView(false),
1341 mStyleSheetChangeEventsEnabled(false),
1342 mDevToolsAnonymousAndShadowEventsEnabled(false),
1343 mIsSrcdocDocument(false),
1344 mHasDisplayDocument(false),
1345 mFontFaceSetDirty(true),
1346 mDidFireDOMContentLoaded(true),
1347 mFrameRequestCallbacksScheduled(false),
1348 mIsTopLevelContentDocument(false),
1349 mIsContentDocument(false),
1350 mDidCallBeginLoad(false),
1351 mEncodingMenuDisabled(false),
1352 mLinksEnabled(true),
1353 mIsSVGGlyphsDocument(false),
1354 mInDestructor(false),
1355 mIsGoingAway(false),
1356 mInXBLUpdate(false),
1357 mStyleSetFilled(false),
1358 mQuirkSheetAdded(false),
1359 mContentEditableSheetAdded(false),
1360 mDesignModeSheetAdded(false),
1361 mMayHaveTitleElement(false),
1362 mDOMLoadingSet(false),
1363 mDOMInteractiveSet(false),
1364 mDOMCompleteSet(false),
1365 mAutoFocusFired(false),
1366 mScrolledToRefAlready(false),
1367 mChangeScrollPosWhenScrollingToRef(false),
1368 mDelayFrameLoaderInitialization(false),
1369 mSynchronousDOMContentLoaded(false),
1370 mMaybeServiceWorkerControlled(false),
1371 mAllowZoom(false),
1372 mValidScaleFloat(false),
1373 mValidMinScale(false),
1374 mValidMaxScale(false),
1375 mWidthStrEmpty(false),
1376 mParserAborted(false),
1377 mReportedDocumentUseCounters(false),
1378 mHasReportedShadowDOMUsage(false),
1379 mHasDelayedRefreshEvent(false),
1380 mLoadEventFiring(false),
1381 mSkipLoadEventAfterClose(false),
1382 mDisableCookieAccess(false),
1383 mDisableDocWrite(false),
1384 mTooDeepWriteRecursion(false),
1385 mPendingMaybeEditingStateChanged(false),
1386 mHasBeenEditable(false),
1387 mHasWarnedAboutZoom(false),
1388 mIsRunningExecCommand(false),
1389 mSetCompleteAfterDOMContentLoaded(false),
1390 mDidHitCompleteSheetCache(false),
1391 mUseCountersInitialized(false),
1392 mShouldReportUseCounters(false),
1393 mShouldSendPageUseCounters(false),
1394 mUserHasInteracted(false),
1395 mHasUserInteractionTimerScheduled(false),
1396 mShouldResistFingerprinting(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_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(Document)
2438 NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(
2439 Document, LastRelease())
2441 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2442 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2443 EventListenerManager* elm = tmp->GetExistingListenerManager();
2444 if (elm) {
2445 elm->MarkForCC();
2447 return true;
2449 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2451 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2452 return Element::CanSkipInCC(tmp);
2453 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2455 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2456 return Element::CanSkipThis(tmp);
2457 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2459 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2460 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2461 char name[512];
2462 nsAutoCString loadedAsData;
2463 if (tmp->IsLoadedAsData()) {
2464 loadedAsData.AssignLiteral("data");
2465 } else {
2466 loadedAsData.AssignLiteral("normal");
2468 uint32_t nsid = tmp->GetDefaultNamespaceID();
2469 nsAutoCString uri;
2470 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2471 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2472 "(xhtml)", "(XLink)", "(XSLT)",
2473 "(MathML)", "(RDF)", "(XUL)"};
2474 if (nsid < ArrayLength(kNSURIs)) {
2475 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2476 kNSURIs[nsid], uri.get());
2477 } else {
2478 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2480 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2481 } else {
2482 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2485 if (!nsINode::Traverse(tmp, cb)) {
2486 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2489 tmp->mExternalResourceMap.Traverse(&cb);
2491 // Traverse all Document pointer members.
2492 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2493 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2494 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2495 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2496 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2497 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry)
2499 // Traverse all Document nsCOMPtrs.
2500 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2501 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2502 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2503 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2504 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2506 DocumentOrShadowRoot::Traverse(tmp, cb);
2508 for (auto& sheets : tmp->mAdditionalSheets) {
2509 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2512 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2513 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserver)
2514 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserverViewport)
2515 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRememberedSizeObserver)
2516 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentVisibilityObserver)
2517 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2518 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2519 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2520 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2521 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2522 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2523 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
2524 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker)
2525 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2526 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2527 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2528 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2529 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2530 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2531 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2532 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2533 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2534 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2535 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2536 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2537 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2538 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2539 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2540 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2541 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2542 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
2544 // Traverse all our nsCOMArrays.
2545 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2547 // Traverse animation components
2548 if (tmp->mAnimationController) {
2549 tmp->mAnimationController->Traverse(&cb);
2552 if (tmp->mSubDocuments) {
2553 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2554 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2556 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2557 cb.NoteXPCOMChild(entry->mKey);
2558 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2559 "mSubDocuments entry->mSubDocument");
2560 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2564 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2566 // We own only the items in mDOMMediaQueryLists that have listeners;
2567 // this reference is managed by their AddListener and RemoveListener
2568 // methods.
2569 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2570 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2571 if (mql->HasListeners() &&
2572 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2573 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2574 cb.NoteXPCOMChild(mql);
2578 // XXX: This should be not needed once bug 1569185 lands.
2579 for (const auto& entry : tmp->mL10nProtoElements) {
2580 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2581 cb.NoteXPCOMChild(entry.GetKey());
2582 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
2585 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2586 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2587 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2588 mPendingFrameStaticClones[i].mStaticCloneOf);
2590 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2592 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2594 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document)
2595 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
2596 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject)
2597 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2599 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2600 tmp->mInUnlinkOrDeletion = true;
2602 tmp->SetStateObject(nullptr);
2604 // Clear out our external resources
2605 tmp->mExternalResourceMap.Shutdown();
2607 nsAutoScriptBlocker scriptBlocker;
2609 nsINode::Unlink(tmp);
2611 while (tmp->HasChildren()) {
2612 // Hold a strong ref to the node when we remove it, because we may be
2613 // the last reference to it.
2614 // If this code changes, change the corresponding code in Document's
2615 // unlink impl and ContentUnbinder::UnbindSubtree.
2616 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2617 tmp->DisconnectChild(child);
2618 child->UnbindFromTree();
2621 tmp->UnlinkOriginalDocumentIfStatic();
2623 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2625 tmp->SetScriptGlobalObject(nullptr);
2627 for (auto& sheets : tmp->mAdditionalSheets) {
2628 tmp->UnlinkStyleSheets(sheets);
2631 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2632 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2633 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserver)
2634 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserverViewport)
2635 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentVisibilityObserver)
2636 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastRememberedSizeObserver)
2637 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2638 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2639 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2640 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry)
2641 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2642 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2643 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2644 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2645 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2646 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2647 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2648 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2649 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
2650 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker)
2651 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2652 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2653 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2654 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2655 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2656 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2657 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2658 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2659 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2660 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2661 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2662 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2663 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2664 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2665 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2666 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2667 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2668 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2670 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
2671 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
2672 tmp->mDocGroup);
2674 tmp->mDocGroup = nullptr;
2676 if (tmp->IsTopLevelContentDocument()) {
2677 RemoveToplevelLoadingDocument(tmp);
2680 tmp->mParentDocument = nullptr;
2682 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2684 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2686 if (tmp->mListenerManager) {
2687 tmp->mListenerManager->Disconnect();
2688 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2689 tmp->mListenerManager = nullptr;
2692 if (tmp->mStyleSheetSetList) {
2693 tmp->mStyleSheetSetList->Disconnect();
2694 tmp->mStyleSheetSetList = nullptr;
2697 delete tmp->mSubDocuments;
2698 tmp->mSubDocuments = nullptr;
2700 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
2701 MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
2702 "How did we get here without our presshell going away "
2703 "first?");
2705 DocumentOrShadowRoot::Unlink(tmp);
2707 // Document has a pretty complex destructor, so we're going to
2708 // assume that *most* cycles you actually want to break somewhere
2709 // else, and not unlink an awful lot here.
2711 tmp->mExpandoAndGeneration.OwnerUnlinked();
2713 if (tmp->mAnimationController) {
2714 tmp->mAnimationController->Unlink();
2717 tmp->mPendingTitleChangeEvent.Revoke();
2719 if (tmp->mCSSLoader) {
2720 tmp->mCSSLoader->DropDocumentReference();
2721 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2724 // We own only the items in mDOMMediaQueryLists that have listeners;
2725 // this reference is managed by their AddListener and RemoveListener
2726 // methods.
2727 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2728 MediaQueryList* next =
2729 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2730 mql->Disconnect();
2731 mql = next;
2734 tmp->mPendingFrameStaticClones.Clear();
2736 tmp->mInUnlinkOrDeletion = false;
2738 tmp->UnregisterFromMemoryReportingForDataDocument();
2740 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2741 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2742 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2743 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2745 nsresult Document::Init(nsIPrincipal* aPrincipal,
2746 nsIPrincipal* aPartitionedPrincipal) {
2747 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2748 return NS_ERROR_ALREADY_INITIALIZED;
2751 // Force initialization.
2752 mOnloadBlocker = new OnloadBlocker();
2753 mStyleImageLoader = new css::ImageLoader(this);
2755 mNodeInfoManager = new nsNodeInfoManager(this, aPrincipal);
2757 // mNodeInfo keeps NodeInfoManager alive!
2758 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2759 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2760 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2761 "Bad NodeType in aNodeInfo");
2763 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2765 mCSSLoader = new css::Loader(this);
2766 // Assume we're not quirky, until we know otherwise
2767 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2769 // If after creation the owner js global is not set for a document
2770 // we use the default compartment for this document, instead of creating
2771 // wrapper in some random compartment when the document is exposed to js
2772 // via some events.
2773 nsCOMPtr<nsIGlobalObject> global =
2774 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2775 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2776 mScopeObject = do_GetWeakReference(global);
2777 MOZ_ASSERT(mScopeObject);
2779 mScriptLoader = new dom::ScriptLoader(this);
2781 // we need to create a policy here so getting the policy within
2782 // ::Policy() can *always* return a non null policy
2783 mFeaturePolicy = new dom::FeaturePolicy(this);
2784 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2786 mStyleSet = MakeUnique<ServoStyleSet>(*this);
2788 if (aPrincipal) {
2789 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2792 RecomputeResistFingerprinting();
2794 return NS_OK;
2797 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2799 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2800 PropertyTable().RemoveAllPropertiesFor(aNode);
2803 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2804 nsCOMPtr<nsIURI> uri;
2805 nsCOMPtr<nsIPrincipal> principal;
2806 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2807 if (aChannel) {
2808 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2809 // nsScriptSecurityManager::GetChannelResultPrincipals.
2810 // Note: this should match the uri used for the OnNewURI call in
2811 // nsDocShell::CreateContentViewer.
2812 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2814 nsIScriptSecurityManager* securityManager =
2815 nsContentUtils::GetSecurityManager();
2816 if (securityManager) {
2817 securityManager->GetChannelResultPrincipals(
2818 aChannel, getter_AddRefs(principal),
2819 getter_AddRefs(partitionedPrincipal));
2823 bool equal = principal->Equals(partitionedPrincipal);
2825 principal = MaybeDowngradePrincipal(principal);
2826 if (equal) {
2827 partitionedPrincipal = principal;
2828 } else {
2829 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2832 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2834 // Note that, since mTiming does not change during a reset, the
2835 // navigationStart time remains unchanged and therefore any future new
2836 // timeline will have the same global clock time as the old one.
2837 mDocumentTimeline = nullptr;
2839 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
2840 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
2841 mDocumentBaseURI = baseURI.forget();
2842 mChromeXHRDocBaseURI = nullptr;
2846 mChannel = aChannel;
2847 RecomputeResistFingerprinting();
2850 void Document::DisconnectNodeTree() {
2851 // Delete references to sub-documents and kill the subdocument map,
2852 // if any. This is not strictly needed, but makes the node tree
2853 // teardown a bit faster.
2854 delete mSubDocuments;
2855 mSubDocuments = nullptr;
2857 bool oldVal = mInUnlinkOrDeletion;
2858 mInUnlinkOrDeletion = true;
2859 { // Scope for update
2860 MOZ_AUTO_DOC_UPDATE(this, true);
2862 // Destroy link map now so we don't waste time removing
2863 // links one by one
2864 DestroyElementMaps();
2866 // Invalidate cached array of child nodes
2867 InvalidateChildNodes();
2869 while (HasChildren()) {
2870 nsMutationGuard::DidMutate();
2871 nsCOMPtr<nsIContent> content = GetLastChild();
2872 nsIContent* previousSibling = content->GetPreviousSibling();
2873 DisconnectChild(content);
2874 if (content == mCachedRootElement) {
2875 // Immediately clear mCachedRootElement, now that it's been removed
2876 // from mChildren, so that GetRootElement() will stop returning this
2877 // now-stale value.
2878 mCachedRootElement = nullptr;
2880 MutationObservers::NotifyContentRemoved(this, content, previousSibling);
2881 content->UnbindFromTree();
2883 MOZ_ASSERT(!mCachedRootElement,
2884 "After removing all children, there should be no root elem");
2886 mInUnlinkOrDeletion = oldVal;
2889 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2890 nsIPrincipal* aPrincipal,
2891 nsIPrincipal* aPartitionedPrincipal) {
2892 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2893 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2895 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2896 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2898 mSecurityInfo = nullptr;
2900 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2901 if (!aLoadGroup || group != aLoadGroup) {
2902 mDocumentLoadGroup = nullptr;
2905 DisconnectNodeTree();
2907 // Reset our stylesheets
2908 ResetStylesheetsToURI(aURI);
2910 // Release the listener manager
2911 if (mListenerManager) {
2912 mListenerManager->Disconnect();
2913 mListenerManager = nullptr;
2916 // Release the stylesheets list.
2917 mDOMStyleSheets = nullptr;
2919 // Release our principal after tearing down the document, rather than before.
2920 // This ensures that, during teardown, the document and the dying window
2921 // (which already nulled out its document pointer and cached the principal)
2922 // have matching principals.
2923 SetPrincipals(nullptr, nullptr);
2925 // Clear the original URI so SetDocumentURI sets it.
2926 mOriginalURI = nullptr;
2928 SetDocumentURI(aURI);
2929 mChromeXHRDocURI = nullptr;
2930 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
2931 // mDocumentURI.
2932 mDocumentBaseURI = nullptr;
2933 mChromeXHRDocBaseURI = nullptr;
2935 // Check if the current document is the top-level DevTools document.
2936 // For inner DevTools frames, mIsDevToolsDocument will be set when
2937 // calling SetDocumentParent.
2938 if (aURI && aURI->SchemeIs("about") &&
2939 aURI->GetSpecOrDefault().EqualsLiteral("about:devtools-toolbox")) {
2940 mIsDevToolsDocument = true;
2943 if (aLoadGroup) {
2944 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
2945 // there was an assertion here that aLoadGroup was not null. This
2946 // is no longer valid: nsDocShell::SetDocument does not create a
2947 // load group, and it works just fine
2949 // XXXbz what does "just fine" mean exactly? And given that there
2950 // is no nsDocShell::SetDocument, what is this talking about?
2952 if (IsContentDocument()) {
2953 // Inform the associated request context about this load start so
2954 // any of its internal load progress flags gets reset.
2955 nsCOMPtr<nsIRequestContextService> rcsvc =
2956 net::RequestContextService::GetOrCreate();
2957 if (rcsvc) {
2958 nsCOMPtr<nsIRequestContext> rc;
2959 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
2960 if (rc) {
2961 rc->BeginLoad();
2967 mLastModified.Truncate();
2968 // XXXbz I guess we're assuming that the caller will either pass in
2969 // a channel with a useful type or call SetContentType?
2970 SetContentType(""_ns);
2971 mContentLanguage.Truncate();
2972 mBaseTarget.Truncate();
2974 mXMLDeclarationBits = 0;
2976 // Now get our new principal
2977 if (aPrincipal) {
2978 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2979 } else {
2980 nsIScriptSecurityManager* securityManager =
2981 nsContentUtils::GetSecurityManager();
2982 if (securityManager) {
2983 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
2985 if (!loadContext && aLoadGroup) {
2986 nsCOMPtr<nsIInterfaceRequestor> cbs;
2987 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
2988 loadContext = do_GetInterface(cbs);
2991 MOZ_ASSERT(loadContext,
2992 "must have a load context or pass in an explicit principal");
2994 nsCOMPtr<nsIPrincipal> principal;
2995 nsresult rv = securityManager->GetLoadContextContentPrincipal(
2996 mDocumentURI, loadContext, getter_AddRefs(principal));
2997 if (NS_SUCCEEDED(rv)) {
2998 SetPrincipals(principal, principal);
3003 if (mFontFaceSet) {
3004 mFontFaceSet->RefreshStandardFontLoadPrincipal();
3007 // Refresh the principal on the realm.
3008 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
3009 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
3013 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
3014 nsIPrincipal* aPrincipal) {
3015 if (!aPrincipal) {
3016 return nullptr;
3019 // We can't load a document with an expanded principal. If we're given one,
3020 // automatically downgrade it to the last principal it subsumes (which is the
3021 // extension principal, in the case of extension content scripts).
3022 auto* basePrin = BasePrincipal::Cast(aPrincipal);
3023 if (basePrin->Is<ExpandedPrincipal>()) {
3024 MOZ_DIAGNOSTIC_ASSERT(false,
3025 "Should never try to create a document with "
3026 "an expanded principal");
3028 auto* expanded = basePrin->As<ExpandedPrincipal>();
3029 return do_AddRef(expanded->AllowList().LastElement());
3032 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
3033 // We basically want the parent document here, but because this is very
3034 // early in the load, GetInProcessParentDocument() returns null, so we use
3035 // the docshell hierarchy to get this information instead.
3036 if (RefPtr<BrowsingContext> parent =
3037 mDocumentContainer->GetBrowsingContext()->GetParent()) {
3038 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
3039 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
3040 nsCOMPtr<nsIPrincipal> nullPrincipal =
3041 NullPrincipal::CreateWithoutOriginAttributes();
3042 return nullPrincipal.forget();
3046 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
3047 return principal.forget();
3050 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
3051 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3053 // lowest index first
3054 int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
3056 size_t count = mStyleSet->SheetCount(StyleOrigin::Author);
3057 size_t index = 0;
3058 for (; index < count; index++) {
3059 auto* sheet = mStyleSet->SheetAt(StyleOrigin::Author, index);
3060 MOZ_ASSERT(sheet);
3061 int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
3062 if (sheetDocIndex > newDocIndex) {
3063 break;
3066 // If the sheet is not owned by the document it can be an author
3067 // sheet registered at nsStyleSheetService or an additional author
3068 // sheet on the document, which means the new
3069 // doc sheet should end up before it.
3070 if (sheetDocIndex < 0) {
3071 if (sheetService) {
3072 auto& authorSheets = *sheetService->AuthorStyleSheets();
3073 if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
3074 break;
3077 if (sheet == GetFirstAdditionalAuthorSheet()) {
3078 break;
3083 return index;
3086 void Document::ResetStylesheetsToURI(nsIURI* aURI) {
3087 MOZ_ASSERT(aURI);
3089 ClearAdoptedStyleSheets();
3091 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
3092 for (auto& sheet : Reversed(aSheetList)) {
3093 sheet->ClearAssociatedDocumentOrShadowRoot();
3094 if (mStyleSetFilled) {
3095 mStyleSet->RemoveStyleSheet(*sheet);
3098 aSheetList.Clear();
3100 ClearSheetList(mStyleSheets);
3101 for (auto& sheets : mAdditionalSheets) {
3102 ClearSheetList(sheets);
3104 if (mStyleSetFilled) {
3105 if (auto* ss = nsStyleSheetService::GetInstance()) {
3106 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
3107 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
3108 if (sheet->IsApplicable()) {
3109 mStyleSet->RemoveStyleSheet(*sheet);
3115 // Now reset our inline style and attribute sheets.
3116 if (mAttributeStyles) {
3117 mAttributeStyles->Reset();
3118 mAttributeStyles->SetOwningDocument(this);
3119 } else {
3120 mAttributeStyles = new AttributeStyles(this);
3123 if (mStyleSetFilled) {
3124 FillStyleSetDocumentSheets();
3126 if (mStyleSet->StyleSheetsHaveChanged()) {
3127 ApplicableStylesChanged();
3132 static void AppendSheetsToStyleSet(
3133 ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
3134 for (StyleSheet* sheet : Reversed(aSheets)) {
3135 aStyleSet->AppendStyleSheet(*sheet);
3139 void Document::FillStyleSetUserAndUASheets() {
3140 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
3141 // ordering.
3143 // The document will fill in the document sheets when we create the presshell
3144 auto* cache = GlobalStyleSheetCache::Singleton();
3146 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3147 MOZ_ASSERT(sheetService,
3148 "should never be creating a StyleSet after the style sheet "
3149 "service has gone");
3151 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
3152 mStyleSet->AppendStyleSheet(*sheet);
3155 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
3156 : cache->GetUserContentSheet();
3157 if (sheet) {
3158 mStyleSet->AppendStyleSheet(*sheet);
3161 mStyleSet->AppendStyleSheet(*cache->UASheet());
3163 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
3164 mStyleSet->AppendStyleSheet(*cache->MathMLSheet());
3167 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
3168 mStyleSet->AppendStyleSheet(*cache->SVGSheet());
3171 mStyleSet->AppendStyleSheet(*cache->HTMLSheet());
3173 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
3174 mStyleSet->AppendStyleSheet(*cache->NoFramesSheet());
3177 mStyleSet->AppendStyleSheet(*cache->CounterStylesSheet());
3179 // Only load the full XUL sheet if we'll need it.
3180 if (LoadsFullXULStyleSheetUpFront()) {
3181 mStyleSet->AppendStyleSheet(*cache->XULSheet());
3184 mStyleSet->AppendStyleSheet(*cache->FormsSheet());
3185 mStyleSet->AppendStyleSheet(*cache->ScrollbarsSheet());
3187 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
3188 mStyleSet->AppendStyleSheet(*sheet);
3191 MOZ_ASSERT(!mQuirkSheetAdded);
3192 if (NeedsQuirksSheet()) {
3193 mStyleSet->AppendStyleSheet(*cache->QuirkSheet());
3194 mQuirkSheetAdded = true;
3198 void Document::FillStyleSet() {
3199 MOZ_ASSERT(!mStyleSetFilled);
3200 FillStyleSetUserAndUASheets();
3201 FillStyleSetDocumentSheets();
3202 mStyleSetFilled = true;
3205 void Document::RemoveContentEditableStyleSheets() {
3206 MOZ_ASSERT(IsHTMLOrXHTML());
3208 auto* cache = GlobalStyleSheetCache::Singleton();
3209 bool changed = false;
3210 if (mDesignModeSheetAdded) {
3211 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3212 mDesignModeSheetAdded = false;
3213 changed = true;
3215 if (mContentEditableSheetAdded) {
3216 mStyleSet->RemoveStyleSheet(*cache->ContentEditableSheet());
3217 mContentEditableSheetAdded = false;
3218 changed = true;
3220 if (changed) {
3221 MOZ_ASSERT(mStyleSetFilled);
3222 ApplicableStylesChanged();
3226 void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
3227 MOZ_ASSERT(IsHTMLOrXHTML());
3228 MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
3229 "Caller should ensure we're being rendered");
3231 auto* cache = GlobalStyleSheetCache::Singleton();
3232 bool changed = false;
3233 if (!mContentEditableSheetAdded) {
3234 mStyleSet->AppendStyleSheet(*cache->ContentEditableSheet());
3235 mContentEditableSheetAdded = true;
3236 changed = true;
3238 if (mDesignModeSheetAdded != aDesignMode) {
3239 if (mDesignModeSheetAdded) {
3240 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3241 } else {
3242 mStyleSet->AppendStyleSheet(*cache->DesignModeSheet());
3244 mDesignModeSheetAdded = !mDesignModeSheetAdded;
3245 changed = true;
3247 if (changed) {
3248 ApplicableStylesChanged();
3252 void Document::FillStyleSetDocumentSheets() {
3253 MOZ_ASSERT(mStyleSet->SheetCount(StyleOrigin::Author) == 0,
3254 "Style set already has document sheets?");
3256 // Sheets are added in reverse order to avoid worst-case time complexity when
3257 // looking up the index of a sheet.
3259 // Note that usually appending is faster (rebuilds less stuff in the
3260 // styleset), but in this case it doesn't matter since we're filling the
3261 // styleset from scratch anyway.
3262 for (StyleSheet* sheet : Reversed(mStyleSheets)) {
3263 if (sheet->IsApplicable()) {
3264 mStyleSet->AddDocStyleSheet(*sheet);
3268 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
3269 if (aSheet.IsApplicable()) {
3270 mStyleSet->AddDocStyleSheet(aSheet);
3274 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3275 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
3276 mStyleSet->AppendStyleSheet(*sheet);
3279 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAgentSheet]);
3280 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eUserSheet]);
3281 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAuthorSheet]);
3284 void Document::CompatibilityModeChanged() {
3285 MOZ_ASSERT(IsHTMLOrXHTML());
3286 CSSLoader()->SetCompatibilityMode(mCompatMode);
3287 mStyleSet->CompatibilityModeChanged();
3288 if (PresShell* presShell = GetPresShell()) {
3289 // Selectors may have become case-sensitive / case-insensitive, the stylist
3290 // has already performed the relevant invalidation.
3291 presShell->EnsureStyleFlush();
3293 if (!mStyleSetFilled) {
3294 MOZ_ASSERT(!mQuirkSheetAdded);
3295 return;
3297 if (mQuirkSheetAdded == NeedsQuirksSheet()) {
3298 return;
3300 auto* cache = GlobalStyleSheetCache::Singleton();
3301 StyleSheet* sheet = cache->QuirkSheet();
3302 if (mQuirkSheetAdded) {
3303 mStyleSet->RemoveStyleSheet(*sheet);
3304 } else {
3305 mStyleSet->AppendStyleSheet(*sheet);
3307 mQuirkSheetAdded = !mQuirkSheetAdded;
3308 ApplicableStylesChanged();
3311 void Document::SetCompatibilityMode(nsCompatibility aMode) {
3312 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
3313 "Bad compat mode for XHTML document!");
3315 if (mCompatMode == aMode) {
3316 return;
3318 mCompatMode = aMode;
3319 CompatibilityModeChanged();
3320 // Trigger recomputation of the nsViewportInfo the next time it's queried.
3321 mViewportType = Unknown;
3324 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
3325 uint32_t aSandboxFlags,
3326 nsIChannel* aChannel) {
3327 // If the document permits allow-top-navigation and
3328 // allow-top-navigation-by-user-activation this will permit all top
3329 // navigation.
3330 if (aSandboxFlags != SANDBOXED_NONE &&
3331 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
3332 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
3333 nsContentUtils::ReportToConsole(
3334 nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
3335 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
3336 "BothAllowTopNavigationAndUserActivationPresent");
3338 // If the document is sandboxed (via the HTML5 iframe sandbox
3339 // attribute) and both the allow-scripts and allow-same-origin
3340 // keywords are supplied, the sandboxed document can call into its
3341 // parent document and remove its sandboxing entirely - we print a
3342 // warning to the web console in this case.
3343 if (aSandboxFlags & SANDBOXED_NAVIGATION &&
3344 !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
3345 !(aSandboxFlags & SANDBOXED_ORIGIN)) {
3346 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
3347 MOZ_ASSERT(bc->IsInProcess());
3349 RefPtr<BrowsingContext> parentBC = bc->GetParent();
3350 if (!parentBC || !parentBC->IsInProcess()) {
3351 // If parent document is not in process, then by construction it
3352 // cannot be same origin.
3353 return;
3356 // Don't warn if our parent is not the top-level document.
3357 if (!parentBC->IsTopContent()) {
3358 return;
3361 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
3362 MOZ_ASSERT(parentDocShell);
3364 nsCOMPtr<nsIChannel> parentChannel;
3365 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
3366 if (!parentChannel) {
3367 return;
3369 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
3370 if (NS_FAILED(rv)) {
3371 return;
3374 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
3375 nsCOMPtr<nsIURI> iframeUri;
3376 parentChannel->GetURI(getter_AddRefs(iframeUri));
3377 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3378 "Iframe Sandbox"_ns, parentDocument,
3379 nsContentUtils::eSECURITY_PROPERTIES,
3380 "BothAllowScriptsAndSameOriginPresent",
3381 nsTArray<nsString>(), iframeUri);
3385 bool Document::IsSynthesized() {
3386 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
3387 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
3390 // static
3391 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
3392 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
3393 return principal && (principal->IsSystemPrincipal() ||
3394 principal->GetIsAddonOrExpandedAddonPrincipal());
3397 static void CheckIsBadPolicy(nsILoadInfo::CrossOriginOpenerPolicy aPolicy,
3398 BrowsingContext* aContext, nsIChannel* aChannel) {
3399 #if defined(EARLY_BETA_OR_EARLIER)
3400 auto requireCORP =
3401 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
3403 if (aContext->GetOpenerPolicy() == aPolicy ||
3404 (aContext->GetOpenerPolicy() != requireCORP && aPolicy != requireCORP)) {
3405 return;
3408 nsCOMPtr<nsIURI> uri;
3409 bool hasURI = NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(uri)));
3411 bool isViewSource = hasURI && uri->SchemeIs("view-source");
3413 nsCString contentType;
3414 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3415 bool isPDFJS = bag &&
3416 NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3417 contentType)) &&
3418 contentType.EqualsLiteral(APPLICATION_PDF);
3420 MOZ_DIAGNOSTIC_ASSERT(!isViewSource,
3421 "Bug 1834864: Assert due to view-source.");
3422 MOZ_DIAGNOSTIC_ASSERT(!isPDFJS, "Bug 1834864: Assert due to pdfjs.");
3423 MOZ_DIAGNOSTIC_ASSERT(aPolicy == requireCORP,
3424 "Assert due to clearing REQUIRE_CORP.");
3425 MOZ_DIAGNOSTIC_ASSERT(aContext->GetOpenerPolicy() == requireCORP,
3426 "Assert due to setting REQUIRE_CORP.");
3427 #endif // defined(EARLY_BETA_OR_EARLIER)
3430 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
3431 nsILoadGroup* aLoadGroup,
3432 nsISupports* aContainer,
3433 nsIStreamListener** aDocListener,
3434 bool aReset) {
3435 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
3436 nsCOMPtr<nsIURI> uri;
3437 aChannel->GetURI(getter_AddRefs(uri));
3438 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
3439 ("DOCUMENT %p StartDocumentLoad %s", this,
3440 uri ? uri->GetSpecOrDefault().get() : ""));
3443 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
3444 "Bad readyState");
3445 SetReadyStateInternal(READYSTATE_LOADING);
3447 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
3448 mLoadedAsData = true;
3449 SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true);
3450 // We need to disable script & style loading in this case.
3451 // We leave them disabled even in EndLoad(), and let anyone
3452 // who puts the document on display to worry about enabling.
3454 // Do not load/process scripts when loading as data
3455 ScriptLoader()->SetEnabled(false);
3457 // styles
3458 CSSLoader()->SetEnabled(
3459 false); // Do not load/process styles when loading as data
3460 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
3461 // Allow CSS, but not scripts
3462 ScriptLoader()->SetEnabled(false);
3465 mMayStartLayout = false;
3466 MOZ_ASSERT(!mReadyForIdle,
3467 "We should never hit DOMContentLoaded before this point");
3469 if (aReset) {
3470 Reset(aChannel, aLoadGroup);
3473 nsAutoCString contentType;
3474 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3475 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3476 contentType))) ||
3477 NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
3478 // XXX this is only necessary for viewsource:
3479 nsACString::const_iterator start, end, semicolon;
3480 contentType.BeginReading(start);
3481 contentType.EndReading(end);
3482 semicolon = start;
3483 FindCharInReadable(';', semicolon, end);
3484 SetContentType(Substring(start, semicolon));
3487 RetrieveRelevantHeaders(aChannel);
3489 mChannel = aChannel;
3490 RecomputeResistFingerprinting();
3491 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
3492 if (inStrmChan) {
3493 bool isSrcdocChannel;
3494 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
3495 if (isSrcdocChannel) {
3496 mIsSrcdocDocument = true;
3500 if (mChannel) {
3501 nsLoadFlags loadFlags;
3502 mChannel->GetLoadFlags(&loadFlags);
3503 bool isDocument = false;
3504 mChannel->GetIsDocument(&isDocument);
3505 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
3506 IsSynthesized() && XRE_IsContentProcess()) {
3507 ContentChild::UpdateCookieStatus(mChannel);
3510 // Store the security info for future use.
3511 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
3514 // If this document is being loaded by a docshell, copy its sandbox flags
3515 // to the document, and store the fullscreen enabled flag. These are
3516 // immutable after being set here.
3517 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
3519 // If this is an error page, don't inherit sandbox flags
3520 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3521 if (docShell && !loadInfo->GetLoadErrorPage()) {
3522 mSandboxFlags = loadInfo->GetSandboxFlags();
3523 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
3526 // Set the opener policy for the top level content document.
3527 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
3528 nsILoadInfo::CrossOriginOpenerPolicy policy =
3529 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
3530 if (IsTopLevelContentDocument() && httpChan &&
3531 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
3532 docShell->GetBrowsingContext()) {
3533 CheckIsBadPolicy(policy, docShell->GetBrowsingContext(), aChannel);
3535 // Setting the opener policy on a discarded context has no effect.
3536 Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
3539 // The CSP directives upgrade-insecure-requests as well as
3540 // block-all-mixed-content not only apply to the toplevel document,
3541 // but also to nested documents. The loadInfo of a subdocument
3542 // load already holds the correct flag, so let's just set it here
3543 // on the document. Please note that we set the appropriate preload
3544 // bits just for the sake of completeness here, because the preloader
3545 // does not reach into subdocuments.
3546 mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
3547 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3548 mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
3549 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3551 // HTTPS-Only Mode flags
3552 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
3553 // sub-resources and sub-documents.
3554 mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
3556 nsresult rv = InitReferrerInfo(aChannel);
3557 NS_ENSURE_SUCCESS(rv, rv);
3559 rv = InitCOEP(aChannel);
3560 NS_ENSURE_SUCCESS(rv, rv);
3562 // Check CSP navigate-to
3563 // We need to enforce the CSP of the document that initiated the load,
3564 // which is the CSP to inherit.
3565 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3566 if (cspToInherit) {
3567 bool allowsNavigateTo = false;
3568 rv = cspToInherit->GetAllowsNavigateTo(
3569 mDocumentURI, loadInfo->GetIsFormSubmission(),
3570 !loadInfo->RedirectChain().IsEmpty(), /* aWasRedirected */
3571 true, /* aEnforceWhitelist */
3572 &allowsNavigateTo);
3573 NS_ENSURE_SUCCESS(rv, rv);
3575 if (!allowsNavigateTo) {
3576 aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
3577 return NS_OK;
3581 rv = InitCSP(aChannel);
3582 NS_ENSURE_SUCCESS(rv, rv);
3584 // Initialize FeaturePolicy
3585 rv = InitFeaturePolicy(aChannel);
3586 NS_ENSURE_SUCCESS(rv, rv);
3588 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3589 NS_ENSURE_SUCCESS(rv, rv);
3591 // Generally XFO and CSP frame-ancestors is handled within
3592 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3593 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3594 nsContentPolicyType internalContentType =
3595 loadInfo->InternalContentPolicyType();
3596 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3597 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3598 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3600 nsresult status;
3601 aChannel->GetStatus(&status);
3602 if (status == NS_ERROR_XFO_VIOLATION) {
3603 // stop! ERROR page!
3604 // But before we have to reset the principal of the document
3605 // because the onload() event fires before the error page
3606 // is displayed and we do not want the enclosing document
3607 // to access the contentDocument.
3608 RefPtr<NullPrincipal> nullPrincipal =
3609 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3610 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3611 // and also GetInnerWindow() is still null at this point, before
3612 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3613 // within Document.cpp
3614 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3615 SetPrincipals(nullPrincipal, nullPrincipal);
3619 return NS_OK;
3622 void Document::SetLoadedAsData(bool aLoadedAsData,
3623 bool aConsiderForMemoryReporting) {
3624 mLoadedAsData = aLoadedAsData;
3625 if (aConsiderForMemoryReporting) {
3626 nsIGlobalObject* global = GetScopeObject();
3627 if (global) {
3628 if (nsPIDOMWindowInner* window = global->AsInnerWindow()) {
3629 nsGlobalWindowInner::Cast(window)
3630 ->RegisterDataDocumentForMemoryReporting(this);
3636 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3638 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
3640 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3641 return mPreloadCSP;
3644 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3645 mPreloadCSP = aPreloadCSP;
3648 void Document::GetCspJSON(nsString& aJSON) {
3649 aJSON.Truncate();
3651 if (!mCSP) {
3652 dom::CSPPolicies jsonPolicies;
3653 jsonPolicies.ToJSON(aJSON);
3654 return;
3656 mCSP->ToJSON(aJSON);
3659 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3660 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3661 nsAutoString messageTag;
3662 aMessages[i]->GetTag(messageTag);
3664 nsAutoString category;
3665 aMessages[i]->GetCategory(category);
3667 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3668 NS_ConvertUTF16toUTF8(category), this,
3669 nsContentUtils::eSECURITY_PROPERTIES,
3670 NS_ConvertUTF16toUTF8(messageTag).get());
3674 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3675 nsresult rv = NS_OK;
3676 if (!aSpeculative) {
3677 // 1) apply settings from regular CSP
3678 if (mCSP) {
3679 // Set up 'block-all-mixed-content' if not already inherited
3680 // from the parent context or set by any other CSP.
3681 if (!mBlockAllMixedContent) {
3682 bool block = false;
3683 rv = mCSP->GetBlockAllMixedContent(&block);
3684 NS_ENSURE_SUCCESS_VOID(rv);
3685 mBlockAllMixedContent = block;
3687 if (!mBlockAllMixedContentPreloads) {
3688 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3691 // Set up 'upgrade-insecure-requests' if not already inherited
3692 // from the parent context or set by any other CSP.
3693 if (!mUpgradeInsecureRequests) {
3694 bool upgrade = false;
3695 rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
3696 NS_ENSURE_SUCCESS_VOID(rv);
3697 mUpgradeInsecureRequests = upgrade;
3699 if (!mUpgradeInsecurePreloads) {
3700 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3702 // Update csp settings in the parent process
3703 if (auto* wgc = GetWindowGlobalChild()) {
3704 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3705 mUpgradeInsecureRequests);
3708 return;
3711 // 2) apply settings from speculative csp
3712 if (mPreloadCSP) {
3713 if (!mBlockAllMixedContentPreloads) {
3714 bool block = false;
3715 rv = mPreloadCSP->GetBlockAllMixedContent(&block);
3716 NS_ENSURE_SUCCESS_VOID(rv);
3717 mBlockAllMixedContent = block;
3719 if (!mUpgradeInsecurePreloads) {
3720 bool upgrade = false;
3721 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
3722 NS_ENSURE_SUCCESS_VOID(rv);
3723 mUpgradeInsecurePreloads = upgrade;
3728 nsresult Document::InitCSP(nsIChannel* aChannel) {
3729 MOZ_ASSERT(!mScriptGlobalObject,
3730 "CSP must be initialized before mScriptGlobalObject is set!");
3732 // If this is a data document - no need to set CSP.
3733 if (mLoadedAsData) {
3734 return NS_OK;
3737 // If this is an image, no need to set a CSP. Otherwise SVG images
3738 // served with a CSP might block internally applied inline styles.
3739 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3740 if (loadInfo->GetExternalContentPolicyType() ==
3741 ExtContentPolicy::TYPE_IMAGE ||
3742 loadInfo->GetExternalContentPolicyType() ==
3743 ExtContentPolicy::TYPE_IMAGESET) {
3744 return NS_OK;
3747 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3749 // If there is a CSP that needs to be inherited from whatever
3750 // global is considered the client of the document fetch then
3751 // we query it here from the loadinfo in case the newly created
3752 // document needs to inherit the CSP. See:
3753 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3754 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
3755 if (inheritedCSP) {
3756 mCSP = loadInfo->GetCspToInherit();
3759 // If there is no CSP to inherit, then we create a new CSP here so
3760 // that history entries always have the right reference in case a
3761 // Meta CSP gets dynamically added after the history entry has
3762 // already been created.
3763 if (!mCSP) {
3764 mCSP = new nsCSPContext();
3767 // Always overwrite the requesting context of the CSP so that any new
3768 // 'self' keyword added to an inherited CSP translates correctly.
3769 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3770 if (NS_WARN_IF(NS_FAILED(rv))) {
3771 return rv;
3774 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3776 nsCOMPtr<nsIHttpChannel> httpChannel;
3777 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3778 if (NS_WARN_IF(NS_FAILED(rv))) {
3779 return rv;
3782 if (httpChannel) {
3783 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3784 tCspHeaderValue);
3786 Unused << httpChannel->GetResponseHeader(
3787 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3789 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3790 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3792 // Check if this is a document from a WebExtension.
3793 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3794 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3796 // If there's no CSP to apply, go ahead and return early
3797 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
3798 cspROHeaderValue.IsEmpty()) {
3799 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3800 nsCOMPtr<nsIURI> chanURI;
3801 aChannel->GetURI(getter_AddRefs(chanURI));
3802 nsAutoCString aspec;
3803 chanURI->GetAsciiSpec(aspec);
3804 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3805 ("no CSP for document, %s", aspec.get()));
3808 return NS_OK;
3811 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3812 ("Document is an add-on or CSP header specified %p", this));
3814 // ----- if the doc is an addon, apply its CSP.
3815 if (addonPolicy) {
3816 mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
3818 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3819 // Bug 1548468: Move CSP off ExpandedPrincipal
3820 // Currently the LoadInfo holds the source of truth for every resource load
3821 // because LoadInfo::GetCsp() queries the CSP from an ExpandedPrincipal
3822 // (and not from the Client) if the load was triggered by an extension.
3823 auto* basePrin = BasePrincipal::Cast(principal);
3824 if (basePrin->Is<ExpandedPrincipal>()) {
3825 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3829 // ----- if there's a full-strength CSP header, apply it.
3830 if (!cspHeaderValue.IsEmpty()) {
3831 mHasCSPDeliveredThroughHeader = true;
3832 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3833 NS_ENSURE_SUCCESS(rv, rv);
3836 // ----- if there's a report-only CSP header, apply it.
3837 if (!cspROHeaderValue.IsEmpty()) {
3838 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3839 NS_ENSURE_SUCCESS(rv, rv);
3842 // ----- Enforce sandbox policy if supplied in CSP header
3843 // The document may already have some sandbox flags set (e.g. if the document
3844 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3845 // directive, intersect the CSP sandbox flags with the existing flags. This
3846 // corresponds to the _least_ permissive policy.
3847 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3848 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3849 NS_ENSURE_SUCCESS(rv, rv);
3851 // Probably the iframe sandbox attribute already caused the creation of a
3852 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3853 // and no one has been created yet.
3854 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3855 !(mSandboxFlags & SANDBOXED_ORIGIN);
3857 mSandboxFlags |= cspSandboxFlags;
3859 if (needNewNullPrincipal) {
3860 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3861 // Skip setting the content blocking allowlist principal to NullPrincipal.
3862 // The principal is only used to enable/disable trackingprotection via
3863 // permission and can be shared with the top level sandboxed site.
3864 // See Bug 1654546.
3865 SetPrincipals(principal, principal);
3868 ApplySettingsFromCSP(false);
3869 return NS_OK;
3872 static Document* GetInProcessParentDocumentFrom(BrowsingContext* aContext) {
3873 BrowsingContext* parentContext = aContext->GetParent();
3874 if (!parentContext) {
3875 return nullptr;
3878 WindowContext* windowContext = parentContext->GetCurrentWindowContext();
3879 if (!windowContext) {
3880 return nullptr;
3883 return windowContext->GetDocument();
3886 already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
3887 BrowsingContext* browsingContext = GetBrowsingContext();
3888 if (!browsingContext) {
3889 return nullptr;
3891 if (!browsingContext->IsContentSubframe()) {
3892 return nullptr;
3895 HTMLIFrameElement* iframe =
3896 HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement());
3897 if (iframe) {
3898 return do_AddRef(iframe->FeaturePolicy());
3901 if (XRE_IsParentProcess()) {
3902 return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy());
3905 if (Document* parentDocument =
3906 GetInProcessParentDocumentFrom(browsingContext)) {
3907 return do_AddRef(parentDocument->FeaturePolicy());
3910 WindowContext* windowContext = browsingContext->GetCurrentWindowContext();
3911 if (!windowContext) {
3912 return nullptr;
3915 WindowGlobalChild* child = windowContext->GetWindowGlobalChild();
3916 if (!child) {
3917 return nullptr;
3920 return do_AddRef(child->GetContainerFeaturePolicy());
3923 void Document::InitFeaturePolicy() {
3924 MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created");
3926 mFeaturePolicy->ResetDeclaredPolicy();
3928 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
3930 RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
3931 if (parentPolicy) {
3932 // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
3933 mFeaturePolicy->InheritPolicy(parentPolicy);
3934 mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
3938 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
3939 InitFeaturePolicy();
3941 // We don't want to parse the http Feature-Policy header if this pref is off.
3942 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
3943 return NS_OK;
3946 nsCOMPtr<nsIHttpChannel> httpChannel;
3947 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3948 if (NS_WARN_IF(NS_FAILED(rv))) {
3949 return rv;
3952 if (!httpChannel) {
3953 return NS_OK;
3956 // query the policy from the header
3957 nsAutoCString value;
3958 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
3959 if (NS_SUCCEEDED(rv)) {
3960 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
3961 NodePrincipal(), nullptr);
3964 return NS_OK;
3967 void Document::EnsureNotEnteringAndExitFullscreen() {
3968 Document::ClearPendingFullscreenRequests(this);
3969 if (GetFullscreenElement()) {
3970 Document::AsyncExitFullscreen(this);
3974 void Document::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
3975 mReferrerInfo = aReferrerInfo;
3976 mCachedReferrerInfoForInternalCSSAndSVGResources = nullptr;
3977 mCachedURLData = nullptr;
3980 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
3981 MOZ_ASSERT(mReferrerInfo);
3982 MOZ_ASSERT(mPreloadReferrerInfo);
3984 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
3985 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
3986 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
3987 // they have an opaque origin.
3988 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
3989 if (BrowsingContext* bc = GetBrowsingContext()) {
3990 // At this point the document is not fully created and mParentDocument has
3991 // not been set yet,
3992 Document* parentDoc = bc->GetEmbedderElement()
3993 ? bc->GetEmbedderElement()->OwnerDoc()
3994 : nullptr;
3995 if (parentDoc) {
3996 SetReferrerInfo(parentDoc->GetReferrerInfo());
3997 mPreloadReferrerInfo = mReferrerInfo;
3998 return NS_OK;
4001 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
4002 "srcdoc without null principal as toplevel!");
4006 nsCOMPtr<nsIHttpChannel> httpChannel;
4007 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4008 if (NS_WARN_IF(NS_FAILED(rv))) {
4009 return rv;
4012 if (!httpChannel) {
4013 return NS_OK;
4016 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo()) {
4017 SetReferrerInfo(referrerInfo);
4020 // Override policy if we get one from Referrerr-Policy header
4021 mozilla::dom::ReferrerPolicy policy =
4022 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
4023 nsCOMPtr<nsIReferrerInfo> clone =
4024 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
4025 ->CloneWithNewPolicy(policy);
4026 SetReferrerInfo(clone);
4027 mPreloadReferrerInfo = mReferrerInfo;
4028 return NS_OK;
4031 nsresult Document::InitCOEP(nsIChannel* aChannel) {
4032 nsCOMPtr<nsIHttpChannel> httpChannel;
4033 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4034 if (NS_FAILED(rv)) {
4035 return NS_OK;
4038 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
4040 if (!intChannel) {
4041 return NS_OK;
4044 nsILoadInfo::CrossOriginEmbedderPolicy policy =
4045 nsILoadInfo::EMBEDDER_POLICY_NULL;
4046 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(
4047 mTrials.IsEnabled(OriginTrial::CoepCredentialless), &policy))) {
4048 mEmbedderPolicy = Some(policy);
4051 return NS_OK;
4054 void Document::StopDocumentLoad() {
4055 if (mParser) {
4056 mParserAborted = true;
4057 mParser->Terminate();
4061 void Document::SetDocumentURI(nsIURI* aURI) {
4062 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
4063 mDocumentURI = aURI;
4064 nsIURI* newBase = GetDocBaseURI();
4066 mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI);
4068 bool equalBases = false;
4069 // Changing just the ref of a URI does not change how relative URIs would
4070 // resolve wrt to it, so we can treat the bases as equal as long as they're
4071 // equal ignoring the ref.
4072 if (oldBase && newBase) {
4073 oldBase->EqualsExceptRef(newBase, &equalBases);
4074 } else {
4075 equalBases = !oldBase && !newBase;
4078 // If this is the first time we're setting the document's URI, set the
4079 // document's original URI.
4080 if (!mOriginalURI) mOriginalURI = mDocumentURI;
4082 // If changing the document's URI changed the base URI of the document, we
4083 // need to refresh the hrefs of all the links on the page.
4084 if (!equalBases) {
4085 mCachedURLData = nullptr;
4086 RefreshLinkHrefs();
4089 // Recalculate our base domain
4090 mBaseDomain.Truncate();
4091 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
4092 if (thirdPartyUtil) {
4093 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
4096 // Tell our WindowGlobalParent that the document's URI has been changed.
4097 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
4098 wgc->SetDocumentURI(mDocumentURI);
4102 static void GetFormattedTimeString(PRTime aTime,
4103 nsAString& aFormattedTimeString) {
4104 PRExplodedTime prtime;
4105 PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
4106 // "MM/DD/YYYY hh:mm:ss"
4107 char formatedTime[24];
4108 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
4109 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
4110 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
4111 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
4112 } else {
4113 // If we for whatever reason failed to find the last modified time
4114 // (or even the current time), fall back to what NS4.x returned.
4115 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
4119 void Document::GetLastModified(nsAString& aLastModified) const {
4120 if (!mLastModified.IsEmpty()) {
4121 aLastModified.Assign(mLastModified);
4122 } else {
4123 GetFormattedTimeString(PR_Now(), aLastModified);
4127 static void IncrementExpandoGeneration(Document& aDoc) {
4128 ++aDoc.mExpandoAndGeneration.generation;
4131 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
4132 MOZ_ASSERT(
4133 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
4134 "Only put elements that need to be exposed as document['name'] in "
4135 "the named table.");
4137 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
4139 // Null for out-of-memory
4140 if (entry) {
4141 if (!entry->HasNameElement() &&
4142 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4143 IncrementExpandoGeneration(*this);
4145 entry->AddNameElement(this, aElement);
4149 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
4150 // Speed up document teardown
4151 if (mIdentifierMap.Count() == 0) return;
4153 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
4154 if (!entry) // Could be false if the element was anonymous, hence never added
4155 return;
4157 entry->RemoveNameElement(aElement);
4158 if (!entry->HasNameElement() &&
4159 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4160 IncrementExpandoGeneration(*this);
4164 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
4165 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
4167 if (entry) { /* True except on OOM */
4168 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4169 !entry->HasNameElement() &&
4170 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4171 IncrementExpandoGeneration(*this);
4173 entry->AddIdElement(aElement);
4177 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
4178 NS_ASSERTION(aId, "huhwhatnow?");
4180 // Speed up document teardown
4181 if (mIdentifierMap.Count() == 0) {
4182 return;
4185 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
4186 if (!entry) // Can be null for XML elements with changing ids.
4187 return;
4189 entry->RemoveIdElement(aElement);
4190 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4191 !entry->HasNameElement() &&
4192 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4193 IncrementExpandoGeneration(*this);
4195 if (entry->IsEmpty()) {
4196 mIdentifierMap.RemoveEntry(entry);
4200 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
4201 bool aPreload) {
4202 ReferrerPolicyEnum policy =
4203 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
4204 // The empty string "" corresponds to no referrer policy, causing a fallback
4205 // to a referrer policy defined elsewhere.
4206 if (policy == ReferrerPolicy::_empty) {
4207 return;
4210 MOZ_ASSERT(mReferrerInfo);
4211 MOZ_ASSERT(mPreloadReferrerInfo);
4213 if (aPreload) {
4214 mPreloadReferrerInfo =
4215 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
4216 ->CloneWithNewPolicy(policy);
4217 } else {
4218 nsCOMPtr<nsIReferrerInfo> clone =
4219 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
4220 ->CloneWithNewPolicy(policy);
4221 SetReferrerInfo(clone);
4225 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
4226 nsIPrincipal* aNewPartitionedPrincipal) {
4227 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
4228 if (aNewPrincipal && mAllowDNSPrefetch &&
4229 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
4230 if (aNewPrincipal->SchemeIs("https")) {
4231 mAllowDNSPrefetch = false;
4235 mCSSLoader->DeregisterFromSheetCache();
4237 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
4238 mPartitionedPrincipal = aNewPartitionedPrincipal;
4240 mCachedURLData = nullptr;
4242 mCSSLoader->RegisterInSheetCache();
4244 #ifdef DEBUG
4245 // Validate that the docgroup is set correctly by calling its getter and
4246 // triggering its sanity check.
4248 // If we're setting the principal to null, we don't want to perform the check,
4249 // as the document is entering an intermediate state where it does not have a
4250 // principal. It will be given another real principal shortly which we will
4251 // check. It's not unsafe to have a document which has a null principal in the
4252 // same docgroup as another document, so this should not be a problem.
4253 if (aNewPrincipal) {
4254 GetDocGroup();
4256 #endif
4259 #ifdef DEBUG
4260 void Document::AssertDocGroupMatchesKey() const {
4261 // Sanity check that we have an up-to-date and accurate docgroup
4262 // We only check if the principal when we can get the browsing context.
4264 // Note that we can be invoked during cycle collection, so we need to handle
4265 // the browsingcontext being partially unlinked - normally you shouldn't
4266 // null-check `Group()` as it shouldn't return nullptr.
4267 if (!GetBrowsingContext() || !GetBrowsingContext()->Group()) {
4268 return;
4271 if (mDocGroup && mDocGroup->GetBrowsingContextGroup()) {
4272 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
4273 GetBrowsingContext()->Group());
4275 // GetKey() can fail, e.g. after the TLD service has shut down.
4276 nsAutoCString docGroupKey;
4277 nsresult rv = mozilla::dom::DocGroup::GetKey(
4278 NodePrincipal(),
4279 GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated(),
4280 docGroupKey);
4281 if (NS_SUCCEEDED(rv)) {
4282 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
4286 #endif
4288 nsresult Document::Dispatch(TaskCategory aCategory,
4289 already_AddRefed<nsIRunnable>&& aRunnable) {
4290 // Note that this method may be called off the main thread.
4291 if (mDocGroup) {
4292 return mDocGroup->Dispatch(aCategory, std::move(aRunnable));
4294 return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
4297 nsISerialEventTarget* Document::EventTargetFor(TaskCategory aCategory) const {
4298 if (mDocGroup) {
4299 return mDocGroup->EventTargetFor(aCategory);
4301 return DispatcherTrait::EventTargetFor(aCategory);
4304 AbstractThread* Document::AbstractMainThreadFor(
4305 mozilla::TaskCategory aCategory) {
4306 MOZ_ASSERT(NS_IsMainThread());
4307 if (mDocGroup) {
4308 return mDocGroup->AbstractMainThreadFor(aCategory);
4310 return DispatcherTrait::AbstractMainThreadFor(aCategory);
4313 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
4314 bool aIsTracking) {
4315 if (aIsTracking) {
4316 mTrackingScripts.Insert(aURL);
4317 } else {
4318 MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
4322 bool Document::IsScriptTracking(JSContext* aCx) const {
4323 JS::AutoFilename filename;
4324 if (!JS::DescribeScriptedCaller(aCx, &filename)) {
4325 return false;
4327 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
4330 void Document::GetContentType(nsAString& aContentType) {
4331 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
4334 void Document::SetContentType(const nsACString& aContentType) {
4335 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
4336 aContentType.EqualsLiteral("application/xhtml+xml")) {
4337 mDefaultElementType = kNameSpaceID_XHTML;
4340 mCachedEncoder = nullptr;
4341 mContentType = aContentType;
4344 bool Document::GetAllowPlugins() {
4345 // First, we ask our docshell if it allows plugins.
4346 auto* browsingContext = GetBrowsingContext();
4348 if (browsingContext) {
4349 if (!browsingContext->GetAllowPlugins()) {
4350 return false;
4353 // If the docshell allows plugins, we check whether
4354 // we are sandboxed and plugins should not be allowed.
4355 if (mSandboxFlags & SANDBOXED_PLUGINS) {
4356 return false;
4360 return true;
4363 bool Document::HasPendingInitialTranslation() {
4364 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
4367 bool Document::HasPendingL10nMutations() const {
4368 return mDocumentL10n && mDocumentL10n->HasPendingMutations();
4371 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
4372 JS::Rooted<JSObject*> object(aCx, aObject);
4373 nsCOMPtr<nsIPrincipal> callerPrincipal =
4374 nsContentUtils::SubjectPrincipal(aCx);
4375 nsGlobalWindowInner* win = xpc::WindowOrNull(object);
4376 bool allowed = false;
4377 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
4378 &allowed);
4379 return allowed;
4382 void Document::LocalizationLinkAdded(Element* aLinkElement) {
4383 if (!AllowsL10n()) {
4384 return;
4387 nsAutoString href;
4388 aLinkElement->GetAttr(nsGkAtoms::href, href);
4390 if (!mDocumentL10n) {
4391 Element* elem = GetDocumentElement();
4392 MOZ_DIAGNOSTIC_ASSERT(elem);
4394 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
4395 mDocumentL10n = DocumentL10n::Create(this, isSync);
4396 if (NS_WARN_IF(!mDocumentL10n)) {
4397 return;
4401 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
4403 if (mReadyState >= READYSTATE_INTERACTIVE) {
4404 nsContentUtils::AddScriptRunner(NewRunnableMethod(
4405 "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
4406 &DocumentL10n::TriggerInitialTranslation));
4407 } else {
4408 if (!mDocumentL10n->mBlockingLayout) {
4409 // Our initial translation is going to block layout start. Make sure
4410 // we don't fire the load event until after that stops happening and
4411 // layout has a chance to start.
4412 BlockOnload();
4413 mDocumentL10n->mBlockingLayout = true;
4418 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
4419 if (!AllowsL10n()) {
4420 return;
4423 if (mDocumentL10n) {
4424 nsAutoString href;
4425 aLinkElement->GetAttr(nsGkAtoms::href, href);
4426 uint32_t remaining =
4427 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
4428 if (remaining == 0) {
4429 if (mDocumentL10n->mBlockingLayout) {
4430 mDocumentL10n->mBlockingLayout = false;
4431 UnblockOnload(/* aFireSync = */ false);
4433 mDocumentL10n = nullptr;
4439 * This method should be called once the end of the l10n
4440 * resource container has been parsed.
4442 * In XUL this is the end of the first </linkset>,
4443 * In XHTML/HTML this is the end of </head>.
4445 * This milestone is used to allow for batch
4446 * localization context I/O and building done
4447 * once when all resources in the document have been
4448 * collected.
4450 void Document::OnL10nResourceContainerParsed() {
4451 // XXX: This is a scaffolding for where we might inject prefetch
4452 // in bug 1717241.
4455 void Document::OnParsingCompleted() {
4456 // Let's call it again, in case the resource
4457 // container has not been closed, and only
4458 // now we're closing the document.
4459 OnL10nResourceContainerParsed();
4461 if (mDocumentL10n) {
4462 RefPtr<DocumentL10n> l10n = mDocumentL10n;
4463 l10n->TriggerInitialTranslation();
4467 void Document::InitialTranslationCompleted(bool aL10nCached) {
4468 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4469 // This means we blocked the load event in LocalizationLinkAdded. It's
4470 // important that the load blocker removal here be async, because our caller
4471 // will notify the content sink after us, and we want the content sync's
4472 // work to happen before the load event fires.
4473 mDocumentL10n->mBlockingLayout = false;
4474 UnblockOnload(/* aFireSync = */ false);
4477 mL10nProtoElements.Clear();
4479 nsXULPrototypeDocument* proto = GetPrototype();
4480 if (proto) {
4481 proto->SetIsL10nCached(aL10nCached);
4485 bool Document::AllowsL10n() const {
4486 if (IsStaticDocument()) {
4487 // We don't allow l10n on static documents, because the nodes are already
4488 // cloned translated, and static docs don't get parsed so we never
4489 // TriggerInitialTranslation, etc, so a load blocker would keep hanging
4490 // forever.
4491 return false;
4493 bool allowed = false;
4494 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4495 return allowed;
4498 bool Document::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) {
4499 MOZ_ASSERT(NS_IsMainThread());
4501 return nsContentUtils::IsSystemCaller(aCx) ||
4502 StaticPrefs::dom_animations_api_core_enabled();
4505 bool Document::IsWebAnimationsEnabled(CallerType aCallerType) {
4506 MOZ_ASSERT(NS_IsMainThread());
4508 return aCallerType == dom::CallerType::System ||
4509 StaticPrefs::dom_animations_api_core_enabled();
4512 bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
4513 JSObject* /*unused*/
4515 MOZ_ASSERT(NS_IsMainThread());
4517 return nsContentUtils::IsSystemCaller(aCx) ||
4518 StaticPrefs::dom_animations_api_getAnimations_enabled();
4521 bool Document::AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
4522 JSObject* /*unused*/
4524 MOZ_ASSERT(NS_IsMainThread());
4526 return nsContentUtils::IsSystemCaller(aCx) ||
4527 StaticPrefs::dom_animations_api_implicit_keyframes_enabled();
4530 bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
4531 JSObject* /*unused*/
4533 MOZ_ASSERT(NS_IsMainThread());
4535 return nsContentUtils::IsSystemCaller(aCx) ||
4536 StaticPrefs::dom_animations_api_timelines_enabled();
4539 DocumentTimeline* Document::Timeline() {
4540 if (!mDocumentTimeline) {
4541 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4544 return mDocumentTimeline;
4547 SVGSVGElement* Document::GetSVGRootElement() const {
4548 Element* root = GetRootElement();
4549 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4550 return nullptr;
4552 return static_cast<SVGSVGElement*>(root);
4555 /* Return true if the document is in the focused top-level window, and is an
4556 * ancestor of the focused DOMWindow. */
4557 bool Document::HasFocus(ErrorResult& rv) const {
4558 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4559 if (!fm) {
4560 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4561 return false;
4564 BrowsingContext* bc = GetBrowsingContext();
4565 if (!bc) {
4566 return false;
4569 if (!fm->IsInActiveWindow(bc)) {
4570 return false;
4573 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
4576 bool Document::ThisDocumentHasFocus() const {
4577 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4578 return fm && fm->GetFocusedWindow() &&
4579 fm->GetFocusedWindow()->GetExtantDoc() == this;
4582 void Document::GetDesignMode(nsAString& aDesignMode) {
4583 if (IsInDesignMode()) {
4584 aDesignMode.AssignLiteral("on");
4585 } else {
4586 aDesignMode.AssignLiteral("off");
4590 void Document::SetDesignMode(const nsAString& aDesignMode,
4591 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4592 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4595 static void NotifyEditableStateChange(Document& aDoc) {
4596 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4597 nsMutationGuard g;
4598 #endif
4599 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4600 node = node->GetNextNode(&aDoc)) {
4601 if (auto* element = Element::FromNode(node)) {
4602 element->UpdateEditableState(true);
4605 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4608 void Document::SetDesignMode(const nsAString& aDesignMode,
4609 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4610 ErrorResult& rv) {
4611 if (aSubjectPrincipal.isSome() &&
4612 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4613 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4614 return;
4616 const bool editableMode = IsInDesignMode();
4617 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4618 SetEditableFlag(!editableMode);
4619 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4620 // state of all descendant elements of it. Update that now.
4621 NotifyEditableStateChange(*this);
4622 rv = EditingStateChanged();
4626 nsCommandManager* Document::GetMidasCommandManager() {
4627 // check if we have it cached
4628 if (mMidasCommandManager) {
4629 return mMidasCommandManager;
4632 nsPIDOMWindowOuter* window = GetWindow();
4633 if (!window) {
4634 return nullptr;
4637 nsIDocShell* docshell = window->GetDocShell();
4638 if (!docshell) {
4639 return nullptr;
4642 mMidasCommandManager = docshell->GetCommandManager();
4643 return mMidasCommandManager;
4646 // static
4647 void Document::EnsureInitializeInternalCommandDataHashtable() {
4648 if (sInternalCommandDataHashtable) {
4649 return;
4651 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
4652 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4653 // clang-format off
4654 sInternalCommandDataHashtable->InsertOrUpdate(
4655 u"bold"_ns,
4656 InternalCommandData(
4657 "cmd_bold",
4658 Command::FormatBold,
4659 ExecCommandParam::Ignore,
4660 StyleUpdatingCommand::GetInstance,
4661 CommandOnTextEditor::Disabled));
4662 sInternalCommandDataHashtable->InsertOrUpdate(
4663 u"italic"_ns,
4664 InternalCommandData(
4665 "cmd_italic",
4666 Command::FormatItalic,
4667 ExecCommandParam::Ignore,
4668 StyleUpdatingCommand::GetInstance,
4669 CommandOnTextEditor::Disabled));
4670 sInternalCommandDataHashtable->InsertOrUpdate(
4671 u"underline"_ns,
4672 InternalCommandData(
4673 "cmd_underline",
4674 Command::FormatUnderline,
4675 ExecCommandParam::Ignore,
4676 StyleUpdatingCommand::GetInstance,
4677 CommandOnTextEditor::Disabled));
4678 sInternalCommandDataHashtable->InsertOrUpdate(
4679 u"strikethrough"_ns,
4680 InternalCommandData(
4681 "cmd_strikethrough",
4682 Command::FormatStrikeThrough,
4683 ExecCommandParam::Ignore,
4684 StyleUpdatingCommand::GetInstance,
4685 CommandOnTextEditor::Disabled));
4686 sInternalCommandDataHashtable->InsertOrUpdate(
4687 u"subscript"_ns,
4688 InternalCommandData(
4689 "cmd_subscript",
4690 Command::FormatSubscript,
4691 ExecCommandParam::Ignore,
4692 StyleUpdatingCommand::GetInstance,
4693 CommandOnTextEditor::Disabled));
4694 sInternalCommandDataHashtable->InsertOrUpdate(
4695 u"superscript"_ns,
4696 InternalCommandData(
4697 "cmd_superscript",
4698 Command::FormatSuperscript,
4699 ExecCommandParam::Ignore,
4700 StyleUpdatingCommand::GetInstance,
4701 CommandOnTextEditor::Disabled));
4702 sInternalCommandDataHashtable->InsertOrUpdate(
4703 u"cut"_ns,
4704 InternalCommandData(
4705 "cmd_cut",
4706 Command::Cut,
4707 ExecCommandParam::Ignore,
4708 CutCommand::GetInstance,
4709 CommandOnTextEditor::Enabled));
4710 sInternalCommandDataHashtable->InsertOrUpdate(
4711 u"copy"_ns,
4712 InternalCommandData(
4713 "cmd_copy",
4714 Command::Copy,
4715 ExecCommandParam::Ignore,
4716 CopyCommand::GetInstance,
4717 CommandOnTextEditor::Enabled));
4718 sInternalCommandDataHashtable->InsertOrUpdate(
4719 u"paste"_ns,
4720 InternalCommandData(
4721 "cmd_paste",
4722 Command::Paste,
4723 ExecCommandParam::Ignore,
4724 PasteCommand::GetInstance,
4725 CommandOnTextEditor::Enabled));
4726 sInternalCommandDataHashtable->InsertOrUpdate(
4727 u"delete"_ns,
4728 InternalCommandData(
4729 "cmd_deleteCharBackward",
4730 Command::DeleteCharBackward,
4731 ExecCommandParam::Ignore,
4732 DeleteCommand::GetInstance,
4733 CommandOnTextEditor::Enabled));
4734 sInternalCommandDataHashtable->InsertOrUpdate(
4735 u"forwarddelete"_ns,
4736 InternalCommandData(
4737 "cmd_deleteCharForward",
4738 Command::DeleteCharForward,
4739 ExecCommandParam::Ignore,
4740 DeleteCommand::GetInstance,
4741 CommandOnTextEditor::Enabled));
4742 sInternalCommandDataHashtable->InsertOrUpdate(
4743 u"selectall"_ns,
4744 InternalCommandData(
4745 "cmd_selectAll",
4746 Command::SelectAll,
4747 ExecCommandParam::Ignore,
4748 SelectAllCommand::GetInstance,
4749 CommandOnTextEditor::Enabled));
4750 sInternalCommandDataHashtable->InsertOrUpdate(
4751 u"undo"_ns,
4752 InternalCommandData(
4753 "cmd_undo",
4754 Command::HistoryUndo,
4755 ExecCommandParam::Ignore,
4756 UndoCommand::GetInstance,
4757 CommandOnTextEditor::Enabled));
4758 sInternalCommandDataHashtable->InsertOrUpdate(
4759 u"redo"_ns,
4760 InternalCommandData(
4761 "cmd_redo",
4762 Command::HistoryRedo,
4763 ExecCommandParam::Ignore,
4764 RedoCommand::GetInstance,
4765 CommandOnTextEditor::Enabled));
4766 sInternalCommandDataHashtable->InsertOrUpdate(
4767 u"indent"_ns,
4768 InternalCommandData("cmd_indent",
4769 Command::FormatIndent,
4770 ExecCommandParam::Ignore,
4771 IndentCommand::GetInstance,
4772 CommandOnTextEditor::Disabled));
4773 sInternalCommandDataHashtable->InsertOrUpdate(
4774 u"outdent"_ns,
4775 InternalCommandData(
4776 "cmd_outdent",
4777 Command::FormatOutdent,
4778 ExecCommandParam::Ignore,
4779 OutdentCommand::GetInstance,
4780 CommandOnTextEditor::Disabled));
4781 sInternalCommandDataHashtable->InsertOrUpdate(
4782 u"backcolor"_ns,
4783 InternalCommandData(
4784 "cmd_highlight",
4785 Command::FormatBackColor,
4786 ExecCommandParam::String,
4787 HighlightColorStateCommand::GetInstance,
4788 CommandOnTextEditor::Disabled));
4789 sInternalCommandDataHashtable->InsertOrUpdate(
4790 u"hilitecolor"_ns,
4791 InternalCommandData(
4792 "cmd_highlight",
4793 Command::FormatBackColor,
4794 ExecCommandParam::String,
4795 HighlightColorStateCommand::GetInstance,
4796 CommandOnTextEditor::Disabled));
4797 sInternalCommandDataHashtable->InsertOrUpdate(
4798 u"forecolor"_ns,
4799 InternalCommandData(
4800 "cmd_fontColor",
4801 Command::FormatFontColor,
4802 ExecCommandParam::String,
4803 FontColorStateCommand::GetInstance,
4804 CommandOnTextEditor::Disabled));
4805 sInternalCommandDataHashtable->InsertOrUpdate(
4806 u"fontname"_ns,
4807 InternalCommandData(
4808 "cmd_fontFace",
4809 Command::FormatFontName,
4810 ExecCommandParam::String,
4811 FontFaceStateCommand::GetInstance,
4812 CommandOnTextEditor::Disabled));
4813 sInternalCommandDataHashtable->InsertOrUpdate(
4814 u"fontsize"_ns,
4815 InternalCommandData(
4816 "cmd_fontSize",
4817 Command::FormatFontSize,
4818 ExecCommandParam::String,
4819 FontSizeStateCommand::GetInstance,
4820 CommandOnTextEditor::Disabled));
4821 sInternalCommandDataHashtable->InsertOrUpdate(
4822 u"inserthorizontalrule"_ns,
4823 InternalCommandData(
4824 "cmd_insertHR",
4825 Command::InsertHorizontalRule,
4826 ExecCommandParam::Ignore,
4827 InsertTagCommand::GetInstance,
4828 CommandOnTextEditor::Disabled));
4829 sInternalCommandDataHashtable->InsertOrUpdate(
4830 u"createlink"_ns,
4831 InternalCommandData(
4832 "cmd_insertLinkNoUI",
4833 Command::InsertLink,
4834 ExecCommandParam::String,
4835 InsertTagCommand::GetInstance,
4836 CommandOnTextEditor::Disabled));
4837 sInternalCommandDataHashtable->InsertOrUpdate(
4838 u"insertimage"_ns,
4839 InternalCommandData(
4840 "cmd_insertImageNoUI",
4841 Command::InsertImage,
4842 ExecCommandParam::String,
4843 InsertTagCommand::GetInstance,
4844 CommandOnTextEditor::Disabled));
4845 sInternalCommandDataHashtable->InsertOrUpdate(
4846 u"inserthtml"_ns,
4847 InternalCommandData(
4848 "cmd_insertHTML",
4849 Command::InsertHTML,
4850 ExecCommandParam::String,
4851 InsertHTMLCommand::GetInstance,
4852 // TODO: Chromium inserts text content of the document fragment
4853 // created from the param.
4854 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
4855 CommandOnTextEditor::Disabled));
4856 sInternalCommandDataHashtable->InsertOrUpdate(
4857 u"inserttext"_ns,
4858 InternalCommandData(
4859 "cmd_insertText",
4860 Command::InsertText,
4861 ExecCommandParam::String,
4862 InsertPlaintextCommand::GetInstance,
4863 CommandOnTextEditor::Enabled));
4864 sInternalCommandDataHashtable->InsertOrUpdate(
4865 u"justifyleft"_ns,
4866 InternalCommandData(
4867 "cmd_align",
4868 Command::FormatJustifyLeft,
4869 ExecCommandParam::Ignore, // Will be set to "left"
4870 AlignCommand::GetInstance,
4871 CommandOnTextEditor::Disabled));
4872 sInternalCommandDataHashtable->InsertOrUpdate(
4873 u"justifyright"_ns,
4874 InternalCommandData(
4875 "cmd_align",
4876 Command::FormatJustifyRight,
4877 ExecCommandParam::Ignore, // Will be set to "right"
4878 AlignCommand::GetInstance,
4879 CommandOnTextEditor::Disabled));
4880 sInternalCommandDataHashtable->InsertOrUpdate(
4881 u"justifycenter"_ns,
4882 InternalCommandData(
4883 "cmd_align",
4884 Command::FormatJustifyCenter,
4885 ExecCommandParam::Ignore, // Will be set to "center"
4886 AlignCommand::GetInstance,
4887 CommandOnTextEditor::Disabled));
4888 sInternalCommandDataHashtable->InsertOrUpdate(
4889 u"justifyfull"_ns,
4890 InternalCommandData(
4891 "cmd_align",
4892 Command::FormatJustifyFull,
4893 ExecCommandParam::Ignore, // Will be set to "justify"
4894 AlignCommand::GetInstance,
4895 CommandOnTextEditor::Disabled));
4896 sInternalCommandDataHashtable->InsertOrUpdate(
4897 u"removeformat"_ns,
4898 InternalCommandData(
4899 "cmd_removeStyles",
4900 Command::FormatRemove,
4901 ExecCommandParam::Ignore,
4902 RemoveStylesCommand::GetInstance,
4903 CommandOnTextEditor::Disabled));
4904 sInternalCommandDataHashtable->InsertOrUpdate(
4905 u"unlink"_ns,
4906 InternalCommandData(
4907 "cmd_removeLinks",
4908 Command::FormatRemoveLink,
4909 ExecCommandParam::Ignore,
4910 StyleUpdatingCommand::GetInstance,
4911 CommandOnTextEditor::Disabled));
4912 sInternalCommandDataHashtable->InsertOrUpdate(
4913 u"insertorderedlist"_ns,
4914 InternalCommandData(
4915 "cmd_ol",
4916 Command::InsertOrderedList,
4917 ExecCommandParam::Ignore,
4918 ListCommand::GetInstance,
4919 CommandOnTextEditor::Disabled));
4920 sInternalCommandDataHashtable->InsertOrUpdate(
4921 u"insertunorderedlist"_ns,
4922 InternalCommandData(
4923 "cmd_ul",
4924 Command::InsertUnorderedList,
4925 ExecCommandParam::Ignore,
4926 ListCommand::GetInstance,
4927 CommandOnTextEditor::Disabled));
4928 sInternalCommandDataHashtable->InsertOrUpdate(
4929 u"insertparagraph"_ns,
4930 InternalCommandData(
4931 "cmd_insertParagraph",
4932 Command::InsertParagraph,
4933 ExecCommandParam::Ignore,
4934 InsertParagraphCommand::GetInstance,
4935 CommandOnTextEditor::Enabled));
4936 sInternalCommandDataHashtable->InsertOrUpdate(
4937 u"insertlinebreak"_ns,
4938 InternalCommandData(
4939 "cmd_insertLineBreak",
4940 Command::InsertLineBreak,
4941 ExecCommandParam::Ignore,
4942 InsertLineBreakCommand::GetInstance,
4943 CommandOnTextEditor::Enabled));
4944 sInternalCommandDataHashtable->InsertOrUpdate(
4945 u"formatblock"_ns,
4946 InternalCommandData(
4947 "cmd_paragraphState",
4948 Command::FormatBlock,
4949 ExecCommandParam::String,
4950 ParagraphStateCommand::GetInstance,
4951 CommandOnTextEditor::Disabled));
4952 sInternalCommandDataHashtable->InsertOrUpdate(
4953 u"styleWithCSS"_ns,
4954 InternalCommandData(
4955 "cmd_setDocumentUseCSS",
4956 Command::SetDocumentUseCSS,
4957 ExecCommandParam::Boolean,
4958 SetDocumentStateCommand::GetInstance,
4959 CommandOnTextEditor::FallThrough));
4960 sInternalCommandDataHashtable->InsertOrUpdate(
4961 u"usecss"_ns, // Legacy command
4962 InternalCommandData(
4963 "cmd_setDocumentUseCSS",
4964 Command::SetDocumentUseCSS,
4965 ExecCommandParam::InvertedBoolean,
4966 SetDocumentStateCommand::GetInstance,
4967 CommandOnTextEditor::FallThrough));
4968 sInternalCommandDataHashtable->InsertOrUpdate(
4969 u"contentReadOnly"_ns,
4970 InternalCommandData(
4971 "cmd_setDocumentReadOnly",
4972 Command::SetDocumentReadOnly,
4973 ExecCommandParam::Boolean,
4974 SetDocumentStateCommand::GetInstance,
4975 CommandOnTextEditor::Enabled));
4976 sInternalCommandDataHashtable->InsertOrUpdate(
4977 u"insertBrOnReturn"_ns,
4978 InternalCommandData(
4979 "cmd_insertBrOnReturn",
4980 Command::SetDocumentInsertBROnEnterKeyPress,
4981 ExecCommandParam::Boolean,
4982 SetDocumentStateCommand::GetInstance,
4983 CommandOnTextEditor::FallThrough));
4984 sInternalCommandDataHashtable->InsertOrUpdate(
4985 u"defaultParagraphSeparator"_ns,
4986 InternalCommandData(
4987 "cmd_defaultParagraphSeparator",
4988 Command::SetDocumentDefaultParagraphSeparator,
4989 ExecCommandParam::String,
4990 SetDocumentStateCommand::GetInstance,
4991 CommandOnTextEditor::FallThrough));
4992 sInternalCommandDataHashtable->InsertOrUpdate(
4993 u"enableObjectResizing"_ns,
4994 InternalCommandData(
4995 "cmd_enableObjectResizing",
4996 Command::ToggleObjectResizers,
4997 ExecCommandParam::Boolean,
4998 SetDocumentStateCommand::GetInstance,
4999 CommandOnTextEditor::FallThrough));
5000 sInternalCommandDataHashtable->InsertOrUpdate(
5001 u"enableInlineTableEditing"_ns,
5002 InternalCommandData(
5003 "cmd_enableInlineTableEditing",
5004 Command::ToggleInlineTableEditor,
5005 ExecCommandParam::Boolean,
5006 SetDocumentStateCommand::GetInstance,
5007 CommandOnTextEditor::FallThrough));
5008 sInternalCommandDataHashtable->InsertOrUpdate(
5009 u"enableAbsolutePositionEditing"_ns,
5010 InternalCommandData(
5011 "cmd_enableAbsolutePositionEditing",
5012 Command::ToggleAbsolutePositionEditor,
5013 ExecCommandParam::Boolean,
5014 SetDocumentStateCommand::GetInstance,
5015 CommandOnTextEditor::FallThrough));
5016 sInternalCommandDataHashtable->InsertOrUpdate(
5017 u"enableCompatibleJoinSplitDirection"_ns,
5018 InternalCommandData("cmd_enableCompatibleJoinSplitNodeDirection",
5019 Command::EnableCompatibleJoinSplitNodeDirection,
5020 ExecCommandParam::Boolean,
5021 SetDocumentStateCommand::GetInstance,
5022 CommandOnTextEditor::FallThrough));
5023 #if 0
5024 // with empty string
5025 sInternalCommandDataHashtable->InsertOrUpdate(
5026 u"justifynone"_ns,
5027 InternalCommandData(
5028 "cmd_align",
5029 Command::Undefined,
5030 ExecCommandParam::Ignore,
5031 nullptr,
5032 CommandOnTextEditor::Disabled)); // Not implemented yet.
5033 // REQUIRED SPECIAL REVIEW special review
5034 sInternalCommandDataHashtable->InsertOrUpdate(
5035 u"saveas"_ns,
5036 InternalCommandData(
5037 "cmd_saveAs",
5038 Command::Undefined,
5039 ExecCommandParam::Boolean,
5040 nullptr,
5041 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5042 // REQUIRED SPECIAL REVIEW special review
5043 sInternalCommandDataHashtable->InsertOrUpdate(
5044 u"print"_ns,
5045 InternalCommandData(
5046 "cmd_print",
5047 Command::Undefined,
5048 ExecCommandParam::Boolean,
5049 nullptr,
5050 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5051 #endif // #if 0
5052 // clang-format on
5055 Document::InternalCommandData Document::ConvertToInternalCommand(
5056 const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */,
5057 nsAString* aAdjustedValue /* = nullptr */) {
5058 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
5059 EnsureInitializeInternalCommandDataHashtable();
5060 InternalCommandData commandData;
5061 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
5062 return InternalCommandData();
5064 // Ignore if the command is disabled by a corresponding pref due to Gecko
5065 // specific.
5066 switch (commandData.mCommand) {
5067 case Command::SetDocumentReadOnly:
5068 if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() &&
5069 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) {
5070 return InternalCommandData();
5072 break;
5073 case Command::SetDocumentInsertBROnEnterKeyPress:
5074 MOZ_DIAGNOSTIC_ASSERT(
5075 aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn"));
5076 if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) {
5077 return InternalCommandData();
5079 break;
5080 default:
5081 break;
5083 if (!aAdjustedValue) {
5084 // No further work to do
5085 return commandData;
5087 switch (commandData.mExecCommandParam) {
5088 case ExecCommandParam::Ignore:
5089 // Just have to copy it, no checking
5090 switch (commandData.mCommand) {
5091 case Command::FormatJustifyLeft:
5092 aAdjustedValue->AssignLiteral("left");
5093 break;
5094 case Command::FormatJustifyRight:
5095 aAdjustedValue->AssignLiteral("right");
5096 break;
5097 case Command::FormatJustifyCenter:
5098 aAdjustedValue->AssignLiteral("center");
5099 break;
5100 case Command::FormatJustifyFull:
5101 aAdjustedValue->AssignLiteral("justify");
5102 break;
5103 default:
5104 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
5105 EditorCommandParamType::None);
5106 break;
5108 return commandData;
5110 case ExecCommandParam::Boolean:
5111 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5112 EditorCommandParamType::Bool));
5113 // If this is a boolean value and it's not explicitly false (e.g. no
5114 // value). We default to "true" (see bug 301490).
5115 if (!aValue.LowerCaseEqualsLiteral("false")) {
5116 aAdjustedValue->AssignLiteral("true");
5117 } else {
5118 aAdjustedValue->AssignLiteral("false");
5120 return commandData;
5122 case ExecCommandParam::InvertedBoolean:
5123 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5124 EditorCommandParamType::Bool));
5125 // For old backwards commands we invert the check.
5126 if (aValue.LowerCaseEqualsLiteral("false")) {
5127 aAdjustedValue->AssignLiteral("true");
5128 } else {
5129 aAdjustedValue->AssignLiteral("false");
5131 return commandData;
5133 case ExecCommandParam::String:
5134 MOZ_ASSERT(!!(
5135 EditorCommand::GetParamType(commandData.mCommand) &
5136 (EditorCommandParamType::String | EditorCommandParamType::CString)));
5137 switch (commandData.mCommand) {
5138 case Command::FormatBlock: {
5139 const char16_t* start = aValue.BeginReading();
5140 const char16_t* end = aValue.EndReading();
5141 if (start != end && *start == '<' && *(end - 1) == '>') {
5142 ++start;
5143 --end;
5145 // XXX Should we reorder this array with actual usage?
5146 static const nsStaticAtom* kFormattableBlockTags[] = {
5147 // clang-format off
5148 nsGkAtoms::address,
5149 nsGkAtoms::blockquote,
5150 nsGkAtoms::dd,
5151 nsGkAtoms::div,
5152 nsGkAtoms::dl,
5153 nsGkAtoms::dt,
5154 nsGkAtoms::h1,
5155 nsGkAtoms::h2,
5156 nsGkAtoms::h3,
5157 nsGkAtoms::h4,
5158 nsGkAtoms::h5,
5159 nsGkAtoms::h6,
5160 nsGkAtoms::p,
5161 nsGkAtoms::pre,
5162 // clang-format on
5164 nsAutoString value(nsDependentSubstring(start, end));
5165 ToLowerCase(value);
5166 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
5167 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
5168 if (valueAtom == kTag) {
5169 kTag->ToString(*aAdjustedValue);
5170 return commandData;
5173 return InternalCommandData();
5175 case Command::FormatFontSize: {
5176 // Per editing spec as of April 23, 2012, we need to reject the value
5177 // if it's not a valid floating-point number surrounded by optional
5178 // whitespace. Otherwise, we parse it as a legacy font size. For
5179 // now, we just parse as a legacy font size regardless (matching
5180 // WebKit) -- bug 747879.
5181 int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
5182 if (!size) {
5183 return InternalCommandData();
5185 MOZ_ASSERT(aAdjustedValue->IsEmpty());
5186 aAdjustedValue->AppendInt(size);
5187 return commandData;
5189 case Command::InsertImage:
5190 case Command::InsertLink:
5191 if (aValue.IsEmpty()) {
5192 // Invalid value, return false
5193 return InternalCommandData();
5195 aAdjustedValue->Assign(aValue);
5196 return commandData;
5197 case Command::SetDocumentDefaultParagraphSeparator:
5198 if (!aValue.LowerCaseEqualsLiteral("div") &&
5199 !aValue.LowerCaseEqualsLiteral("p") &&
5200 !aValue.LowerCaseEqualsLiteral("br")) {
5201 // Invalid value
5202 return InternalCommandData();
5204 aAdjustedValue->Assign(aValue);
5205 return commandData;
5206 default:
5207 aAdjustedValue->Assign(aValue);
5208 return commandData;
5211 default:
5212 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
5213 return InternalCommandData();
5217 Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
5218 Document& aDocument, const InternalCommandData& aCommandData)
5219 : mCommandData(aCommandData) {
5220 // We'll retrieve an editor with current DOM tree and layout information.
5221 // However, JS may have already hidden or remove exposed root content of
5222 // the editor. Therefore, we need the latest layout information here.
5223 aDocument.FlushPendingNotifications(FlushType::Layout);
5224 if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) {
5225 mDoNothing = true;
5226 return;
5229 if (nsPresContext* presContext = aDocument.GetPresContext()) {
5230 // Consider context of command handling which is automatically resolved
5231 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5232 // The order is:
5233 // 1. TextEditor if there is an active element and it has TextEditor like
5234 // <input type="text"> or <textarea>.
5235 // 2. HTMLEditor for the document, if there is.
5236 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
5237 if (aCommandData.IsCutOrCopyCommand()) {
5238 // Note that we used to use DocShell to handle `cut` and `copy` command
5239 // for dispatching corresponding events for making possible web apps to
5240 // implement their own editor without editable elements but supports
5241 // standard shortcut keys, etc. In this case, we prefer to use active
5242 // element's editor to keep same behavior.
5243 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5244 } else {
5245 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5246 mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
5247 if (!mActiveEditor) {
5248 mActiveEditor = mHTMLEditor;
5253 // Then, retrieve editor command class instance which should handle it
5254 // and can handle it now.
5255 if (!mActiveEditor) {
5256 // If the command is available without editor, we should redirect the
5257 // command to focused descendant with DocShell.
5258 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5259 mDoNothing = true;
5260 return;
5262 return;
5265 // Otherwise, we should use EditorCommand instance (which is singleton
5266 // instance) when it's enabled.
5267 mEditorCommand = aCommandData.mGetEditorCommandFunc
5268 ? aCommandData.mGetEditorCommandFunc()
5269 : nullptr;
5270 if (!mEditorCommand) {
5271 mDoNothing = true;
5272 mActiveEditor = nullptr;
5273 mHTMLEditor = nullptr;
5274 return;
5277 if (IsCommandEnabled()) {
5278 return;
5281 // If the EditorCommand instance is disabled, we should do nothing if
5282 // the command requires an editor.
5283 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5284 // Do nothing if editor specific commands is disabled (bug 760052).
5285 mDoNothing = true;
5286 return;
5289 // Otherwise, we should redirect it to focused descendant with DocShell.
5290 mEditorCommand = nullptr;
5291 mActiveEditor = nullptr;
5292 mHTMLEditor = nullptr;
5295 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
5296 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
5297 switch (mCommandData.mCommandOnTextEditor) {
5298 case CommandOnTextEditor::Enabled:
5299 return mActiveEditor;
5300 case CommandOnTextEditor::Disabled:
5301 return mActiveEditor && mActiveEditor->IsTextEditor()
5302 ? nullptr
5303 : mActiveEditor.get();
5304 case CommandOnTextEditor::FallThrough:
5305 return mHTMLEditor;
5307 return nullptr;
5310 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
5311 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
5312 // Make sure frames are up to date, since that can affect whether
5313 // we're editable.
5314 doc->FlushPendingNotifications(FlushType::Frames);
5316 EditorBase* targetEditor = GetTargetEditor();
5317 if (targetEditor && targetEditor->IsTextEditor()) {
5318 // FYI: When `disabled` attribute is set, `TextEditor` treats it as
5319 // "readonly" too.
5320 return !targetEditor->IsReadonly();
5322 return aDocument->IsEditingOn();
5325 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
5326 EditorBase* targetEditor = GetTargetEditor();
5327 if (!targetEditor) {
5328 return false;
5330 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5331 return MOZ_KnownLive(mEditorCommand)
5332 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
5335 nsresult Document::AutoEditorCommandTarget::DoCommand(
5336 nsIPrincipal* aPrincipal) const {
5337 MOZ_ASSERT(!DoNothing());
5338 MOZ_ASSERT(mEditorCommand);
5339 EditorBase* targetEditor = GetTargetEditor();
5340 if (!targetEditor) {
5341 return NS_SUCCESS_DOM_NO_OPERATION;
5343 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5344 return MOZ_KnownLive(mEditorCommand)
5345 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
5346 aPrincipal);
5349 template <typename ParamType>
5350 nsresult Document::AutoEditorCommandTarget::DoCommandParam(
5351 const ParamType& aParam, nsIPrincipal* aPrincipal) const {
5352 MOZ_ASSERT(!DoNothing());
5353 MOZ_ASSERT(mEditorCommand);
5354 EditorBase* targetEditor = GetTargetEditor();
5355 if (!targetEditor) {
5356 return NS_SUCCESS_DOM_NO_OPERATION;
5358 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5359 return MOZ_KnownLive(mEditorCommand)
5360 ->DoCommandParam(mCommandData.mCommand, aParam,
5361 MOZ_KnownLive(*targetEditor), aPrincipal);
5364 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
5365 nsCommandParams& aParams) const {
5366 MOZ_ASSERT(mEditorCommand);
5367 EditorBase* targetEditor = GetTargetEditor();
5368 if (!targetEditor) {
5369 return NS_OK;
5371 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5372 return MOZ_KnownLive(mEditorCommand)
5373 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
5374 MOZ_KnownLive(targetEditor), nullptr);
5377 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
5378 const nsAString& aValue,
5379 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
5380 // Only allow on HTML documents.
5381 if (!IsHTMLOrXHTML()) {
5382 aRv.ThrowInvalidStateError(
5383 "execCommand is only supported on HTML documents");
5384 return false;
5386 // Otherwise, don't throw exception for compatibility with Chrome.
5388 // if they are requesting UI from us, let's fail since we have no UI
5389 if (aShowUI) {
5390 return false;
5393 // If we're running an execCommand, we should just return false.
5394 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
5395 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
5396 mIsRunningExecCommand) {
5397 return false;
5400 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
5401 // this might add some ugly JS dependencies?
5403 nsAutoString adjustedValue;
5404 InternalCommandData commandData =
5405 ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
5406 switch (commandData.mCommand) {
5407 case Command::DoNothing:
5408 return false;
5409 case Command::SetDocumentReadOnly:
5410 SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly);
5411 break;
5412 case Command::EnableCompatibleJoinSplitNodeDirection:
5413 // We don't allow to take the legacy behavior back if the new one is
5414 // enabled by default.
5415 if (StaticPrefs::
5416 editor_join_split_direction_compatible_with_the_other_browsers() &&
5417 !adjustedValue.EqualsLiteral("true") &&
5418 !aSubjectPrincipal.IsSystemPrincipal()) {
5419 return false;
5421 break;
5422 default:
5423 break;
5426 // Do security check first.
5427 if (commandData.IsCutOrCopyCommand()) {
5428 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
5429 // We have rejected the event due to it not being performed in an
5430 // input-driven context therefore, we report the error to the console.
5431 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
5432 this, nsContentUtils::eDOM_PROPERTIES,
5433 "ExecCommandCutCopyDeniedNotInputDriven");
5434 return false;
5436 } else if (commandData.IsPasteCommand()) {
5437 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
5438 nsGkAtoms::clipboardRead)) {
5439 return false;
5443 AutoRunningExecCommandMarker markRunningExecCommand(*this);
5445 // Next, consider context of command handling which is automatically resolved
5446 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5447 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5448 if (commandData.IsAvailableOnlyWhenEditable() &&
5449 !editCommandTarget.IsEditable(this)) {
5450 return false;
5453 if (editCommandTarget.DoNothing()) {
5454 return false;
5457 // If we cannot use EditorCommand instance directly, we need to handle the
5458 // command with traditional path (i.e., with DocShell or nsCommandManager).
5459 if (!editCommandTarget.IsEditor()) {
5460 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
5462 // Special case clipboard write commands like Command::Cut and
5463 // Command::Copy. For such commands, we need the behaviour from
5464 // nsWindowRoot::GetControllers() which is to look at the focused element,
5465 // and defer to a focused textbox's controller. The code past taken by
5466 // other commands in ExecCommand() always uses the window directly, rather
5467 // than deferring to the textbox, which is desireable for most editor
5468 // commands, but not these commands (as those should allow copying out of
5469 // embedded editors). This behaviour is invoked if we call DoCommand()
5470 // directly on the docShell.
5471 // XXX This means that we allow web app to pick up selected content in
5472 // descendant document and write it into the clipboard when a
5473 // descendant document has focus. However, Chromium does not allow
5474 // this and this seems that it's not good behavior from point of view
5475 // of security. We should treat this issue in another bug.
5476 if (commandData.IsCutOrCopyCommand()) {
5477 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
5478 if (!docShell) {
5479 return false;
5481 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
5482 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
5483 return false;
5485 return NS_SUCCEEDED(rv);
5488 // Otherwise (currently, only clipboard read commands like Command::Paste),
5489 // we don't need to redirect the command to focused subdocument.
5490 // Therefore, we should handle it with nsCommandManager as used to be.
5491 // It may dispatch only preceding event of editing on non-editable element
5492 // to make web apps possible to handle standard shortcut key, etc in
5493 // their own editor.
5494 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5495 if (!commandManager) {
5496 return false;
5499 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5500 if (!window) {
5501 return false;
5504 // Return false for disabled commands (bug 760052)
5505 if (!commandManager->IsCommandEnabled(
5506 nsDependentCString(commandData.mXULCommandName), window)) {
5507 return false;
5510 MOZ_ASSERT(commandData.IsPasteCommand() ||
5511 commandData.mCommand == Command::SelectAll);
5512 nsresult rv =
5513 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
5514 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5517 // Now, our target is fixed to the editor. So, we can use EditorCommand
5518 // in EditorCommandTarget directly.
5520 EditorCommandParamType paramType =
5521 EditorCommand::GetParamType(commandData.mCommand);
5523 // If we don't have meaningful parameter or the EditorCommand does not
5524 // require additional parameter, we can use `DoCommand()`.
5525 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
5526 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
5527 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
5528 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5531 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
5532 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
5533 // a `bool` value.
5534 if (!!(paramType & EditorCommandParamType::Bool)) {
5535 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
5536 adjustedValue.EqualsLiteral("false"));
5537 nsresult rv = editCommandTarget.DoCommandParam(
5538 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
5539 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5542 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
5543 // in this case. However, `paramType` may contain both `String` and
5544 // `CString` but in such case, we should use `DoCommandParam()` which
5545 // takes `nsAString`. So, we should check whether `paramType` contains
5546 // `String` or not first.
5547 if (!!(paramType & EditorCommandParamType::String)) {
5548 MOZ_ASSERT(!adjustedValue.IsVoid());
5549 nsresult rv =
5550 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
5551 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5554 // Finally, `paramType` should have `CString`. We should use
5555 // `DoCommandParam()` which takes `nsACString`.
5556 if (!!(paramType & EditorCommandParamType::CString)) {
5557 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
5558 MOZ_ASSERT(!utf8Value.IsVoid());
5559 nsresult rv =
5560 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
5561 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5564 MOZ_ASSERT_UNREACHABLE(
5565 "Not yet implemented to handle new EditorCommandParamType");
5566 return false;
5569 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
5570 nsIPrincipal& aSubjectPrincipal,
5571 ErrorResult& aRv) {
5572 // Only allow on HTML documents.
5573 if (!IsHTMLOrXHTML()) {
5574 aRv.ThrowInvalidStateError(
5575 "queryCommandEnabled is only supported on HTML documents");
5576 return false;
5578 // Otherwise, don't throw exception for compatibility with Chrome.
5580 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5581 switch (commandData.mCommand) {
5582 case Command::DoNothing:
5583 return false;
5584 case Command::SetDocumentReadOnly:
5585 SetUseCounter(
5586 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5587 break;
5588 case Command::SetDocumentInsertBROnEnterKeyPress:
5589 SetUseCounter(
5590 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5591 break;
5592 default:
5593 break;
5596 // cut & copy are always allowed
5597 if (commandData.IsCutOrCopyCommand()) {
5598 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
5601 // Report false for restricted commands
5602 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
5603 return false;
5606 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5607 if (commandData.IsAvailableOnlyWhenEditable() &&
5608 !editCommandTarget.IsEditable(this)) {
5609 return false;
5612 if (editCommandTarget.IsEditor()) {
5613 return editCommandTarget.IsCommandEnabled();
5616 // get command manager and dispatch command to our window if it's acceptable
5617 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5618 if (!commandManager) {
5619 return false;
5622 nsPIDOMWindowOuter* window = GetWindow();
5623 if (!window) {
5624 return false;
5627 return commandManager->IsCommandEnabled(
5628 nsDependentCString(commandData.mXULCommandName), window);
5631 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5632 ErrorResult& aRv) {
5633 // Only allow on HTML documents.
5634 if (!IsHTMLOrXHTML()) {
5635 aRv.ThrowInvalidStateError(
5636 "queryCommandIndeterm is only supported on HTML documents");
5637 return false;
5639 // Otherwise, don't throw exception for compatibility with Chrome.
5641 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5642 if (commandData.mCommand == Command::DoNothing) {
5643 return false;
5646 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5647 if (commandData.IsAvailableOnlyWhenEditable() &&
5648 !editCommandTarget.IsEditable(this)) {
5649 return false;
5651 RefPtr<nsCommandParams> params = new nsCommandParams();
5652 if (editCommandTarget.IsEditor()) {
5653 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5654 return false;
5656 } else {
5657 // get command manager and dispatch command to our window if it's acceptable
5658 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5659 if (!commandManager) {
5660 return false;
5663 nsPIDOMWindowOuter* window = GetWindow();
5664 if (!window) {
5665 return false;
5668 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5669 window, params))) {
5670 return false;
5674 // If command does not have a state_mixed value, this call fails and sets
5675 // retval to false. This is fine -- we want to return false in that case
5676 // anyway (bug 738385), so we just don't throw regardless.
5677 return params->GetBool("state_mixed");
5680 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5681 ErrorResult& aRv) {
5682 // Only allow on HTML documents.
5683 if (!IsHTMLOrXHTML()) {
5684 aRv.ThrowInvalidStateError(
5685 "queryCommandState is only supported on HTML documents");
5686 return false;
5688 // Otherwise, don't throw exception for compatibility with Chrome.
5690 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5691 switch (commandData.mCommand) {
5692 case Command::DoNothing:
5693 return false;
5694 case Command::SetDocumentReadOnly:
5695 SetUseCounter(
5696 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5697 break;
5698 case Command::SetDocumentInsertBROnEnterKeyPress:
5699 SetUseCounter(
5700 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5701 break;
5702 default:
5703 break;
5706 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5707 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5708 // return false always.
5709 return false;
5712 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5713 if (commandData.IsAvailableOnlyWhenEditable() &&
5714 !editCommandTarget.IsEditable(this)) {
5715 return false;
5717 RefPtr<nsCommandParams> params = new nsCommandParams();
5718 if (editCommandTarget.IsEditor()) {
5719 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5720 return false;
5722 } else {
5723 // get command manager and dispatch command to our window if it's acceptable
5724 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5725 if (!commandManager) {
5726 return false;
5729 nsPIDOMWindowOuter* window = GetWindow();
5730 if (!window) {
5731 return false;
5734 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5735 window, params))) {
5736 return false;
5740 // handle alignment as a special case (possibly other commands too?)
5741 // Alignment is special because the external api is individual
5742 // commands but internally we use cmd_align with different
5743 // parameters. When getting the state of this command, we need to
5744 // return the boolean for this particular alignment rather than the
5745 // string of 'which alignment is this?'
5746 switch (commandData.mCommand) {
5747 case Command::FormatJustifyLeft: {
5748 nsAutoCString currentValue;
5749 nsresult rv = params->GetCString("state_attribute", currentValue);
5750 if (NS_FAILED(rv)) {
5751 return false;
5753 return currentValue.EqualsLiteral("left");
5755 case Command::FormatJustifyRight: {
5756 nsAutoCString currentValue;
5757 nsresult rv = params->GetCString("state_attribute", currentValue);
5758 if (NS_FAILED(rv)) {
5759 return false;
5761 return currentValue.EqualsLiteral("right");
5763 case Command::FormatJustifyCenter: {
5764 nsAutoCString currentValue;
5765 nsresult rv = params->GetCString("state_attribute", currentValue);
5766 if (NS_FAILED(rv)) {
5767 return false;
5769 return currentValue.EqualsLiteral("center");
5771 case Command::FormatJustifyFull: {
5772 nsAutoCString currentValue;
5773 nsresult rv = params->GetCString("state_attribute", currentValue);
5774 if (NS_FAILED(rv)) {
5775 return false;
5777 return currentValue.EqualsLiteral("justify");
5779 default:
5780 break;
5783 // If command does not have a state_all value, this call fails and sets
5784 // retval to false. This is fine -- we want to return false in that case
5785 // anyway (bug 738385), so we just succeed and return false regardless.
5786 return params->GetBool("state_all");
5789 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5790 CallerType aCallerType, ErrorResult& aRv) {
5791 // Only allow on HTML documents.
5792 if (!IsHTMLOrXHTML()) {
5793 aRv.ThrowInvalidStateError(
5794 "queryCommandSupported is only supported on HTML documents");
5795 return false;
5797 // Otherwise, don't throw exception for compatibility with Chrome.
5799 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5800 switch (commandData.mCommand) {
5801 case Command::DoNothing:
5802 return false;
5803 case Command::SetDocumentReadOnly:
5804 SetUseCounter(
5805 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5806 break;
5807 case Command::SetDocumentInsertBROnEnterKeyPress:
5808 SetUseCounter(
5809 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5810 break;
5811 default:
5812 break;
5815 // Gecko technically supports all the clipboard commands including
5816 // cut/copy/paste, but non-privileged content will be unable to call
5817 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5818 // may also be disallowed to be called from non-privileged content.
5819 // For that reason, we report the support status of corresponding
5820 // command accordingly.
5821 if (aCallerType != CallerType::System) {
5822 if (commandData.IsPasteCommand()) {
5823 return false;
5825 if (commandData.IsCutOrCopyCommand() &&
5826 !StaticPrefs::dom_allow_cut_copy()) {
5827 // XXXbz should we worry about correctly reporting "true" in the
5828 // "restricted, but we're an addon with clipboardWrite permissions" case?
5829 // See also nsContentUtils::IsCutCopyAllowed.
5830 return false;
5834 // aHTMLCommandName is supported if it can be converted to a Midas command
5835 return true;
5838 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5839 nsAString& aValue, ErrorResult& aRv) {
5840 aValue.Truncate();
5842 // Only allow on HTML documents.
5843 if (!IsHTMLOrXHTML()) {
5844 aRv.ThrowInvalidStateError(
5845 "queryCommandValue is only supported on HTML documents");
5846 return;
5848 // Otherwise, don't throw exception for compatibility with Chrome.
5850 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5851 switch (commandData.mCommand) {
5852 case Command::DoNothing:
5853 // Return empty string
5854 return;
5855 case Command::SetDocumentReadOnly:
5856 SetUseCounter(
5857 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5858 break;
5859 case Command::SetDocumentInsertBROnEnterKeyPress:
5860 SetUseCounter(
5861 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5862 break;
5863 default:
5864 break;
5867 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5868 if (commandData.IsAvailableOnlyWhenEditable() &&
5869 !editCommandTarget.IsEditable(this)) {
5870 return;
5872 RefPtr<nsCommandParams> params = new nsCommandParams();
5873 if (editCommandTarget.IsEditor()) {
5874 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5875 return;
5878 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5879 return;
5881 } else {
5882 // get command manager and dispatch command to our window if it's acceptable
5883 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5884 if (!commandManager) {
5885 return;
5888 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5889 if (!window) {
5890 return;
5893 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5894 return;
5897 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5898 window, params))) {
5899 return;
5903 // If command does not have a state_attribute value, this call fails, and
5904 // aValue will wind up being the empty string. This is fine -- we want to
5905 // return "" in that case anyway (bug 738385), so we just return NS_OK
5906 // regardless.
5907 nsAutoCString result;
5908 params->GetCString("state_attribute", result);
5909 CopyUTF8toUTF16(result, aValue);
5912 void Document::MaybeEditingStateChanged() {
5913 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
5914 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
5915 if (nsContentUtils::IsSafeToRunScript()) {
5916 EditingStateChanged();
5917 } else if (!mInDestructor) {
5918 nsContentUtils::AddScriptRunner(
5919 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
5920 &Document::MaybeEditingStateChanged));
5925 void Document::NotifyFetchOrXHRSuccess() {
5926 if (mShouldNotifyFetchSuccess) {
5927 nsContentUtils::DispatchEventOnlyToChrome(
5928 this, this, u"DOMDocFetchSuccess"_ns, CanBubble::eNo, Cancelable::eNo,
5929 /* DefaultAction */ nullptr);
5933 void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
5934 mShouldNotifyFetchSuccess = aShouldNotify;
5937 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
5938 mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
5941 void Document::TearingDownEditor() {
5942 if (IsEditingOn()) {
5943 mEditingState = EditingState::eTearingDown;
5944 if (IsHTMLOrXHTML()) {
5945 RemoveContentEditableStyleSheets();
5950 nsresult Document::TurnEditingOff() {
5951 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
5953 nsPIDOMWindowOuter* window = GetWindow();
5954 if (!window) {
5955 return NS_ERROR_FAILURE;
5958 nsIDocShell* docshell = window->GetDocShell();
5959 if (!docshell) {
5960 return NS_ERROR_FAILURE;
5963 bool isBeingDestroyed = false;
5964 docshell->IsBeingDestroyed(&isBeingDestroyed);
5965 if (isBeingDestroyed) {
5966 return NS_ERROR_FAILURE;
5969 nsCOMPtr<nsIEditingSession> editSession;
5970 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5971 NS_ENSURE_SUCCESS(rv, rv);
5973 // turn editing off
5974 rv = editSession->TearDownEditorOnWindow(window);
5975 NS_ENSURE_SUCCESS(rv, rv);
5977 mEditingState = EditingState::eOff;
5979 // Editor resets selection since it is being destroyed. But if focus is
5980 // still into editable control, we have to initialize selection again.
5981 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
5982 if (RefPtr<TextControlElement> textControlElement =
5983 TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
5984 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
5985 textEditor->ReinitializeSelection(*textControlElement);
5990 return NS_OK;
5993 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
5994 nsIDocShell* docShell = aWindow->GetDocShell();
5995 if (!docShell) {
5996 return false;
5998 return docShell->GetPresShell() != nullptr;
6001 HTMLEditor* Document::GetHTMLEditor() const {
6002 nsPIDOMWindowOuter* window = GetWindow();
6003 if (!window) {
6004 return nullptr;
6007 nsIDocShell* docshell = window->GetDocShell();
6008 if (!docshell) {
6009 return nullptr;
6012 return docshell->GetHTMLEditor();
6015 nsresult Document::EditingStateChanged() {
6016 if (mRemovedFromDocShell) {
6017 return NS_OK;
6020 if (mEditingState == EditingState::eSettingUp ||
6021 mEditingState == EditingState::eTearingDown) {
6022 // XXX We shouldn't recurse
6023 return NS_OK;
6026 const bool designMode = IsInDesignMode();
6027 EditingState newState =
6028 designMode ? EditingState::eDesignMode
6029 : (mContentEditableCount > 0 ? EditingState::eContentEditable
6030 : EditingState::eOff);
6031 if (mEditingState == newState) {
6032 // No changes in editing mode.
6033 return NS_OK;
6036 const bool thisDocumentHasFocus = ThisDocumentHasFocus();
6037 if (newState == EditingState::eOff) {
6038 // Editing is being turned off.
6039 nsAutoScriptBlocker scriptBlocker;
6040 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6041 NotifyEditableStateChange(*this);
6042 nsresult rv = TurnEditingOff();
6043 // If this document has focus and the editing state of this document
6044 // becomes "off", it means that HTMLEditor won't handle any inputs nor
6045 // modify the DOM tree. However, HTMLEditor may not receive `blur`
6046 // event for this state change since this may occur without focus change.
6047 // Therefore, let's notify HTMLEditor of this editing state change.
6048 // Note that even if focusedElement is an editable text control element,
6049 // it becomes not editable from HTMLEditor point of view since text
6050 // control elements are manged by TextEditor.
6051 RefPtr<Element> focusedElement =
6052 nsFocusManager::GetFocusManager()
6053 ? nsFocusManager::GetFocusManager()->GetFocusedElement()
6054 : nullptr;
6055 DebugOnly<nsresult> rvIgnored =
6056 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6057 htmlEditor, *this, focusedElement);
6058 NS_WARNING_ASSERTION(
6059 NS_SUCCEEDED(rvIgnored),
6060 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but "
6061 "ignored");
6062 return rv;
6065 // Flush out style changes on our _parent_ document, if any, so that
6066 // our check for a presshell won't get stale information.
6067 if (mParentDocument) {
6068 mParentDocument->FlushPendingNotifications(FlushType::Style);
6071 // get editing session, make sure this is a strong reference so the
6072 // window can't get deleted during the rest of this call.
6073 const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6074 if (!window) {
6075 return NS_ERROR_FAILURE;
6078 nsIDocShell* docshell = window->GetDocShell();
6079 if (!docshell) {
6080 return NS_ERROR_FAILURE;
6083 // FlushPendingNotifications might destroy our docshell.
6084 bool isBeingDestroyed = false;
6085 docshell->IsBeingDestroyed(&isBeingDestroyed);
6086 if (isBeingDestroyed) {
6087 return NS_ERROR_FAILURE;
6090 nsCOMPtr<nsIEditingSession> editSession;
6091 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6092 NS_ENSURE_SUCCESS(rv, rv);
6094 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
6095 if (htmlEditor) {
6096 // We might already have an editor if it was set up for mail, let's see
6097 // if this is actually the case.
6098 uint32_t flags = 0;
6099 htmlEditor->GetFlags(&flags);
6100 if (flags & nsIEditor::eEditorMailMask) {
6101 // We already have a mail editor, then we should not attempt to create
6102 // another one.
6103 return NS_OK;
6107 if (!HasPresShell(window)) {
6108 // We should not make the window editable or setup its editor.
6109 // It's probably style=display:none.
6110 return NS_OK;
6113 bool makeWindowEditable = mEditingState == EditingState::eOff;
6114 bool spellRecheckAll = false;
6115 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
6116 htmlEditor = nullptr;
6119 EditingState oldState = mEditingState;
6120 nsAutoEditingState push(this, EditingState::eSettingUp);
6122 RefPtr<PresShell> presShell = GetPresShell();
6123 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
6125 // If we're entering the design mode from non-editable state, put the
6126 // selection at the beginning of the document for compatibility reasons.
6127 bool collapseSelectionAtBeginningOfDocument =
6128 designMode && oldState == EditingState::eOff;
6129 // However, mEditingState may be eOff even if there is some
6130 // `contenteditable` area and selection has been initialized for it because
6131 // mEditingState for `contenteditable` may have been scheduled to modify
6132 // when safe. In such case, we should not reinitialize selection.
6133 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
6134 Selection* selection =
6135 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
6136 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
6137 if (selection && selection->RangeCount()) {
6138 // Perhaps, we don't need to check whether the selection is in
6139 // an editing host or not because all contents will be editable
6140 // in designMode. (And we don't want to make this code so complicated
6141 // because of legacy API.)
6142 collapseSelectionAtBeginningOfDocument = false;
6146 MOZ_ASSERT(mStyleSetFilled);
6148 // Before making this window editable, we need to modify UA style sheet
6149 // because new style may change whether focused element will be focusable
6150 // or not.
6151 if (IsHTMLOrXHTML()) {
6152 AddContentEditableStyleSheetsToStyleSet(designMode);
6155 if (designMode) {
6156 // designMode is being turned on (overrides contentEditable).
6157 spellRecheckAll = oldState == EditingState::eContentEditable;
6160 // Adjust focused element with new style but blur event shouldn't be fired
6161 // until mEditingState is modified with newState.
6162 nsAutoScriptBlocker scriptBlocker;
6163 if (designMode) {
6164 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6165 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6166 window, nsFocusManager::eOnlyCurrentWindow,
6167 getter_AddRefs(focusedWindow));
6168 if (focusedContent) {
6169 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
6170 bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable()
6171 : !focusedContent->IsFocusable();
6172 if (clearFocus) {
6173 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
6174 fm->ClearFocus(window);
6175 // If we need to dispatch blur event, we should put off after
6176 // modifying mEditingState since blur event handler may change
6177 // designMode state again.
6178 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
6184 if (makeWindowEditable) {
6185 // Editing is being turned on (through designMode or contentEditable)
6186 // Turn on editor.
6187 // XXX This can cause flushing which can change the editing state, so make
6188 // sure to avoid recursing.
6189 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
6190 NS_ENSURE_SUCCESS(rv, rv);
6193 // XXX Need to call TearDownEditorOnWindow for all failures.
6194 htmlEditor = docshell->GetHTMLEditor();
6195 if (!htmlEditor) {
6196 // Return NS_OK even though we've failed to create an editor here. This
6197 // is so that the setter of designMode on non-HTML documents does not
6198 // fail.
6199 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
6200 // would detect that we can't support the mimetype if appropriate and
6201 // would fall onto the eEditorErrorCantEditMimeType path.
6202 return NS_OK;
6205 if (collapseSelectionAtBeginningOfDocument) {
6206 htmlEditor->BeginningOfDocument();
6209 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6210 nsContentUtils::AddScriptBlocker();
6214 mEditingState = newState;
6215 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6216 nsContentUtils::RemoveScriptBlocker();
6217 // If mEditingState is overwritten by another call and already disabled
6218 // the editing, we shouldn't keep making window editable.
6219 if (mEditingState == EditingState::eOff) {
6220 return NS_OK;
6224 if (makeWindowEditable) {
6225 // TODO: We should do this earlier in this method.
6226 // Previously, we called `ExecCommand` with `insertBrOnReturn` command
6227 // whose argument is false here. Then, if it returns error, we
6228 // stopped making it editable. However, after bug 1697078 fixed,
6229 // `ExecCommand` returns error only when the document is not XHTML's
6230 // nor HTML's. Therefore, we use same error handling for now.
6231 if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
6232 // Editor setup failed. Editing is not on after all.
6233 // XXX Should we reset the editable flag on nodes?
6234 editSession->TearDownEditorOnWindow(window);
6235 mEditingState = EditingState::eOff;
6236 return NS_ERROR_DOM_INVALID_STATE_ERR;
6238 // Set the editor to not insert <br> elements on return when in <p> elements
6239 // by default.
6240 htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
6243 // Resync the editor's spellcheck state.
6244 if (spellRecheckAll) {
6245 nsCOMPtr<nsISelectionController> selectionController =
6246 htmlEditor->GetSelectionController();
6247 if (NS_WARN_IF(!selectionController)) {
6248 return NS_ERROR_FAILURE;
6251 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
6252 nsISelectionController::SELECTION_SPELLCHECK);
6253 if (spellCheckSelection) {
6254 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
6257 htmlEditor->SyncRealTimeSpell();
6259 MaybeDispatchCheckKeyPressEventModelEvent();
6261 // If this document keeps having focus and the HTMLEditor is in the design
6262 // mode, it may not receive `focus` event for this editing state change since
6263 // this may occur without a focus change. Therefore, let's notify HTMLEditor
6264 // of this editing state change.
6265 if (thisDocumentHasFocus && htmlEditor->IsInDesignMode() &&
6266 ThisDocumentHasFocus()) {
6267 DebugOnly<nsresult> rvIgnored =
6268 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, nullptr);
6269 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6270 "HTMLEditor::FocusedElementOrDocumentBecomesEditable()"
6271 " failed, but ignored");
6274 return NS_OK;
6277 // Helper class, used below in ChangeContentEditableCount().
6278 class DeferredContentEditableCountChangeEvent : public Runnable {
6279 public:
6280 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
6281 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
6282 mDoc(aDoc),
6283 mElement(aElement) {}
6285 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
6286 if (mElement && mElement->OwnerDoc() == mDoc) {
6287 RefPtr<Document> doc = std::move(mDoc);
6288 RefPtr<Element> element = std::move(mElement);
6289 doc->DeferredContentEditableCountChange(element);
6291 return NS_OK;
6294 private:
6295 RefPtr<Document> mDoc;
6296 RefPtr<Element> mElement;
6299 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
6300 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
6301 "Trying to decrement too much.");
6303 mContentEditableCount += aChange;
6305 if (aElement) {
6306 nsContentUtils::AddScriptRunner(
6307 new DeferredContentEditableCountChangeEvent(this, aElement));
6311 void Document::DeferredContentEditableCountChange(Element* aElement) {
6312 const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
6313 const bool elementHasFocus =
6314 aElement && fm && fm->GetFocusedElement() == aElement;
6315 if (elementHasFocus) {
6316 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
6317 // When contenteditable of aElement is changed and HTMLEditor works with it
6318 // or needs to start working with it, HTMLEditor may not receive `focus`
6319 // event nor `blur` event because this may occur without a focus change.
6320 // Therefore, we need to notify HTMLEditor of this contenteditable attribute
6321 // change.
6322 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6323 if (aElement->HasFlag(NODE_IS_EDITABLE)) {
6324 if (htmlEditor) {
6325 DebugOnly<nsresult> rvIgnored =
6326 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this,
6327 aElement);
6328 NS_WARNING_ASSERTION(
6329 NS_SUCCEEDED(rvIgnored),
6330 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6331 "ignored");
6333 } else {
6334 DebugOnly<nsresult> rvIgnored =
6335 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6336 htmlEditor, *this, aElement);
6337 NS_WARNING_ASSERTION(
6338 NS_SUCCEEDED(rvIgnored),
6339 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, "
6340 "but ignored");
6344 if (mParser ||
6345 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
6346 return;
6349 EditingState oldState = mEditingState;
6351 nsresult rv = EditingStateChanged();
6352 NS_ENSURE_SUCCESS_VOID(rv);
6354 if (oldState == mEditingState &&
6355 mEditingState == EditingState::eContentEditable) {
6356 // We just changed the contentEditable state of a node, we need to reset
6357 // the spellchecking state of that node.
6358 if (aElement) {
6359 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6360 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
6361 rv = htmlEditor->GetInlineSpellChecker(false,
6362 getter_AddRefs(spellChecker));
6363 NS_ENSURE_SUCCESS_VOID(rv);
6365 if (spellChecker &&
6366 aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) {
6367 RefPtr<nsRange> range = nsRange::Create(aElement);
6368 IgnoredErrorResult res;
6369 range->SelectNode(*aElement, res);
6370 if (res.Failed()) {
6371 // The node might be detached from the document at this point,
6372 // which would cause this call to fail. In this case, we can
6373 // safely ignore the contenteditable count change.
6374 return;
6377 rv = spellChecker->SpellCheckRange(range);
6378 NS_ENSURE_SUCCESS_VOID(rv);
6384 // aElement causes creating new HTMLEditor and the element had and keep
6385 // having focus, the HTMLEditor won't receive `focus` event. Therefore, we
6386 // need to notify HTMLEditor of it becomes editable.
6387 if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) &&
6388 fm->GetFocusedElement() == aElement) {
6389 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6390 DebugOnly<nsresult> rvIgnored =
6391 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement);
6392 NS_WARNING_ASSERTION(
6393 NS_SUCCEEDED(rvIgnored),
6394 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6395 "ignored");
6400 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
6401 // Currently, we need to check only when we're becoming editable for
6402 // contenteditable.
6403 if (mEditingState != EditingState::eContentEditable) {
6404 return;
6407 if (mHasBeenEditable) {
6408 return;
6410 mHasBeenEditable = true;
6412 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
6413 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
6414 // with proper keypress event for the active web app.
6415 WidgetEvent checkEvent(true, eUnidentifiedEvent);
6416 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
6417 checkEvent.mFlags.mCancelable = false;
6418 checkEvent.mFlags.mBubbles = false;
6419 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
6420 // Post the event rather than dispatching it synchronously because we need
6421 // a call of SetKeyPressEventModel() before first key input. Therefore, we
6422 // can avoid paying unnecessary runtime cost for most web apps.
6423 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
6426 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
6427 PresShell* presShell = GetPresShell();
6428 if (!presShell) {
6429 return;
6431 presShell->SetKeyPressEventModel(aKeyPressEventModel);
6434 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
6436 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
6437 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
6438 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
6439 aFocusTime >= mLastFocusTime);
6440 mLastFocusTime = aFocusTime;
6443 void Document::GetReferrer(nsAString& aReferrer) const {
6444 aReferrer.Truncate();
6445 if (!mReferrerInfo) {
6446 return;
6449 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
6450 if (!referrer) {
6451 return;
6454 nsAutoCString uri;
6455 nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
6456 if (NS_WARN_IF(NS_FAILED(rv))) {
6457 return;
6460 CopyUTF8toUTF16(uri, aReferrer);
6463 void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
6464 aCookie.Truncate(); // clear current cookie in case service fails;
6465 // no cookie isn't an error condition.
6467 if (mDisableCookieAccess) {
6468 return;
6471 // If the document's sandboxed origin flag is set, then reading cookies
6472 // is prohibited.
6473 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6474 aRv.ThrowSecurityError(
6475 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6476 "flag.");
6477 return;
6480 StorageAccess storageAccess = CookieAllowedForDocument(this);
6481 if (storageAccess == StorageAccess::eDeny) {
6482 return;
6485 if (ShouldPartitionStorage(storageAccess) &&
6486 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6487 return;
6490 // If the document is a cookie-averse Document... return the empty string.
6491 if (IsCookieAverse()) {
6492 return;
6495 // not having a cookie service isn't an error
6496 nsCOMPtr<nsICookieService> service =
6497 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6498 if (service) {
6499 nsAutoCString cookie;
6500 service->GetCookieStringFromDocument(this, cookie);
6501 // CopyUTF8toUTF16 doesn't handle error
6502 // because it assumes that the input is valid.
6503 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
6507 void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
6508 if (mDisableCookieAccess) {
6509 return;
6512 // If the document's sandboxed origin flag is set, then setting cookies
6513 // is prohibited.
6514 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6515 aRv.ThrowSecurityError(
6516 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6517 "flag.");
6518 return;
6521 StorageAccess storageAccess = CookieAllowedForDocument(this);
6522 if (storageAccess == StorageAccess::eDeny) {
6523 return;
6526 if (ShouldPartitionStorage(storageAccess) &&
6527 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6528 return;
6531 // If the document is a cookie-averse Document... do nothing.
6532 if (IsCookieAverse()) {
6533 return;
6536 if (!mDocumentURI) {
6537 return;
6540 // not having a cookie service isn't an error
6541 nsCOMPtr<nsICookieService> service =
6542 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6543 if (!service) {
6544 return;
6547 NS_ConvertUTF16toUTF8 cookie(aCookie);
6548 nsresult rv = service->SetCookieStringFromDocument(this, cookie);
6550 // No warning messages here.
6551 if (NS_FAILED(rv)) {
6552 return;
6555 nsCOMPtr<nsIObserverService> observerService =
6556 mozilla::services::GetObserverService();
6557 if (observerService) {
6558 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
6559 nsString(aCookie).get());
6563 ReferrerPolicy Document::GetReferrerPolicy() const {
6564 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
6565 : ReferrerPolicy::_empty;
6568 void Document::GetAlinkColor(nsAString& aAlinkColor) {
6569 aAlinkColor.Truncate();
6571 HTMLBodyElement* body = GetBodyElement();
6572 if (body) {
6573 body->GetALink(aAlinkColor);
6577 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
6578 HTMLBodyElement* body = GetBodyElement();
6579 if (body) {
6580 body->SetALink(aAlinkColor);
6584 void Document::GetLinkColor(nsAString& aLinkColor) {
6585 aLinkColor.Truncate();
6587 HTMLBodyElement* body = GetBodyElement();
6588 if (body) {
6589 body->GetLink(aLinkColor);
6593 void Document::SetLinkColor(const nsAString& aLinkColor) {
6594 HTMLBodyElement* body = GetBodyElement();
6595 if (body) {
6596 body->SetLink(aLinkColor);
6600 void Document::GetVlinkColor(nsAString& aVlinkColor) {
6601 aVlinkColor.Truncate();
6603 HTMLBodyElement* body = GetBodyElement();
6604 if (body) {
6605 body->GetVLink(aVlinkColor);
6609 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
6610 HTMLBodyElement* body = GetBodyElement();
6611 if (body) {
6612 body->SetVLink(aVlinkColor);
6616 void Document::GetBgColor(nsAString& aBgColor) {
6617 aBgColor.Truncate();
6619 HTMLBodyElement* body = GetBodyElement();
6620 if (body) {
6621 body->GetBgColor(aBgColor);
6625 void Document::SetBgColor(const nsAString& aBgColor) {
6626 HTMLBodyElement* body = GetBodyElement();
6627 if (body) {
6628 body->SetBgColor(aBgColor);
6632 void Document::GetFgColor(nsAString& aFgColor) {
6633 aFgColor.Truncate();
6635 HTMLBodyElement* body = GetBodyElement();
6636 if (body) {
6637 body->GetText(aFgColor);
6641 void Document::SetFgColor(const nsAString& aFgColor) {
6642 HTMLBodyElement* body = GetBodyElement();
6643 if (body) {
6644 body->SetText(aFgColor);
6648 void Document::CaptureEvents() {
6649 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
6652 void Document::ReleaseEvents() {
6653 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
6656 HTMLAllCollection* Document::All() {
6657 if (!mAll) {
6658 mAll = new HTMLAllCollection(this);
6660 return mAll;
6663 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
6664 if (mIsSrcdocDocument) {
6665 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
6666 if (inStrmChan) {
6667 return inStrmChan->GetSrcdocData(aSrcdocData);
6670 aSrcdocData = VoidString();
6671 return NS_OK;
6674 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
6675 nsPIDOMWindowOuter* win = GetWindow();
6676 if (!win) {
6677 return nullptr;
6679 return WindowProxyHolder(win->GetBrowsingContext());
6682 nsIContent* Document::GetUnretargetedFocusedContent(
6683 IncludeChromeOnly aIncludeChromeOnly) const {
6684 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6685 if (!window) {
6686 return nullptr;
6688 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6689 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6690 window, nsFocusManager::eOnlyCurrentWindow,
6691 getter_AddRefs(focusedWindow));
6692 if (!focusedContent) {
6693 return nullptr;
6695 // be safe and make sure the element is from this document
6696 if (focusedContent->OwnerDoc() != this) {
6697 return nullptr;
6699 if (focusedContent->ChromeOnlyAccess() &&
6700 aIncludeChromeOnly == IncludeChromeOnly::No) {
6701 return focusedContent->FindFirstNonChromeOnlyAccessContent();
6703 return focusedContent;
6706 Element* Document::GetActiveElement() {
6707 // Get the focused element.
6708 Element* focusedElement = GetRetargetedFocusedElement();
6709 if (focusedElement) {
6710 return focusedElement;
6713 // No focused element anywhere in this document. Try to get the BODY.
6714 if (IsHTMLOrXHTML()) {
6715 Element* bodyElement = AsHTMLDocument()->GetBody();
6716 if (bodyElement) {
6717 return bodyElement;
6719 // Special case to handle the transition to XHTML from XUL documents
6720 // where there currently isn't a body element, but we need to match the
6721 // XUL behavior. This should be removed when bug 1540278 is resolved.
6722 if (nsContentUtils::IsChromeDoc(this)) {
6723 Element* docElement = GetDocumentElement();
6724 if (docElement && docElement->IsXULElement()) {
6725 return docElement;
6728 // Because of IE compatibility, return null when html document doesn't have
6729 // a body.
6730 return nullptr;
6733 // If we couldn't get a BODY, return the root element.
6734 return GetDocumentElement();
6737 Element* Document::GetCurrentScript() {
6738 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
6739 return el;
6742 void Document::ReleaseCapture() const {
6743 // only release the capture if the caller can access it. This prevents a
6744 // page from stopping a scrollbar grab for example.
6745 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
6746 if (node && nsContentUtils::CanCallerAccess(node)) {
6747 PresShell::ReleaseCapturingContent();
6751 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
6752 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
6753 return mChromeXHRDocBaseURI;
6756 return GetDocBaseURI();
6759 void Document::SetBaseURI(nsIURI* aURI) {
6760 if (!aURI && !mDocumentBaseURI) {
6761 return;
6764 // Don't do anything if the URI wasn't actually changed.
6765 if (aURI && mDocumentBaseURI) {
6766 bool equalBases = false;
6767 mDocumentBaseURI->Equals(aURI, &equalBases);
6768 if (equalBases) {
6769 return;
6773 mDocumentBaseURI = aURI;
6774 mCachedURLData = nullptr;
6775 RefreshLinkHrefs();
6778 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
6779 const nsAString& aURI) {
6780 RefPtr<nsIURI> resolvedURI;
6781 MOZ_TRY(
6782 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
6783 return OwningNonNull<nsIURI>(std::move(resolvedURI));
6786 nsIReferrerInfo* Document::ReferrerInfoForInternalCSSAndSVGResources() {
6787 if (!mCachedReferrerInfoForInternalCSSAndSVGResources) {
6788 mCachedReferrerInfoForInternalCSSAndSVGResources =
6789 ReferrerInfo::CreateForInternalCSSAndSVGResources(this);
6791 return mCachedReferrerInfoForInternalCSSAndSVGResources;
6794 URLExtraData* Document::DefaultStyleAttrURLData() {
6795 MOZ_ASSERT(NS_IsMainThread());
6796 if (!mCachedURLData) {
6797 mCachedURLData = new URLExtraData(
6798 GetDocBaseURI(), ReferrerInfoForInternalCSSAndSVGResources(),
6799 NodePrincipal());
6801 return mCachedURLData;
6804 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
6805 if (mCharacterSet != aEncoding) {
6806 mCharacterSet = aEncoding;
6807 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
6808 RecomputeLanguageFromCharset();
6810 if (nsPresContext* context = GetPresContext()) {
6811 context->DocumentCharSetChanged(aEncoding);
6816 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
6817 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
6820 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
6821 aData.Truncate();
6822 const HeaderData* data = mHeaderData.get();
6823 while (data) {
6824 if (data->mField == aHeaderField) {
6825 aData = data->mData;
6826 break;
6828 data = data->mNext.get();
6832 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
6833 if (!aHeaderField) {
6834 NS_ERROR("null headerField");
6835 return;
6838 if (!mHeaderData) {
6839 if (!aData.IsEmpty()) { // don't bother storing empty string
6840 mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
6842 } else {
6843 HeaderData* data = mHeaderData.get();
6844 UniquePtr<HeaderData>* lastPtr = &mHeaderData;
6845 bool found = false;
6846 do { // look for existing and replace
6847 if (data->mField == aHeaderField) {
6848 if (!aData.IsEmpty()) {
6849 data->mData.Assign(aData);
6850 } else { // don't store empty string
6851 // Note that data->mNext is moved to a temporary before the old value
6852 // of *lastPtr is deleted.
6853 *lastPtr = std::move(data->mNext);
6855 found = true;
6857 break;
6859 lastPtr = &data->mNext;
6860 data = lastPtr->get();
6861 } while (data);
6863 if (!aData.IsEmpty() && !found) {
6864 // didn't find, append
6865 *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
6869 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
6870 CopyUTF16toUTF8(aData, mContentLanguage);
6871 mMayNeedFontPrefsUpdate = true;
6872 if (auto* presContext = GetPresContext()) {
6873 presContext->ContentLanguageChanged();
6877 if (aHeaderField == nsGkAtoms::origin_trial) {
6878 mTrials.UpdateFromToken(aData, NodePrincipal());
6879 if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) {
6880 InitCOEP(mChannel);
6882 // If we still don't have a WindowContext, WindowContext::OnNewDocument
6883 // will take care of this.
6884 if (WindowContext* ctx = GetWindowContext()) {
6885 if (mEmbedderPolicy) {
6886 Unused << ctx->SetEmbedderPolicy(mEmbedderPolicy.value());
6892 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
6893 SetPreferredStyleSheetSet(aData);
6896 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
6897 // We get into this code before we have a script global yet, so get to our
6898 // container via mDocumentContainer.
6899 if (mDocumentContainer) {
6900 // Note: using mDocumentURI instead of mBaseURI here, for consistency
6901 // (used to just use the current URI of our webnavigation, but that
6902 // should really be the same thing). Note that this code can run
6903 // before the current URI of the webnavigation has been updated, so we
6904 // can't assert equality here.
6905 mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
6909 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
6910 mAllowDNSPrefetch) {
6911 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
6912 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
6915 if (aHeaderField == nsGkAtoms::handheldFriendly) {
6916 mViewportType = Unknown;
6920 void Document::SetEarlyHints(
6921 nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) {
6922 mEarlyHints = std::move(aEarlyHints);
6925 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
6926 NotNull<const Encoding*>& aEncoding,
6927 nsHtml5TreeOpExecutor* aExecutor) {
6928 if (aChannel) {
6929 nsAutoCString charsetVal;
6930 nsresult rv = aChannel->GetContentCharset(charsetVal);
6931 if (NS_SUCCEEDED(rv)) {
6932 const Encoding* preferred = Encoding::ForLabel(charsetVal);
6933 if (preferred) {
6934 if (aExecutor && preferred == REPLACEMENT_ENCODING) {
6935 aExecutor->ComplainAboutBogusProtocolCharset(this, false);
6937 aEncoding = WrapNotNull(preferred);
6938 aCharsetSource = kCharsetFromChannel;
6939 return;
6940 } else if (aExecutor && !charsetVal.IsEmpty()) {
6941 aExecutor->ComplainAboutBogusProtocolCharset(this, true);
6947 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
6948 #ifdef DEBUG
6949 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
6950 const Element* element = Element::FromNode(node);
6951 if (!element) {
6952 continue;
6954 MOZ_ASSERT(!element->HasServoData());
6956 #endif
6959 already_AddRefed<PresShell> Document::CreatePresShell(
6960 nsPresContext* aContext, nsViewManager* aViewManager) {
6961 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
6963 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
6965 AssertNoStaleServoDataIn(*this);
6967 RefPtr<PresShell> presShell = new PresShell(this);
6968 // Note: we don't hold a ref to the shell (it holds a ref to us)
6969 mPresShell = presShell;
6971 if (!mStyleSetFilled) {
6972 FillStyleSet();
6975 presShell->Init(aContext, aViewManager);
6976 if (RefPtr<class HighlightRegistry> highlightRegistry = mHighlightRegistry) {
6977 highlightRegistry->AddHighlightSelectionsToFrameSelection();
6979 // Gaining a shell causes changes in how media queries are evaluated, so
6980 // invalidate that.
6981 aContext->MediaFeatureValuesChanged(
6982 {MediaFeatureChange::kAllChanges},
6983 MediaFeatureChangePropagation::JustThisDocument);
6985 // Make sure to never paint if we belong to an invisible DocShell.
6986 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
6987 if (docShell && docShell->IsInvisible()) {
6988 presShell->SetNeverPainting(true);
6991 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
6992 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
6993 presShell.get(), docShell.get()));
6995 mExternalResourceMap.ShowViewers();
6997 UpdateFrameRequestCallbackSchedulingState();
6999 if (mDocumentL10n) {
7000 // In case we already accumulated mutations,
7001 // we'll trigger the refresh driver now.
7002 mDocumentL10n->OnCreatePresShell();
7005 if (HasAutoFocusCandidates()) {
7006 ScheduleFlushAutoFocusCandidates();
7008 // Now that we have a shell, we might have @font-face rules (the presence of a
7009 // shell may change which rules apply to us). We don't need to do anything
7010 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
7011 // is ready to update we'll flush the font set.
7012 MarkUserFontSetDirty();
7014 // Take the author style disabled state from the top browsing cvontext.
7015 // (PageStyleChild.sys.mjs ensures this is up to date.)
7016 if (BrowsingContext* bc = GetBrowsingContext()) {
7017 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
7020 return presShell.forget();
7023 void Document::UpdateFrameRequestCallbackSchedulingState(
7024 PresShell* aOldPresShell) {
7025 // If this condition changes to depend on some other variable, make sure to
7026 // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
7027 // that variable can change. Also consider if you should change
7028 // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
7029 // condition.
7030 bool shouldBeScheduled =
7031 WouldScheduleFrameRequestCallbacks() && !mFrameRequestManager.IsEmpty();
7032 if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
7033 // nothing to do
7034 return;
7037 PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
7038 MOZ_RELEASE_ASSERT(presShell);
7040 nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
7041 if (shouldBeScheduled) {
7042 rd->ScheduleFrameRequestCallbacks(this);
7043 } else {
7044 rd->RevokeFrameRequestCallbacks(this);
7047 mFrameRequestCallbacksScheduled = shouldBeScheduled;
7050 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
7051 MOZ_ASSERT(aCallbacks.IsEmpty());
7052 mFrameRequestManager.Take(aCallbacks);
7053 // No need to manually remove ourselves from the refresh driver; it will
7054 // handle that part. But we do have to update our state.
7055 mFrameRequestCallbacksScheduled = false;
7058 bool Document::ShouldThrottleFrameRequests() const {
7059 if (mStaticCloneCount > 0) {
7060 // Even if we're not visible, a static clone may be, so run at full speed.
7061 return false;
7064 if (Hidden()) {
7065 // We're not visible (probably in a background tab or the bf cache).
7066 return true;
7069 if (!mPresShell) {
7070 // Can't do anything smarter. We don't run frame requests in documents
7071 // without a pres shell anyways.
7072 return false;
7075 if (!mPresShell->IsActive()) {
7076 // The pres shell is not active (we're an invisible OOP iframe or such), so
7077 // throttle.
7078 return true;
7081 if (mPresShell->IsPaintingSuppressed()) {
7082 // Historically we have throttled frame requests until we've painted at
7083 // least once, so keep doing that.
7084 return true;
7087 Element* el = GetEmbedderElement();
7088 if (!el) {
7089 // If we're not in-process, our refresh driver is throttled separately (via
7090 // PresShell::SetIsActive, so not much more we can do here.
7091 return false;
7094 if (!StaticPrefs::layout_throttle_in_process_iframes()) {
7095 return false;
7098 // Note that because we have to scroll this document into view at least once
7099 // to unthrottle it, we will drop one requestAnimationFrame frame when a
7100 // document that previously wasn't visible scrolls into view. This is
7101 // acceptable / unlikely to be human-perceivable, though we could improve on
7102 // it if needed by adding an intersection margin or something of that sort.
7103 const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
7104 *el->OwnerDoc(), /* aRoot = */ nullptr, /* aMargin = */ nullptr);
7105 const IntersectionOutput output =
7106 DOMIntersectionObserver::Intersect(input, *el);
7107 return !output.Intersects();
7110 void Document::DeletePresShell() {
7111 mExternalResourceMap.HideViewers();
7112 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
7113 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
7114 presContext->RefreshDriver()->CancelFlushAutoFocus(this);
7117 // When our shell goes away, request that all our images be immediately
7118 // discarded, so we don't carry around decoded image data for a document we
7119 // no longer intend to paint.
7120 ImageTracker()->RequestDiscardAll();
7122 // Now that we no longer have a shell, we need to forget about any FontFace
7123 // objects for @font-face rules that came from the style set. There's no need
7124 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
7125 // no point on it.
7126 MarkUserFontSetDirty();
7128 if (mResizeObserverController) {
7129 mResizeObserverController->ShellDetachedFromDocument();
7132 if (IsEditingOn()) {
7133 TurnEditingOff();
7136 PresShell* oldPresShell = mPresShell;
7137 mPresShell = nullptr;
7138 UpdateFrameRequestCallbackSchedulingState(oldPresShell);
7140 ClearStaleServoData();
7141 AssertNoStaleServoDataIn(*this);
7143 mStyleSet->ShellDetachedFromDocument();
7144 mStyleSetFilled = false;
7145 mQuirkSheetAdded = false;
7146 mContentEditableSheetAdded = false;
7147 mDesignModeSheetAdded = false;
7150 void Document::DisallowBFCaching(uint32_t aStatus) {
7151 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
7152 if (!mBFCacheDisallowed) {
7153 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
7154 wgc->SendUpdateBFCacheStatus(aStatus, 0);
7157 mBFCacheDisallowed = true;
7160 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
7161 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
7163 if (mPresShell) {
7164 if (aEntry) {
7165 mPresShell->StopObservingRefreshDriver();
7166 } else if (mBFCacheEntry) {
7167 mPresShell->StartObservingRefreshDriver();
7170 mBFCacheEntry = aEntry;
7173 bool Document::RemoveFromBFCacheSync() {
7174 bool removed = false;
7175 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
7176 entry->RemoveFromBFCacheSync();
7177 removed = true;
7178 } else if (!IsCurrentActiveDocument()) {
7179 // In the old bfcache implementation while the new page is loading, but
7180 // before nsIContentViewer.show() has been called, the previous page doesn't
7181 // yet have nsIBFCacheEntry. However, the previous page isn't the current
7182 // active document anymore.
7183 DisallowBFCaching();
7184 removed = true;
7187 if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
7188 if (BrowsingContext* bc = GetBrowsingContext()) {
7189 if (bc->IsInBFCache()) {
7190 ContentChild* cc = ContentChild::GetSingleton();
7191 // IPC is asynchronous but the caller is supposed to check the return
7192 // value. The reason for 'Sync' in the method name is that the old
7193 // implementation may run scripts. There is Async variant in
7194 // the old session history implementation for the cases where
7195 // synchronous operation isn't safe.
7196 cc->SendRemoveFromBFCache(bc->Top());
7197 removed = true;
7201 return removed;
7204 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
7205 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
7207 NS_RELEASE(e->mKey);
7208 if (e->mSubDocument) {
7209 e->mSubDocument->SetParentDocument(nullptr);
7210 NS_RELEASE(e->mSubDocument);
7214 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
7215 SubDocMapEntry* e =
7216 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
7218 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
7219 NS_ADDREF(e->mKey);
7221 e->mSubDocument = nullptr;
7224 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
7225 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
7227 if (!aSubDoc) {
7228 // aSubDoc is nullptr, remove the mapping
7230 if (mSubDocuments) {
7231 mSubDocuments->Remove(aElement);
7233 } else {
7234 if (!mSubDocuments) {
7235 // Create a new hashtable
7237 static const PLDHashTableOps hash_table_ops = {
7238 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
7239 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
7241 mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
7244 // Add a mapping to the hash table
7245 auto entry =
7246 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
7248 if (!entry) {
7249 return NS_ERROR_OUT_OF_MEMORY;
7252 if (entry->mSubDocument) {
7253 entry->mSubDocument->SetParentDocument(nullptr);
7255 // Release the old sub document
7256 NS_RELEASE(entry->mSubDocument);
7259 entry->mSubDocument = aSubDoc;
7260 NS_ADDREF(entry->mSubDocument);
7262 aSubDoc->SetParentDocument(this);
7265 return NS_OK;
7268 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
7269 if (mSubDocuments && aContent->IsElement()) {
7270 auto entry = static_cast<SubDocMapEntry*>(
7271 mSubDocuments->Search(aContent->AsElement()));
7273 if (entry) {
7274 return entry->mSubDocument;
7278 return nullptr;
7281 Element* Document::GetEmbedderElement() const {
7282 // We check if we're the active document in our BrowsingContext
7283 // by comparing against its document, rather than checking if the
7284 // WindowContext is cached, since mWindow may be null when we're
7285 // called (such as in nsPresContext::Init).
7286 if (BrowsingContext* bc = GetBrowsingContext()) {
7287 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
7290 return nullptr;
7293 Element* Document::GetRootElement() const {
7294 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
7295 ? mCachedRootElement
7296 : GetRootElementInternal();
7299 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
7301 Element* Document::GetRootElementInternal() const {
7302 // We invoke GetRootElement() immediately before the servo traversal, so we
7303 // should always have a cache hit from Servo.
7304 MOZ_ASSERT(NS_IsMainThread());
7306 // Loop backwards because any non-elements, such as doctypes and PIs
7307 // are likely to appear before the root element.
7308 for (nsIContent* child = GetLastChild(); child;
7309 child = child->GetPreviousSibling()) {
7310 if (Element* element = Element::FromNode(child)) {
7311 const_cast<Document*>(this)->mCachedRootElement = element;
7312 return element;
7316 const_cast<Document*>(this)->mCachedRootElement = nullptr;
7317 return nullptr;
7320 void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
7321 bool aNotify, ErrorResult& aRv) {
7322 if (aKid->IsElement() && GetRootElement()) {
7323 NS_WARNING("Inserting root element when we already have one");
7324 aRv.ThrowHierarchyRequestError("There is already a root element.");
7325 return;
7328 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
7331 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
7332 Maybe<mozAutoDocUpdate> updateBatch;
7333 if (aKid->IsElement()) {
7334 updateBatch.emplace(this, aNotify);
7335 // Destroy the link map up front before we mess with the child list.
7336 DestroyElementMaps();
7339 // Preemptively clear mCachedRootElement, since we may be about to remove it
7340 // from our child list, and we don't want to return this maybe-obsolete value
7341 // from any GetRootElement() calls that happen inside of RemoveChildNode().
7342 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
7343 // GetRootElement() calls until after it's removed the child from mChildren.
7344 // Any call before that point would restore this soon-to-be-obsolete cached
7345 // answer, and our clearing here would be fruitless.)
7346 mCachedRootElement = nullptr;
7347 nsINode::RemoveChildNode(aKid, aNotify);
7348 MOZ_ASSERT(mCachedRootElement != aKid,
7349 "Stale pointer in mCachedRootElement, after we tried to clear it "
7350 "(maybe somebody called GetRootElement() too early?)");
7353 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
7354 if (mStyleSetFilled) {
7355 mStyleSet->AddDocStyleSheet(aSheet);
7356 ApplicableStylesChanged();
7360 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
7361 mStyleSet->RecordShadowStyleChange(aShadowRoot);
7362 ApplicableStylesChanged();
7365 void Document::ApplicableStylesChanged() {
7366 // TODO(emilio): if we decide to resolve style in display: none iframes, then
7367 // we need to always track style changes and remove the mStyleSetFilled.
7368 if (!mStyleSetFilled) {
7369 return;
7372 MarkUserFontSetDirty();
7373 PresShell* ps = GetPresShell();
7374 if (!ps) {
7375 return;
7378 ps->EnsureStyleFlush();
7379 nsPresContext* pc = ps->GetPresContext();
7380 if (!pc) {
7381 return;
7384 pc->MarkCounterStylesDirty();
7385 pc->MarkFontFeatureValuesDirty();
7386 pc->MarkFontPaletteValuesDirty();
7387 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
7390 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
7391 if (mStyleSetFilled) {
7392 mStyleSet->RemoveStyleSheet(aSheet);
7393 ApplicableStylesChanged();
7397 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
7398 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
7400 if (aSheet.IsApplicable()) {
7401 AddStyleSheetToStyleSets(aSheet);
7405 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
7406 const bool applicable = aSheet.IsApplicable();
7407 // If we're actually in the document style sheet list
7408 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
7409 if (applicable) {
7410 AddStyleSheetToStyleSets(aSheet);
7411 } else {
7412 RemoveStyleSheetFromStyleSets(aSheet);
7417 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
7418 if (!StyleSheetChangeEventsEnabled()) {
7419 return;
7422 StyleSheetApplicableStateChangeEventInit init;
7423 init.mBubbles = true;
7424 init.mCancelable = true;
7425 init.mStylesheet = &aSheet;
7426 init.mApplicable = aSheet.IsApplicable();
7428 RefPtr<StyleSheetApplicableStateChangeEvent> event =
7429 StyleSheetApplicableStateChangeEvent::Constructor(
7430 this, u"StyleSheetApplicableStateChanged"_ns, init);
7431 event->SetTrusted(true);
7432 event->SetTarget(this);
7433 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7434 new AsyncEventDispatcher(this, event);
7435 asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
7436 asyncDispatcher->PostDOMEvent();
7439 void Document::PostStyleSheetRemovedEvent(StyleSheet& aSheet) {
7440 if (!StyleSheetChangeEventsEnabled()) {
7441 return;
7444 StyleSheetRemovedEventInit init;
7445 init.mBubbles = true;
7446 init.mCancelable = false;
7447 init.mStylesheet = &aSheet;
7449 RefPtr<StyleSheetRemovedEvent> event =
7450 StyleSheetRemovedEvent::Constructor(this, u"StyleSheetRemoved"_ns, init);
7451 event->SetTrusted(true);
7452 event->SetTarget(this);
7453 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7454 new AsyncEventDispatcher(this, event);
7455 asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
7456 asyncDispatcher->PostDOMEvent();
7459 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
7460 nsIURI* aSheetURI) {
7461 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
7462 bool bEqual;
7463 nsIURI* uri = aSheets[i]->GetSheetURI();
7465 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
7466 return i;
7469 return -1;
7472 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
7473 nsIURI* aSheetURI) {
7474 MOZ_ASSERT(aSheetURI, "null arg");
7476 // Checking if we have loaded this one already.
7477 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
7478 return NS_ERROR_INVALID_ARG;
7480 // Loading the sheet sync.
7481 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
7483 css::SheetParsingMode parsingMode;
7484 switch (aType) {
7485 case Document::eAgentSheet:
7486 parsingMode = css::eAgentSheetFeatures;
7487 break;
7489 case Document::eUserSheet:
7490 parsingMode = css::eUserSheetFeatures;
7491 break;
7493 case Document::eAuthorSheet:
7494 parsingMode = css::eAuthorSheetFeatures;
7495 break;
7497 default:
7498 MOZ_CRASH("impossible value for aType");
7501 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
7502 css::Loader::UseSystemPrincipal::Yes);
7503 if (result.isErr()) {
7504 return result.unwrapErr();
7507 RefPtr<StyleSheet> sheet = result.unwrap();
7509 sheet->SetAssociatedDocumentOrShadowRoot(this);
7510 MOZ_ASSERT(sheet->IsApplicable());
7512 return AddAdditionalStyleSheet(aType, sheet);
7515 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
7516 StyleSheet* aSheet) {
7517 if (mAdditionalSheets[aType].Contains(aSheet)) {
7518 return NS_ERROR_INVALID_ARG;
7521 if (!aSheet->IsApplicable()) {
7522 return NS_ERROR_INVALID_ARG;
7525 mAdditionalSheets[aType].AppendElement(aSheet);
7527 if (mStyleSetFilled) {
7528 mStyleSet->AppendStyleSheet(*aSheet);
7529 ApplicableStylesChanged();
7531 return NS_OK;
7534 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
7535 nsIURI* aSheetURI) {
7536 MOZ_ASSERT(aSheetURI);
7538 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
7540 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
7541 if (i >= 0) {
7542 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
7543 sheets.RemoveElementAt(i);
7545 if (!mIsGoingAway) {
7546 MOZ_ASSERT(sheetRef->IsApplicable());
7547 if (mStyleSetFilled) {
7548 mStyleSet->RemoveStyleSheet(*sheetRef);
7549 ApplicableStylesChanged();
7552 sheetRef->ClearAssociatedDocumentOrShadowRoot();
7556 nsIGlobalObject* Document::GetScopeObject() const {
7557 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
7558 return scope;
7561 DocGroup* Document::GetDocGroupOrCreate() {
7562 if (!mDocGroup && GetBrowsingContext()) {
7563 BrowsingContextGroup* group = GetBrowsingContext()->Group();
7564 MOZ_ASSERT(group);
7566 nsAutoCString docGroupKey;
7567 nsresult rv = mozilla::dom::DocGroup::GetKey(
7568 NodePrincipal(), group->IsPotentiallyCrossOriginIsolated(),
7569 docGroupKey);
7570 if (NS_SUCCEEDED(rv)) {
7571 mDocGroup = group->AddDocument(docGroupKey, this);
7574 return mDocGroup;
7577 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
7578 mScopeObject = do_GetWeakReference(aGlobal);
7579 if (aGlobal) {
7580 mHasHadScriptHandlingObject = true;
7582 nsPIDOMWindowInner* window = aGlobal->AsInnerWindow();
7583 if (!window) {
7584 return;
7587 // Same origin data documents should have the same docGroup as their scope
7588 // window.
7589 if (mLoadedAsData && window->GetExtantDoc() &&
7590 window->GetExtantDoc() != this &&
7591 window->GetExtantDoc()->NodePrincipal() == NodePrincipal()) {
7592 DocGroup* docGroup = window->GetExtantDoc()->GetDocGroup();
7594 if (docGroup) {
7595 if (!mDocGroup) {
7596 mDocGroup = docGroup;
7597 mDocGroup->AddDocument(this);
7598 } else {
7599 MOZ_ASSERT(mDocGroup == docGroup,
7600 "Data document has a mismatched doc group?");
7602 #ifdef DEBUG
7603 AssertDocGroupMatchesKey();
7604 #endif
7605 return;
7608 MOZ_ASSERT_UNREACHABLE(
7609 "Scope window doesn't have DocGroup when creating data document?");
7610 // ... but fall through to be safe.
7613 BrowsingContextGroup* browsingContextGroup =
7614 window->GetBrowsingContextGroup();
7616 // We should already have the principal, and now that we have been added
7617 // to a window, we should be able to join a DocGroup!
7618 nsAutoCString docGroupKey;
7619 nsresult rv = mozilla::dom::DocGroup::GetKey(
7620 NodePrincipal(),
7621 browsingContextGroup->IsPotentiallyCrossOriginIsolated(), docGroupKey);
7622 if (mDocGroup) {
7623 if (NS_SUCCEEDED(rv)) {
7624 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
7626 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
7627 browsingContextGroup);
7628 } else {
7629 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
7631 MOZ_ASSERT(mDocGroup);
7634 MOZ_ASSERT_IF(
7635 mNodeInfoManager->GetArenaAllocator(),
7636 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
7640 bool Document::ContainsEMEContent() {
7641 nsPIDOMWindowInner* win = GetInnerWindow();
7642 // Note this case is different from checking just media elements in that
7643 // it covers when we've created MediaKeys but not associated them with a
7644 // media element.
7645 return win && win->HasActiveMediaKeysInstance();
7648 bool Document::ContainsMSEContent() {
7649 bool containsMSE = false;
7651 auto check = [&containsMSE](nsISupports* aSupports) {
7652 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7653 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7654 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
7655 if (ms) {
7656 containsMSE = true;
7661 EnumerateActivityObservers(check);
7662 return containsMSE;
7665 static void NotifyActivityChangedCallback(nsISupports* aSupports) {
7666 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7667 if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7668 mediaElem->NotifyOwnerDocumentActivityChanged();
7670 nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
7671 do_QueryInterface(aSupports));
7672 if (objectLoadingContent) {
7673 nsObjectLoadingContent* olc =
7674 static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
7675 olc->NotifyOwnerDocumentActivityChanged();
7677 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
7678 do_QueryInterface(aSupports));
7679 if (objectDocumentActivity) {
7680 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
7681 } else {
7682 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
7683 do_QueryInterface(aSupports));
7684 if (imageLoadingContent) {
7685 auto ilc = static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
7686 ilc->NotifyOwnerDocumentActivityChanged();
7691 void Document::NotifyActivityChanged() {
7692 EnumerateActivityObservers(NotifyActivityChangedCallback);
7695 bool Document::IsTopLevelWindowInactive() const {
7696 if (BrowsingContext* bc = GetBrowsingContext()) {
7697 return !bc->GetIsActiveBrowserWindow();
7700 return false;
7703 void Document::SetContainer(nsDocShell* aContainer) {
7704 if (aContainer) {
7705 mDocumentContainer = aContainer;
7706 } else {
7707 mDocumentContainer = WeakPtr<nsDocShell>();
7710 mInChromeDocShell =
7711 aContainer && aContainer->GetBrowsingContext()->IsChrome();
7713 NotifyActivityChanged();
7715 // IsTopLevelWindowInactive depends on the docshell, so
7716 // update the cached value now that it's available.
7717 UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false);
7718 if (!aContainer) {
7719 return;
7722 BrowsingContext* context = aContainer->GetBrowsingContext();
7723 MOZ_ASSERT_IF(context && mDocGroup,
7724 context->Group() == mDocGroup->GetBrowsingContextGroup());
7725 if (context && context->IsContent()) {
7726 SetIsTopLevelContentDocument(context->IsTopContent());
7727 SetIsContentDocument(true);
7728 } else {
7729 SetIsTopLevelContentDocument(false);
7730 SetIsContentDocument(false);
7734 nsISupports* Document::GetContainer() const {
7735 return static_cast<nsIDocShell*>(mDocumentContainer);
7738 void Document::SetScriptGlobalObject(
7739 nsIScriptGlobalObject* aScriptGlobalObject) {
7740 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
7741 mAnimationController->IsPausedByType(
7742 SMILTimeContainer::PAUSE_PAGEHIDE |
7743 SMILTimeContainer::PAUSE_BEGIN),
7744 "Clearing window pointer while animations are unpaused");
7746 if (mScriptGlobalObject && !aScriptGlobalObject) {
7747 // We're detaching from the window. We need to grab a pointer to
7748 // our layout history state now.
7749 mLayoutHistoryState = GetLayoutHistoryState();
7751 // Also make sure to remove our onload blocker now if we haven't done it yet
7752 if (mOnloadBlockCount != 0) {
7753 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
7754 if (loadGroup) {
7755 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
7759 if (GetController().isSome()) {
7760 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
7761 loader->ClearCacheForControlledDocument(this);
7764 // We may become controlled again if this document comes back out
7765 // of bfcache. Clear our state to allow that to happen. Only
7766 // clear this flag if we are actually controlled, though, so pages
7767 // that were force reloaded don't become controlled when they
7768 // come out of bfcache.
7769 mMaybeServiceWorkerControlled = false;
7772 if (GetWindowContext()) {
7773 // The document is about to lose its window, so this is a good time to
7774 // send our page use counters, while we still have access to our
7775 // WindowContext.
7777 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
7778 // catches some cases of documents losing their window that don't
7779 // get in here.)
7780 SendPageUseCounters();
7784 // BlockOnload() might be called before mScriptGlobalObject is set.
7785 // We may need to add the blocker once mScriptGlobalObject is set.
7786 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
7788 mScriptGlobalObject = aScriptGlobalObject;
7790 if (needOnloadBlocker) {
7791 EnsureOnloadBlocker();
7794 UpdateFrameRequestCallbackSchedulingState();
7796 if (aScriptGlobalObject) {
7797 // Go back to using the docshell for the layout history state
7798 mLayoutHistoryState = nullptr;
7799 SetScopeObject(aScriptGlobalObject);
7800 mHasHadDefaultView = true;
7802 if (mAllowDNSPrefetch) {
7803 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7804 if (docShell) {
7805 #ifdef DEBUG
7806 nsCOMPtr<nsIWebNavigation> webNav =
7807 do_GetInterface(aScriptGlobalObject);
7808 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
7809 "Unexpected container or script global?");
7810 #endif
7811 bool allowDNSPrefetch;
7812 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
7813 mAllowDNSPrefetch = allowDNSPrefetch;
7817 // If we are set in a window that is already focused we should remember this
7818 // as the time the document gained focus.
7819 if (HasFocus(IgnoreErrors())) {
7820 SetLastFocusTime(TimeStamp::Now());
7824 // Remember the pointer to our window (or lack there of), to avoid
7825 // having to QI every time it's asked for.
7826 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
7827 mWindow = window;
7829 // Now that we know what our window is, we can flush the CSP errors to the
7830 // Web Console. We are flushing all messages that occurred and were stored in
7831 // the queue prior to this point.
7832 if (mCSP) {
7833 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
7836 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
7837 do_QueryInterface(GetChannel());
7838 if (internalChannel) {
7839 nsCOMArray<nsISecurityConsoleMessage> messages;
7840 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
7841 MOZ_ASSERT(NS_SUCCEEDED(rv));
7842 SendToConsole(messages);
7845 // Set our visibility state, but do not fire the event. This is correct
7846 // because either we're coming out of bfcache (in which case IsVisible() will
7847 // still test false at this point and no state change will happen) or we're
7848 // doing the initial document load and don't want to fire the event for this
7849 // change.
7851 // When the visibility is changed, notify it to observers.
7852 // Some observers need the notification, for example HTMLMediaElement uses
7853 // it to update internal media resource allocation.
7854 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
7855 // creation are already done before Document::SetScriptGlobalObject() call.
7856 // MediaDecoder decides whether starting decoding is decided based on
7857 // document's visibility. When the MediaDecoder is created,
7858 // Document::SetScriptGlobalObject() is not yet called and document is
7859 // hidden state. Therefore the MediaDecoder decides that decoding is
7860 // not yet necessary. But soon after Document::SetScriptGlobalObject()
7861 // call, the document becomes not hidden. At the time, MediaDecoder needs
7862 // to know it and needs to start updating decoding.
7863 UpdateVisibilityState(DispatchVisibilityChange::No);
7865 // The global in the template contents owner document should be the same.
7866 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
7867 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
7870 // Tell the script loader about the new global object.
7871 if (mScriptLoader && !IsTemplateContentsOwner()) {
7872 mScriptLoader->SetGlobalObject(mScriptGlobalObject);
7875 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
7876 mScriptGlobalObject && GetChannel()) {
7877 // If we are shift-reloaded, don't associate with a ServiceWorker.
7878 if (mDocumentContainer->IsForceReloading()) {
7879 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
7880 return;
7883 mMaybeServiceWorkerControlled = true;
7887 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
7888 MOZ_ASSERT(!mScriptGlobalObject,
7889 "Do not call this when mScriptGlobalObject is set!");
7890 if (mHasHadDefaultView) {
7891 return nullptr;
7894 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
7895 do_QueryReferent(mScopeObject);
7896 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
7897 if (win) {
7898 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
7899 if (!outer || outer->GetCurrentInnerWindow() != win) {
7900 NS_WARNING("Wrong inner/outer window combination!");
7901 return nullptr;
7904 return scriptHandlingObject;
7906 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
7907 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
7908 "Wrong script object!");
7909 if (aScriptObject) {
7910 SetScopeObject(aScriptObject);
7911 mHasHadDefaultView = false;
7915 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
7916 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
7917 // Let's use mScriptGlobalObject. Even if the document is already removed from
7918 // the docshell, the outer window might be still obtainable from the it.
7919 nsCOMPtr<nsPIDOMWindowOuter> win;
7920 if (mRemovedFromDocShell) {
7921 // The docshell returns the outer window we are done.
7922 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
7923 if (kungFuDeathGrip) {
7924 win = kungFuDeathGrip->GetWindow();
7926 } else {
7927 if (nsCOMPtr<nsPIDOMWindowInner> inner =
7928 do_QueryInterface(mScriptGlobalObject)) {
7929 // mScriptGlobalObject is always the inner window, let's get the outer.
7930 win = inner->GetOuterWindow();
7934 return win;
7937 bool Document::InternalAllowXULXBL() {
7938 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
7939 mAllowXULXBL = eTriTrue;
7940 return true;
7943 mAllowXULXBL = eTriFalse;
7944 return false;
7947 // Note: We don't hold a reference to the document observer; we assume
7948 // that it has a live reference to the document.
7949 void Document::AddObserver(nsIDocumentObserver* aObserver) {
7950 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
7951 "Observer already in the list");
7952 mObservers.AppendElement(aObserver);
7953 AddMutationObserver(aObserver);
7956 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
7957 // If we're in the process of destroying the document (and we're
7958 // informing the observers of the destruction), don't remove the
7959 // observers from the list. This is not a big deal, since we
7960 // don't hold a live reference to the observers.
7961 if (!mInDestructor) {
7962 RemoveMutationObserver(aObserver);
7963 return mObservers.RemoveElement(aObserver);
7966 return mObservers.Contains(aObserver);
7969 void Document::BeginUpdate() {
7970 ++mUpdateNestLevel;
7971 nsContentUtils::AddScriptBlocker();
7972 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
7975 void Document::EndUpdate() {
7976 const bool reset = !mPendingMaybeEditingStateChanged;
7977 mPendingMaybeEditingStateChanged = true;
7979 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
7981 --mUpdateNestLevel;
7983 nsContentUtils::RemoveScriptBlocker();
7985 if (mXULBroadcastManager) {
7986 mXULBroadcastManager->MaybeBroadcast();
7989 if (reset) {
7990 mPendingMaybeEditingStateChanged = false;
7992 MaybeEditingStateChanged();
7995 void Document::BeginLoad() {
7996 if (IsEditingOn()) {
7997 // Reset() blows away all event listeners in the document, and our
7998 // editor relies heavily on those. Midas is turned on, to make it
7999 // work, re-initialize it to give it a chance to add its event
8000 // listeners again.
8002 TurnEditingOff();
8003 EditingStateChanged();
8006 MOZ_ASSERT(!mDidCallBeginLoad);
8007 mDidCallBeginLoad = true;
8009 // Block onload here to prevent having to deal with blocking and
8010 // unblocking it while we know the document is loading.
8011 BlockOnload();
8012 mDidFireDOMContentLoaded = false;
8013 BlockDOMContentLoaded();
8015 if (mScriptLoader) {
8016 mScriptLoader->BeginDeferringScripts();
8019 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
8022 void Document::MozSetImageElement(const nsAString& aImageElementId,
8023 Element* aElement) {
8024 if (aImageElementId.IsEmpty()) return;
8026 // Hold a script blocker while calling SetImageElement since that can call
8027 // out to id-observers
8028 nsAutoScriptBlocker scriptBlocker;
8030 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
8031 if (entry) {
8032 entry->SetImageElement(aElement);
8033 if (entry->IsEmpty()) {
8034 mIdentifierMap.RemoveEntry(entry);
8039 void Document::DispatchContentLoadedEvents() {
8040 // If you add early returns from this method, make sure you're
8041 // calling UnblockOnload properly.
8043 // Unpin references to preloaded images
8044 mPreloadingImages.Clear();
8046 // DOM manipulation after content loaded should not care if the element
8047 // came from the preloader.
8048 mPreloadedPreconnects.Clear();
8050 if (mTiming) {
8051 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
8054 // Dispatch observer notification to notify observers document is interactive.
8055 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
8056 if (os) {
8057 nsIPrincipal* principal = NodePrincipal();
8058 os->NotifyObservers(ToSupports(this),
8059 principal->IsSystemPrincipal()
8060 ? "chrome-document-interactive"
8061 : "content-document-interactive",
8062 nullptr);
8065 // Fire a DOM event notifying listeners that this document has been
8066 // loaded (excluding images and other loads initiated by this
8067 // document).
8068 nsContentUtils::DispatchTrustedEvent(this, this, u"DOMContentLoaded"_ns,
8069 CanBubble::eYes, Cancelable::eNo);
8071 if (auto* const window = GetInnerWindow()) {
8072 const RefPtr<ServiceWorkerContainer> serviceWorker =
8073 window->Navigator()->ServiceWorker();
8075 // This could cause queued messages from a service worker to get
8076 // dispatched on serviceWorker.
8077 serviceWorker->StartMessages();
8080 if (MayStartLayout()) {
8081 MaybeResolveReadyForIdle();
8084 nsIDocShell* docShell = GetDocShell();
8086 if (TimelineConsumers::HasConsumer(docShell)) {
8087 TimelineConsumers::AddMarkerForDocShell(
8088 docShell,
8089 MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
8092 if (mTiming) {
8093 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
8096 // If this document is a [i]frame, fire a DOMFrameContentLoaded
8097 // event on all parent documents notifying that the HTML (excluding
8098 // other external files such as images and stylesheets) in a frame
8099 // has finished loading.
8101 // target_frame is the [i]frame element that will be used as the
8102 // target for the event. It's the [i]frame whose content is done
8103 // loading.
8104 nsCOMPtr<Element> target_frame = GetEmbedderElement();
8106 if (target_frame && target_frame->IsInComposedDoc()) {
8107 nsCOMPtr<Document> parent = target_frame->OwnerDoc();
8108 while (parent) {
8109 RefPtr<Event> event;
8110 if (parent) {
8111 IgnoredErrorResult ignored;
8112 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
8115 if (event) {
8116 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
8118 event->SetTarget(target_frame);
8119 event->SetTrusted(true);
8121 // To dispatch this event we must manually call
8122 // EventDispatcher::Dispatch() on the ancestor document since the
8123 // target is not in the same document, so the event would never reach
8124 // the ancestor document if we used the normal event
8125 // dispatching code.
8127 WidgetEvent* innerEvent = event->WidgetEventPtr();
8128 if (innerEvent) {
8129 nsEventStatus status = nsEventStatus_eIgnore;
8131 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
8132 EventDispatcher::Dispatch(parent, context, innerEvent, event,
8133 &status);
8138 parent = parent->GetInProcessParentDocument();
8142 nsPIDOMWindowInner* inner = GetInnerWindow();
8143 if (inner) {
8144 inner->NoteDOMContentLoaded();
8147 // TODO
8148 if (mMaybeServiceWorkerControlled) {
8149 using mozilla::dom::ServiceWorkerManager;
8150 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
8151 if (swm) {
8152 Maybe<ClientInfo> clientInfo = GetClientInfo();
8153 if (clientInfo.isSome()) {
8154 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
8159 if (mSetCompleteAfterDOMContentLoaded) {
8160 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
8161 mSetCompleteAfterDOMContentLoaded = false;
8164 UnblockOnload(true);
8167 void Document::EndLoad() {
8168 bool turnOnEditing =
8169 mParser && (IsInDesignMode() || mContentEditableCount > 0);
8171 #if defined(DEBUG)
8172 // only assert if nothing stopped the load on purpose
8173 if (!mParserAborted) {
8174 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
8176 #endif
8178 // EndLoad may have been called without a matching call to BeginLoad, in the
8179 // case of a failed parse (for example, due to timeout). In such a case, we
8180 // still want to execute part of this code to do appropriate cleanup, but we
8181 // gate part of it because it is intended to match 1-for-1 with calls to
8182 // BeginLoad. We have an explicit flag bit for this purpose, since it's
8183 // complicated and error prone to derive this condition from other related
8184 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
8186 // Part 1: Code that always executes to cleanup end of parsing, whether
8187 // that parsing was successful or not.
8189 // Drop the ref to our parser, if any, but keep hold of the sink so that we
8190 // can flush it from FlushPendingNotifications as needed. We might have to
8191 // do that to get a StartLayout() to happen.
8192 if (mParser) {
8193 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
8194 mParser = nullptr;
8197 // Update the attributes on the PerformanceNavigationTiming before notifying
8198 // the onload observers.
8199 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
8200 if (RefPtr<Performance> performance = window->GetPerformance()) {
8201 performance->UpdateNavigationTimingEntry();
8205 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
8207 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
8209 if (!mDidCallBeginLoad) {
8210 return;
8212 mDidCallBeginLoad = false;
8214 UnblockDOMContentLoaded();
8216 if (turnOnEditing) {
8217 EditingStateChanged();
8220 if (!GetWindow()) {
8221 // This is a document that's not in a window. For example, this could be an
8222 // XMLHttpRequest responseXML document, or a document created via DOMParser
8223 // or DOMImplementation. We don't reach this code normally for such
8224 // documents (which is not obviously correct), but can reach it via
8225 // document.open()/document.close().
8227 // Such documents don't fire load events, but per spec should set their
8228 // readyState to "complete" when parsing and all loading of subresources is
8229 // done. Parsing is done now, and documents not in a window don't load
8230 // subresources, so just go ahead and mark ourselves as complete.
8231 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
8232 /* updateTimingInformation = */ false);
8234 // Reset mSkipLoadEventAfterClose just in case.
8235 mSkipLoadEventAfterClose = false;
8239 void Document::UnblockDOMContentLoaded() {
8240 MOZ_ASSERT(mBlockDOMContentLoaded);
8241 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
8242 return;
8245 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
8246 ("DOCUMENT %p UnblockDOMContentLoaded", this));
8248 mDidFireDOMContentLoaded = true;
8249 if (PresShell* presShell = GetPresShell()) {
8250 presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
8253 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
8254 if (!mSynchronousDOMContentLoaded) {
8255 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8256 nsCOMPtr<nsIRunnable> ev =
8257 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
8258 &Document::DispatchContentLoadedEvents);
8259 Dispatch(TaskCategory::Other, ev.forget());
8260 } else {
8261 DispatchContentLoadedEvents();
8265 void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) {
8266 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
8267 "Someone forgot a scriptblocker");
8268 NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged,
8269 (this, aElement, aStateMask));
8272 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
8273 StyleRuleChangeKind) {
8274 if (aSheet.IsApplicable()) {
8275 ApplicableStylesChanged();
8279 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
8280 if (aRule.IsIncompleteImportRule()) {
8281 return;
8284 if (aSheet.IsApplicable()) {
8285 ApplicableStylesChanged();
8289 void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
8290 if (aSheet.IsApplicable()) {
8291 ApplicableStylesChanged();
8295 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
8296 if (aSheet.IsApplicable()) {
8297 ApplicableStylesChanged();
8301 static Element* GetCustomContentContainer(PresShell* aPresShell) {
8302 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
8303 return nullptr;
8306 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
8309 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
8310 bool aForce, ErrorResult& aRv) {
8311 RefPtr<PresShell> shell = GetPresShell();
8312 if (aForce && !GetCustomContentContainer(shell)) {
8313 FlushPendingNotifications(FlushType::Layout);
8314 shell = GetPresShell();
8317 nsAutoScriptBlocker scriptBlocker;
8319 RefPtr<AnonymousContent> anonContent = AnonymousContent::Create(*this);
8320 if (!anonContent) {
8321 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
8322 return nullptr;
8325 mAnonymousContents.AppendElement(anonContent);
8327 if (RefPtr<Element> container = GetCustomContentContainer(shell)) {
8328 // If the container is empty and we have other anon content we should be
8329 // about to show all the other anonymous content nodes.
8330 if (container->HasChildren() || mAnonymousContents.Length() == 1) {
8331 container->AppendChildTo(anonContent->Host(), true, IgnoreErrors());
8332 if (auto* canvasFrame = shell->GetCanvasFrame()) {
8333 canvasFrame->ShowCustomContentContainer();
8338 return anonContent.forget();
8341 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
8342 PresShell* aPresShell) {
8343 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
8344 if (!container) {
8345 return;
8347 container->RemoveChild(*aAnonContent.Host(), IgnoreErrors());
8350 void Document::RemoveAnonymousContent(AnonymousContent& aContent) {
8351 nsAutoScriptBlocker scriptBlocker;
8353 auto index = mAnonymousContents.IndexOf(&aContent);
8354 if (index == mAnonymousContents.NoIndex) {
8355 return;
8358 mAnonymousContents.RemoveElementAt(index);
8359 RemoveAnonContentFromCanvas(aContent, GetPresShell());
8361 if (mAnonymousContents.IsEmpty() &&
8362 GetCustomContentContainer(GetPresShell())) {
8363 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
8367 Element* Document::GetAnonRootIfInAnonymousContentContainer(
8368 nsINode* aNode) const {
8369 if (!aNode->IsInNativeAnonymousSubtree()) {
8370 return nullptr;
8373 PresShell* presShell = GetPresShell();
8374 if (!presShell || !presShell->GetCanvasFrame()) {
8375 return nullptr;
8378 nsAutoScriptBlocker scriptBlocker;
8379 nsCOMPtr<Element> customContainer =
8380 presShell->GetCanvasFrame()->GetCustomContentContainer();
8381 if (!customContainer) {
8382 return nullptr;
8385 // An arbitrary number of elements can be inserted as children of the custom
8386 // container frame. We want the one that was added that contains aNode, so
8387 // we need to keep track of the last child separately using |child| here.
8388 nsINode* child = aNode;
8389 nsINode* parent = aNode->GetParentNode();
8390 while (parent && parent->IsInNativeAnonymousSubtree()) {
8391 if (parent == customContainer) {
8392 return Element::FromNode(child);
8394 child = parent;
8395 parent = child->GetParentNode();
8397 return nullptr;
8400 Maybe<ClientInfo> Document::GetClientInfo() const {
8401 if (const Document* orig = GetOriginalDocument()) {
8402 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
8403 return info;
8407 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8408 return inner->GetClientInfo();
8411 return Maybe<ClientInfo>();
8414 Maybe<ClientState> Document::GetClientState() const {
8415 if (const Document* orig = GetOriginalDocument()) {
8416 if (Maybe<ClientState> state = orig->GetClientState()) {
8417 return state;
8421 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8422 return inner->GetClientState();
8425 return Maybe<ClientState>();
8428 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
8429 if (const Document* orig = GetOriginalDocument()) {
8430 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
8431 return controller;
8435 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8436 return inner->GetController();
8439 return Maybe<ServiceWorkerDescriptor>();
8443 // Document interface
8445 DocumentType* Document::GetDoctype() const {
8446 for (nsIContent* child = GetFirstChild(); child;
8447 child = child->GetNextSibling()) {
8448 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
8449 return static_cast<DocumentType*>(child);
8452 return nullptr;
8455 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
8456 if (!mDOMImplementation) {
8457 nsCOMPtr<nsIURI> uri;
8458 NS_NewURI(getter_AddRefs(uri), "about:blank");
8459 if (!uri) {
8460 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
8461 return nullptr;
8463 bool hasHadScriptObject = true;
8464 nsIScriptGlobalObject* scriptObject =
8465 GetScriptHandlingObject(hasHadScriptObject);
8466 if (!scriptObject && hasHadScriptObject) {
8467 rv.Throw(NS_ERROR_UNEXPECTED);
8468 return nullptr;
8470 mDOMImplementation = new DOMImplementation(
8471 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
8474 return mDOMImplementation;
8477 bool IsLowercaseASCII(const nsAString& aValue) {
8478 int32_t len = aValue.Length();
8479 for (int32_t i = 0; i < len; ++i) {
8480 char16_t c = aValue[i];
8481 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
8482 return false;
8485 return true;
8488 already_AddRefed<Element> Document::CreateElement(
8489 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8490 ErrorResult& rv) {
8491 rv = nsContentUtils::CheckQName(aTagName, false);
8492 if (rv.Failed()) {
8493 return nullptr;
8496 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
8497 nsAutoString lcTagName;
8498 if (needsLowercase) {
8499 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
8502 const nsString* is = nullptr;
8503 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
8504 if (aOptions.IsElementCreationOptions()) {
8505 const ElementCreationOptions& options =
8506 aOptions.GetAsElementCreationOptions();
8508 if (options.mIs.WasPassed()) {
8509 is = &options.mIs.Value();
8512 // Check 'pseudo' and throw an exception if it's not one allowed
8513 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
8514 if (options.mPseudo.WasPassed()) {
8515 Maybe<PseudoStyleType> type =
8516 nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
8517 if (!type || *type == PseudoStyleType::NotPseudo ||
8518 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
8519 rv.ThrowNotSupportedError("Invalid pseudo-element");
8520 return nullptr;
8522 pseudoType = *type;
8526 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
8527 nullptr, mDefaultElementType, is);
8529 if (pseudoType != PseudoStyleType::NotPseudo) {
8530 elem->SetPseudoElementType(pseudoType);
8533 return elem.forget();
8536 already_AddRefed<Element> Document::CreateElementNS(
8537 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8538 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
8539 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8540 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8541 mNodeInfoManager, ELEMENT_NODE,
8542 getter_AddRefs(nodeInfo));
8543 if (rv.Failed()) {
8544 return nullptr;
8547 const nsString* is = nullptr;
8548 if (aOptions.IsElementCreationOptions()) {
8549 const ElementCreationOptions& options =
8550 aOptions.GetAsElementCreationOptions();
8551 if (options.mIs.WasPassed()) {
8552 is = &options.mIs.Value();
8556 nsCOMPtr<Element> element;
8557 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
8558 NOT_FROM_PARSER, is);
8559 if (rv.Failed()) {
8560 return nullptr;
8563 return element.forget();
8566 already_AddRefed<Element> Document::CreateXULElement(
8567 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8568 ErrorResult& aRv) {
8569 aRv = nsContentUtils::CheckQName(aTagName, false);
8570 if (aRv.Failed()) {
8571 return nullptr;
8574 const nsString* is = nullptr;
8575 if (aOptions.IsElementCreationOptions()) {
8576 const ElementCreationOptions& options =
8577 aOptions.GetAsElementCreationOptions();
8578 if (options.mIs.WasPassed()) {
8579 is = &options.mIs.Value();
8583 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
8584 if (!elem) {
8585 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
8586 return nullptr;
8588 return elem.forget();
8591 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
8592 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8593 return text.forget();
8596 already_AddRefed<nsTextNode> Document::CreateTextNode(
8597 const nsAString& aData) const {
8598 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8599 // Don't notify; this node is still being created.
8600 text->SetText(aData, false);
8601 return text.forget();
8604 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
8605 RefPtr<DocumentFragment> frag =
8606 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
8607 return frag.forget();
8610 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
8611 already_AddRefed<dom::Comment> Document::CreateComment(
8612 const nsAString& aData) const {
8613 RefPtr<dom::Comment> comment =
8614 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
8616 // Don't notify; this node is still being created.
8617 comment->SetText(aData, false);
8618 return comment.forget();
8621 already_AddRefed<CDATASection> Document::CreateCDATASection(
8622 const nsAString& aData, ErrorResult& rv) {
8623 if (IsHTMLDocument()) {
8624 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8625 return nullptr;
8628 if (FindInReadable(u"]]>"_ns, aData)) {
8629 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8630 return nullptr;
8633 RefPtr<CDATASection> cdata =
8634 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
8636 // Don't notify; this node is still being created.
8637 cdata->SetText(aData, false);
8639 return cdata.forget();
8642 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
8643 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
8644 nsresult res = nsContentUtils::CheckQName(aTarget, false);
8645 if (NS_FAILED(res)) {
8646 rv.Throw(res);
8647 return nullptr;
8650 if (FindInReadable(u"?>"_ns, aData)) {
8651 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8652 return nullptr;
8655 RefPtr<ProcessingInstruction> pi =
8656 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
8658 return pi.forget();
8661 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
8662 ErrorResult& rv) {
8663 if (!mNodeInfoManager) {
8664 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8665 return nullptr;
8668 nsresult res = nsContentUtils::CheckQName(aName, false);
8669 if (NS_FAILED(res)) {
8670 rv.Throw(res);
8671 return nullptr;
8674 nsAutoString name;
8675 if (IsHTMLDocument()) {
8676 nsContentUtils::ASCIIToLower(aName, name);
8677 } else {
8678 name = aName;
8681 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8682 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
8683 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
8684 if (NS_FAILED(res)) {
8685 rv.Throw(res);
8686 return nullptr;
8689 RefPtr<Attr> attribute =
8690 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8691 return attribute.forget();
8694 already_AddRefed<Attr> Document::CreateAttributeNS(
8695 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8696 ErrorResult& rv) {
8697 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8698 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8699 mNodeInfoManager, ATTRIBUTE_NODE,
8700 getter_AddRefs(nodeInfo));
8701 if (rv.Failed()) {
8702 return nullptr;
8705 RefPtr<Attr> attribute =
8706 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8707 return attribute.forget();
8710 void Document::ScheduleForPresAttrEvaluation(Element* aElement) {
8711 MOZ_ASSERT(aElement->IsInComposedDoc());
8712 DebugOnly<bool> inserted = mLazyPresElements.EnsureInserted(aElement);
8713 MOZ_ASSERT(inserted);
8714 if (aElement->HasServoData()) {
8715 // TODO(emilio): RESTYLE_SELF is too strong, there should be no need to
8716 // re-selector-match, but right now this is needed to pick up the new mapped
8717 // attributes. We need something like RESTYLE_STYLE_ATTRIBUTE but for mapped
8718 // attributes.
8719 nsLayoutUtils::PostRestyleEvent(aElement, RestyleHint::RESTYLE_SELF,
8720 nsChangeHint(0));
8724 void Document::UnscheduleForPresAttrEvaluation(Element* aElement) {
8725 mLazyPresElements.Remove(aElement);
8728 void Document::DoResolveScheduledPresAttrs() {
8729 MOZ_ASSERT(!mLazyPresElements.IsEmpty());
8730 for (Element* el : mLazyPresElements) {
8731 MOZ_ASSERT(el->IsInComposedDoc(),
8732 "Un-schedule when removing from the document");
8733 MOZ_ASSERT(el->IsPendingMappedAttributeEvaluation());
8734 if (auto* svg = SVGElement::FromNode(el)) {
8735 // SVG does its own (very similar) thing, for now at least.
8736 svg->UpdateMappedDeclarationBlock();
8737 } else {
8738 MappedDeclarationsBuilder builder(*el, *this,
8739 el->GetMappedAttributeStyle());
8740 auto function = el->GetAttributeMappingFunction();
8741 function(builder);
8742 el->SetMappedDeclarationBlock(builder.TakeDeclarationBlock());
8744 MOZ_ASSERT(!el->IsPendingMappedAttributeEvaluation());
8746 mLazyPresElements.Clear();
8749 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
8750 const {
8751 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
8753 const nsTArray<nsWeakPtr> blockedNodes = mBlockedNodesByClassifier.Clone();
8755 for (unsigned long i = 0; i < blockedNodes.Length(); i++) {
8756 nsWeakPtr weakNode = blockedNodes[i];
8757 nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode);
8758 // Consider only nodes to which we have managed to get strong references.
8759 // Coping with nullptrs since it's expected for nodes to disappear when
8760 // nobody else is referring to them.
8761 if (node) {
8762 list->AppendElement(node);
8766 return list.forget();
8769 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
8770 aSheetSet.Truncate();
8772 // Look through our sheets, find the selected set title
8773 size_t count = SheetCount();
8774 nsAutoString title;
8775 for (size_t index = 0; index < count; index++) {
8776 StyleSheet* sheet = SheetAt(index);
8777 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8779 if (sheet->Disabled()) {
8780 // Disabled sheets don't affect the currently selected set
8781 continue;
8784 sheet->GetTitle(title);
8786 if (aSheetSet.IsEmpty()) {
8787 aSheetSet = title;
8788 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
8789 // Sheets from multiple sets enabled; return null string, per spec.
8790 SetDOMStringToNull(aSheetSet);
8791 return;
8796 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
8797 if (DOMStringIsNull(aSheetSet)) {
8798 return;
8801 // Must update mLastStyleSheetSet before doing anything else with stylesheets
8802 // or CSSLoaders.
8803 mLastStyleSheetSet = aSheetSet;
8804 EnableStyleSheetsForSetInternal(aSheetSet, true);
8807 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
8808 mPreferredStyleSheetSet = aSheetSet;
8809 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
8810 // spec.
8811 if (DOMStringIsNull(mLastStyleSheetSet)) {
8812 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
8813 // per spec. The idea here is that we're changing our preferred set and
8814 // that shouldn't change the value of lastStyleSheetSet. Also, we're
8815 // using the Internal version so we can update the CSSLoader and not have
8816 // to worry about null strings.
8817 EnableStyleSheetsForSetInternal(aSheetSet, true);
8821 DOMStringList* Document::StyleSheetSets() {
8822 if (!mStyleSheetSetList) {
8823 mStyleSheetSetList = new DOMStyleSheetSetList(this);
8825 return mStyleSheetSetList;
8828 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
8829 // Per spec, passing in null is a no-op.
8830 if (!DOMStringIsNull(aSheetSet)) {
8831 // Note: must make sure to not change the CSSLoader's preferred sheet --
8832 // that value should be equal to either our lastStyleSheetSet (if that's
8833 // non-null) or to our preferredStyleSheetSet. And this method doesn't
8834 // change either of those.
8835 EnableStyleSheetsForSetInternal(aSheetSet, false);
8839 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
8840 bool aUpdateCSSLoader) {
8841 size_t count = SheetCount();
8842 nsAutoString title;
8843 for (size_t index = 0; index < count; index++) {
8844 StyleSheet* sheet = SheetAt(index);
8845 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8847 sheet->GetTitle(title);
8848 if (!title.IsEmpty()) {
8849 sheet->SetEnabled(title.Equals(aSheetSet));
8852 if (aUpdateCSSLoader) {
8853 CSSLoader()->DocumentStyleSheetSetChanged();
8855 if (mStyleSet->StyleSheetsHaveChanged()) {
8856 ApplicableStylesChanged();
8860 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
8861 nsAutoCString charset;
8862 GetDocumentCharacterSet()->Name(charset);
8863 CopyASCIItoUTF16(charset, aCharacterSet);
8866 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
8867 ErrorResult& rv) const {
8868 nsINode* imported = &aNode;
8870 switch (imported->NodeType()) {
8871 case DOCUMENT_NODE: {
8872 break;
8874 case DOCUMENT_FRAGMENT_NODE:
8875 case ATTRIBUTE_NODE:
8876 case ELEMENT_NODE:
8877 case PROCESSING_INSTRUCTION_NODE:
8878 case TEXT_NODE:
8879 case CDATA_SECTION_NODE:
8880 case COMMENT_NODE:
8881 case DOCUMENT_TYPE_NODE: {
8882 return imported->Clone(aDeep, mNodeInfoManager, rv);
8884 default: {
8885 NS_WARNING("Don't know how to clone this nodetype for importNode.");
8889 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8890 return nullptr;
8893 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
8894 return nsRange::Create(this, 0, this, 0, rv);
8897 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
8898 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
8899 ErrorResult& rv) const {
8900 RefPtr<NodeIterator> iterator =
8901 new NodeIterator(&aRoot, aWhatToShow, aFilter);
8902 return iterator.forget();
8905 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
8906 uint32_t aWhatToShow,
8907 NodeFilter* aFilter,
8908 ErrorResult& rv) const {
8909 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
8910 return walker.forget();
8913 already_AddRefed<Location> Document::GetLocation() const {
8914 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
8916 if (!w) {
8917 return nullptr;
8920 return do_AddRef(w->Location());
8923 already_AddRefed<nsIURI> Document::GetDomainURI() {
8924 nsIPrincipal* principal = NodePrincipal();
8926 nsCOMPtr<nsIURI> uri;
8927 principal->GetDomain(getter_AddRefs(uri));
8928 if (uri) {
8929 return uri.forget();
8931 auto* basePrin = BasePrincipal::Cast(principal);
8932 basePrin->GetURI(getter_AddRefs(uri));
8933 return uri.forget();
8936 void Document::GetDomain(nsAString& aDomain) {
8937 nsCOMPtr<nsIURI> uri = GetDomainURI();
8939 if (!uri) {
8940 aDomain.Truncate();
8941 return;
8944 nsAutoCString hostName;
8945 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
8946 if (NS_SUCCEEDED(rv)) {
8947 CopyUTF8toUTF16(hostName, aDomain);
8948 } else {
8949 // If we can't get the host from the URI (e.g. about:, javascript:,
8950 // etc), just return an empty string.
8951 aDomain.Truncate();
8955 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
8956 if (!GetBrowsingContext()) {
8957 // If our browsing context is null; disallow setting domain
8958 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8959 return;
8962 if (mSandboxFlags & SANDBOXED_DOMAIN) {
8963 // We're sandboxed; disallow setting domain
8964 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8965 return;
8968 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
8969 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8970 return;
8973 if (aDomain.IsEmpty()) {
8974 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8975 return;
8978 nsCOMPtr<nsIURI> uri = GetDomainURI();
8979 if (!uri) {
8980 rv.Throw(NS_ERROR_FAILURE);
8981 return;
8984 // Check new domain - must be a superdomain of the current host
8985 // For example, a page from foo.bar.com may set domain to bar.com,
8986 // but not to ar.com, baz.com, or fi.foo.bar.com.
8988 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
8989 if (!newURI) {
8990 // Error: illegal domain
8991 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8992 return;
8995 if (GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated()) {
8996 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
8997 return;
9000 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
9001 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
9002 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
9003 wgc->SendSetDocumentDomain(newURI);
9007 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
9008 const nsACString& aHostString) {
9009 if (aHostString.IsEmpty()) {
9010 return nullptr;
9013 // Create new URI
9014 nsCOMPtr<nsIURI> uri = GetDomainURI();
9015 if (!uri) {
9016 return nullptr;
9019 nsresult rv;
9020 rv = NS_MutateURI(uri)
9021 .SetUserPass(""_ns)
9022 .SetPort(-1) // we want to reset the port number if needed.
9023 .SetHostPort(aHostString)
9024 .Finalize(uri);
9025 if (NS_FAILED(rv)) {
9026 return nullptr;
9029 return uri.forget();
9032 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
9033 const nsAString& aNewDomain, nsIURI* aOrigHost) {
9034 if (NS_WARN_IF(!aOrigHost)) {
9035 return nullptr;
9038 nsCOMPtr<nsIURI> newURI =
9039 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
9040 if (!newURI) {
9041 // Error: failed to parse input domain
9042 return nullptr;
9045 if (!IsValidDomain(aOrigHost, newURI)) {
9046 // Error: illegal domain
9047 return nullptr;
9050 nsAutoCString domain;
9051 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
9052 return nullptr;
9055 return CreateInheritingURIForHost(domain);
9058 /* static */
9059 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
9060 // Check new domain - must be a superdomain of the current host
9061 // For example, a page from foo.bar.com may set domain to bar.com,
9062 // but not to ar.com, baz.com, or fi.foo.bar.com.
9063 nsAutoCString current;
9064 nsAutoCString domain;
9065 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
9066 current.Truncate();
9068 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
9069 domain.Truncate();
9072 bool ok = current.Equals(domain);
9073 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
9074 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
9075 // We're golden if the new domain is the current page's base domain or a
9076 // subdomain of it.
9077 nsCOMPtr<nsIEffectiveTLDService> tldService =
9078 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
9079 if (!tldService) {
9080 return false;
9083 nsAutoCString currentBaseDomain;
9084 ok = NS_SUCCEEDED(
9085 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
9086 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
9087 (domain.Length() >= currentBaseDomain.Length()),
9088 "uh-oh! slight optimization wasn't valid somehow!");
9089 ok = ok && domain.Length() >= currentBaseDomain.Length();
9092 return ok;
9095 Element* Document::GetHtmlElement() const {
9096 Element* rootElement = GetRootElement();
9097 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
9098 return rootElement;
9099 return nullptr;
9102 Element* Document::GetHtmlChildElement(nsAtom* aTag) {
9103 Element* html = GetHtmlElement();
9104 if (!html) return nullptr;
9106 // Look for the element with aTag inside html. This needs to run
9107 // forwards to find the first such element.
9108 for (nsIContent* child = html->GetFirstChild(); child;
9109 child = child->GetNextSibling()) {
9110 if (child->IsHTMLElement(aTag)) return child->AsElement();
9112 return nullptr;
9115 nsGenericHTMLElement* Document::GetBody() {
9116 Element* html = GetHtmlElement();
9117 if (!html) {
9118 return nullptr;
9121 for (nsIContent* child = html->GetFirstChild(); child;
9122 child = child->GetNextSibling()) {
9123 if (child->IsHTMLElement(nsGkAtoms::body) ||
9124 child->IsHTMLElement(nsGkAtoms::frameset)) {
9125 return static_cast<nsGenericHTMLElement*>(child);
9129 return nullptr;
9132 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
9133 nsCOMPtr<Element> root = GetRootElement();
9135 // The body element must be either a body tag or a frameset tag. And we must
9136 // have a root element to be able to add kids to it.
9137 if (!newBody ||
9138 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
9139 rv.ThrowHierarchyRequestError(
9140 "The new body must be either a body tag or frameset tag.");
9141 return;
9144 if (!root) {
9145 rv.ThrowHierarchyRequestError("No root element.");
9146 return;
9149 // Use DOM methods so that we pass through the appropriate security checks.
9150 nsCOMPtr<Element> currentBody = GetBody();
9151 if (currentBody) {
9152 root->ReplaceChild(*newBody, *currentBody, rv);
9153 } else {
9154 root->AppendChild(*newBody, rv);
9158 HTMLSharedElement* Document::GetHead() {
9159 return static_cast<HTMLSharedElement*>(GetHeadElement());
9162 Element* Document::GetTitleElement() {
9163 // mMayHaveTitleElement will have been set to true if any HTML or SVG
9164 // <title> element has been bound to this document. So if it's false,
9165 // we know there is nothing to do here. This avoids us having to search
9166 // the whole DOM if someone calls document.title on a large document
9167 // without a title.
9168 if (!mMayHaveTitleElement) {
9169 return nullptr;
9172 Element* root = GetRootElement();
9173 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
9174 // In SVG, the document's title must be a child
9175 for (nsIContent* child = root->GetFirstChild(); child;
9176 child = child->GetNextSibling()) {
9177 if (child->IsSVGElement(nsGkAtoms::title)) {
9178 return child->AsElement();
9181 return nullptr;
9184 // We check the HTML namespace even for non-HTML documents, except SVG. This
9185 // matches the spec and the behavior of all tested browsers.
9186 for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
9187 if (node->IsHTMLElement(nsGkAtoms::title)) {
9188 return node->AsElement();
9191 return nullptr;
9194 void Document::GetTitle(nsAString& aTitle) {
9195 aTitle.Truncate();
9197 Element* rootElement = GetRootElement();
9198 if (!rootElement) {
9199 return;
9202 if (rootElement->IsXULElement()) {
9203 rootElement->GetAttr(nsGkAtoms::title, aTitle);
9204 } else if (Element* title = GetTitleElement()) {
9205 nsContentUtils::GetNodeTextContent(title, false, aTitle);
9206 } else {
9207 return;
9210 aTitle.CompressWhitespace();
9213 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
9214 Element* rootElement = GetRootElement();
9215 if (!rootElement) {
9216 return;
9219 if (rootElement->IsXULElement()) {
9220 aRv =
9221 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
9222 return;
9225 Maybe<mozAutoDocUpdate> updateBatch;
9226 nsCOMPtr<Element> title = GetTitleElement();
9227 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
9228 if (!title) {
9229 // Batch updates so that mutation events don't change "the title
9230 // element" under us
9231 updateBatch.emplace(this, true);
9232 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
9233 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
9234 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
9235 NOT_FROM_PARSER);
9236 if (!title) {
9237 return;
9239 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
9240 IgnoreErrors());
9242 } else if (rootElement->IsHTMLElement()) {
9243 if (!title) {
9244 // Batch updates so that mutation events don't change "the title
9245 // element" under us
9246 updateBatch.emplace(this, true);
9247 Element* head = GetHeadElement();
9248 if (!head) {
9249 return;
9252 RefPtr<mozilla::dom::NodeInfo> titleInfo;
9253 titleInfo = mNodeInfoManager->GetNodeInfo(
9254 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
9255 title = NS_NewHTMLTitleElement(titleInfo.forget());
9256 if (!title) {
9257 return;
9260 head->AppendChildTo(title, true, IgnoreErrors());
9262 } else {
9263 return;
9266 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
9269 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
9270 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
9271 "Setting a title while unlinking or destroying the element?");
9272 if (mInUnlinkOrDeletion) {
9273 return;
9276 if (aBoundTitleElement) {
9277 mMayHaveTitleElement = true;
9279 if (mPendingTitleChangeEvent.IsPending()) {
9280 return;
9283 MOZ_RELEASE_ASSERT(NS_IsMainThread());
9284 RefPtr<nsRunnableMethod<Document, void, false>> event =
9285 NewNonOwningRunnableMethod("Document::DoNotifyPossibleTitleChange", this,
9286 &Document::DoNotifyPossibleTitleChange);
9287 if (NS_WARN_IF(NS_FAILED(Dispatch(TaskCategory::Other, do_AddRef(event))))) {
9288 return;
9290 mPendingTitleChangeEvent = std::move(event);
9293 void Document::DoNotifyPossibleTitleChange() {
9294 if (!mPendingTitleChangeEvent.IsPending()) {
9295 return;
9297 // Make sure the pending runnable method is cleared.
9298 mPendingTitleChangeEvent.Revoke();
9299 mHaveFiredTitleChange = true;
9301 nsAutoString title;
9302 GetTitle(title);
9304 if (RefPtr<PresShell> presShell = GetPresShell()) {
9305 nsCOMPtr<nsISupports> container =
9306 presShell->GetPresContext()->GetContainerWeak();
9307 if (container) {
9308 if (nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container)) {
9309 docShellWin->SetTitle(title);
9314 if (WindowGlobalChild* child = GetWindowGlobalChild()) {
9315 child->SendUpdateDocumentTitle(title);
9318 // Fire a DOM event for the title change.
9319 nsContentUtils::DispatchChromeEvent(this, this, u"DOMTitleChanged"_ns,
9320 CanBubble::eYes, Cancelable::eYes);
9322 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
9323 if (obs) {
9324 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
9328 already_AddRefed<MediaQueryList> Document::MatchMedia(
9329 const nsACString& aMediaQueryList, CallerType aCallerType) {
9330 RefPtr<MediaQueryList> result =
9331 new MediaQueryList(this, aMediaQueryList, aCallerType);
9333 mDOMMediaQueryLists.insertBack(result);
9335 return result.forget();
9338 void Document::SetMayStartLayout(bool aMayStartLayout) {
9339 mMayStartLayout = aMayStartLayout;
9340 if (MayStartLayout()) {
9341 // Before starting layout, check whether we're a toplevel chrome
9342 // window. If we are, setup some state so that we don't have to restyle
9343 // the whole tree after StartLayout.
9344 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
9345 // We're the chrome document!
9346 win->BeforeStartLayout();
9348 ReadyState state = GetReadyStateEnum();
9349 if (state >= READYSTATE_INTERACTIVE) {
9350 // DOMContentLoaded has fired already.
9351 MaybeResolveReadyForIdle();
9355 MaybeEditingStateChanged();
9358 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
9359 mInitializableFrameLoaders.RemoveElement(aLoader);
9360 // Don't even try to initialize.
9361 if (mInDestructor) {
9362 NS_WARNING(
9363 "Trying to initialize a frame loader while"
9364 "document is being deleted");
9365 return NS_ERROR_FAILURE;
9368 mInitializableFrameLoaders.AppendElement(aLoader);
9369 if (!mFrameLoaderRunner) {
9370 mFrameLoaderRunner =
9371 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9372 &Document::MaybeInitializeFinalizeFrameLoaders);
9373 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9374 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9376 return NS_OK;
9379 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
9380 nsIRunnable* aFinalizer) {
9381 mInitializableFrameLoaders.RemoveElement(aLoader);
9382 if (mInDestructor) {
9383 return NS_ERROR_FAILURE;
9386 LogRunnable::LogDispatch(aFinalizer);
9387 mFrameLoaderFinalizers.AppendElement(aFinalizer);
9388 if (!mFrameLoaderRunner) {
9389 mFrameLoaderRunner =
9390 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9391 &Document::MaybeInitializeFinalizeFrameLoaders);
9392 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9393 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9395 return NS_OK;
9398 void Document::MaybeInitializeFinalizeFrameLoaders() {
9399 if (mDelayFrameLoaderInitialization) {
9400 // This method will be recalled when !mDelayFrameLoaderInitialization.
9401 mFrameLoaderRunner = nullptr;
9402 return;
9405 // We're not in an update, but it is not safe to run scripts, so
9406 // postpone frameloader initialization and finalization.
9407 if (!nsContentUtils::IsSafeToRunScript()) {
9408 if (!mInDestructor && !mFrameLoaderRunner &&
9409 (mInitializableFrameLoaders.Length() ||
9410 mFrameLoaderFinalizers.Length())) {
9411 mFrameLoaderRunner = NewRunnableMethod(
9412 "Document::MaybeInitializeFinalizeFrameLoaders", this,
9413 &Document::MaybeInitializeFinalizeFrameLoaders);
9414 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9416 return;
9418 mFrameLoaderRunner = nullptr;
9420 // Don't use a temporary array for mInitializableFrameLoaders, because
9421 // loading a frame may cause some other frameloader to be removed from the
9422 // array. But be careful to keep the loader alive when starting the load!
9423 while (mInitializableFrameLoaders.Length()) {
9424 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
9425 mInitializableFrameLoaders.RemoveElementAt(0);
9426 NS_ASSERTION(loader, "null frameloader in the array?");
9427 loader->ReallyStartLoading();
9430 uint32_t length = mFrameLoaderFinalizers.Length();
9431 if (length > 0) {
9432 nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
9433 std::move(mFrameLoaderFinalizers);
9434 for (uint32_t i = 0; i < length; ++i) {
9435 LogRunnable::Run run(finalizers[i]);
9436 finalizers[i]->Run();
9441 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
9442 uint32_t length = mInitializableFrameLoaders.Length();
9443 for (uint32_t i = 0; i < length; ++i) {
9444 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
9445 mInitializableFrameLoaders.RemoveElementAt(i);
9446 return;
9451 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
9452 mPrototypeDocument = aPrototype;
9453 mSynchronousDOMContentLoaded = true;
9456 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
9457 return GetPermissionDelegateHandler();
9460 Document* Document::RequestExternalResource(
9461 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
9462 ExternalResourceLoad** aPendingLoad) {
9463 MOZ_ASSERT(aURI, "Must have a URI");
9464 MOZ_ASSERT(aRequestingNode, "Must have a node");
9465 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
9466 if (mDisplayDocument) {
9467 return mDisplayDocument->RequestExternalResource(
9468 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
9471 return mExternalResourceMap.RequestResource(
9472 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
9475 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
9476 mExternalResourceMap.EnumerateResources(aCallback);
9479 SMILAnimationController* Document::GetAnimationController() {
9480 // We create the animation controller lazily because most documents won't want
9481 // one and only SVG documents and the like will call this
9482 if (mAnimationController) return mAnimationController;
9483 // Refuse to create an Animation Controller for data documents.
9484 if (mLoadedAsData) return nullptr;
9486 mAnimationController = new SMILAnimationController(this);
9488 // If there's a presContext then check the animation mode and pause if
9489 // necessary.
9490 nsPresContext* context = GetPresContext();
9491 if (mAnimationController && context &&
9492 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
9493 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
9496 // If we're hidden (or being hidden), notify the newly-created animation
9497 // controller. (Skip this check for SVG-as-an-image documents, though,
9498 // because they don't get OnPageShow / OnPageHide calls).
9499 if (!mIsShowing && !mIsBeingUsedAsImage) {
9500 mAnimationController->OnPageHide();
9503 return mAnimationController;
9506 PendingAnimationTracker* Document::GetOrCreatePendingAnimationTracker() {
9507 if (!mPendingAnimationTracker) {
9508 mPendingAnimationTracker = new PendingAnimationTracker(this);
9511 return mPendingAnimationTracker;
9514 ScrollTimelineAnimationTracker*
9515 Document::GetOrCreateScrollTimelineAnimationTracker() {
9516 if (!mScrollTimelineAnimationTracker) {
9517 mScrollTimelineAnimationTracker = new ScrollTimelineAnimationTracker(this);
9520 return mScrollTimelineAnimationTracker;
9524 * Retrieve the "direction" property of the document.
9526 * @lina 01/09/2001
9528 void Document::GetDir(nsAString& aDirection) const {
9529 aDirection.Truncate();
9530 Element* rootElement = GetHtmlElement();
9531 if (rootElement) {
9532 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
9537 * Set the "direction" property of the document.
9539 * @lina 01/09/2001
9541 void Document::SetDir(const nsAString& aDirection) {
9542 Element* rootElement = GetHtmlElement();
9543 if (rootElement) {
9544 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
9548 nsIHTMLCollection* Document::Images() {
9549 if (!mImages) {
9550 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
9551 nsGkAtoms::img);
9553 return mImages;
9556 nsIHTMLCollection* Document::Embeds() {
9557 if (!mEmbeds) {
9558 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
9559 nsGkAtoms::embed);
9561 return mEmbeds;
9564 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9565 void* aData) {
9566 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
9567 aElement->HasAttr(nsGkAtoms::href);
9570 nsIHTMLCollection* Document::Links() {
9571 if (!mLinks) {
9572 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
9574 return mLinks;
9577 nsIHTMLCollection* Document::Forms() {
9578 if (!mForms) {
9579 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
9580 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
9581 nsGkAtoms::form);
9584 return mForms;
9587 nsIHTMLCollection* Document::Scripts() {
9588 if (!mScripts) {
9589 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
9590 nsGkAtoms::script);
9592 return mScripts;
9595 nsIHTMLCollection* Document::Applets() {
9596 if (!mApplets) {
9597 mApplets = new nsEmptyContentList(this);
9599 return mApplets;
9602 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9603 void* aData) {
9604 return aElement->IsHTMLElement(nsGkAtoms::a) &&
9605 aElement->HasAttr(nsGkAtoms::name);
9608 nsIHTMLCollection* Document::Anchors() {
9609 if (!mAnchors) {
9610 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
9612 return mAnchors;
9615 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
9616 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
9617 ErrorResult& rv) {
9618 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9619 "XOW should have caught this!");
9621 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
9622 if (!window) {
9623 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
9624 return nullptr;
9626 nsCOMPtr<nsPIDOMWindowOuter> outer =
9627 nsPIDOMWindowOuter::GetFromCurrentInner(window);
9628 if (!outer) {
9629 rv.Throw(NS_ERROR_NOT_INITIALIZED);
9630 return nullptr;
9632 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
9633 RefPtr<BrowsingContext> newBC;
9634 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
9635 if (!newBC) {
9636 return nullptr;
9638 return WindowProxyHolder(std::move(newBC));
9641 Document* Document::Open(const Optional<nsAString>& /* unused */,
9642 const Optional<nsAString>& /* unused */,
9643 ErrorResult& aError) {
9644 // Implements
9645 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
9647 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9648 "XOW should have caught this!");
9650 // Step 1 -- throw if we're an XML document.
9651 if (!IsHTMLDocument() || mDisableDocWrite) {
9652 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9653 return nullptr;
9656 // Step 2 -- throw if dynamic markup insertion should throw.
9657 if (ShouldThrowOnDynamicMarkupInsertion()) {
9658 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9659 return nullptr;
9662 // Step 3 -- get the entry document, so we can use it for security checks.
9663 nsCOMPtr<Document> callerDoc = GetEntryDocument();
9664 if (!callerDoc) {
9665 // If we're called from C++ or in some other way without an originating
9666 // document we can't do a document.open w/o changing the principal of the
9667 // document to something like about:blank (as that's the only sane thing to
9668 // do when we don't know the origin of this call), and since we can't
9669 // change the principals of a document for security reasons we'll have to
9670 // refuse to go ahead with this call.
9672 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9673 return nullptr;
9676 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
9677 // the entry document.
9678 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
9679 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9680 return nullptr;
9683 // Step 5 -- if we have an active parser with a nonzero script nesting level,
9684 // just no-op.
9685 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
9686 return this;
9689 // Step 6 -- check for open() during unload. Per spec, this is just a check
9690 // of the ignore-opens-during-unload counter, but our unload event code
9691 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
9692 // do), so we check for unload directly.
9693 if (ShouldIgnoreOpens()) {
9694 return this;
9697 RefPtr<nsDocShell> shell(mDocumentContainer);
9698 if (shell) {
9699 bool inUnload;
9700 shell->GetIsInUnload(&inUnload);
9701 if (inUnload) {
9702 return this;
9706 // document.open() inherits the CSP from the opening document.
9707 // Please create an actual copy of the CSP (do not share the same
9708 // reference) otherwise appending a new policy within the opened
9709 // document will be incorrectly propagated to the opening doc.
9710 nsCOMPtr<nsIContentSecurityPolicy> csp = callerDoc->GetCsp();
9711 if (csp) {
9712 RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
9713 cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
9714 mCSP = cspToInherit;
9717 // At this point we know this is a valid-enough document.open() call
9718 // and not a no-op. Increment our use counter.
9719 SetUseCounter(eUseCounter_custom_DocumentOpen);
9721 // Step 7 -- stop existing navigation of our browsing context (and all other
9722 // loads it's doing) if we're the active document of our browsing context.
9723 // Note that we do not want to stop anything if there is no existing
9724 // navigation.
9725 if (shell && IsCurrentActiveDocument() &&
9726 shell->GetIsAttemptingToNavigate()) {
9727 shell->Stop(nsIWebNavigation::STOP_NETWORK);
9729 // The Stop call may have cancelled the onload blocker request or
9730 // prevented it from getting added, so we need to make sure it gets added
9731 // to the document again otherwise the document could have a non-zero
9732 // onload block count without the onload blocker request being in the
9733 // loadgroup.
9734 EnsureOnloadBlocker();
9737 // Step 8 -- clear event listeners out of our DOM tree
9738 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
9739 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
9740 elm->RemoveAllListeners();
9744 // Step 9 -- clear event listeners from our window, if we have one.
9746 // Note that we explicitly want the inner window, and only if we're its
9747 // document. We want to do this (per spec) even when we're not the "active
9748 // document", so we can't go through GetWindow(), because it might forward to
9749 // the wrong inner.
9750 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
9751 if (win->GetExtantDoc() == this) {
9752 if (EventListenerManager* elm =
9753 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
9754 elm->RemoveAllListeners();
9759 // If we have a parser that has a zero script nesting level, we need to
9760 // properly terminate it. We do that after we've removed all the event
9761 // listeners (so termination won't trigger event listeners if it does
9762 // something to the DOM), but before we remove all elements from the document
9763 // (so if termination does modify the DOM in some way we will just blow it
9764 // away immediately. See the similar code in WriteCommon that handles the
9765 // !IsInsertionPointDefined() case and should stay in sync with this code.
9766 if (mParser) {
9767 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
9768 "Why didn't we take the early return?");
9769 // Make sure we don't re-enter.
9770 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9771 mParser->Terminate();
9772 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9775 // Step 10 -- remove all our DOM kids without firing any mutation events.
9777 // We want to ignore any recursive calls to Open() that happen while
9778 // disconnecting the node tree. The spec doesn't say to do this, but the
9779 // spec also doesn't envision unload events on subframes firing while we do
9780 // this, while all browsers fire them in practice. See
9781 // <https://github.com/whatwg/html/issues/4611>.
9782 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9783 DisconnectNodeTree();
9786 // Step 11 -- if we're the current document in our docshell, do the
9787 // equivalent of pushState() with the new URL we should have.
9788 if (shell && IsCurrentActiveDocument()) {
9789 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
9790 if (callerDoc != this) {
9791 nsCOMPtr<nsIURI> noFragmentURI;
9792 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
9793 if (NS_WARN_IF(NS_FAILED(rv))) {
9794 aError.Throw(rv);
9795 return nullptr;
9797 newURI = std::move(noFragmentURI);
9800 // UpdateURLAndHistory might do various member-setting, so make sure we're
9801 // holding strong refs to all the refcounted args on the stack. We can
9802 // assume that our caller is holding on to "this" already.
9803 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
9804 bool equalURIs;
9805 nsresult rv = currentURI->Equals(newURI, &equalURIs);
9806 if (NS_WARN_IF(NS_FAILED(rv))) {
9807 aError.Throw(rv);
9808 return nullptr;
9810 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
9811 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
9812 /* aReplace = */ true, currentURI,
9813 equalURIs);
9814 if (NS_WARN_IF(NS_FAILED(rv))) {
9815 aError.Throw(rv);
9816 return nullptr;
9819 // And use the security info of the caller document as well, since
9820 // it's the thing providing our data.
9821 mSecurityInfo = callerDoc->GetSecurityInfo();
9823 // This is not mentioned in the spec, but I think that's a spec bug. See
9824 // <https://github.com/whatwg/html/issues/4299>. In any case, since our
9825 // URL may be changing away from about:blank here, we really want to unset
9826 // this flag no matter what, since only about:blank can be an initial
9827 // document.
9828 SetIsInitialDocument(false);
9830 // And let our docloader know that it will need to track our load event.
9831 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
9834 // Per spec nothing happens with our URI in other cases, though note
9835 // <https://github.com/whatwg/html/issues/4286>.
9837 // Note that we don't need to do anything here with base URIs per spec.
9838 // That said, this might be assuming that we implement
9839 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
9840 // correctly, which we don't right now for the about:blank case.
9842 // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
9843 mSkipLoadEventAfterClose = mLoadEventFiring;
9845 // Preliminary to steps 13-16. Set our ready state to uninitialized before
9846 // we do anything else, so we can then proceed to later ready state levels.
9847 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
9848 /* updateTimingInformation = */ false);
9849 // Reset a flag that affects readyState behavior.
9850 mSetCompleteAfterDOMContentLoaded = false;
9852 // Step 13 -- set our compat mode to standards.
9853 SetCompatibilityMode(eCompatibility_FullStandards);
9855 // Step 14 -- create a new parser associated with document. This also does
9856 // step 16 implicitly.
9857 mParserAborted = false;
9858 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
9859 mParser = parser;
9860 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
9861 nsresult rv = parser->StartExecutor();
9862 if (NS_WARN_IF(NS_FAILED(rv))) {
9863 aError.Throw(rv);
9864 return nullptr;
9867 // Clear out our form control state, because the state of controls
9868 // in the pre-open() document should not affect the state of
9869 // controls that are now going to be written.
9870 mLayoutHistoryState = nullptr;
9872 if (shell) {
9873 // Prepare the docshell and the document viewer for the impending
9874 // out-of-band document.write()
9875 shell->PrepareForNewContentModel();
9877 nsCOMPtr<nsIContentViewer> cv;
9878 shell->GetContentViewer(getter_AddRefs(cv));
9879 if (cv) {
9880 cv->LoadStart(this);
9884 // Step 15.
9885 SetReadyStateInternal(Document::READYSTATE_LOADING,
9886 /* updateTimingInformation = */ false);
9888 // Step 16 happened with step 14 above.
9890 // Step 17.
9891 return this;
9894 void Document::Close(ErrorResult& rv) {
9895 if (!IsHTMLDocument()) {
9896 // No calling document.close() on XHTML!
9898 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9899 return;
9902 if (ShouldThrowOnDynamicMarkupInsertion()) {
9903 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9904 return;
9907 if (!mParser || !mParser->IsScriptCreated()) {
9908 return;
9911 ++mWriteLevel;
9912 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
9913 ->Parse(u""_ns, nullptr, true);
9914 --mWriteLevel;
9917 void Document::WriteCommon(const Sequence<nsString>& aText,
9918 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
9919 // Fast path the common case
9920 if (aText.Length() == 1) {
9921 WriteCommon(aText[0], aNewlineTerminate, rv);
9922 } else {
9923 // XXXbz it would be nice if we could pass all the strings to the parser
9924 // without having to do all this copying and then ask it to start
9925 // parsing....
9926 nsString text;
9927 for (size_t i = 0; i < aText.Length(); ++i) {
9928 text.Append(aText[i]);
9930 WriteCommon(text, aNewlineTerminate, rv);
9934 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
9935 ErrorResult& aRv) {
9936 #ifdef DEBUG
9938 // Assert that we do not use or accidentally introduce doc.write()
9939 // in system privileged context or in any of our about: pages.
9940 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
9941 bool isAboutOrPrivContext = principal->IsSystemPrincipal();
9942 if (!isAboutOrPrivContext) {
9943 if (principal->SchemeIs("about")) {
9944 // about:blank inherits the security contetext and this assertion
9945 // is only meant for actual about: pages.
9946 nsAutoCString host;
9947 principal->GetHost(host);
9948 isAboutOrPrivContext = !host.EqualsLiteral("blank");
9951 // Some automated tests use an empty string to kick off some parsing
9952 // mechansims, but they do not do any harm since they use an empty string.
9953 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
9954 "do not use doc.write in privileged context!");
9956 #endif
9958 mTooDeepWriteRecursion =
9959 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
9960 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
9961 aRv.Throw(NS_ERROR_UNEXPECTED);
9962 return;
9965 if (!IsHTMLDocument() || mDisableDocWrite) {
9966 // No calling document.write*() on XHTML!
9968 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9969 return;
9972 if (ShouldThrowOnDynamicMarkupInsertion()) {
9973 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9974 return;
9977 if (mParserAborted) {
9978 // Hixie says aborting the parser doesn't undefine the insertion point.
9979 // However, since we null out mParser in that case, we track the
9980 // theoretically defined insertion point using mParserAborted.
9981 return;
9984 // Implement Step 4.1 of:
9985 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
9986 if (ShouldIgnoreOpens()) {
9987 return;
9990 void* key = GenerateParserKey();
9991 if (mParser && !mParser->IsInsertionPointDefined()) {
9992 if (mIgnoreDestructiveWritesCounter) {
9993 // Instead of implying a call to document.open(), ignore the call.
9994 nsContentUtils::ReportToConsole(
9995 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9996 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9997 return;
9999 // The spec doesn't tell us to ignore opens from here, but we need to
10000 // ensure opens are ignored here. See similar code in Open() that handles
10001 // the case of an existing parser which is not currently running script and
10002 // should stay in sync with this code.
10003 IgnoreOpensDuringUnload ignoreOpenGuard(this);
10004 mParser->Terminate();
10005 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
10008 if (!mParser) {
10009 if (mIgnoreDestructiveWritesCounter) {
10010 // Instead of implying a call to document.open(), ignore the call.
10011 nsContentUtils::ReportToConsole(
10012 nsIScriptError::warningFlag, "DOM Events"_ns, this,
10013 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
10014 return;
10017 Open({}, {}, aRv);
10019 // If Open() fails, or if it didn't create a parser (as it won't
10020 // if the user chose to not discard the current document through
10021 // onbeforeunload), don't write anything.
10022 if (aRv.Failed() || !mParser) {
10023 return;
10027 static constexpr auto new_line = u"\n"_ns;
10029 ++mWriteLevel;
10031 // This could be done with less code, but for performance reasons it
10032 // makes sense to have the code for two separate Parse() calls here
10033 // since the concatenation of strings costs more than we like. And
10034 // why pay that price when we don't need to?
10035 if (aNewlineTerminate) {
10036 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
10037 ->Parse(aText + new_line, key, false);
10038 } else {
10039 aRv =
10040 (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
10043 --mWriteLevel;
10045 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
10048 void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
10049 WriteCommon(aText, false, rv);
10052 void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
10053 WriteCommon(aText, true, rv);
10056 void* Document::GenerateParserKey(void) {
10057 if (!mScriptLoader) {
10058 // If we don't have a script loader, then the parser probably isn't parsing
10059 // anything anyway, so just return null.
10060 return nullptr;
10063 // The script loader provides us with the currently executing script element,
10064 // which is guaranteed to be unique per script.
10065 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
10066 if (script && mParser && mParser->IsScriptCreated()) {
10067 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
10068 if (creatorParser != mParser) {
10069 // Make scripts that aren't inserted by the active parser of this document
10070 // participate in the context of the script that document.open()ed
10071 // this document.
10072 return nullptr;
10075 return script;
10078 /* static */
10079 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
10080 nsAtom* aAtom, void* aData) {
10081 MOZ_ASSERT(aElement, "Must have element to work with!");
10083 if (!aElement->HasName()) {
10084 return false;
10087 nsString* elementName = static_cast<nsString*>(aData);
10088 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
10089 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
10090 eCaseMatters);
10093 /* static */
10094 void* Document::UseExistingNameString(nsINode* aRootNode,
10095 const nsString* aName) {
10096 return const_cast<nsString*>(aName);
10099 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
10100 if (mDocumentURI) {
10101 nsAutoCString uri;
10102 nsresult rv = mDocumentURI->GetSpec(uri);
10103 NS_ENSURE_SUCCESS(rv, rv);
10105 CopyUTF8toUTF16(uri, aDocumentURI);
10106 } else {
10107 aDocumentURI.Truncate();
10110 return NS_OK;
10113 // Alias of above
10114 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
10116 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
10117 CallerType aCallerType,
10118 ErrorResult& aRv) const {
10119 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
10120 aRv = GetDocumentURI(aDocumentURI);
10121 return;
10124 nsAutoCString uri;
10125 nsresult res = mChromeXHRDocURI->GetSpec(uri);
10126 if (NS_FAILED(res)) {
10127 aRv.Throw(res);
10128 return;
10130 CopyUTF8toUTF16(uri, aDocumentURI);
10133 nsIURI* Document::GetDocumentURIObject() const {
10134 if (!mChromeXHRDocURI) {
10135 return GetDocumentURI();
10138 return mChromeXHRDocURI;
10141 void Document::GetCompatMode(nsString& aCompatMode) const {
10142 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
10143 mCompatMode == eCompatibility_AlmostStandards ||
10144 mCompatMode == eCompatibility_FullStandards,
10145 "mCompatMode is neither quirks nor strict for this document");
10147 if (mCompatMode == eCompatibility_NavQuirks) {
10148 aCompatMode.AssignLiteral("BackCompat");
10149 } else {
10150 aCompatMode.AssignLiteral("CSS1Compat");
10154 } // namespace dom
10155 } // namespace mozilla
10157 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
10158 if (Element* element = Element::FromNode(aNode)) {
10159 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
10160 while (true) {
10161 RefPtr<Attr> attr;
10163 // Use an iterator to get an arbitrary attribute from the
10164 // cache. The iterator must be destroyed before any other
10165 // operations on mAttributeCache, to avoid hash table
10166 // assertions.
10167 auto iter = map->mAttributeCache.ConstIter();
10168 if (iter.Done()) {
10169 break;
10171 attr = iter.UserData();
10174 BlastSubtreeToPieces(attr);
10176 mozilla::DebugOnly<nsresult> rv =
10177 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
10178 attr->NodeInfo()->NameAtom(), false);
10180 // XXX Should we abort here?
10181 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
10185 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
10186 BlastSubtreeToPieces(shadow);
10187 element->UnattachShadow();
10191 while (aNode->HasChildren()) {
10192 nsIContent* node = aNode->GetFirstChild();
10193 BlastSubtreeToPieces(node);
10194 aNode->RemoveChildNode(node, false);
10198 namespace mozilla::dom {
10200 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv,
10201 bool aAcceptShadowRoot) {
10202 OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
10203 if (adoptedNode->IsShadowRoot() && !aAcceptShadowRoot) {
10204 rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
10205 return nullptr;
10208 // Scope firing mutation events so that we don't carry any state that
10209 // might be stale
10211 if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
10212 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
10216 nsAutoScriptBlocker scriptBlocker;
10218 switch (adoptedNode->NodeType()) {
10219 case ATTRIBUTE_NODE: {
10220 // Remove from ownerElement.
10221 OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
10223 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement();
10224 if (rv.Failed()) {
10225 return nullptr;
10228 if (ownerElement) {
10229 OwningNonNull<Attr> newAttr =
10230 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
10231 if (rv.Failed()) {
10232 return nullptr;
10236 break;
10238 case DOCUMENT_FRAGMENT_NODE:
10239 case ELEMENT_NODE:
10240 case PROCESSING_INSTRUCTION_NODE:
10241 case TEXT_NODE:
10242 case CDATA_SECTION_NODE:
10243 case COMMENT_NODE:
10244 case DOCUMENT_TYPE_NODE: {
10245 // Don't allow adopting a node's anonymous subtree out from under it.
10246 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
10247 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10248 return nullptr;
10251 // We don't want to adopt an element into its own contentDocument or into
10252 // a descendant contentDocument, so we check if the frameElement of this
10253 // document or any of its parents is the adopted node or one of its
10254 // descendants.
10255 RefPtr<BrowsingContext> bc = GetBrowsingContext();
10256 while (bc) {
10257 nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
10258 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
10259 rv.ThrowHierarchyRequestError(
10260 "Trying to adopt a node into its own contentDocument or a "
10261 "descendant contentDocument.");
10262 return nullptr;
10265 if (XRE_IsParentProcess()) {
10266 bc = bc->Canonical()->GetParentCrossChromeBoundary();
10267 } else {
10268 bc = bc->GetParent();
10272 // Remove from parent.
10273 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
10274 if (parent) {
10275 parent->RemoveChildNode(adoptedNode->AsContent(), true);
10276 } else {
10277 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
10280 break;
10282 case DOCUMENT_NODE: {
10283 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10284 return nullptr;
10286 default: {
10287 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
10289 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10290 return nullptr;
10294 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
10295 bool sameDocument = oldDocument == this;
10297 AutoJSContext cx;
10298 JS::Rooted<JSObject*> newScope(cx, nullptr);
10299 if (!sameDocument) {
10300 newScope = GetWrapper();
10301 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
10302 // Make sure cx is in a semi-sane compartment before we call WrapNative.
10303 // It's kind of irrelevant, given that we're passing aAllowWrapping =
10304 // false, and documents should always insist on being wrapped in an
10305 // canonical scope. But we try to pass something sane anyway.
10306 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
10307 JSAutoRealm ar(cx, globalObject);
10308 JS::Rooted<JS::Value> v(cx);
10309 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
10310 /* aAllowWrapping = */ false);
10311 if (rv.Failed()) return nullptr;
10312 newScope = &v.toObject();
10316 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
10317 if (rv.Failed()) {
10318 // Disconnect all nodes from their parents, since some have the old document
10319 // as their ownerDocument and some have this as their ownerDocument.
10320 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
10321 return nullptr;
10324 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
10325 "Should still be in the document we just got adopted into");
10327 return adoptedNode;
10330 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
10332 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
10333 const nsString& aScaleString) {
10334 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
10335 if (aScaleString.EqualsLiteral("device-width") ||
10336 aScaleString.EqualsLiteral("device-height")) {
10337 return Some(LayoutDeviceToScreenScale(10.0f));
10338 } else if (aScaleString.EqualsLiteral("yes")) {
10339 return Some(LayoutDeviceToScreenScale(1.0f));
10340 } else if (aScaleString.EqualsLiteral("no")) {
10341 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10342 } else if (aScaleString.IsEmpty()) {
10343 return Nothing();
10346 nsresult scaleErrorCode;
10347 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
10348 if (NS_FAILED(scaleErrorCode)) {
10349 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10352 if (scale < 0) {
10353 return Nothing();
10355 return Some(clamped(LayoutDeviceToScreenScale(scale), ViewportMinScale(),
10356 ViewportMaxScale()));
10359 void Document::ParseScalesInViewportMetaData(
10360 const ViewportMetaData& aViewportMetaData) {
10361 Maybe<LayoutDeviceToScreenScale> scale;
10363 scale = ParseScaleString(aViewportMetaData.mInitialScale);
10364 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
10365 mValidScaleFloat = scale.isSome();
10367 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
10368 // Chrome uses '5' for the fallback value of maximum-scale, we might
10369 // consider matching it in future.
10370 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
10371 mScaleMaxFloat = scale.valueOr(ViewportMaxScale());
10372 mValidMaxScale = scale.isSome();
10374 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
10375 mScaleMinFloat = scale.valueOr(ViewportMinScale());
10376 mValidMinScale = scale.isSome();
10378 // Resolve min-zoom and max-zoom values.
10379 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
10380 if (mValidMaxScale && mValidMinScale) {
10381 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
10385 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
10386 const nsAString& aHeightString,
10387 bool aHasValidScale) {
10388 // The width and height properties
10389 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
10391 // The width and height viewport <META> properties are translated into width
10392 // and height descriptors, setting the min-width/min-height value to
10393 // extend-to-zoom and the max-width/max-height value to the length from the
10394 // viewport <META> property as follows:
10396 // 1. Non-negative number values are translated to pixel lengths, clamped to
10397 // the range: [1px, 10000px]
10398 // 2. Negative number values are dropped
10399 // 3. device-width and device-height translate to 100vw and 100vh respectively
10400 // 4. Other keywords and unknown values are also dropped
10401 mMinWidth = nsViewportInfo::kAuto;
10402 mMaxWidth = nsViewportInfo::kAuto;
10403 if (!aWidthString.IsEmpty()) {
10404 mMinWidth = nsViewportInfo::kExtendToZoom;
10405 if (aWidthString.EqualsLiteral("device-width")) {
10406 mMaxWidth = nsViewportInfo::kDeviceSize;
10407 } else {
10408 nsresult widthErrorCode;
10409 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
10410 if (NS_FAILED(widthErrorCode)) {
10411 mMaxWidth = nsViewportInfo::kAuto;
10412 } else if (mMaxWidth >= 0.0f) {
10413 mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
10414 } else {
10415 mMaxWidth = nsViewportInfo::kAuto;
10418 } else if (aHasValidScale) {
10419 if (aHeightString.IsEmpty()) {
10420 mMinWidth = nsViewportInfo::kExtendToZoom;
10421 mMaxWidth = nsViewportInfo::kExtendToZoom;
10423 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
10424 mMinWidth = nsViewportInfo::kExtendToZoom;
10425 mMaxWidth = nsViewportInfo::kDeviceSize;
10428 mMinHeight = nsViewportInfo::kAuto;
10429 mMaxHeight = nsViewportInfo::kAuto;
10430 if (!aHeightString.IsEmpty()) {
10431 mMinHeight = nsViewportInfo::kExtendToZoom;
10432 if (aHeightString.EqualsLiteral("device-height")) {
10433 mMaxHeight = nsViewportInfo::kDeviceSize;
10434 } else {
10435 nsresult heightErrorCode;
10436 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
10437 if (NS_FAILED(heightErrorCode)) {
10438 mMaxHeight = nsViewportInfo::kAuto;
10439 } else if (mMaxHeight >= 0.0f) {
10440 mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
10441 } else {
10442 mMaxHeight = nsViewportInfo::kAuto;
10448 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
10449 MOZ_ASSERT(mPresShell);
10451 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
10452 // widget scale and the full zoom.
10453 nsPresContext* context = mPresShell->GetPresContext();
10454 // When querying the full zoom, get it from the device context rather than
10455 // directly from the pres context, because the device context's value can
10456 // include an adjustment necessary to keep the number of app units per device
10457 // pixel an integer, and we want the adjusted value.
10458 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
10459 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
10460 CSSToLayoutDeviceScale layoutDeviceScale =
10461 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
10463 CSSToScreenScale defaultScale =
10464 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
10466 // Special behaviour for desktop mode, provided we are not on an about: page,
10467 // or fullscreen.
10468 const bool fullscreen = Fullscreen();
10469 auto* bc = GetBrowsingContext();
10470 if (bc && bc->ForceDesktopViewport() && !IsAboutPage() && !fullscreen) {
10471 CSSCoord viewportWidth =
10472 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
10473 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
10474 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
10475 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
10476 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
10477 return nsViewportInfo(fakeDesktopSize, scaleToFit,
10478 nsViewportInfo::ZoomFlag::AllowZoom,
10479 nsViewportInfo::ZoomBehaviour::Mobile,
10480 nsViewportInfo::AutoScaleFlag::AutoScale);
10483 // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
10484 if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
10485 return nsViewportInfo(aDisplaySize, defaultScale,
10486 nsLayoutUtils::AllowZoomingForDocument(this)
10487 ? nsViewportInfo::ZoomFlag::AllowZoom
10488 : nsViewportInfo::ZoomFlag::DisallowZoom,
10489 StaticPrefs::apz_allow_zooming_out()
10490 ? nsViewportInfo::ZoomBehaviour::Mobile
10491 : nsViewportInfo::ZoomBehaviour::Desktop);
10494 // In cases where the width of the CSS viewport is less than or equal to the
10495 // width of the display (i.e. width <= device-width) then we disable
10496 // double-tap-to-zoom behaviour. See bug 941995 for details.
10498 switch (mViewportType) {
10499 case DisplayWidthHeight:
10500 return nsViewportInfo(aDisplaySize, defaultScale,
10501 nsViewportInfo::ZoomFlag::AllowZoom,
10502 nsViewportInfo::ZoomBehaviour::Mobile);
10503 case Unknown: {
10504 // We might early exit if the viewport is empty. Even if we don't,
10505 // at the end of this case we'll note that it was empty. Later, when
10506 // we're using the cached values, this will trigger alternate code paths.
10507 if (!mLastModifiedViewportMetaData) {
10508 // If the docType specifies that we are on a site optimized for mobile,
10509 // then we want to return specially crafted defaults for the viewport
10510 // info.
10511 if (RefPtr<DocumentType> docType = GetDoctype()) {
10512 nsAutoString docId;
10513 docType->GetPublicId(docId);
10514 if ((docId.Find(u"WAP") != -1) || (docId.Find(u"Mobile") != -1) ||
10515 (docId.Find(u"WML") != -1)) {
10516 // We're making an assumption that the docType can't change here
10517 mViewportType = DisplayWidthHeight;
10518 return nsViewportInfo(aDisplaySize, defaultScale,
10519 nsViewportInfo::ZoomFlag::AllowZoom,
10520 nsViewportInfo::ZoomBehaviour::Mobile);
10524 nsAutoString handheldFriendly;
10525 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
10526 if (handheldFriendly.EqualsLiteral("true")) {
10527 mViewportType = DisplayWidthHeight;
10528 return nsViewportInfo(aDisplaySize, defaultScale,
10529 nsViewportInfo::ZoomFlag::AllowZoom,
10530 nsViewportInfo::ZoomBehaviour::Mobile);
10534 ViewportMetaData metaData = GetViewportMetaData();
10536 // Parse initial-scale, minimum-scale and maximum-scale.
10537 ParseScalesInViewportMetaData(metaData);
10539 // Parse width and height properties
10540 // This function sets m{Min,Max}{Width,Height}.
10541 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
10542 mValidScaleFloat);
10544 mAllowZoom = true;
10545 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
10546 (metaData.mUserScalable.EqualsLiteral("no")) ||
10547 (metaData.mUserScalable.EqualsLiteral("false"))) {
10548 mAllowZoom = false;
10551 // Resolve viewport-fit value.
10552 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
10553 mViewportFit = ViewportFitType::Auto;
10554 if (!metaData.mViewportFit.IsEmpty()) {
10555 if (metaData.mViewportFit.EqualsLiteral("contain")) {
10556 mViewportFit = ViewportFitType::Contain;
10557 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
10558 mViewportFit = ViewportFitType::Cover;
10562 mWidthStrEmpty = metaData.mWidth.IsEmpty();
10564 mViewportType = Specified;
10565 [[fallthrough]];
10567 case Specified:
10568 default:
10569 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
10570 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
10571 bool effectiveValidMaxScale = mValidMaxScale;
10573 nsViewportInfo::ZoomFlag effectiveZoomFlag =
10574 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
10575 : nsViewportInfo::ZoomFlag::DisallowZoom;
10576 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
10577 // If the pref to force user-scalable is enabled, we ignore the values
10578 // from the meta-viewport tag for these properties and just assume they
10579 // allow the page to be scalable. Note in particular that this code is
10580 // in the "Specified" branch of the enclosing switch statement, so that
10581 // calls to GetViewportInfo always use the latest value of the
10582 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
10583 // return nsViewportInfo instances are all consistent with
10584 // browser_ui_zoom_force_user_scalable() already.
10585 effectiveMinScale = ViewportMinScale();
10586 effectiveMaxScale = ViewportMaxScale();
10587 effectiveValidMaxScale = true;
10588 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
10591 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
10592 auto ComputeExtendZoom = [&]() -> float {
10593 if (mValidScaleFloat && effectiveValidMaxScale) {
10594 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
10596 if (mValidScaleFloat) {
10597 return mScaleFloat.scale;
10599 if (effectiveValidMaxScale) {
10600 return effectiveMaxScale.scale;
10602 return nsViewportInfo::kAuto;
10605 // Resolving 'extend-to-zoom'
10606 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
10607 float extendZoom = ComputeExtendZoom();
10609 CSSCoord minWidth = mMinWidth;
10610 CSSCoord maxWidth = mMaxWidth;
10611 CSSCoord minHeight = mMinHeight;
10612 CSSCoord maxHeight = mMaxHeight;
10614 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
10615 // viewport size. We need to use this scaled size for any clamping of
10616 // width or height.
10617 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
10619 // Our min and max width and height values are mostly as specified by
10620 // the viewport declaration, but we make an exception for max width.
10621 // Max width, if auto, and if there's no initial-scale, will be set
10622 // to a default size. This is to support legacy site design with no
10623 // viewport declaration, and to do that using the same scheme as
10624 // Chrome does, in order to maintain web compatibility. Since the
10625 // default size has a complicated calculation, we fixup the maxWidth
10626 // value after setting it, above.
10627 if (maxWidth == nsViewportInfo::kAuto && !mValidScaleFloat) {
10628 if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
10629 bc->InRDMPane()) {
10630 // If RDM and touch simulation are active, then use the simulated
10631 // screen width to accommodate for cases where the screen width is
10632 // larger than the desktop viewport default.
10633 maxWidth = nsViewportInfo::Max(
10634 displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
10635 } else {
10636 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
10638 // Divide by fullZoom to stretch CSS pixel size of viewport in order
10639 // to keep device pixel size unchanged after full zoom applied.
10640 // See bug 974242.
10641 maxWidth /= fullZoom;
10643 // We set minWidth to ExtendToZoom, which will cause our later width
10644 // calculation to expand to maxWidth, if scale restrictions allow it.
10645 minWidth = nsViewportInfo::kExtendToZoom;
10648 // Resolve device-width and device-height first.
10649 if (maxWidth == nsViewportInfo::kDeviceSize) {
10650 maxWidth = displaySize.width;
10652 if (maxHeight == nsViewportInfo::kDeviceSize) {
10653 maxHeight = displaySize.height;
10655 if (extendZoom == nsViewportInfo::kAuto) {
10656 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10657 maxWidth = nsViewportInfo::kAuto;
10659 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10660 maxHeight = nsViewportInfo::kAuto;
10662 if (minWidth == nsViewportInfo::kExtendToZoom) {
10663 minWidth = maxWidth;
10665 if (minHeight == nsViewportInfo::kExtendToZoom) {
10666 minHeight = maxHeight;
10668 } else {
10669 CSSSize extendSize = displaySize / extendZoom;
10670 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10671 maxWidth = extendSize.width;
10673 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10674 maxHeight = extendSize.height;
10676 if (minWidth == nsViewportInfo::kExtendToZoom) {
10677 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
10679 if (minHeight == nsViewportInfo::kExtendToZoom) {
10680 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
10684 // Resolve initial width and height from min/max descriptors
10685 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
10686 CSSCoord width = nsViewportInfo::kAuto;
10687 if (minWidth != nsViewportInfo::kAuto ||
10688 maxWidth != nsViewportInfo::kAuto) {
10689 width = nsViewportInfo::Max(
10690 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
10692 CSSCoord height = nsViewportInfo::kAuto;
10693 if (minHeight != nsViewportInfo::kAuto ||
10694 maxHeight != nsViewportInfo::kAuto) {
10695 height = nsViewportInfo::Max(
10696 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
10699 // Resolve width value
10700 // https://drafts.csswg.org/css-device-adapt/#resolve-width
10701 if (width == nsViewportInfo::kAuto) {
10702 if (height == nsViewportInfo::kAuto || aDisplaySize.height == 0) {
10703 width = displaySize.width;
10704 } else {
10705 width = height * aDisplaySize.width / aDisplaySize.height;
10709 // Resolve height value
10710 // https://drafts.csswg.org/css-device-adapt/#resolve-height
10711 if (height == nsViewportInfo::kAuto) {
10712 if (aDisplaySize.width == 0) {
10713 height = displaySize.height;
10714 } else {
10715 height = width * aDisplaySize.height / aDisplaySize.width;
10718 MOZ_ASSERT(width != nsViewportInfo::kAuto &&
10719 height != nsViewportInfo::kAuto);
10721 CSSSize size(width, height);
10723 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
10724 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
10725 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
10727 nsViewportInfo::AutoSizeFlag sizeFlag =
10728 nsViewportInfo::AutoSizeFlag::FixedSize;
10729 if (mMaxWidth == nsViewportInfo::kDeviceSize ||
10730 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::kDeviceSize ||
10731 mScaleFloat.scale == 1.0f)) ||
10732 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::kAuto &&
10733 mMaxHeight < 0)) {
10734 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
10737 // FIXME: Resolving width and height should be done above 'Resolve width
10738 // value' and 'Resolve height value'.
10739 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
10740 size = displaySize;
10743 // The purpose of clamping the viewport width to a minimum size is to
10744 // prevent page authors from setting it to a ridiculously small value.
10745 // If the page is actually being rendered in a very small area (as might
10746 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
10747 // prevent the viewport from taking on that size.
10748 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
10750 size.width = clamped(size.width, effectiveMinSize.width,
10751 float(kViewportMaxSize.width));
10753 // Also recalculate the default zoom, if it wasn't specified in the
10754 // metadata, and the width is specified.
10755 if (!mValidScaleFloat && !mWidthStrEmpty) {
10756 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
10757 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
10760 size.height = clamped(size.height, effectiveMinSize.height,
10761 float(kViewportMaxSize.height));
10763 // In cases of user-scalable=no, if we have a positive scale, clamp it to
10764 // min and max, and then use the clamped value for the scale, the min, and
10765 // the max. If we don't have a positive scale, assert that we are setting
10766 // the auto scale flag.
10767 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
10768 scaleFloat > CSSToScreenScale(0.0f)) {
10769 scaleFloat = scaleMinFloat = scaleMaxFloat =
10770 clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
10772 MOZ_ASSERT(
10773 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
10774 "If we don't have a positive scale, we should be using auto scale.");
10776 // We need to perform a conversion, but only if the initial or maximum
10777 // scale were set explicitly by the user.
10778 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
10779 scaleFloat <= scaleMaxFloat) {
10780 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
10781 size.width = std::max(size.width, displaySize.width);
10782 size.height = std::max(size.height, displaySize.height);
10783 } else if (effectiveValidMaxScale) {
10784 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
10785 size.width = std::max(size.width, displaySize.width);
10786 size.height = std::max(size.height, displaySize.height);
10789 return nsViewportInfo(
10790 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
10791 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
10792 : nsViewportInfo::AutoScaleFlag::AutoScale,
10793 effectiveZoomFlag, mViewportFit);
10797 ViewportMetaData Document::GetViewportMetaData() const {
10798 return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
10799 : ViewportMetaData();
10802 void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
10803 mLastModifiedViewportMetaData = std::move(aData);
10804 // Trigger recomputation of the nsViewportInfo the next time it's queried.
10805 mViewportType = Unknown;
10807 AsyncEventDispatcher::RunDOMEventWhenSafe(
10808 *this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes,
10809 ChromeOnlyDispatch::eYes);
10812 EventListenerManager* Document::GetOrCreateListenerManager() {
10813 if (!mListenerManager) {
10814 mListenerManager =
10815 new EventListenerManager(static_cast<EventTarget*>(this));
10816 SetFlags(NODE_HAS_LISTENERMANAGER);
10819 return mListenerManager;
10822 EventListenerManager* Document::GetExistingListenerManager() const {
10823 return mListenerManager;
10826 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
10827 aVisitor.mCanHandle = true;
10828 // FIXME! This is a hack to make middle mouse paste working also in Editor.
10829 // Bug 329119
10830 aVisitor.mForceContentDispatch = true;
10832 // Load events must not propagate to |window| object, see bug 335251.
10833 if (aVisitor.mEvent->mMessage != eLoad) {
10834 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
10835 aVisitor.SetParentTarget(
10836 window ? window->GetTargetForEventTargetChain() : nullptr, false);
10840 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
10841 CallerType aCallerType,
10842 ErrorResult& rv) const {
10843 nsPresContext* presContext = GetPresContext();
10845 // Create event even without presContext.
10846 RefPtr<Event> ev =
10847 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
10848 nullptr, aEventType, aCallerType);
10849 if (!ev) {
10850 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10851 return nullptr;
10853 WidgetEvent* e = ev->WidgetEventPtr();
10854 e->mFlags.mBubbles = false;
10855 e->mFlags.mCancelable = false;
10856 return ev.forget();
10859 void Document::FlushPendingNotifications(FlushType aType) {
10860 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
10861 FlushPendingNotifications(flush);
10864 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
10865 FlushType flushType = aFlush.mFlushType;
10867 RefPtr<Document> documentOnStack = this;
10869 // We need to flush the sink for non-HTML documents (because the XML
10870 // parser still does insertion with deferred notifications). We
10871 // also need to flush the sink if this is a layout-related flush, to
10872 // make sure that layout is started as needed. But we can skip that
10873 // part if we have no presshell or if it's already done an initial
10874 // reflow.
10875 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
10876 mPresShell && !mPresShell->DidInitialize())) &&
10877 (mParser || mWeakSink)) {
10878 nsCOMPtr<nsIContentSink> sink;
10879 if (mParser) {
10880 sink = mParser->GetContentSink();
10881 } else {
10882 sink = do_QueryReferent(mWeakSink);
10883 if (!sink) {
10884 mWeakSink = nullptr;
10887 // Determine if it is safe to flush the sink notifications
10888 // by determining if it safe to flush all the presshells.
10889 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
10890 sink->FlushPendingNotifications(flushType);
10894 // Should we be flushing pending binding constructors in here?
10896 if (flushType <= FlushType::ContentAndNotify) {
10897 // Nothing to do here
10898 return;
10901 // If we have a parent we must flush the parent too to ensure that our
10902 // container is reflowed if its size was changed.
10904 // We do it only if the subdocument and the parent can observe each other
10905 // synchronously (that is, if we're not cross-origin), to avoid work that is
10906 // not observable, and if the parent document has finished loading all its
10907 // render-blocking stylesheets and may start laying out the document, to avoid
10908 // unnecessary flashes of unstyled content on the parent document. Note that
10909 // this last bit means that size-dependent media queries in this document may
10910 // produce incorrect results temporarily.
10912 // But if it's not safe to flush ourselves, then don't flush the parent, since
10913 // that can cause things like resizes of our frame's widget, which we can't
10914 // handle while flushing is unsafe.
10915 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
10916 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
10917 ChangesToFlush parentFlush = aFlush;
10918 if (flushType >= FlushType::Style) {
10919 // Since media queries mean that a size change of our container can affect
10920 // style, we need to promote a style flush on ourself to a layout flush on
10921 // our parent, since we need our container to be the correct size to
10922 // determine the correct style.
10923 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
10925 mParentDocument->FlushPendingNotifications(parentFlush);
10928 if (RefPtr<PresShell> presShell = GetPresShell()) {
10929 presShell->FlushPendingNotifications(aFlush);
10933 void Document::FlushExternalResources(FlushType aType) {
10934 NS_ASSERTION(
10935 aType >= FlushType::Style,
10936 "should only need to flush for style or higher in external resources");
10937 if (GetDisplayDocument()) {
10938 return;
10941 auto flush = [aType](Document& aDoc) {
10942 aDoc.FlushPendingNotifications(aType);
10943 return CallState::Continue;
10946 EnumerateExternalResources(flush);
10949 void Document::SetXMLDeclaration(const char16_t* aVersion,
10950 const char16_t* aEncoding,
10951 const int32_t aStandalone) {
10952 if (!aVersion || *aVersion == '\0') {
10953 mXMLDeclarationBits = 0;
10954 return;
10957 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
10959 if (aEncoding && *aEncoding != '\0') {
10960 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
10963 if (aStandalone == 1) {
10964 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
10965 XML_DECLARATION_BITS_STANDALONE_YES;
10966 } else if (aStandalone == 0) {
10967 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
10971 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
10972 nsAString& aStandalone) {
10973 aVersion.Truncate();
10974 aEncoding.Truncate();
10975 aStandalone.Truncate();
10977 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
10978 return;
10981 // always until we start supporting 1.1 etc.
10982 aVersion.AssignLiteral("1.0");
10984 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
10985 // This is what we have stored, not necessarily what was written
10986 // in the original
10987 GetCharacterSet(aEncoding);
10990 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
10991 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
10992 aStandalone.AssignLiteral("yes");
10993 } else {
10994 aStandalone.AssignLiteral("no");
10999 void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
11000 mColorSchemeMetaTags.Insert(aMeta);
11001 RecomputeColorScheme();
11004 void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
11005 mColorSchemeMetaTags.RemoveElement(aMeta);
11006 RecomputeColorScheme();
11009 void Document::RecomputeColorScheme() {
11010 auto oldColorScheme = mColorSchemeBits;
11011 mColorSchemeBits = 0;
11012 const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
11013 for (const HTMLMetaElement* el : elements) {
11014 nsAutoString content;
11015 if (!el->GetAttr(nsGkAtoms::content, content)) {
11016 continue;
11019 NS_ConvertUTF16toUTF8 contentU8(content);
11020 if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
11021 break;
11025 if (mColorSchemeBits == oldColorScheme) {
11026 return;
11029 if (nsPresContext* pc = GetPresContext()) {
11030 // This affects system colors, which are inherited, so we need to recascade.
11031 pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
11035 bool Document::IsScriptEnabled() const {
11036 // If this document is sandboxed without 'allow-scripts'
11037 // script is not enabled
11038 if (HasScriptsBlockedBySandbox()) {
11039 return false;
11042 nsCOMPtr<nsIScriptGlobalObject> globalObject =
11043 do_QueryInterface(GetInnerWindow());
11044 if (!globalObject || !globalObject->HasJSGlobal()) {
11045 return false;
11048 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
11049 .Allowed();
11052 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
11053 PRTime modDate = 0;
11054 nsresult rv;
11056 nsCOMPtr<nsIHttpChannel> httpChannel;
11057 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
11058 if (NS_WARN_IF(NS_FAILED(rv))) {
11059 return;
11062 if (httpChannel) {
11063 nsAutoCString tmp;
11064 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
11066 if (NS_SUCCEEDED(rv)) {
11067 PRTime time;
11068 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
11069 if (st == PR_SUCCESS) {
11070 modDate = time;
11074 static const char* const headers[] = {
11075 "default-style", "content-style-type", "content-language",
11076 "content-disposition", "refresh", "x-dns-prefetch-control",
11077 "x-frame-options", "origin-trial",
11078 // add more http headers if you need
11079 // XXXbz don't add content-location support without reading bug
11080 // 238654 and its dependencies/dups first.
11083 nsAutoCString headerVal;
11084 const char* const* name = headers;
11085 while (*name) {
11086 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
11087 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
11088 RefPtr<nsAtom> key = NS_Atomize(*name);
11089 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
11091 ++name;
11093 } else {
11094 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
11095 if (fileChannel) {
11096 nsCOMPtr<nsIFile> file;
11097 fileChannel->GetFile(getter_AddRefs(file));
11098 if (file) {
11099 PRTime msecs;
11100 rv = file->GetLastModifiedTime(&msecs);
11102 if (NS_SUCCEEDED(rv)) {
11103 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
11106 } else {
11107 nsAutoCString contentDisp;
11108 rv = aChannel->GetContentDispositionHeader(contentDisp);
11109 if (NS_SUCCEEDED(rv)) {
11110 SetHeaderData(nsGkAtoms::headerContentDisposition,
11111 NS_ConvertASCIItoUTF16(contentDisp));
11116 mLastModified.Truncate();
11117 if (modDate != 0) {
11118 GetFormattedTimeString(modDate, mLastModified);
11122 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
11123 // set any HTTP-EQUIV data into document's header data as well as url
11124 nsAutoString header;
11125 aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
11126 if (!header.IsEmpty()) {
11127 // Ignore META REFRESH when document is sandboxed from automatic features.
11128 nsContentUtils::ASCIIToLower(header);
11129 if (nsGkAtoms::refresh->Equals(header) &&
11130 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
11131 return;
11134 nsAutoString result;
11135 aMetaElement->GetAttr(nsGkAtoms::content, result);
11136 if (!result.IsEmpty()) {
11137 RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
11138 SetHeaderData(fieldAtom, result);
11142 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
11143 nsGkAtoms::handheldFriendly, eIgnoreCase)) {
11144 nsAutoString result;
11145 aMetaElement->GetAttr(nsGkAtoms::content, result);
11146 if (!result.IsEmpty()) {
11147 nsContentUtils::ASCIIToLower(result);
11148 SetHeaderData(nsGkAtoms::handheldFriendly, result);
11153 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
11154 nsAtom* aPrefix,
11155 int32_t aNamespaceID,
11156 const nsAString* aIs) {
11157 #ifdef DEBUG
11158 nsAutoString qName;
11159 if (aPrefix) {
11160 aPrefix->ToString(qName);
11161 qName.Append(':');
11163 qName.Append(aName);
11165 // Note: "a:b:c" is a valid name in non-namespaces XML, and
11166 // Document::CreateElement can call us with such a name and no prefix,
11167 // which would cause an error if we just used true here.
11168 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
11169 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
11170 "Don't pass invalid prefixes to Document::CreateElem, "
11171 "check caller.");
11172 #endif
11174 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
11175 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
11176 getter_AddRefs(nodeInfo));
11177 NS_ENSURE_TRUE(nodeInfo, nullptr);
11179 nsCOMPtr<Element> element;
11180 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
11181 NOT_FROM_PARSER, aIs);
11182 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
11185 bool Document::IsSafeToFlush() const {
11186 PresShell* presShell = GetPresShell();
11187 if (!presShell) {
11188 return true;
11190 return presShell->IsSafeToFlush();
11193 void Document::Sanitize() {
11194 // Sanitize the document by resetting all (current and former) password fields
11195 // and any form fields with autocomplete=off to their default values. We do
11196 // this now, instead of when the presentation is restored, to offer some
11197 // protection in case there is ever an exploit that allows a cached document
11198 // to be accessed from a different document.
11200 // First locate all input elements, regardless of whether they are
11201 // in a form, and reset the password and autocomplete=off elements.
11203 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
11205 nsAutoString value;
11207 uint32_t length = nodes->Length(true);
11208 for (uint32_t i = 0; i < length; ++i) {
11209 NS_ASSERTION(nodes->Item(i), "null item in node list!");
11211 RefPtr<HTMLInputElement> input =
11212 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
11213 if (!input) continue;
11215 input->GetAttr(nsGkAtoms::autocomplete, value);
11216 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
11217 input->Reset();
11221 // Now locate all _form_ elements that have autocomplete=off and reset them
11222 nodes = GetElementsByTagName(u"form"_ns);
11224 length = nodes->Length(true);
11225 for (uint32_t i = 0; i < length; ++i) {
11226 // Reset() may change the list dynamically.
11227 RefPtr<HTMLFormElement> form =
11228 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
11229 if (!form) continue;
11231 form->GetAttr(nsGkAtoms::autocomplete, value);
11232 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
11236 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
11237 if (!mSubDocuments) {
11238 return;
11241 // PLDHashTable::Iterator can't handle modifications while iterating so we
11242 // copy all entries to an array first before calling any callbacks.
11243 AutoTArray<RefPtr<Document>, 8> subdocs;
11244 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11245 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11246 if (Document* subdoc = entry->mSubDocument) {
11247 subdocs.AppendElement(subdoc);
11250 for (auto& subdoc : subdocs) {
11251 if (aCallback(*subdoc) == CallState::Stop) {
11252 break;
11257 void Document::CollectDescendantDocuments(
11258 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
11259 if (!mSubDocuments) {
11260 return;
11263 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11264 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11265 const Document* subdoc = entry->mSubDocument;
11266 if (subdoc) {
11267 if (aCallback(subdoc)) {
11268 aDescendants.AppendElement(entry->mSubDocument);
11270 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
11275 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
11276 uint32_t& aBFCacheCombo,
11277 bool aIncludeSubdocuments,
11278 bool aAllowUnloadListeners) {
11279 bool ret = true;
11281 if (!IsBFCachingAllowed()) {
11282 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
11283 ret = false;
11286 nsAutoCString uri;
11287 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11288 if (mDocumentURI) {
11289 mDocumentURI->GetSpec(uri);
11293 if (EventHandlingSuppressed()) {
11294 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11295 ("Save of %s blocked on event handling suppression", uri.get()));
11296 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
11297 ret = false;
11300 // Do not allow suspended windows to be placed in the
11301 // bfcache. This method is also used to verify a document
11302 // coming out of the bfcache is ok to restore, though. So
11303 // we only want to block suspend windows that aren't also
11304 // frozen.
11305 nsPIDOMWindowInner* win = GetInnerWindow();
11306 if (win && win->IsSuspended() && !win->IsFrozen()) {
11307 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11308 ("Save of %s blocked on suspended Window", uri.get()));
11309 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
11310 ret = false;
11313 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
11314 bool thirdParty = false;
11315 // Currently some other mobile browsers seem to bfcache only cross-domain
11316 // pages, but bfcache those also when there are unload event listeners, so
11317 // this is trying to match that behavior as much as possible.
11318 bool allowUnloadListeners =
11319 aAllowUnloadListeners &&
11320 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
11321 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
11322 channel, &thirdParty)) &&
11323 thirdParty));
11325 // Check our event listener manager for unload/beforeunload listeners.
11326 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
11327 if (!allowUnloadListeners && piTarget) {
11328 EventListenerManager* manager = piTarget->GetExistingListenerManager();
11329 if (manager) {
11330 if (manager->HasUnloadListeners()) {
11331 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11332 ("Save of %s blocked due to unload handlers", uri.get()));
11333 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
11334 ret = false;
11336 if (manager->HasBeforeUnloadListeners()) {
11337 if (!mozilla::SessionHistoryInParent() ||
11338 !StaticPrefs::
11339 docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
11340 MOZ_LOG(
11341 gPageCacheLog, mozilla::LogLevel::Verbose,
11342 ("Save of %s blocked due to beforeUnload handlers", uri.get()));
11343 aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
11344 ret = false;
11350 // Check if we have pending network requests
11351 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11352 if (loadGroup) {
11353 nsCOMPtr<nsISimpleEnumerator> requests;
11354 loadGroup->GetRequests(getter_AddRefs(requests));
11356 bool hasMore = false;
11358 // We want to bail out if we have any requests other than aNewRequest (or
11359 // in the case when aNewRequest is a part of a multipart response the base
11360 // channel the multipart response is coming in on).
11361 nsCOMPtr<nsIChannel> baseChannel;
11362 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
11363 if (part) {
11364 part->GetBaseChannel(getter_AddRefs(baseChannel));
11367 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11368 nsCOMPtr<nsISupports> elem;
11369 requests->GetNext(getter_AddRefs(elem));
11371 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11372 if (request && request != aNewRequest && request != baseChannel) {
11373 // Favicon loads don't need to block caching.
11374 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
11375 if (channel) {
11376 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
11377 if (li->InternalContentPolicyType() ==
11378 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
11379 continue;
11383 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11384 nsAutoCString requestName;
11385 request->GetName(requestName);
11386 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
11387 ("Save of %s blocked because document has request %s",
11388 uri.get(), requestName.get()));
11390 aBFCacheCombo |= BFCacheStatus::REQUEST;
11391 ret = false;
11396 // Check if we have active GetUserMedia use
11397 if (MediaManager::Exists() && win &&
11398 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
11399 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11400 ("Save of %s blocked due to GetUserMedia", uri.get()));
11401 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
11402 ret = false;
11405 #ifdef MOZ_WEBRTC
11406 // Check if we have active PeerConnections
11407 if (win && win->HasActivePeerConnections()) {
11408 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11409 ("Save of %s blocked due to PeerConnection", uri.get()));
11410 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
11411 ret = false;
11413 #endif // MOZ_WEBRTC
11415 // Don't save presentations for documents containing EME content, so that
11416 // CDMs reliably shutdown upon user navigation.
11417 if (ContainsEMEContent()) {
11418 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
11419 ret = false;
11422 // Don't save presentations for documents containing MSE content, to
11423 // reduce memory usage.
11424 if (ContainsMSEContent()) {
11425 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11426 ("Save of %s blocked due to MSE use", uri.get()));
11427 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
11428 ret = false;
11431 if (aIncludeSubdocuments && mSubDocuments) {
11432 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11433 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11434 Document* subdoc = entry->mSubDocument;
11436 uint32_t subDocBFCacheCombo = 0;
11437 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
11438 bool canCache =
11439 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
11440 true, allowUnloadListeners)
11441 : false;
11442 if (!canCache) {
11443 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11444 ("Save of %s blocked due to subdocument blocked", uri.get()));
11445 aBFCacheCombo |= subDocBFCacheCombo;
11446 ret = false;
11451 if (!mozilla::BFCacheInParent()) {
11452 // BFCache is currently not compatible with remote subframes (bug 1609324)
11453 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
11454 for (auto& child : browsingContext->Children()) {
11455 if (!child->IsInProcess()) {
11456 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
11457 ret = false;
11458 break;
11464 if (win) {
11465 auto* globalWindow = nsGlobalWindowInner::Cast(win);
11466 #ifdef MOZ_WEBSPEECH
11467 if (globalWindow->HasActiveSpeechSynthesis()) {
11468 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11469 ("Save of %s blocked due to Speech use", uri.get()));
11470 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
11471 ret = false;
11473 #endif
11474 if (globalWindow->HasUsedVR()) {
11475 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11476 ("Save of %s blocked due to having used VR", uri.get()));
11477 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
11478 ret = false;
11481 if (win->HasActiveLocks()) {
11482 MOZ_LOG(
11483 gPageCacheLog, mozilla::LogLevel::Verbose,
11484 ("Save of %s blocked due to having active lock requests", uri.get()));
11485 aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
11486 ret = false;
11489 if (win->HasActiveWebTransports()) {
11490 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11491 ("Save of %s blocked due to WebTransport", uri.get()));
11492 aBFCacheCombo |= BFCacheStatus::ACTIVE_WEBTRANSPORT;
11493 ret = false;
11497 return ret;
11500 void Document::Destroy() {
11501 // The ContentViewer wants to release the document now. So, tell our content
11502 // to drop any references to the document so that it can be destroyed.
11503 if (mIsGoingAway) {
11504 return;
11507 ReportDocumentUseCounters();
11508 SetDevToolsWatchingDOMMutations(false);
11510 mIsGoingAway = true;
11512 ScriptLoader()->Destroy();
11513 SetScriptGlobalObject(nullptr);
11514 RemovedFromDocShell();
11516 bool oldVal = mInUnlinkOrDeletion;
11517 mInUnlinkOrDeletion = true;
11519 #ifdef DEBUG
11520 uint32_t oldChildCount = GetChildCount();
11521 #endif
11523 for (nsIContent* child = GetFirstChild(); child;
11524 child = child->GetNextSibling()) {
11525 child->DestroyContent();
11526 MOZ_ASSERT(child->GetParentNode() == this);
11528 MOZ_ASSERT(oldChildCount == GetChildCount());
11529 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
11531 mInUnlinkOrDeletion = oldVal;
11533 mLayoutHistoryState = nullptr;
11535 if (mOriginalDocument) {
11536 mOriginalDocument->mLatestStaticClone = nullptr;
11539 if (IsStaticDocument()) {
11540 RemoveProperty(nsGkAtoms::printisfocuseddoc);
11541 RemoveProperty(nsGkAtoms::printselectionranges);
11544 // Shut down our external resource map. We might not need this for
11545 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
11546 // tearing down all those frame trees right now is the right thing to do.
11547 mExternalResourceMap.Shutdown();
11549 // Manually break cycles via promise's global object pointer.
11550 mReadyForIdle = nullptr;
11551 mOrientationPendingPromise = nullptr;
11553 // To break cycles.
11554 mPreloadService.ClearAllPreloads();
11556 if (mDocumentL10n) {
11557 mDocumentL10n->Destroy();
11561 void Document::RemovedFromDocShell() {
11562 mEditingState = EditingState::eOff;
11564 if (mRemovedFromDocShell) return;
11566 mRemovedFromDocShell = true;
11567 NotifyActivityChanged();
11569 for (nsIContent* child = GetFirstChild(); child;
11570 child = child->GetNextSibling()) {
11571 child->SaveSubtreeState();
11574 nsIDocShell* docShell = GetDocShell();
11575 if (docShell) {
11576 docShell->SynchronizeLayoutHistoryState();
11580 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
11581 const {
11582 nsCOMPtr<nsILayoutHistoryState> state;
11583 if (!mScriptGlobalObject) {
11584 state = mLayoutHistoryState;
11585 } else {
11586 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
11587 if (docShell) {
11588 docShell->GetLayoutHistoryState(getter_AddRefs(state));
11592 return state.forget();
11595 void Document::EnsureOnloadBlocker() {
11596 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11597 // -- it's not ours.
11598 if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
11599 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11600 if (loadGroup) {
11601 // Check first to see if mOnloadBlocker is in the loadgroup.
11602 nsCOMPtr<nsISimpleEnumerator> requests;
11603 loadGroup->GetRequests(getter_AddRefs(requests));
11605 bool hasMore = false;
11606 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11607 nsCOMPtr<nsISupports> elem;
11608 requests->GetNext(getter_AddRefs(elem));
11609 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11610 if (request && request == mOnloadBlocker) {
11611 return;
11615 // Not in the loadgroup, so add it.
11616 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11621 void Document::BlockOnload() {
11622 if (mDisplayDocument) {
11623 mDisplayDocument->BlockOnload();
11624 return;
11627 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11628 // -- it's not ours.
11629 if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
11630 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11631 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11634 ++mOnloadBlockCount;
11637 void Document::UnblockOnload(bool aFireSync) {
11638 if (mDisplayDocument) {
11639 mDisplayDocument->UnblockOnload(aFireSync);
11640 return;
11643 --mOnloadBlockCount;
11645 if (mOnloadBlockCount == 0) {
11646 if (mScriptGlobalObject) {
11647 // Only manipulate the loadgroup in this case, because if
11648 // mScriptGlobalObject is null, it's not ours.
11649 if (aFireSync) {
11650 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
11651 ++mOnloadBlockCount;
11652 DoUnblockOnload();
11653 } else {
11654 PostUnblockOnloadEvent();
11656 } else if (mIsBeingUsedAsImage) {
11657 // To correctly unblock onload for a document that contains an SVG
11658 // image, we need to know when all of the SVG document's resources are
11659 // done loading, in a way comparable to |window.onload|. We fire this
11660 // event to indicate that the SVG should be considered fully loaded.
11661 // Because scripting is disabled on SVG-as-image documents, this event
11662 // is not accessible to content authors. (See bug 837315.)
11663 RefPtr<AsyncEventDispatcher> asyncDispatcher =
11664 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
11665 CanBubble::eNo, ChromeOnlyDispatch::eNo);
11666 asyncDispatcher->PostDOMEvent();
11671 class nsUnblockOnloadEvent : public Runnable {
11672 public:
11673 explicit nsUnblockOnloadEvent(Document* aDoc)
11674 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
11675 NS_IMETHOD Run() override {
11676 mDoc->DoUnblockOnload();
11677 return NS_OK;
11680 private:
11681 RefPtr<Document> mDoc;
11684 void Document::PostUnblockOnloadEvent() {
11685 MOZ_RELEASE_ASSERT(NS_IsMainThread());
11686 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
11687 nsresult rv = Dispatch(TaskCategory::Other, evt.forget());
11688 if (NS_SUCCEEDED(rv)) {
11689 // Stabilize block count so we don't post more events while this one is up
11690 ++mOnloadBlockCount;
11691 } else {
11692 NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
11696 void Document::DoUnblockOnload() {
11697 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
11698 MOZ_ASSERT(mOnloadBlockCount != 0,
11699 "Shouldn't have a count of zero here, since we stabilized in "
11700 "PostUnblockOnloadEvent");
11702 --mOnloadBlockCount;
11704 if (mOnloadBlockCount != 0) {
11705 // We blocked again after the last unblock. Nothing to do here. We'll
11706 // post a new event when we unblock again.
11707 return;
11710 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11711 // -- it's not ours.
11712 if (mScriptGlobalObject) {
11713 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11714 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
11719 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
11720 for (nsIFrame* f = aFrame; f;
11721 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
11722 nsIContent* content = f->GetContent();
11723 if (!content) {
11724 continue;
11727 if (content->OwnerDoc() == this) {
11728 return content;
11730 // We must be in a subdocument so jump directly to the root frame.
11731 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
11732 // the containing document.
11733 f = f->PresContext()->GetPresShell()->GetRootFrame();
11736 return nullptr;
11739 void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
11740 const nsAString& aType, bool aInFrameSwap,
11741 bool aPersisted, bool aOnlySystemGroup) {
11742 if (!aDispatchTarget) {
11743 return;
11746 PageTransitionEventInit init;
11747 init.mBubbles = true;
11748 init.mCancelable = true;
11749 init.mPersisted = aPersisted;
11750 init.mInFrameSwap = aInFrameSwap;
11752 RefPtr<PageTransitionEvent> event =
11753 PageTransitionEvent::Constructor(this, aType, init);
11755 event->SetTrusted(true);
11756 event->SetTarget(this);
11757 if (aOnlySystemGroup) {
11758 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
11760 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
11761 nullptr);
11764 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
11765 bool aOnlySystemGroup) {
11766 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11767 nsCString uri;
11768 if (GetDocumentURI()) {
11769 uri = GetDocumentURI()->GetSpecOrDefault();
11771 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11772 ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
11775 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11776 MOZ_DIAGNOSTIC_ASSERT(
11777 inFrameLoaderSwap ==
11778 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11780 Element* root = GetRootElement();
11781 if (aPersisted && root) {
11782 // Send out notifications that our <link> elements are attached.
11783 RefPtr<nsContentList> links =
11784 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11786 uint32_t linkCount = links->Length(true);
11787 for (uint32_t i = 0; i < linkCount; ++i) {
11788 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
11792 // See Document
11793 if (!inFrameLoaderSwap) {
11794 if (aPersisted) {
11795 ImageTracker()->SetAnimatingState(true);
11798 // Set mIsShowing before firing events, in case those event handlers
11799 // move us around.
11800 mIsShowing = true;
11801 mVisible = true;
11803 UpdateVisibilityState();
11806 NotifyActivityChanged();
11808 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11809 aExternalResource.OnPageShow(aPersisted, nullptr);
11810 return CallState::Continue;
11812 EnumerateExternalResources(notifyExternal);
11814 if (mAnimationController) {
11815 mAnimationController->OnPageShow();
11818 if (!mIsBeingUsedAsImage) {
11819 // Dispatch observer notification to notify observers page is shown.
11820 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11821 if (os) {
11822 nsIPrincipal* principal = NodePrincipal();
11823 os->NotifyObservers(ToSupports(this),
11824 principal->IsSystemPrincipal() ? "chrome-page-shown"
11825 : "content-page-shown",
11826 nullptr);
11829 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11830 if (!target) {
11831 target = do_QueryInterface(GetWindow());
11833 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
11834 aPersisted, aOnlySystemGroup);
11838 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
11839 if (nsPresContext* presContext = aDocument.GetPresContext()) {
11840 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
11841 FullscreenEventType::Change, &aDocument, aTarget);
11842 presContext->RefreshDriver()->ScheduleFullscreenEvent(
11843 std::move(pendingEvent));
11847 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
11848 bool aOnlySystemGroup) {
11849 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11850 nsCString uri;
11851 if (GetDocumentURI()) {
11852 uri = GetDocumentURI()->GetSpecOrDefault();
11854 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11855 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
11858 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11859 MOZ_DIAGNOSTIC_ASSERT(
11860 inFrameLoaderSwap ==
11861 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11863 // Send out notifications that our <link> elements are detached,
11864 // but only if this is not a full unload.
11865 Element* root = GetRootElement();
11866 if (aPersisted && root) {
11867 RefPtr<nsContentList> links =
11868 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11870 uint32_t linkCount = links->Length(true);
11871 for (uint32_t i = 0; i < linkCount; ++i) {
11872 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved();
11876 if (mAnimationController) {
11877 mAnimationController->OnPageHide();
11880 if (!inFrameLoaderSwap) {
11881 if (aPersisted) {
11882 // We do not stop the animations (bug 1024343) when the page is refreshing
11883 // while being dragged out.
11884 ImageTracker()->SetAnimatingState(false);
11887 // Set mIsShowing before firing events, in case those event handlers
11888 // move us around.
11889 mIsShowing = false;
11890 mVisible = false;
11893 ExitPointerLock();
11895 if (!mIsBeingUsedAsImage) {
11896 // Dispatch observer notification to notify observers page is hidden.
11897 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11898 if (os) {
11899 nsIPrincipal* principal = NodePrincipal();
11900 os->NotifyObservers(ToSupports(this),
11901 principal->IsSystemPrincipal()
11902 ? "chrome-page-hidden"
11903 : "content-page-hidden",
11904 nullptr);
11907 // Now send out a PageHide event.
11908 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11909 if (!target) {
11910 target = do_QueryInterface(GetWindow());
11913 PageUnloadingEventTimeStamp timeStamp(this);
11914 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
11915 aPersisted, aOnlySystemGroup);
11919 if (!inFrameLoaderSwap) {
11920 UpdateVisibilityState();
11923 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11924 aExternalResource.OnPageHide(aPersisted, nullptr);
11925 return CallState::Continue;
11927 EnumerateExternalResources(notifyExternal);
11928 NotifyActivityChanged();
11930 ClearPendingFullscreenRequests(this);
11931 if (Fullscreen()) {
11932 // If this document was fullscreen, we should exit fullscreen in this
11933 // doctree branch. This ensures that if the user navigates while in
11934 // fullscreen mode we don't leave its still visible ancestor documents
11935 // in fullscreen mode. So exit fullscreen in the document's fullscreen
11936 // root document, as this will exit fullscreen in all the root's
11937 // descendant documents. Note that documents are removed from the
11938 // doctree by the time OnPageHide() is called, so we must store a
11939 // reference to the root (in Document::mFullscreenRoot) since we can't
11940 // just traverse the doctree to get the root.
11941 Document::ExitFullscreenInDocTree(this);
11943 // Since the document is removed from the doctree before OnPageHide() is
11944 // called, ExitFullscreen() can't traverse from the root down to *this*
11945 // document, so we must manually call CleanupFullscreenState() below too.
11946 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
11947 // so we *must* call it after ExitFullscreen(), not before.
11948 // OnPageHide() is called in every hidden (i.e. descendant) document,
11949 // so calling CleanupFullscreenState() here will ensure all hidden
11950 // documents have their fullscreen state reset.
11951 CleanupFullscreenState();
11953 // The fullscreenchange event is to be queued in the refresh driver,
11954 // however a hidden page wouldn't trigger that again, so it makes no
11955 // sense to dispatch such event here.
11959 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
11960 NS_ASSERTION(
11961 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
11962 "mSubtreeModifiedTargets not cleared after dispatching?");
11963 ++mSubtreeModifiedDepth;
11964 if (aTarget) {
11965 // MayDispatchMutationEvent is often called just before this method,
11966 // so it has already appended the node to mSubtreeModifiedTargets.
11967 int32_t count = mSubtreeModifiedTargets.Count();
11968 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
11969 mSubtreeModifiedTargets.AppendObject(aTarget);
11974 void Document::MutationEventDispatched(nsINode* aTarget) {
11975 if (--mSubtreeModifiedDepth) {
11976 return;
11979 int32_t count = mSubtreeModifiedTargets.Count();
11980 if (!count) {
11981 return;
11984 nsPIDOMWindowInner* window = GetInnerWindow();
11985 if (window &&
11986 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
11987 mSubtreeModifiedTargets.Clear();
11988 return;
11991 nsCOMArray<nsINode> realTargets;
11992 for (nsINode* possibleTarget : mSubtreeModifiedTargets) {
11993 if (possibleTarget->ChromeOnlyAccess()) {
11994 continue;
11997 nsINode* commonAncestor = nullptr;
11998 int32_t realTargetCount = realTargets.Count();
11999 for (int32_t j = 0; j < realTargetCount; ++j) {
12000 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
12001 possibleTarget, realTargets[j]);
12002 if (commonAncestor) {
12003 realTargets.ReplaceObjectAt(commonAncestor, j);
12004 break;
12007 if (!commonAncestor) {
12008 realTargets.AppendObject(possibleTarget);
12012 mSubtreeModifiedTargets.Clear();
12014 for (const nsCOMPtr<nsINode>& target : realTargets) {
12015 InternalMutationEvent mutation(true, eLegacySubtreeModified);
12016 // MOZ_KnownLive due to bug 1620312
12017 AsyncEventDispatcher::RunDOMEventWhenSafe(MOZ_KnownLive(*target), mutation);
12021 void Document::DestroyElementMaps() {
12022 #ifdef DEBUG
12023 mStyledLinksCleared = true;
12024 #endif
12025 mStyledLinks.Clear();
12026 // Notify ID change listeners before clearing the identifier map.
12027 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
12028 iter.Get()->ClearAndNotify();
12030 mIdentifierMap.Clear();
12031 mComposedShadowRoots.Clear();
12032 mResponsiveContent.Clear();
12033 IncrementExpandoGeneration(*this);
12036 void Document::RefreshLinkHrefs() {
12037 // Get a list of all links we know about. We will reset them, which will
12038 // remove them from the document, so we need a copy of what is in the
12039 // hashtable.
12040 const nsTArray<Link*> linksToNotify = ToArray(mStyledLinks);
12042 // Reset all of our styled links.
12043 nsAutoScriptBlocker scriptBlocker;
12044 for (Link* link : linksToNotify) {
12045 link->ResetLinkState(true);
12049 nsresult Document::CloneDocHelper(Document* clone) const {
12050 clone->mIsStaticDocument = mCreatingStaticClone;
12052 // Init document
12053 nsresult rv = clone->Init(NodePrincipal(), mPartitionedPrincipal);
12054 NS_ENSURE_SUCCESS(rv, rv);
12056 if (mCreatingStaticClone) {
12057 if (mOriginalDocument) {
12058 clone->mOriginalDocument = mOriginalDocument;
12059 } else {
12060 clone->mOriginalDocument = const_cast<Document*>(this);
12062 clone->mOriginalDocument->mLatestStaticClone = clone;
12063 clone->mOriginalDocument->mStaticCloneCount++;
12065 nsCOMPtr<nsILoadGroup> loadGroup;
12067 // |mDocumentContainer| is the container of the document that is being
12068 // created and not the original container. See CreateStaticClone function().
12069 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
12070 if (docLoader) {
12071 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
12073 nsCOMPtr<nsIChannel> channel = GetChannel();
12074 nsCOMPtr<nsIURI> uri;
12075 if (channel) {
12076 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
12077 } else {
12078 uri = Document::GetDocumentURI();
12080 clone->mChannel = channel;
12081 clone->mShouldResistFingerprinting = mShouldResistFingerprinting;
12082 if (uri) {
12083 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
12086 clone->mIsSrcdocDocument = mIsSrcdocDocument;
12087 clone->SetContainer(mDocumentContainer);
12089 // Setup the navigation time. This will be needed by any animations in the
12090 // document, even if they are only paused.
12091 MOZ_ASSERT(!clone->GetNavigationTiming(),
12092 "Navigation time was already set?");
12093 if (mTiming) {
12094 RefPtr<nsDOMNavigationTiming> timing =
12095 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
12096 clone->SetNavigationTiming(timing);
12098 clone->SetCsp(mCSP);
12101 // Now ensure that our clone has the same URI, base URI, and principal as us.
12102 // We do this after the mCreatingStaticClone block above, because that block
12103 // can set the base URI to an incorrect value in cases when base URI
12104 // information came from the channel. So we override explicitly, and do it
12105 // for all these properties, in case ResetToURI messes with any of the rest of
12106 // them.
12107 clone->SetDocumentURI(Document::GetDocumentURI());
12108 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
12109 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
12110 clone->mActiveCookiePrincipal = mActiveCookiePrincipal;
12111 // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
12112 // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
12113 // when printed standalone via window.print() (where there won't be a parent
12114 // document to grab the URI from).
12115 clone->mDocumentBaseURI = GetDocBaseURI();
12116 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
12117 clone->mReferrerInfo =
12118 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
12119 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
12121 bool hasHadScriptObject = true;
12122 nsIScriptGlobalObject* scriptObject =
12123 GetScriptHandlingObject(hasHadScriptObject);
12124 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
12125 if (mCreatingStaticClone) {
12126 // If we're doing a static clone (print, print preview), then we're going to
12127 // be setting a scope object after the clone. It's better to set it only
12128 // once, so we don't do that here. However, we do want to act as if there is
12129 // a script handling object. So we set mHasHadScriptHandlingObject.
12130 clone->mHasHadScriptHandlingObject = true;
12131 } else if (scriptObject) {
12132 clone->SetScriptHandlingObject(scriptObject);
12133 } else {
12134 clone->SetScopeObject(GetScopeObject());
12136 // Make the clone a data document
12137 clone->SetLoadedAsData(
12138 true,
12139 /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
12141 // Misc state
12143 // State from Document
12144 clone->mCharacterSet = mCharacterSet;
12145 clone->mCharacterSetSource = mCharacterSetSource;
12146 clone->SetCompatibilityMode(mCompatMode);
12147 clone->mBidiOptions = mBidiOptions;
12148 clone->mContentLanguage = mContentLanguage;
12149 clone->SetContentType(GetContentTypeInternal());
12150 clone->mSecurityInfo = mSecurityInfo;
12152 // State from Document
12153 clone->mType = mType;
12154 clone->mXMLDeclarationBits = mXMLDeclarationBits;
12155 clone->mBaseTarget = mBaseTarget;
12157 return NS_OK;
12160 void Document::NotifyLoading(bool aNewParentIsLoading,
12161 const ReadyState& aCurrentState,
12162 ReadyState aNewState) {
12163 // Mirror the top-level loading state down to all subdocuments
12164 bool was_loading = mAncestorIsLoading ||
12165 aCurrentState == READYSTATE_LOADING ||
12166 aCurrentState == READYSTATE_INTERACTIVE;
12167 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
12168 aNewState == READYSTATE_INTERACTIVE; // new value for state
12169 bool set_load_state = was_loading != is_loading;
12171 MOZ_LOG(
12172 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12173 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
12174 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
12175 "set_load_state: %d",
12176 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
12177 (int)aNewState, was_loading, is_loading, set_load_state));
12179 mAncestorIsLoading = aNewParentIsLoading;
12180 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
12181 // Tell our innerwindow (and thus TimeoutManager)
12182 nsPIDOMWindowInner* inner = GetInnerWindow();
12183 if (inner) {
12184 inner->SetActiveLoadingState(is_loading);
12186 BrowsingContext* context = GetBrowsingContext();
12187 if (context) {
12188 // Don't use PreOrderWalk to mirror this down; go down one level as a
12189 // time so we can set mAncestorIsLoading and take into account the
12190 // readystates of the subdocument. In the child process it will call
12191 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
12192 // iterate it's children
12193 for (auto& child : context->Children()) {
12194 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12195 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
12196 // Setting ancestor loading on a discarded browsing context has no
12197 // effect.
12198 Unused << child->SetAncestorLoading(is_loading);
12204 void Document::SetReadyStateInternal(ReadyState aReadyState,
12205 bool aUpdateTimingInformation) {
12206 if (aReadyState == READYSTATE_UNINITIALIZED) {
12207 // Transition back to uninitialized happens only to keep assertions happy
12208 // right before readyState transitions to something else. Make this
12209 // transition undetectable by Web content.
12210 mReadyState = aReadyState;
12211 return;
12214 if (IsTopLevelContentDocument()) {
12215 if (aReadyState == READYSTATE_LOADING) {
12216 AddToplevelLoadingDocument(this);
12217 } else if (aReadyState == READYSTATE_COMPLETE) {
12218 RemoveToplevelLoadingDocument(this);
12222 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
12223 mLoadingTimeStamp = TimeStamp::Now();
12225 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
12226 mReadyState = aReadyState;
12227 if (aUpdateTimingInformation && mTiming) {
12228 switch (aReadyState) {
12229 case READYSTATE_LOADING:
12230 mTiming->NotifyDOMLoading(GetDocumentURI());
12231 break;
12232 case READYSTATE_INTERACTIVE:
12233 mTiming->NotifyDOMInteractive(GetDocumentURI());
12234 break;
12235 case READYSTATE_COMPLETE:
12236 mTiming->NotifyDOMComplete(GetDocumentURI());
12237 break;
12238 default:
12239 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
12240 break;
12243 // At the time of loading start, we don't have timing object, record time.
12245 if (READYSTATE_INTERACTIVE == aReadyState &&
12246 NodePrincipal()->IsSystemPrincipal()) {
12247 if (!mXULPersist) {
12248 mXULPersist = new XULPersist(this);
12249 mXULPersist->Init();
12251 if (!mChromeObserver) {
12252 mChromeObserver = new ChromeObserver(this);
12253 mChromeObserver->Init();
12257 if (aUpdateTimingInformation) {
12258 RecordNavigationTiming(aReadyState);
12261 AsyncEventDispatcher::RunDOMEventWhenSafe(
12262 *this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
12265 void Document::GetReadyState(nsAString& aReadyState) const {
12266 switch (mReadyState) {
12267 case READYSTATE_LOADING:
12268 aReadyState.AssignLiteral(u"loading");
12269 break;
12270 case READYSTATE_INTERACTIVE:
12271 aReadyState.AssignLiteral(u"interactive");
12272 break;
12273 case READYSTATE_COMPLETE:
12274 aReadyState.AssignLiteral(u"complete");
12275 break;
12276 default:
12277 aReadyState.AssignLiteral(u"uninitialized");
12281 void Document::SuppressEventHandling(uint32_t aIncrease) {
12282 mEventsSuppressed += aIncrease;
12283 if (mEventsSuppressed == aIncrease) {
12284 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
12285 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12288 UpdateFrameRequestCallbackSchedulingState();
12289 for (uint32_t i = 0; i < aIncrease; ++i) {
12290 ScriptLoader()->AddExecuteBlocker();
12293 auto suppressInSubDoc = [aIncrease](Document& aSubDoc) {
12294 aSubDoc.SuppressEventHandling(aIncrease);
12295 return CallState::Continue;
12298 EnumerateSubDocuments(suppressInSubDoc);
12301 void Document::NotifyAbortedLoad() {
12302 // If we still have outstanding work blocking DOMContentLoaded,
12303 // then don't try to change the readystate now, but wait until
12304 // they finish and then do so.
12305 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
12306 mSetCompleteAfterDOMContentLoaded = true;
12307 return;
12310 // Otherwise we're fully done at this point, so set the
12311 // readystate to complete.
12312 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
12313 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
12317 MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents(
12318 nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) {
12319 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
12320 if (MOZ_UNLIKELY(!fm)) {
12321 return;
12324 nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments);
12325 for (uint32_t i = 0; i < documents.Length(); ++i) {
12326 nsCOMPtr<Document> document = std::move(documents[i]);
12327 // NB: Don't bother trying to fire delayed events on documents that were
12328 // closed before this event ran.
12329 if (!document->EventHandlingSuppressed()) {
12330 fm->FireDelayedEvents(document);
12331 RefPtr<PresShell> presShell = document->GetPresShell();
12332 if (presShell) {
12333 // Only fire events for active documents.
12334 bool fire = aFireEvents && document->GetInnerWindow() &&
12335 document->GetInnerWindow()->IsCurrentInnerWindow();
12336 presShell->FireOrClearDelayedEvents(fire);
12338 document->FireOrClearPostMessageEvents(aFireEvents);
12343 void Document::PreloadPictureClosed() {
12344 MOZ_ASSERT(mPreloadPictureDepth > 0);
12345 mPreloadPictureDepth--;
12346 if (mPreloadPictureDepth == 0) {
12347 mPreloadPictureFoundSource.SetIsVoid(true);
12351 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
12352 const nsAString& aSizesAttr,
12353 const nsAString& aTypeAttr,
12354 const nsAString& aMediaAttr) {
12355 // Nested pictures are not valid syntax, so while we'll eventually load them,
12356 // it's not worth tracking sources mixed between nesting levels to preload
12357 // them effectively.
12358 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
12359 // <picture> selects the first matching source, so if this returns a URI we
12360 // needn't consider new sources until a new <picture> is encountered.
12361 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
12362 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
12363 aMediaAttr, mPreloadPictureFoundSource);
12364 if (found && mPreloadPictureFoundSource.IsVoid()) {
12365 // Found an empty source, which counts
12366 mPreloadPictureFoundSource.SetIsVoid(false);
12371 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
12372 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
12373 const nsAString& aSizesAttr, bool* aIsImgSet) {
12374 nsString sourceURL;
12375 bool isImgSet;
12376 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
12377 // We're in a <picture> element and found a URI from a source previous to
12378 // this image, use it.
12379 sourceURL = mPreloadPictureFoundSource;
12380 isImgSet = true;
12381 } else {
12382 // Otherwise try to use this <img> as a source
12383 HTMLImageElement::SelectSourceForTagWithAttrs(
12384 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
12385 VoidString(), sourceURL);
12386 isImgSet = !aSrcsetAttr.IsEmpty();
12389 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
12390 if (sourceURL.IsEmpty()) {
12391 return nullptr;
12394 // Construct into URI using passed baseURI (the parser may know of base URI
12395 // changes that have not reached us)
12396 nsresult rv;
12397 nsCOMPtr<nsIURI> uri;
12398 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
12399 this, aBaseURI);
12400 if (NS_FAILED(rv)) {
12401 return nullptr;
12404 *aIsImgSet = isImgSet;
12406 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
12407 // this this <picture> share the same <sources> (though this is not valid per
12408 // spec)
12409 return uri.forget();
12412 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
12413 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
12414 bool aLinkPreload, uint64_t aEarlyHintPreloaderId) {
12415 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
12416 nsIRequest::LOAD_RECORD_START_REQUEST_DELAY |
12417 nsContentUtils::CORSModeToLoadImageFlags(
12418 Element::StringToCORSMode(aCrossOriginAttr));
12420 nsContentPolicyType policyType =
12421 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
12422 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
12424 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12425 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12427 RefPtr<imgRequestProxy> request;
12429 nsLiteralString initiator = aEarlyHintPreloaderId
12430 ? u"early-hints"_ns
12431 : (aLinkPreload ? u"link"_ns : u"img"_ns);
12433 nsresult rv = nsContentUtils::LoadImage(
12434 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
12435 nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request),
12436 policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId);
12438 // Pin image-reference to avoid evicting it from the img-cache before
12439 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
12440 // unlink
12441 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
12442 mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
12446 void Document::MaybePreLoadImage(nsIURI* aUri,
12447 const nsAString& aCrossOriginAttr,
12448 ReferrerPolicyEnum aReferrerPolicy,
12449 bool aIsImgSet, bool aLinkPreload,
12450 const TimeStamp& aInitTimestamp) {
12451 const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr);
12452 if (aLinkPreload) {
12453 // Check if the image was already preloaded in this document to avoid
12454 // duplicate preloading.
12455 PreloadHashKey key =
12456 PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode);
12457 if (!mPreloadService.PreloadExists(key)) {
12458 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12459 aLinkPreload, 0);
12461 return;
12464 // Early exit if the img is already present in the img-cache
12465 // which indicates that the "real" load has already started and
12466 // that we shouldn't preload it.
12467 if (nsContentUtils::IsImageAvailable(aUri, NodePrincipal(), corsMode, this)) {
12468 return;
12471 #ifdef NIGHTLY_BUILD
12472 Telemetry::Accumulate(
12473 Telemetry::DOCUMENT_PRELOAD_IMAGE_ASYNCOPEN_DELAY,
12474 static_cast<uint32_t>(
12475 (TimeStamp::Now() - aInitTimestamp).ToMilliseconds()));
12476 #endif
12478 // Image not in cache - trigger preload
12479 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload,
12483 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
12484 if (!StaticPrefs::network_preconnect()) {
12485 return;
12488 NS_MutateURI mutator(aOrigURI);
12489 if (NS_FAILED(mutator.GetStatus())) {
12490 return;
12493 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
12494 // which ignores the path and uses only the origin. The other is for the
12495 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
12496 // non-Anonymous preconnects create different connections on the wire and
12497 // therefore should not be considred duplicates of each other and we
12498 // normalize the path before putting it in the hash to accomplish that.
12500 if (aCORSMode == CORS_ANONYMOUS) {
12501 mutator.SetPathQueryRef("/anonymous"_ns);
12502 } else {
12503 mutator.SetPathQueryRef("/"_ns);
12506 nsCOMPtr<nsIURI> uri;
12507 nsresult rv = mutator.Finalize(uri);
12508 if (NS_FAILED(rv)) {
12509 return;
12512 const bool existingEntryFound =
12513 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
12514 if (entry) {
12515 return true;
12517 entry.Insert(true);
12518 return false;
12520 if (existingEntryFound) {
12521 return;
12524 nsCOMPtr<nsISpeculativeConnect> speculator(
12525 do_QueryInterface(nsContentUtils::GetIOService()));
12526 if (!speculator) {
12527 return;
12530 OriginAttributes oa;
12531 StoragePrincipalHelper::GetOriginAttributesForNetworkState(this, oa);
12532 speculator->SpeculativeConnectWithOriginAttributesNative(
12533 uri, std::move(oa), nullptr, aCORSMode == CORS_ANONYMOUS);
12536 void Document::ForgetImagePreload(nsIURI* aURI) {
12537 // Checking count is faster than hashing the URI in the common
12538 // case of empty table.
12539 if (mPreloadingImages.Count() != 0) {
12540 nsCOMPtr<imgIRequest> req;
12541 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
12542 if (req) {
12543 // Make sure to cancel the request so imagelib knows it's gone.
12544 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
12549 void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates,
12550 bool aNotify) {
12551 const DocumentState oldStates = mDocumentState;
12552 if (aMaybeChangedStates.HasAtLeastOneOfStates(
12553 DocumentState::ALL_LOCALEDIR_BITS)) {
12554 mDocumentState &= ~DocumentState::ALL_LOCALEDIR_BITS;
12555 if (IsDocumentRightToLeft()) {
12556 mDocumentState |= DocumentState::RTL_LOCALE;
12557 } else {
12558 mDocumentState |= DocumentState::LTR_LOCALE;
12562 if (aMaybeChangedStates.HasAtLeastOneOfStates(DocumentState::LWTHEME)) {
12563 if (ComputeDocumentLWTheme()) {
12564 mDocumentState |= DocumentState::LWTHEME;
12565 } else {
12566 mDocumentState &= ~DocumentState::LWTHEME;
12570 if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) {
12571 if (IsTopLevelWindowInactive()) {
12572 mDocumentState |= DocumentState::WINDOW_INACTIVE;
12573 } else {
12574 mDocumentState &= ~DocumentState::WINDOW_INACTIVE;
12578 const DocumentState changedStates = oldStates ^ mDocumentState;
12579 if (aNotify && !changedStates.IsEmpty()) {
12580 if (PresShell* ps = GetObservingPresShell()) {
12581 ps->DocumentStatesChanged(changedStates);
12586 namespace {
12589 * Stub for LoadSheet(), since all we want is to get the sheet into
12590 * the CSSLoader's style cache
12592 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
12593 ~StubCSSLoaderObserver() = default;
12595 public:
12596 NS_IMETHOD
12597 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
12598 NS_DECL_ISUPPORTS
12600 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
12602 } // namespace
12604 SheetPreloadStatus Document::PreloadStyle(
12605 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
12606 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aNonce,
12607 const nsAString& aIntegrity, css::StylePreloadKind aKind,
12608 uint64_t aEarlyHintPreloaderId) {
12609 MOZ_ASSERT(aKind != css::StylePreloadKind::None);
12611 // The CSSLoader will retain this object after we return.
12612 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
12614 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12615 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12617 // Charset names are always ASCII.
12618 auto result = CSSLoader()->LoadSheet(
12619 uri, aKind, aEncoding, referrerInfo, obs, aEarlyHintPreloaderId,
12620 Element::StringToCORSMode(aCrossOriginAttr), aNonce, aIntegrity);
12621 if (result.isErr()) {
12622 return SheetPreloadStatus::Errored;
12624 RefPtr<StyleSheet> sheet = result.unwrap();
12625 if (sheet->IsComplete()) {
12626 return SheetPreloadStatus::AlreadyComplete;
12628 return SheetPreloadStatus::InProgress;
12631 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
12632 return CSSLoader()
12633 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
12634 .unwrapOr(nullptr);
12637 void Document::ResetDocumentDirection() {
12638 if (!nsContentUtils::IsChromeDoc(this)) {
12639 return;
12641 UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true);
12644 bool Document::IsDocumentRightToLeft() {
12645 if (!nsContentUtils::IsChromeDoc(this)) {
12646 return false;
12648 // setting the localedir attribute on the root element forces a
12649 // specific direction for the document.
12650 Element* element = GetRootElement();
12651 if (element) {
12652 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
12653 nullptr};
12654 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
12655 strings, eCaseMatters)) {
12656 case 0:
12657 return false;
12658 case 1:
12659 return true;
12660 default:
12661 break; // otherwise, not a valid value, so fall through
12665 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
12666 !mDocumentURI->SchemeIs("resource")) {
12667 return false;
12670 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
12673 class nsDelayedEventDispatcher : public Runnable {
12674 public:
12675 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
12676 : mozilla::Runnable("nsDelayedEventDispatcher"),
12677 mDocuments(std::move(aDocuments)) {}
12678 virtual ~nsDelayedEventDispatcher() = default;
12680 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
12681 // bug 1535398.
12682 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
12683 FireOrClearDelayedEvents(std::move(mDocuments), true);
12684 return NS_OK;
12687 private:
12688 nsTArray<nsCOMPtr<Document>> mDocuments;
12691 static void GetAndUnsuppressSubDocuments(
12692 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
12693 if (aDocument.EventHandlingSuppressed() > 0) {
12694 aDocument.DecreaseEventSuppression();
12695 aDocument.ScriptLoader()->RemoveExecuteBlocker();
12697 aDocuments.AppendElement(&aDocument);
12698 auto recurse = [&aDocuments](Document& aSubDoc) {
12699 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
12700 return CallState::Continue;
12702 aDocument.EnumerateSubDocuments(recurse);
12705 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
12706 nsTArray<nsCOMPtr<Document>> documents;
12707 GetAndUnsuppressSubDocuments(*this, documents);
12709 for (nsCOMPtr<Document>& doc : documents) {
12710 if (!doc->EventHandlingSuppressed()) {
12711 if (WindowGlobalChild* wgc = doc->GetWindowGlobalChild()) {
12712 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12715 MOZ_ASSERT(NS_IsMainThread());
12716 nsTArray<RefPtr<net::ChannelEventQueue>> queues =
12717 std::move(doc->mSuspendedQueues);
12718 for (net::ChannelEventQueue* queue : queues) {
12719 queue->Resume();
12722 // If there have been any events driven by the refresh driver which were
12723 // delayed due to events being suppressed in this document, make sure
12724 // there is a refresh scheduled soon so the events will run.
12725 if (doc->mHasDelayedRefreshEvent) {
12726 doc->mHasDelayedRefreshEvent = false;
12728 if (doc->mPresShell) {
12729 nsRefreshDriver* rd =
12730 doc->mPresShell->GetPresContext()->RefreshDriver();
12731 rd->RunDelayedEventsSoon();
12737 if (aFireEvents) {
12738 MOZ_RELEASE_ASSERT(NS_IsMainThread());
12739 nsCOMPtr<nsIRunnable> ded =
12740 new nsDelayedEventDispatcher(std::move(documents));
12741 Dispatch(TaskCategory::Other, ded.forget());
12742 } else {
12743 FireOrClearDelayedEvents(std::move(documents), false);
12747 bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
12748 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
12751 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
12752 MOZ_ASSERT(NS_IsMainThread());
12753 MOZ_ASSERT(EventHandlingSuppressed());
12754 mSuspendedQueues.AppendElement(aQueue);
12757 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
12758 MOZ_ASSERT(NS_IsMainThread());
12760 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
12761 mSuspendedPostMessageEvents.AppendElement(aEvent);
12762 return true;
12764 return false;
12767 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
12768 nsTArray<RefPtr<PostMessageEvent>> events =
12769 std::move(mSuspendedPostMessageEvents);
12771 if (aFireEvents) {
12772 for (PostMessageEvent* event : events) {
12773 event->Run();
12778 void Document::SetSuppressedEventListener(EventListener* aListener) {
12779 mSuppressedEventListener = aListener;
12780 auto setOnSubDocs = [&](Document& aDocument) {
12781 aDocument.SetSuppressedEventListener(aListener);
12782 return CallState::Continue;
12784 EnumerateSubDocuments(setOnSubDocs);
12787 bool Document::IsActive() const {
12788 return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
12789 !GetBrowsingContext()->IsInBFCache();
12792 nsISupports* Document::GetCurrentContentSink() {
12793 return mParser ? mParser->GetContentSink() : nullptr;
12796 Document* Document::GetTemplateContentsOwner() {
12797 if (!mTemplateContentsOwner) {
12798 bool hasHadScriptObject = true;
12799 nsIScriptGlobalObject* scriptObject =
12800 GetScriptHandlingObject(hasHadScriptObject);
12802 nsCOMPtr<Document> document;
12803 nsresult rv = NS_NewDOMDocument(
12804 getter_AddRefs(document),
12805 u""_ns, // aNamespaceURI
12806 u""_ns, // aQualifiedName
12807 nullptr, // aDoctype
12808 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
12809 true, // aLoadedAsData
12810 scriptObject, // aEventObject
12811 IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
12812 NS_ENSURE_SUCCESS(rv, nullptr);
12814 mTemplateContentsOwner = document;
12815 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
12817 if (!scriptObject) {
12818 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
12821 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
12823 // Set |mTemplateContentsOwner| as the template contents owner of itself so
12824 // that it is the template contents owner of nested template elements.
12825 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
12828 MOZ_ASSERT(mTemplateContentsOwner->IsTemplateContentsOwner());
12829 return mTemplateContentsOwner;
12832 // https://html.spec.whatwg.org/#the-autofocus-attribute
12833 void Document::ElementWithAutoFocusInserted(Element* aAutoFocusCandidate) {
12834 BrowsingContext* bc = GetBrowsingContext();
12835 if (!bc) {
12836 return;
12839 // If target is not fully active, then return.
12840 if (!IsCurrentActiveDocument()) {
12841 return;
12844 // If target's active sandboxing flag set has the sandboxed automatic features
12845 // browsing context flag, then return.
12846 if (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES) {
12847 return;
12850 // For each ancestorBC of target's browsing context's ancestor browsing
12851 // contexts: if ancestorBC's active document's origin is not same origin with
12852 // target's origin, then return.
12853 while (bc) {
12854 BrowsingContext* parent = bc->GetParent();
12855 if (!parent) {
12856 break;
12858 // AncestorBC is not the same site
12859 if (!parent->IsInProcess()) {
12860 return;
12863 Document* currentDocument = bc->GetDocument();
12864 if (!currentDocument) {
12865 return;
12868 Document* parentDocument = parent->GetDocument();
12869 if (!parentDocument) {
12870 return;
12873 // Not same origin
12874 if (!currentDocument->NodePrincipal()->Equals(
12875 parentDocument->NodePrincipal())) {
12876 return;
12879 bc = parent;
12881 MOZ_ASSERT(bc->IsTop());
12883 Document* topDocument = bc->GetDocument();
12884 MOZ_ASSERT(topDocument);
12885 topDocument->AppendAutoFocusCandidateToTopDocument(aAutoFocusCandidate);
12888 void Document::ScheduleFlushAutoFocusCandidates() {
12889 MOZ_ASSERT(mPresShell && mPresShell->DidInitialize());
12890 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12891 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
12892 rd->ScheduleAutoFocusFlush(this);
12896 void Document::AppendAutoFocusCandidateToTopDocument(
12897 Element* aAutoFocusCandidate) {
12898 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12899 if (mAutoFocusFired) {
12900 return;
12903 if (!HasAutoFocusCandidates()) {
12904 // PresShell may be initialized later
12905 if (mPresShell && mPresShell->DidInitialize()) {
12906 ScheduleFlushAutoFocusCandidates();
12910 nsWeakPtr element = do_GetWeakReference(aAutoFocusCandidate);
12911 mAutoFocusCandidates.RemoveElement(element);
12912 mAutoFocusCandidates.AppendElement(element);
12915 void Document::SetAutoFocusFired() {
12916 mAutoFocusCandidates.Clear();
12917 mAutoFocusFired = true;
12920 // https://html.spec.whatwg.org/#flush-autofocus-candidates
12921 void Document::FlushAutoFocusCandidates() {
12922 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12923 if (mAutoFocusFired) {
12924 return;
12927 if (!mPresShell) {
12928 return;
12931 MOZ_ASSERT(HasAutoFocusCandidates());
12932 MOZ_ASSERT(mPresShell->DidInitialize());
12934 nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetWindow();
12935 // We should be the top document
12936 if (!topWindow) {
12937 return;
12940 #ifdef DEBUG
12942 // Trying to find the top window (equivalent to window.top).
12943 nsCOMPtr<nsPIDOMWindowOuter> top = topWindow->GetInProcessTop();
12944 MOZ_ASSERT(topWindow == top);
12946 #endif
12948 // Don't steal the focus from the user
12949 if (topWindow->GetFocusedElement()) {
12950 SetAutoFocusFired();
12951 return;
12954 MOZ_ASSERT(mDocumentURI);
12955 nsAutoCString ref;
12956 // GetRef never fails
12957 nsresult rv = mDocumentURI->GetRef(ref);
12958 if (NS_SUCCEEDED(rv) &&
12959 nsContentUtils::GetTargetElement(this, NS_ConvertUTF8toUTF16(ref))) {
12960 SetAutoFocusFired();
12961 return;
12964 nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mAutoFocusCandidates);
12965 while (iter.HasMore()) {
12966 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(iter.GetNext());
12967 if (!autoFocusElement) {
12968 continue;
12970 RefPtr<Document> autoFocusElementDoc = autoFocusElement->OwnerDoc();
12971 // Get the latest info about the frame and allow scripts
12972 // to run which might affect the focusability of this element.
12973 autoFocusElementDoc->FlushPendingNotifications(FlushType::Frames);
12975 // Above layout flush may cause the PresShell to disappear.
12976 if (!mPresShell) {
12977 return;
12980 // Re-get the element because the ownerDoc() might have changed
12981 autoFocusElementDoc = autoFocusElement->OwnerDoc();
12982 BrowsingContext* bc = autoFocusElementDoc->GetBrowsingContext();
12983 if (!bc) {
12984 continue;
12987 // If doc is not fully active, then remove element from candidates, and
12988 // continue.
12989 if (!autoFocusElementDoc->IsCurrentActiveDocument()) {
12990 iter.Remove();
12991 continue;
12994 nsCOMPtr<nsIContentSink> sink =
12995 do_QueryInterface(autoFocusElementDoc->GetCurrentContentSink());
12996 if (sink) {
12997 nsHtml5TreeOpExecutor* executor =
12998 static_cast<nsHtml5TreeOpExecutor*>(sink->AsExecutor());
12999 if (executor) {
13000 // This is a HTML5 document
13001 MOZ_ASSERT(autoFocusElementDoc->IsHTMLDocument());
13002 // If doc's script-blocking style sheet counter is greater than 0, th
13003 // return.
13004 if (executor->WaitForPendingSheets()) {
13005 // In this case, element is the currently-best candidate, but doc is
13006 // not ready for autofocusing. We'll try again next time flush
13007 // autofocus candidates is called.
13008 ScheduleFlushAutoFocusCandidates();
13009 return;
13014 // The autofocus element could be moved to a different
13015 // top level BC.
13016 if (bc->Top()->GetDocument() != this) {
13017 continue;
13020 iter.Remove();
13022 // Let inclusiveAncestorDocuments be a list consisting of doc, plus the
13023 // active documents of each of doc's browsing context's ancestor browsing
13024 // contexts.
13025 // If any Document in inclusiveAncestorDocuments has non-null target
13026 // element, then continue.
13027 bool shouldFocus = true;
13028 while (bc) {
13029 Document* doc = bc->GetDocument();
13030 if (!doc) {
13031 shouldFocus = false;
13032 break;
13035 nsIURI* uri = doc->GetDocumentURI();
13036 if (!uri) {
13037 shouldFocus = false;
13038 break;
13041 nsAutoCString ref;
13042 nsresult rv = uri->GetRef(ref);
13043 // If there is an element in the document tree that has an ID equal to
13044 // fragment
13045 if (NS_SUCCEEDED(rv) &&
13046 nsContentUtils::GetTargetElement(doc, NS_ConvertUTF8toUTF16(ref))) {
13047 shouldFocus = false;
13048 break;
13050 bc = bc->GetParent();
13053 if (!shouldFocus) {
13054 continue;
13057 MOZ_ASSERT(topWindow);
13058 if (TryAutoFocusCandidate(*autoFocusElement)) {
13059 // We've successfully autofocused an element, don't
13060 // need to try to focus the rest.
13061 SetAutoFocusFired();
13062 break;
13066 if (HasAutoFocusCandidates()) {
13067 ScheduleFlushAutoFocusCandidates();
13071 bool Document::TryAutoFocusCandidate(Element& aElement) {
13072 const FocusOptions options;
13073 if (RefPtr<Element> target = nsFocusManager::GetTheFocusableArea(
13074 &aElement, nsFocusManager::ProgrammaticFocusFlags(options))) {
13075 target->Focus(options, CallerType::NonSystem, IgnoreErrors());
13076 return true;
13079 return false;
13082 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
13083 if (!aDocumentURI) {
13084 return;
13087 nsAutoCString ref;
13089 // Since all URI's that pass through here aren't URL's we can't
13090 // rely on the nsIURI implementation for providing a way for
13091 // finding the 'ref' part of the URI, we'll haveto revert to
13092 // string routines for finding the data past '#'
13094 nsresult rv = aDocumentURI->GetSpec(ref);
13095 if (NS_FAILED(rv)) {
13096 Unused << aDocumentURI->GetRef(mScrollToRef);
13097 return;
13100 nsReadingIterator<char> start, end;
13102 ref.BeginReading(start);
13103 ref.EndReading(end);
13105 if (FindCharInReadable('#', start, end)) {
13106 ++start; // Skip over the '#'
13108 mScrollToRef = Substring(start, end);
13112 void Document::ScrollToRef() {
13113 if (mScrolledToRefAlready) {
13114 RefPtr<PresShell> presShell = GetPresShell();
13115 if (presShell) {
13116 presShell->ScrollToAnchor();
13118 return;
13121 if (mScrollToRef.IsEmpty()) {
13122 return;
13125 RefPtr<PresShell> presShell = GetPresShell();
13126 if (presShell) {
13127 nsresult rv = NS_ERROR_FAILURE;
13128 // We assume that the bytes are in UTF-8, as it says in the spec:
13129 // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
13130 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
13131 // Check an empty string which might be caused by the UTF-8 conversion
13132 if (!ref.IsEmpty()) {
13133 // Note that GoToAnchor will handle flushing layout as needed.
13134 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
13135 } else {
13136 rv = NS_ERROR_FAILURE;
13139 if (NS_FAILED(rv)) {
13140 nsAutoCString buff;
13141 const bool unescaped =
13142 NS_UnescapeURL(mScrollToRef.BeginReading(), mScrollToRef.Length(),
13143 /*aFlags =*/0, buff);
13145 // This attempt is only necessary if characters were unescaped.
13146 if (unescaped) {
13147 NS_ConvertUTF8toUTF16 utf16Str(buff);
13148 if (!utf16Str.IsEmpty()) {
13149 rv = presShell->GoToAnchor(utf16Str,
13150 mChangeScrollPosWhenScrollingToRef);
13154 // If UTF-8 URI failed then try to assume the string as a
13155 // document's charset.
13156 if (NS_FAILED(rv)) {
13157 const Encoding* encoding = GetDocumentCharacterSet();
13158 rv = encoding->DecodeWithoutBOMHandling(unescaped ? buff : mScrollToRef,
13159 ref);
13160 if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
13161 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
13165 if (NS_SUCCEEDED(rv)) {
13166 mScrolledToRefAlready = true;
13171 void Document::RegisterActivityObserver(nsISupports* aSupports) {
13172 if (!mActivityObservers) {
13173 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
13175 mActivityObservers->Insert(aSupports);
13178 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
13179 if (!mActivityObservers) {
13180 return false;
13182 return mActivityObservers->EnsureRemoved(aSupports);
13185 void Document::EnumerateActivityObservers(
13186 ActivityObserverEnumerator aEnumerator) {
13187 if (!mActivityObservers) {
13188 return;
13191 const auto keyArray =
13192 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
13193 for (auto& observer : keyArray) {
13194 aEnumerator(observer.get());
13198 void Document::RegisterPendingLinkUpdate(Link* aLink) {
13199 if (aLink->HasPendingLinkUpdate()) {
13200 return;
13203 aLink->SetHasPendingLinkUpdate();
13205 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
13206 nsCOMPtr<nsIRunnable> event =
13207 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
13208 &Document::FlushPendingLinkUpdates);
13209 // Do this work in a second in the worst case.
13210 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
13211 EventQueuePriority::Idle);
13212 if (NS_FAILED(rv)) {
13213 // If during shutdown posting a runnable doesn't succeed, we probably
13214 // don't need to update link states.
13215 return;
13217 mHasLinksToUpdateRunnable = true;
13220 mLinksToUpdate.InfallibleAppend(aLink);
13223 void Document::FlushPendingLinkUpdates() {
13224 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
13225 MOZ_ASSERT(mHasLinksToUpdateRunnable);
13226 mHasLinksToUpdateRunnable = false;
13228 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
13229 mFlushingPendingLinkUpdates = true;
13231 while (!mLinksToUpdate.IsEmpty()) {
13232 LinksToUpdateList links(std::move(mLinksToUpdate));
13233 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
13234 Link* link = iter.Get();
13235 Element* element = link->GetElement();
13236 if (element->OwnerDoc() == this) {
13237 link->ClearHasPendingLinkUpdate();
13238 if (element->IsInComposedDoc()) {
13239 link->TriggerLinkUpdate(/* aNotify = */ true);
13247 * Retrieves the node in a static-clone document that corresponds to aOrigNode,
13248 * which is a node in the original document from which aStaticClone was cloned.
13250 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
13251 Document& aStaticClone) {
13252 MOZ_ASSERT(aOrigNode);
13254 // Selections in anonymous subtrees aren't supported.
13255 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
13256 return nullptr;
13259 // If the node is disconnected, this is a bug in the selection code, but it
13260 // can happen with shadow DOM so handle it.
13261 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
13262 return nullptr;
13265 AutoTArray<Maybe<uint32_t>, 32> indexArray;
13266 const nsINode* current = aOrigNode;
13267 while (const nsINode* parent = current->GetParentNode()) {
13268 Maybe<uint32_t> index = parent->ComputeIndexOf(current);
13269 NS_ENSURE_TRUE(index.isSome(), nullptr);
13270 indexArray.AppendElement(std::move(index));
13271 current = parent;
13273 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
13274 nsINode* correspondingNode = [&]() -> nsINode* {
13275 if (current->IsDocument()) {
13276 return &aStaticClone;
13278 const auto* shadow = ShadowRoot::FromNode(*current);
13279 if (!shadow) {
13280 return nullptr;
13282 nsINode* correspondingHost =
13283 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
13284 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
13285 return nullptr;
13287 return correspondingHost->AsElement()->GetShadowRoot();
13288 }();
13290 if (NS_WARN_IF(!correspondingNode)) {
13291 return nullptr;
13293 for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
13294 correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
13295 NS_ENSURE_TRUE(correspondingNode, nullptr);
13297 return correspondingNode;
13301 * Caches the selection ranges from the source document onto the static clone in
13302 * case the "Print Selection Only" functionality is invoked.
13304 * Note that we cannot use the selection obtained from GetOriginalDocument()
13305 * since that selection may have mutated after the print was invoked.
13307 * Note also that because nsRange objects point into a specific document's
13308 * nodes, we cannot reuse an array of nsRange objects across multiple static
13309 * clone documents. For that reason we cache a new array of ranges on each
13310 * static clone that we create.
13312 * TODO(emilio): This can be simplified once we don't re-clone from static
13313 * documents.
13315 * @param aSourceDoc the document from which we are caching selection ranges
13316 * @param aStaticClone the document that will hold the cache
13317 * @return true if a selection range was cached
13319 static void CachePrintSelectionRanges(const Document& aSourceDoc,
13320 Document& aStaticClone) {
13321 MOZ_ASSERT(aStaticClone.IsStaticDocument());
13322 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc));
13323 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
13325 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
13327 // When the user opts to "Print Selection Only", the print code prefers any
13328 // selection in the static clone corresponding to the focused frame. If this
13329 // is that static clone, flag it for the printing code:
13330 const bool isFocusedDoc = [&] {
13331 if (sourceDocIsStatic) {
13332 return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc));
13334 nsPIDOMWindowOuter* window = aSourceDoc.GetWindow();
13335 if (!window) {
13336 return false;
13338 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
13339 if (!rootWindow) {
13340 return false;
13342 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
13343 nsFocusManager::GetFocusedDescendant(rootWindow,
13344 nsFocusManager::eIncludeAllDescendants,
13345 getter_AddRefs(focusedWindow));
13346 return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc;
13347 }();
13348 if (isFocusedDoc) {
13349 aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc,
13350 reinterpret_cast<void*>(true));
13353 const Selection* origSelection = nullptr;
13354 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
13356 if (sourceDocIsStatic) {
13357 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
13358 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
13359 } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
13360 origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
13363 if (!origSelection && !origRanges) {
13364 return;
13367 const uint32_t rangeCount =
13368 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
13369 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
13371 for (const uint32_t i : IntegerRange(rangeCount)) {
13372 MOZ_ASSERT_IF(!sourceDocIsStatic,
13373 origSelection->RangeCount() == rangeCount);
13374 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
13375 : origSelection->GetRangeAt(i);
13376 MOZ_ASSERT(range);
13377 nsINode* startContainer = range->GetStartContainer();
13378 nsINode* endContainer = range->GetEndContainer();
13380 if (!startContainer || !endContainer) {
13381 continue;
13384 nsINode* startNode =
13385 GetCorrespondingNodeInDocument(startContainer, aStaticClone);
13386 nsINode* endNode =
13387 GetCorrespondingNodeInDocument(endContainer, aStaticClone);
13389 if (NS_WARN_IF(!startNode || !endNode)) {
13390 continue;
13393 RefPtr<nsRange> clonedRange =
13394 nsRange::Create(startNode, range->StartOffset(), endNode,
13395 range->EndOffset(), IgnoreErrors());
13396 if (clonedRange && !clonedRange->Collapsed()) {
13397 printRanges->AppendElement(std::move(clonedRange));
13401 if (printRanges->IsEmpty()) {
13402 return;
13405 aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
13406 printRanges.release(),
13407 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
13410 already_AddRefed<Document> Document::CreateStaticClone(
13411 nsIDocShell* aCloneContainer, nsIContentViewer* aViewer,
13412 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
13413 MOZ_ASSERT(!mCreatingStaticClone);
13414 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
13415 MOZ_DIAGNOSTIC_ASSERT(aViewer);
13417 mCreatingStaticClone = true;
13418 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
13419 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
13421 auto raii = MakeScopeExit([&] {
13422 RemoveProperty(nsGkAtoms::adoptedsheetclones);
13423 mCreatingStaticClone = false;
13426 // Make document use different container during cloning.
13428 // FIXME(emilio): Why is this needed?
13429 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
13430 SetContainer(nsDocShell::Cast(aCloneContainer));
13431 IgnoredErrorResult rv;
13432 nsCOMPtr<nsINode> clonedNode = CloneNode(true, rv);
13433 SetContainer(originalShell);
13434 if (rv.Failed()) {
13435 return nullptr;
13438 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
13439 if (!clonedDoc) {
13440 return nullptr;
13443 size_t sheetsCount = SheetCount();
13444 for (size_t i = 0; i < sheetsCount; ++i) {
13445 RefPtr<StyleSheet> sheet = SheetAt(i);
13446 if (sheet) {
13447 if (sheet->IsApplicable()) {
13448 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13449 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13450 if (clonedSheet) {
13451 clonedDoc->AddStyleSheet(clonedSheet);
13456 clonedDoc->CloneAdoptedSheetsFrom(*this);
13458 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
13459 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
13460 for (StyleSheet* sheet : sheets) {
13461 if (sheet->IsApplicable()) {
13462 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13463 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13464 if (clonedSheet) {
13465 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
13466 clonedSheet);
13472 // Font faces created with the JS API will not be reflected in the
13473 // stylesheets and need to be copied over to the cloned document.
13474 if (const FontFaceSet* set = GetFonts()) {
13475 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
13478 clonedDoc->mReferrerInfo =
13479 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
13480 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
13481 CachePrintSelectionRanges(*this, *clonedDoc);
13483 // We're done with the clone, embed ourselves into the document viewer and
13484 // clone our children. The order here is pretty important, because our
13485 // document our document needs to have an owner global before we can create
13486 // the frame loaders for subdocuments.
13487 aViewer->SetDocument(clonedDoc);
13489 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
13491 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
13492 for (const auto& clone : pendingClones) {
13493 RefPtr<Element> element = do_QueryObject(clone.mElement);
13494 RefPtr<nsFrameLoader> frameLoader =
13495 nsFrameLoader::Create(element, /* aNetworkCreated */ false);
13497 if (NS_WARN_IF(!frameLoader)) {
13498 continue;
13501 clone.mElement->SetFrameLoader(frameLoader);
13503 nsresult rv = frameLoader->FinishStaticClone(
13504 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
13505 Unused << NS_WARN_IF(NS_FAILED(rv));
13508 return clonedDoc.forget();
13511 void Document::UnlinkOriginalDocumentIfStatic() {
13512 if (IsStaticDocument() && mOriginalDocument) {
13513 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
13514 mOriginalDocument->mStaticCloneCount--;
13515 mOriginalDocument = nullptr;
13517 MOZ_ASSERT(!mOriginalDocument);
13520 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
13521 int32_t* aHandle) {
13522 nsresult rv = mFrameRequestManager.Schedule(aCallback, aHandle);
13523 if (NS_FAILED(rv)) {
13524 return rv;
13527 UpdateFrameRequestCallbackSchedulingState();
13528 return NS_OK;
13531 void Document::CancelFrameRequestCallback(int32_t aHandle) {
13532 if (mFrameRequestManager.Cancel(aHandle)) {
13533 UpdateFrameRequestCallbackSchedulingState();
13537 bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
13538 return mFrameRequestManager.IsCanceled(aHandle);
13541 nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
13542 // Get the document's current state object. This is the object backing both
13543 // history.state and popStateEvent.state.
13545 // mStateObjectContainer may be null; this just means that there's no
13546 // current state object.
13548 if (!mCachedStateObjectValid) {
13549 if (mStateObjectContainer) {
13550 AutoJSAPI jsapi;
13551 // Init with null is "OK" in the sense that it will just fail.
13552 if (!jsapi.Init(GetScopeObject())) {
13553 return NS_ERROR_UNEXPECTED;
13555 JS::Rooted<JS::Value> value(jsapi.cx());
13556 nsresult rv =
13557 mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value);
13558 NS_ENSURE_SUCCESS(rv, rv);
13560 mCachedStateObject = value;
13561 if (!value.isNullOrUndefined()) {
13562 mozilla::HoldJSObjects(this);
13564 } else {
13565 mCachedStateObject = JS::NullValue();
13567 mCachedStateObjectValid = true;
13570 aState.set(mCachedStateObject);
13571 return NS_OK;
13574 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
13575 mTiming = aTiming;
13576 if (!mLoadingTimeStamp.IsNull() && mTiming) {
13577 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(), mLoadingTimeStamp);
13580 // If there's already the DocumentTimeline instance, tell it since the
13581 // DocumentTimeline is based on both the navigation start time stamp and the
13582 // refresh driver timestamp.
13583 if (mDocumentTimeline) {
13584 mDocumentTimeline->UpdateLastRefreshDriverTime();
13588 nsContentList* Document::ImageMapList() {
13589 if (!mImageMaps) {
13590 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
13591 nsGkAtoms::map);
13594 return mImageMaps;
13597 #define DEPRECATED_OPERATION(_op) #_op "Warning",
13598 static const char* kDeprecationWarnings[] = {
13599 #include "nsDeprecatedOperationList.h"
13600 nullptr};
13601 #undef DEPRECATED_OPERATION
13603 #define DOCUMENT_WARNING(_op) #_op "Warning",
13604 static const char* kDocumentWarnings[] = {
13605 #include "nsDocumentWarningList.h"
13606 nullptr};
13607 #undef DOCUMENT_WARNING
13609 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
13610 switch (aOperation) {
13611 #define DEPRECATED_OPERATION(_op) \
13612 case DeprecatedOperations::e##_op: \
13613 return eUseCounter_##_op;
13614 #include "nsDeprecatedOperationList.h"
13615 #undef DEPRECATED_OPERATION
13616 default:
13617 MOZ_CRASH();
13621 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
13622 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
13625 void Document::WarnOnceAbout(
13626 DeprecatedOperations aOperation, bool asError /* = false */,
13627 const nsTArray<nsString>& aParams /* = empty array */) const {
13628 MOZ_ASSERT(NS_IsMainThread());
13629 if (HasWarnedAbout(aOperation)) {
13630 return;
13632 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
13633 // Don't count deprecated operations for about pages since those pages
13634 // are almost in our control, and we always need to remove uses there
13635 // before we remove the operation itself anyway.
13636 if (!IsAboutPage()) {
13637 const_cast<Document*>(this)->SetUseCounter(
13638 OperationToUseCounter(aOperation));
13640 uint32_t flags =
13641 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13642 nsContentUtils::ReportToConsole(
13643 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
13644 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
13647 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
13648 return mDocWarningWarnedAbout[aWarning];
13651 void Document::WarnOnceAbout(
13652 DocumentWarnings aWarning, bool asError /* = false */,
13653 const nsTArray<nsString>& aParams /* = empty array */) const {
13654 MOZ_ASSERT(NS_IsMainThread());
13655 if (HasWarnedAbout(aWarning)) {
13656 return;
13658 mDocWarningWarnedAbout[aWarning] = true;
13659 uint32_t flags =
13660 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13661 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
13662 nsContentUtils::eDOM_PROPERTIES,
13663 kDocumentWarnings[aWarning], aParams);
13666 mozilla::dom::ImageTracker* Document::ImageTracker() {
13667 if (!mImageTracker) {
13668 mImageTracker = new mozilla::dom::ImageTracker;
13670 return mImageTracker;
13673 void Document::ScheduleSVGUseElementShadowTreeUpdate(
13674 SVGUseElement& aUseElement) {
13675 MOZ_ASSERT(aUseElement.IsInComposedDoc());
13677 if (MOZ_UNLIKELY(mIsStaticDocument)) {
13678 // Printing doesn't deal well with dynamic DOM mutations.
13679 return;
13682 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
13684 if (PresShell* presShell = GetPresShell()) {
13685 presShell->EnsureStyleFlush();
13689 void Document::DoUpdateSVGUseElementShadowTrees() {
13690 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13692 do {
13693 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
13694 mSVGUseElementsNeedingShadowTreeUpdate);
13695 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
13697 for (const auto& useElement : useElementsToUpdate) {
13698 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
13699 // The element was in another <use> shadow tree which we processed
13700 // already and also needed an update, and is removed from the document
13701 // now, so nothing to do here.
13702 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
13703 continue;
13705 useElement->UpdateShadowTree();
13707 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13710 void Document::NotifyMediaFeatureValuesChanged() {
13711 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
13712 imageElement->MediaFeatureValuesChanged();
13716 already_AddRefed<Touch> Document::CreateTouch(
13717 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
13718 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
13719 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
13720 float aRotationAngle, float aForce) {
13721 RefPtr<Touch> touch =
13722 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
13723 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
13724 return touch.forget();
13727 already_AddRefed<TouchList> Document::CreateTouchList() {
13728 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13729 return retval.forget();
13732 already_AddRefed<TouchList> Document::CreateTouchList(
13733 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
13734 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13735 retval->Append(&aTouch);
13736 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13737 retval->Append(aTouches[i].get());
13739 return retval.forget();
13742 already_AddRefed<TouchList> Document::CreateTouchList(
13743 const Sequence<OwningNonNull<Touch>>& aTouches) {
13744 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13745 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13746 retval->Append(aTouches[i].get());
13748 return retval.forget();
13751 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
13752 float aX, float aY) {
13753 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
13755 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
13756 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
13757 nsPoint pt(x, y);
13759 FlushPendingNotifications(FlushType::Layout);
13761 PresShell* presShell = GetPresShell();
13762 if (!presShell) {
13763 return nullptr;
13766 nsIFrame* rootFrame = presShell->GetRootFrame();
13768 // XUL docs, unlike HTML, have no frame tree until everything's done loading
13769 if (!rootFrame) {
13770 return nullptr;
13773 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
13774 RelativeTo{rootFrame}, pt,
13775 {{FrameForPointOption::IgnorePaintSuppression,
13776 FrameForPointOption::IgnoreCrossDoc}});
13777 if (!ptFrame) {
13778 return nullptr;
13781 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
13782 nsPoint adjustedPoint = pt;
13783 if (nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
13784 adjustedPoint) !=
13785 nsLayoutUtils::TRANSFORM_SUCCEEDED) {
13786 return nullptr;
13789 nsIFrame::ContentOffsets offsets =
13790 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
13792 nsCOMPtr<nsIContent> node = offsets.content;
13793 uint32_t offset = offsets.offset;
13794 nsCOMPtr<nsIContent> anonNode = node;
13795 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
13796 if (nodeIsAnonymous) {
13797 node = ptFrame->GetContent();
13798 nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
13799 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
13800 nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
13801 if (textFrame) {
13802 // If the anonymous content node has a child, then we need to make sure
13803 // that we get the appropriate child, as otherwise the offset may not be
13804 // correct when we construct a range for it.
13805 nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
13806 if (firstChild) {
13807 anonNode = firstChild;
13810 if (textArea) {
13811 offset =
13812 nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
13815 node = nonanon;
13816 } else {
13817 node = nullptr;
13818 offset = 0;
13822 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
13823 if (nodeIsAnonymous) {
13824 aCaretPos->SetAnonymousContentNode(anonNode);
13826 return aCaretPos.forget();
13829 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
13830 // We rely on correct frame information here, so need to flush frames.
13831 FlushPendingNotifications(FlushType::Frames);
13833 // An element that is the HTML body element is potentially scrollable if all
13834 // of the following conditions are true:
13836 // The element has an associated CSS layout box.
13837 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
13838 if (!bodyFrame) {
13839 return false;
13842 // The element's parent element's computed value of the overflow-x and
13843 // overflow-y properties are visible.
13844 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
13845 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
13846 if (parentFrame &&
13847 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
13848 return false;
13851 // The element's computed value of the overflow-x or overflow-y properties is
13852 // not visible.
13853 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
13856 Element* Document::GetScrollingElement() {
13857 // Keep this in sync with IsScrollingElement.
13858 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
13859 RefPtr<HTMLBodyElement> body = GetBodyElement();
13860 if (body && !IsPotentiallyScrollable(body)) {
13861 return body;
13864 return nullptr;
13867 return GetRootElement();
13870 bool Document::IsScrollingElement(Element* aElement) {
13871 // Keep this in sync with GetScrollingElement.
13872 MOZ_ASSERT(aElement);
13874 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
13875 return aElement == GetRootElement();
13878 // In the common case when aElement != body, avoid refcounting.
13879 HTMLBodyElement* body = GetBodyElement();
13880 if (aElement != body) {
13881 return false;
13884 // Now we know body is non-null, since aElement is not null. It's the
13885 // scrolling element for the document if it itself is not potentially
13886 // scrollable.
13887 RefPtr<HTMLBodyElement> strongBody(body);
13888 return !IsPotentiallyScrollable(strongBody);
13891 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
13892 public:
13893 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
13894 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
13896 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
13897 const BlockParsingOptions& aOptions)
13898 : mPromise(aPromise) {
13899 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
13900 if (parser &&
13901 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
13902 parser->BlockParser();
13903 mParser = do_GetWeakReference(parser);
13904 mDocument = aDocument;
13905 mDocument->BlockOnload();
13906 mDocument->BlockDOMContentLoaded();
13910 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13911 ErrorResult& aRv) override {
13912 MaybeUnblockParser();
13914 mPromise->MaybeResolve(aValue);
13917 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13918 ErrorResult& aRv) override {
13919 MaybeUnblockParser();
13921 mPromise->MaybeReject(aValue);
13924 protected:
13925 virtual ~UnblockParsingPromiseHandler() {
13926 // If we're being cleaned up by the cycle collector, our mDocument reference
13927 // may have been unlinked while our mParser weak reference is still alive.
13928 if (mDocument) {
13929 MaybeUnblockParser();
13933 private:
13934 void MaybeUnblockParser() {
13935 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
13936 if (parser) {
13937 MOZ_DIAGNOSTIC_ASSERT(mDocument);
13938 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
13939 if (parser == docParser) {
13940 parser->UnblockParser();
13941 parser->ContinueInterruptedParsingAsync();
13944 if (mDocument) {
13945 // We blocked DOMContentLoaded and load events on this document. Unblock
13946 // them. Note that we want to do that no matter what's going on with the
13947 // parser state for this document. Maybe someone caused it to stop being
13948 // parsed, so CreatorParserOrNull() is returning null, but we still want
13949 // to unblock these.
13950 mDocument->UnblockDOMContentLoaded();
13951 mDocument->UnblockOnload(false);
13953 mParser = nullptr;
13954 mDocument = nullptr;
13957 nsWeakPtr mParser;
13958 RefPtr<Promise> mPromise;
13959 RefPtr<Document> mDocument;
13962 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
13964 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
13965 NS_INTERFACE_MAP_ENTRY(nsISupports)
13966 NS_INTERFACE_MAP_END
13968 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
13969 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
13971 already_AddRefed<Promise> Document::BlockParsing(
13972 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
13973 RefPtr<Promise> resultPromise =
13974 Promise::Create(aPromise.GetParentObject(), aRv);
13975 if (aRv.Failed()) {
13976 return nullptr;
13979 RefPtr<PromiseNativeHandler> promiseHandler =
13980 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
13981 aPromise.AppendNativeHandler(promiseHandler);
13983 return resultPromise.forget();
13986 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
13987 if (mFailedChannel) {
13988 nsCOMPtr<nsIURI> failedURI;
13989 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
13990 return failedURI.forget();
13994 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
13995 if (!uri) {
13996 return nullptr;
13999 return uri.forget();
14002 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
14003 if (mIsGoingAway) {
14004 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
14005 return nullptr;
14008 if (!mReadyForIdle) {
14009 nsIGlobalObject* global = GetScopeObject();
14010 if (!global) {
14011 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
14012 return nullptr;
14015 mReadyForIdle = Promise::Create(global, aRv);
14016 if (aRv.Failed()) {
14017 return nullptr;
14021 return mReadyForIdle;
14024 void Document::MaybeResolveReadyForIdle() {
14025 IgnoredErrorResult rv;
14026 Promise* readyPromise = GetDocumentReadyForIdle(rv);
14027 if (readyPromise) {
14028 readyPromise->MaybeResolveWithUndefined();
14032 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
14033 // The policy is created when the document is initialized. We _must_ have a
14034 // policy here even if the featurePolicy pref is off. If this assertion fails,
14035 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
14036 MOZ_ASSERT(mFeaturePolicy);
14037 return mFeaturePolicy;
14040 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
14041 // Only chrome documents are allowed to use command dispatcher.
14042 if (!nsContentUtils::IsChromeDoc(this)) {
14043 return nullptr;
14045 if (!mCommandDispatcher) {
14046 // Create our command dispatcher and hook it up.
14047 mCommandDispatcher = new nsXULCommandDispatcher(this);
14049 return mCommandDispatcher;
14052 void Document::InitializeXULBroadcastManager() {
14053 if (mXULBroadcastManager) {
14054 return;
14056 mXULBroadcastManager = new XULBroadcastManager(this);
14059 namespace {
14061 class DevToolsMutationObserver final : public nsStubMutationObserver {
14062 NS_DECL_ISUPPORTS
14063 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
14064 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
14065 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
14067 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
14068 // relies on the event firing _before_ the removal happens.
14069 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
14071 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
14072 // data changes right now (maybe intentionally?).
14073 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
14075 DevToolsMutationObserver() = default;
14077 private:
14078 void FireEvent(nsINode* aTarget, const nsAString& aType);
14080 ~DevToolsMutationObserver() = default;
14083 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
14085 void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
14086 const nsAString& aType) {
14087 AsyncEventDispatcher::RunDOMEventWhenSafe(*aTarget, aType, CanBubble::eNo,
14088 ChromeOnlyDispatch::eYes,
14089 Composed::eYes);
14092 void DevToolsMutationObserver::AttributeChanged(Element* aElement,
14093 int32_t aNamespaceID,
14094 nsAtom* aAttribute,
14095 int32_t aModType,
14096 const nsAttrValue* aOldValue) {
14097 FireEvent(aElement, u"devtoolsattrmodified"_ns);
14100 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
14101 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
14102 ContentInserted(c);
14106 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
14107 FireEvent(aChild, u"devtoolschildinserted"_ns);
14110 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
14112 } // namespace
14114 void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
14115 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
14116 return;
14118 mDevToolsWatchingDOMMutations = aValue;
14119 if (aValue) {
14120 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
14121 sDevToolsMutationObserver = new DevToolsMutationObserver();
14122 ClearOnShutdown(&sDevToolsMutationObserver);
14124 AddMutationObserver(sDevToolsMutationObserver);
14125 } else if (sDevToolsMutationObserver) {
14126 RemoveMutationObserver(sDevToolsMutationObserver);
14130 void EvaluateMediaQueryLists(nsTArray<RefPtr<MediaQueryList>>& aListsToNotify,
14131 Document& aDocument, bool aRecurse) {
14132 if (nsPresContext* pc = aDocument.GetPresContext()) {
14133 pc->FlushPendingMediaFeatureValuesChanged();
14136 for (MediaQueryList* mql : aDocument.MediaQueryLists()) {
14137 if (mql->EvaluateOnRenderingUpdate()) {
14138 aListsToNotify.AppendElement(mql);
14141 if (!aRecurse) {
14142 return;
14144 auto recurse = [&](Document& aSubDoc) {
14145 EvaluateMediaQueryLists(aListsToNotify, aSubDoc, true);
14146 return CallState::Continue;
14148 aDocument.EnumerateSubDocuments(recurse);
14151 void Document::EvaluateMediaQueriesAndReportChanges(bool aRecurse) {
14152 AutoTArray<RefPtr<MediaQueryList>, 32> mqls;
14153 EvaluateMediaQueryLists(mqls, *this, aRecurse);
14154 for (auto& mql : mqls) {
14155 mql->FireChangeEvent();
14159 void Document::MaybeWarnAboutZoom() {
14160 if (mHasWarnedAboutZoom) {
14161 return;
14163 const bool usedZoom = Servo_IsUnknownPropertyRecordedInUseCounter(
14164 mStyleUseCounters.get(), CountedUnknownProperty::Zoom);
14165 if (!usedZoom) {
14166 return;
14169 mHasWarnedAboutZoom = true;
14170 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
14171 this, nsContentUtils::eLAYOUT_PROPERTIES,
14172 "ZoomPropertyWarning");
14175 nsIHTMLCollection* Document::Children() {
14176 if (!mChildrenCollection) {
14177 mChildrenCollection =
14178 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
14179 nsGkAtoms::_asterisk, false);
14182 return mChildrenCollection;
14185 uint32_t Document::ChildElementCount() { return Children()->Length(); }
14187 // Singleton class to manage the list of fullscreen documents which are the
14188 // root of a branch which contains fullscreen documents. We maintain this list
14189 // so that we can easily exit all windows from fullscreen when the user
14190 // presses the escape key.
14191 class FullscreenRoots {
14192 public:
14193 // Adds the root of given document to the manager. Calling this method
14194 // with a document whose root is already contained has no effect.
14195 static void Add(Document* aDoc);
14197 // Iterates over every root in the root list, and calls aFunction, passing
14198 // each root once to aFunction. It is safe to call Add() and Remove() while
14199 // iterating over the list (i.e. in aFunction). Documents that are removed
14200 // from the manager during traversal are not traversed, and documents that
14201 // are added to the manager during traversal are also not traversed.
14202 static void ForEach(void (*aFunction)(Document* aDoc));
14204 // Removes the root of a specific document from the manager.
14205 static void Remove(Document* aDoc);
14207 // Returns true if all roots added to the list have been removed.
14208 static bool IsEmpty();
14210 private:
14211 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
14212 MOZ_COUNTED_DTOR(FullscreenRoots)
14214 enum : uint32_t { NotFound = uint32_t(-1) };
14215 // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
14216 static uint32_t Find(Document* aRoot);
14218 // Returns true if aRoot is in the list of fullscreen roots.
14219 static bool Contains(Document* aRoot);
14221 // Singleton instance of the FullscreenRoots. This is instantiated when a
14222 // root is added, and it is deleted when the last root is removed.
14223 static FullscreenRoots* sInstance;
14225 // List of weak pointers to roots.
14226 nsTArray<nsWeakPtr> mRoots;
14229 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
14231 /* static */
14232 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
14233 if (!sInstance) {
14234 return;
14236 // Create a copy of the roots array, and iterate over the copy. This is so
14237 // that if an element is removed from mRoots we don't mess up our iteration.
14238 nsTArray<nsWeakPtr> roots(sInstance->mRoots.Clone());
14239 // Call aFunction on all entries.
14240 for (uint32_t i = 0; i < roots.Length(); i++) {
14241 nsCOMPtr<Document> root = do_QueryReferent(roots[i]);
14242 // Check that the root isn't in the manager. This is so that new additions
14243 // while we were running don't get traversed.
14244 if (root && FullscreenRoots::Contains(root)) {
14245 aFunction(root);
14250 /* static */
14251 bool FullscreenRoots::Contains(Document* aRoot) {
14252 return FullscreenRoots::Find(aRoot) != NotFound;
14255 /* static */
14256 void FullscreenRoots::Add(Document* aDoc) {
14257 nsCOMPtr<Document> root =
14258 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14259 if (!FullscreenRoots::Contains(root)) {
14260 if (!sInstance) {
14261 sInstance = new FullscreenRoots();
14263 sInstance->mRoots.AppendElement(do_GetWeakReference(root));
14267 /* static */
14268 uint32_t FullscreenRoots::Find(Document* aRoot) {
14269 if (!sInstance) {
14270 return NotFound;
14272 nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
14273 for (uint32_t i = 0; i < roots.Length(); i++) {
14274 nsCOMPtr<Document> otherRoot(do_QueryReferent(roots[i]));
14275 if (otherRoot == aRoot) {
14276 return i;
14279 return NotFound;
14282 /* static */
14283 void FullscreenRoots::Remove(Document* aDoc) {
14284 nsCOMPtr<Document> root =
14285 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14286 uint32_t index = Find(root);
14287 NS_ASSERTION(index != NotFound,
14288 "Should only try to remove roots which are still added!");
14289 if (index == NotFound || !sInstance) {
14290 return;
14292 sInstance->mRoots.RemoveElementAt(index);
14293 if (sInstance->mRoots.IsEmpty()) {
14294 delete sInstance;
14295 sInstance = nullptr;
14299 /* static */
14300 bool FullscreenRoots::IsEmpty() { return !sInstance; }
14302 // Any fullscreen change waiting for the widget to finish transition
14303 // is queued here. This is declared static instead of a member of
14304 // Document because in the majority of time, there would be at most
14305 // one document requesting or exiting fullscreen. We shouldn't waste
14306 // the space to hold for it in every document.
14307 class PendingFullscreenChangeList {
14308 public:
14309 PendingFullscreenChangeList() = delete;
14311 template <typename T>
14312 static void Add(UniquePtr<T> aChange) {
14313 sList.insertBack(aChange.release());
14316 static const FullscreenChange* GetLast() { return sList.getLast(); }
14318 enum IteratorOption {
14319 // When we are committing fullscreen changes or preparing for
14320 // that, we generally want to iterate all requests in the same
14321 // window with eDocumentsWithSameRoot option.
14322 eDocumentsWithSameRoot,
14323 // If we are removing a document from the tree, we would only
14324 // want to remove the requests from the given document and its
14325 // descendants. For that case, use eInclusiveDescendants.
14326 eInclusiveDescendants
14329 template <typename T>
14330 class Iterator {
14331 public:
14332 explicit Iterator(Document* aDoc, IteratorOption aOption)
14333 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
14334 if (mCurrent) {
14335 if (aDoc->GetBrowsingContext()) {
14336 mRootBCForIteration = aDoc->GetBrowsingContext();
14337 if (aOption == eDocumentsWithSameRoot) {
14338 RefPtr<BrowsingContext> bc =
14339 GetParentIgnoreChromeBoundary(mRootBCForIteration);
14340 while (bc) {
14341 mRootBCForIteration = bc;
14342 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
14346 SkipToNextMatch();
14350 UniquePtr<T> TakeAndNext() {
14351 auto thisChange = TakeAndNextInternal();
14352 SkipToNextMatch();
14353 return thisChange;
14355 bool AtEnd() const { return mCurrent == nullptr; }
14357 private:
14358 already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
14359 BrowsingContext* aBC) {
14360 // Chrome BrowsingContexts are only available in the parent process, so if
14361 // we're in a content process, we only worry about the context tree.
14362 if (XRE_IsParentProcess()) {
14363 return aBC->Canonical()->GetParentCrossChromeBoundary();
14365 return do_AddRef(aBC->GetParent());
14368 UniquePtr<T> TakeAndNextInternal() {
14369 FullscreenChange* thisChange = mCurrent;
14370 MOZ_ASSERT(thisChange->Type() == T::kType);
14371 mCurrent = mCurrent->removeAndGetNext();
14372 return WrapUnique(static_cast<T*>(thisChange));
14374 void SkipToNextMatch() {
14375 while (mCurrent) {
14376 if (mCurrent->Type() == T::kType) {
14377 RefPtr<BrowsingContext> bc =
14378 mCurrent->Document()->GetBrowsingContext();
14379 if (!bc) {
14380 // Always automatically drop fullscreen changes which are
14381 // from a document detached from the doc shell.
14382 UniquePtr<T> change = TakeAndNextInternal();
14383 change->MayRejectPromise("Document is not active");
14384 continue;
14386 while (bc && bc != mRootBCForIteration) {
14387 bc = GetParentIgnoreChromeBoundary(bc);
14389 if (bc) {
14390 break;
14393 // The current one either don't have matched type, or isn't
14394 // inside the given subtree, so skip this item.
14395 mCurrent = mCurrent->getNext();
14399 FullscreenChange* mCurrent;
14400 RefPtr<BrowsingContext> mRootBCForIteration;
14403 private:
14404 static LinkedList<FullscreenChange> sList;
14407 /* static */
14408 LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
14410 Document* Document::GetFullscreenRoot() {
14411 nsCOMPtr<Document> root = do_QueryReferent(mFullscreenRoot);
14412 return root;
14415 size_t Document::CountFullscreenElements() const {
14416 size_t count = 0;
14417 for (const nsWeakPtr& ptr : mTopLayer) {
14418 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14419 if (elem->State().HasState(ElementState::FULLSCREEN)) {
14420 count++;
14424 return count;
14427 void Document::SetFullscreenRoot(Document* aRoot) {
14428 mFullscreenRoot = do_GetWeakReference(aRoot);
14431 // https://github.com/whatwg/html/issues/9143
14432 // We need to consider the precedence between active modal dialog, topmost auto
14433 // popover and fullscreen element once it's specified.
14434 void Document::HandleEscKey() {
14435 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14436 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14437 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14438 dialog->QueueCancelDialog();
14439 break;
14441 if (RefPtr<nsGenericHTMLElement> popoverHTMLEl =
14442 nsGenericHTMLElement::FromNodeOrNull(element)) {
14443 if (element->IsAutoPopover() && element->IsPopoverOpen()) {
14444 popoverHTMLEl->HidePopover(IgnoreErrors());
14445 break;
14451 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
14452 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
14453 RefPtr<Promise> promise = exit->GetPromise();
14454 RestorePreviousFullscreenState(std::move(exit));
14455 return promise.forget();
14458 static void AskWindowToExitFullscreen(Document* aDoc) {
14459 if (XRE_GetProcessType() == GeckoProcessType_Content) {
14460 nsContentUtils::DispatchEventOnlyToChrome(
14461 aDoc, aDoc, u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
14462 Cancelable::eNo, /* DefaultAction */ nullptr);
14463 } else {
14464 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
14465 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
14470 class nsCallExitFullscreen : public Runnable {
14471 public:
14472 explicit nsCallExitFullscreen(Document* aDoc)
14473 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
14475 NS_IMETHOD Run() final {
14476 if (!mDoc) {
14477 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
14478 } else {
14479 AskWindowToExitFullscreen(mDoc);
14481 return NS_OK;
14484 private:
14485 nsCOMPtr<Document> mDoc;
14488 /* static */
14489 void Document::AsyncExitFullscreen(Document* aDoc) {
14490 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14491 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
14492 if (aDoc) {
14493 aDoc->Dispatch(TaskCategory::Other, exit.forget());
14494 } else {
14495 NS_DispatchToCurrentThread(exit.forget());
14499 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
14500 uint32_t count = 0;
14501 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
14502 auto subDoc = [&count](Document& aSubDoc) {
14503 if (aSubDoc.Fullscreen()) {
14504 count++;
14506 return CallState::Continue;
14508 aDoc.EnumerateSubDocuments(subDoc);
14509 return count;
14512 bool Document::IsFullscreenLeaf() {
14513 // A fullscreen leaf document is fullscreen, and has no fullscreen
14514 // subdocuments.
14516 // FIXME(emilio): This doesn't seem to account for fission iframes, is that
14517 // ok?
14518 return Fullscreen() && CountFullscreenSubDocuments(*this) == 0;
14521 static Document* GetFullscreenLeaf(Document& aDoc) {
14522 if (aDoc.IsFullscreenLeaf()) {
14523 return &aDoc;
14525 if (!aDoc.Fullscreen()) {
14526 return nullptr;
14528 Document* leaf = nullptr;
14529 auto recurse = [&leaf](Document& aSubDoc) {
14530 leaf = GetFullscreenLeaf(aSubDoc);
14531 return leaf ? CallState::Stop : CallState::Continue;
14533 aDoc.EnumerateSubDocuments(recurse);
14534 return leaf;
14537 static Document* GetFullscreenLeaf(Document* aDoc) {
14538 if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
14539 return leaf;
14541 // Otherwise we could be either in a non-fullscreen doc tree, or we're
14542 // below the fullscreen doc. Start the search from the root.
14543 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14544 return GetFullscreenLeaf(*root);
14547 static CallState ResetFullscreen(Document& aDocument) {
14548 if (Element* fsElement = aDocument.GetUnretargetedFullscreenElement()) {
14549 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
14550 "Should have at most 1 fullscreen subdocument.");
14551 aDocument.CleanupFullscreenState();
14552 NS_ASSERTION(!aDocument.Fullscreen(), "Should reset fullscreen");
14553 DispatchFullscreenChange(aDocument, fsElement);
14554 aDocument.EnumerateSubDocuments(ResetFullscreen);
14556 return CallState::Continue;
14559 // Since Document::ExitFullscreenInDocTree() could be called from
14560 // Element::UnbindFromTree() where it is not safe to synchronously run
14561 // script. This runnable is the script part of that function.
14562 class ExitFullscreenScriptRunnable : public Runnable {
14563 public:
14564 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
14565 : mozilla::Runnable("ExitFullscreenScriptRunnable"),
14566 mRoot(aRoot),
14567 mLeaf(aLeaf) {}
14569 NS_IMETHOD Run() override {
14570 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
14571 // document since we want this event to follow the same path that
14572 // MozDOMFullscreen:Entered was dispatched.
14573 nsContentUtils::DispatchEventOnlyToChrome(
14574 mLeaf, mLeaf, u"MozDOMFullscreen:Exited"_ns, CanBubble::eYes,
14575 Cancelable::eNo, /* DefaultAction */ nullptr);
14576 // Ensure the window exits fullscreen, as long as we don't have
14577 // pending fullscreen requests.
14578 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
14579 if (!mRoot->HasPendingFullscreenRequests()) {
14580 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
14581 false);
14584 return NS_OK;
14587 private:
14588 nsCOMPtr<Document> mRoot;
14589 nsCOMPtr<Document> mLeaf;
14592 /* static */
14593 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
14594 MOZ_ASSERT(aMaybeNotARootDoc);
14596 // Unlock the pointer
14597 PointerLockManager::Unlock();
14599 // Resolve all promises which waiting for exit fullscreen.
14600 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
14601 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14602 while (!iter.AtEnd()) {
14603 UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
14604 exit->MayResolvePromise();
14607 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
14608 if (!root || !root->Fullscreen()) {
14609 // If a document was detached before exiting from fullscreen, it is
14610 // possible that the root had left fullscreen state. In this case,
14611 // we would not get anything from the ResetFullscreen() call. Root's
14612 // not being a fullscreen doc also means the widget should have
14613 // exited fullscreen state. It means even if we do not return here,
14614 // we would actually do nothing below except crashing ourselves via
14615 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
14616 // document.
14617 return;
14620 // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
14621 // See ExitFullscreenScriptRunnable::Run for details. We have to
14622 // record it here because we don't have such information after we
14623 // reset the fullscreen state below.
14624 Document* fullscreenLeaf = GetFullscreenLeaf(root);
14626 // Walk the tree of fullscreen documents, and reset their fullscreen state.
14627 ResetFullscreen(*root);
14629 NS_ASSERTION(!root->Fullscreen(),
14630 "Fullscreen root should no longer be a fullscreen doc...");
14632 // Move the top-level window out of fullscreen mode.
14633 FullscreenRoots::Remove(root);
14635 nsContentUtils::AddScriptRunner(
14636 new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
14639 static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
14640 RefPtr<AsyncEventDispatcher> asyncDispatcher =
14641 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
14642 CanBubble::eYes, ChromeOnlyDispatch::eYes);
14643 asyncDispatcher->PostDOMEvent();
14646 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
14647 NS_ASSERTION(!Fullscreen() || !FullscreenRoots::IsEmpty(),
14648 "Should have at least 1 fullscreen root when fullscreen!");
14650 if (!GetWindow()) {
14651 aExit->MayRejectPromise("No active window");
14652 return;
14654 if (!Fullscreen() || FullscreenRoots::IsEmpty()) {
14655 aExit->MayRejectPromise("Not in fullscreen mode");
14656 return;
14659 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
14660 AutoTArray<Element*, 8> exitElements;
14662 Document* doc = fullScreenDoc;
14663 // Collect all subdocuments.
14664 for (; doc != this; doc = doc->GetInProcessParentDocument()) {
14665 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14666 MOZ_ASSERT(fsElement,
14667 "Parent document of "
14668 "a fullscreen document without fullscreen element?");
14669 exitElements.AppendElement(fsElement);
14671 MOZ_ASSERT(doc == this, "Must have reached this doc");
14672 // Collect all ancestor documents which we are going to change.
14673 for (; doc; doc = doc->GetInProcessParentDocument()) {
14674 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14675 MOZ_ASSERT(fsElement,
14676 "Ancestor of fullscreen document must also be in fullscreen");
14677 if (doc != this) {
14678 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
14679 if (iframe->FullscreenFlag()) {
14680 // If this is an iframe, and it explicitly requested
14681 // fullscreen, don't rollback it automatically.
14682 break;
14686 exitElements.AppendElement(fsElement);
14687 if (doc->CountFullscreenElements() > 1) {
14688 break;
14692 Document* lastDoc = exitElements.LastElement()->OwnerDoc();
14693 size_t fullscreenCount = lastDoc->CountFullscreenElements();
14694 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
14695 // If we are fully exiting fullscreen, don't touch anything here,
14696 // just wait for the window to get out from fullscreen first.
14697 PendingFullscreenChangeList::Add(std::move(aExit));
14698 AskWindowToExitFullscreen(this);
14699 return;
14702 // If fullscreen mode is updated the pointer should be unlocked
14703 PointerLockManager::Unlock();
14704 // All documents listed in the array except the last one are going to
14705 // completely exit from the fullscreen state.
14706 for (auto i : IntegerRange(exitElements.Length() - 1)) {
14707 exitElements[i]->OwnerDoc()->CleanupFullscreenState();
14709 // The last document will either rollback one fullscreen element, or
14710 // completely exit from the fullscreen state as well.
14711 Document* newFullscreenDoc;
14712 if (fullscreenCount > 1) {
14713 DebugOnly<bool> removedFullscreenElement = lastDoc->PopFullscreenElement();
14714 MOZ_ASSERT(removedFullscreenElement);
14715 newFullscreenDoc = lastDoc;
14716 } else {
14717 lastDoc->CleanupFullscreenState();
14718 newFullscreenDoc = lastDoc->GetInProcessParentDocument();
14720 // Dispatch the fullscreenchange event to all document listed. Note
14721 // that the loop order is reversed so that events are dispatched in
14722 // the tree order as indicated in the spec.
14723 for (Element* e : Reversed(exitElements)) {
14724 DispatchFullscreenChange(*e->OwnerDoc(), e);
14726 aExit->MayResolvePromise();
14728 MOZ_ASSERT(newFullscreenDoc,
14729 "If we were going to exit from fullscreen on "
14730 "all documents in this doctree, we should've asked the window to "
14731 "exit first instead of reaching here.");
14732 if (fullScreenDoc != newFullscreenDoc &&
14733 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
14734 // We've popped so enough off the stack that we've rolled back to
14735 // a fullscreen element in a parent document. If this document is
14736 // cross origin, dispatch an event to chrome so it knows to show
14737 // the warning UI.
14738 DispatchFullscreenNewOriginEvent(newFullscreenDoc);
14742 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
14743 if (nsPresContext* presContext = aDoc->GetPresContext()) {
14744 presContext->UpdateViewportScrollStylesOverride();
14748 static void NotifyFullScreenChangedForMediaElement(Element& aElement) {
14749 // When a media element enters the fullscreen, we would like to notify that
14750 // to the media controller in order to update its status.
14751 if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) {
14752 mediaElem->NotifyFullScreenChanged();
14756 void Document::CleanupFullscreenState() {
14757 while (PopFullscreenElement(UpdateViewport::No)) {
14758 // Remove the next one if appropriate
14761 UpdateViewportScrollbarOverrideForFullscreen(this);
14762 mFullscreenRoot = nullptr;
14764 // Restore the zoom level that was in place prior to entering fullscreen.
14765 if (PresShell* presShell = GetPresShell()) {
14766 if (presShell->GetMobileViewportManager()) {
14767 presShell->SetResolutionAndScaleTo(
14768 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
14773 bool Document::PopFullscreenElement(UpdateViewport aUpdateViewport) {
14774 Element* removedElement = TopLayerPop([](Element* element) -> bool {
14775 return element->State().HasState(ElementState::FULLSCREEN);
14778 if (!removedElement) {
14779 return false;
14782 MOZ_ASSERT(removedElement->State().HasState(ElementState::FULLSCREEN));
14783 removedElement->RemoveStates(ElementState::FULLSCREEN | ElementState::MODAL);
14784 NotifyFullScreenChangedForMediaElement(*removedElement);
14785 // Reset iframe fullscreen flag.
14786 if (auto* iframe = HTMLIFrameElement::FromNode(removedElement)) {
14787 iframe->SetFullscreenFlag(false);
14789 if (aUpdateViewport == UpdateViewport::Yes) {
14790 UpdateViewportScrollbarOverrideForFullscreen(this);
14792 return true;
14795 void Document::SetFullscreenElement(Element& aElement) {
14796 ElementState statesToAdd = ElementState::FULLSCREEN;
14797 if (!IsInChromeDocShell()) {
14798 // Don't make the document modal in chrome documents, since we don't want
14799 // the browser UI like the context menu / etc to be inert.
14800 statesToAdd |= ElementState::MODAL;
14802 aElement.AddStates(statesToAdd);
14803 TopLayerPush(aElement);
14804 NotifyFullScreenChangedForMediaElement(aElement);
14805 UpdateViewportScrollbarOverrideForFullscreen(this);
14808 void Document::TopLayerPush(Element& aElement) {
14809 const bool modal = aElement.State().HasState(ElementState::MODAL);
14811 TopLayerPop(aElement);
14812 mTopLayer.AppendElement(do_GetWeakReference(&aElement));
14813 NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
14815 if (modal) {
14816 aElement.AddStates(ElementState::TOPMOST_MODAL);
14818 bool foundExistingModalElement = false;
14819 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14820 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14821 if (element && element != &aElement &&
14822 element->State().HasState(ElementState::TOPMOST_MODAL)) {
14823 element->RemoveStates(ElementState::TOPMOST_MODAL);
14824 foundExistingModalElement = true;
14825 break;
14829 if (!foundExistingModalElement) {
14830 Element* root = GetRootElement();
14831 MOZ_RELEASE_ASSERT(root, "top layer element outside of document?");
14832 if (&aElement != root) {
14833 // Add inert to the root element so that the inertness is applied to the
14834 // entire document.
14835 root->AddStates(ElementState::INERT);
14841 void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
14842 aDialogElement.AddStates(ElementState::MODAL);
14843 TopLayerPush(aDialogElement);
14846 void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
14847 DebugOnly<Element*> removedElement = TopLayerPop(aDialogElement);
14848 MOZ_ASSERT(removedElement == &aDialogElement);
14849 aDialogElement.RemoveStates(ElementState::MODAL);
14852 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
14853 if (mTopLayer.IsEmpty()) {
14854 return nullptr;
14857 // Remove the topmost element that qualifies aPredicate; This
14858 // is required is because the top layer contains not only
14859 // fullscreen elements, but also dialog elements.
14860 Element* removedElement = nullptr;
14861 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
14862 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
14863 if (element && aPredicate(element)) {
14864 removedElement = element;
14865 mTopLayer.RemoveElementAt(i);
14866 break;
14870 // Pop from the stack null elements (references to elements which have
14871 // been GC'd since they were added to the stack) and elements which are
14872 // no longer in this document.
14874 // FIXME(emilio): If this loop does something, it'd violate the assertions
14875 // from GetTopLayerTop()... What gives?
14876 while (!mTopLayer.IsEmpty()) {
14877 Element* element = GetTopLayerTop();
14878 if (!element || element->GetComposedDoc() != this) {
14879 mTopLayer.RemoveLastElement();
14880 } else {
14881 // The top element of the stack is now an in-doc element. Return here.
14882 break;
14886 if (!removedElement) {
14887 return nullptr;
14890 const bool modal = removedElement->State().HasState(ElementState::MODAL);
14892 if (modal) {
14893 removedElement->RemoveStates(ElementState::TOPMOST_MODAL);
14894 bool foundExistingModalElement = false;
14895 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14896 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14897 if (element && element->State().HasState(ElementState::MODAL)) {
14898 element->AddStates(ElementState::TOPMOST_MODAL);
14899 foundExistingModalElement = true;
14900 break;
14903 // No more modal elements, make the document not inert anymore.
14904 if (!foundExistingModalElement) {
14905 Element* root = GetRootElement();
14906 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
14907 root->RemoveStates(ElementState::INERT);
14912 return removedElement;
14915 Element* Document::TopLayerPop(Element& aElement) {
14916 auto predictFunc = [&aElement](Element* element) {
14917 return element == &aElement;
14919 return TopLayerPop(predictFunc);
14922 void Document::GetWireframe(bool aIncludeNodes,
14923 Nullable<Wireframe>& aWireframe) {
14924 FlushPendingNotifications(FlushType::Layout);
14925 GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
14928 void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
14929 Nullable<Wireframe>& aWireframe) {
14930 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
14931 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
14933 PresShell* shell = GetPresShell();
14934 if (!shell) {
14935 return;
14938 nsPresContext* pc = shell->GetPresContext();
14939 if (!pc) {
14940 return;
14943 nsIFrame* rootFrame = shell->GetRootFrame();
14944 if (!rootFrame) {
14945 return;
14948 auto& wireframe = aWireframe.SetValue();
14949 wireframe.mCanvasBackground = shell->ComputeCanvasBackground().mViewportColor;
14951 FrameForPointOptions options;
14952 options.mBits += FrameForPointOption::IgnoreCrossDoc;
14953 options.mBits += FrameForPointOption::IgnorePaintSuppression;
14954 options.mBits += FrameForPointOption::OnlyVisible;
14956 AutoTArray<nsIFrame*, 32> frames;
14957 const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
14958 nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
14959 options);
14961 // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
14962 // something perhaps, but seems hard / like it'd involve at least some extra
14963 // copying around, since they don't outlive GetFramesForArea.
14964 auto& rects = wireframe.mRects.Construct();
14965 if (!rects.SetCapacity(frames.Length(), fallible)) {
14966 return;
14968 for (nsIFrame* frame : Reversed(frames)) {
14969 auto [rectColor,
14970 rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> {
14971 if (frame->IsTextFrame()) {
14972 return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
14973 WireframeRectType::Text};
14975 if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
14976 return {0, WireframeRectType::Image};
14978 if (frame->IsThemed()) {
14979 return {0, WireframeRectType::Background};
14981 bool drawImage = false;
14982 bool drawColor = false;
14983 if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) {
14984 const nscolor color = nsCSSRendering::DetermineBackgroundColor(
14985 pc, bgStyle, frame, drawImage, drawColor);
14986 if (drawImage &&
14987 !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
14988 return {color, WireframeRectType::Image};
14990 if (drawColor && !frame->IsCanvasFrame()) {
14991 // Canvas frame background already accounted for in mCanvasBackground.
14992 return {color, WireframeRectType::Background};
14995 return {0, WireframeRectType::Unknown};
14996 }();
14998 if (rectType == WireframeRectType::Unknown) {
14999 continue;
15002 const auto r =
15003 CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
15004 frame, frame->GetRectRelativeToSelf(), relativeTo));
15005 if ((uint32_t)r.Area() <
15006 StaticPrefs::browser_history_wireframeAreaThreshold()) {
15007 continue;
15010 // Can't really fail because SetCapacity succeeded.
15011 auto& taggedRect = *rects.AppendElement(fallible);
15013 if (aIncludeNodes) {
15014 if (nsIContent* c = frame->GetContent()) {
15015 taggedRect.mNode.Construct(c);
15018 taggedRect.mX = r.x;
15019 taggedRect.mY = r.y;
15020 taggedRect.mWidth = r.width;
15021 taggedRect.mHeight = r.height;
15022 taggedRect.mColor = rectColor;
15023 taggedRect.mType.Construct(rectType);
15027 Element* Document::GetTopLayerTop() {
15028 if (mTopLayer.IsEmpty()) {
15029 return nullptr;
15031 uint32_t last = mTopLayer.Length() - 1;
15032 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
15033 NS_ASSERTION(element, "Should have a top layer element!");
15034 NS_ASSERTION(element->IsInComposedDoc(),
15035 "Top layer element should be in doc");
15036 NS_ASSERTION(element->OwnerDoc() == this,
15037 "Top layer element should be in this doc");
15038 return element;
15041 Element* Document::GetUnretargetedFullscreenElement() const {
15042 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15043 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15044 // Per spec, the fullscreen element is the topmost element in the document’s
15045 // top layer whose fullscreen flag is set, if any, and null otherwise.
15046 if (element && element->State().HasState(ElementState::FULLSCREEN)) {
15047 return element;
15050 return nullptr;
15053 nsTArray<Element*> Document::GetTopLayer() const {
15054 nsTArray<Element*> elements;
15055 for (const nsWeakPtr& ptr : mTopLayer) {
15056 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
15057 elements.AppendElement(elem);
15060 return elements;
15063 bool Document::TopLayerContains(Element& aElement) const {
15064 if (mTopLayer.IsEmpty()) {
15065 return false;
15067 nsWeakPtr weakElement = do_GetWeakReference(&aElement);
15068 return mTopLayer.Contains(weakElement);
15071 void Document::HideAllPopoversUntil(nsINode& aEndpoint,
15072 bool aFocusPreviousElement,
15073 bool aFireEvents) {
15074 auto closeAllOpenPopovers = [&aFocusPreviousElement, &aFireEvents,
15075 this]() MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
15076 while (RefPtr<Element> topmost = GetTopmostAutoPopover()) {
15077 HidePopover(*topmost, aFocusPreviousElement, aFireEvents, IgnoreErrors());
15081 if (&aEndpoint == this) {
15082 closeAllOpenPopovers();
15083 return;
15086 // https://github.com/whatwg/html/pull/9198
15087 auto needRepeatingHide = [&]() {
15088 auto autoList = AutoPopoverList();
15089 return autoList.Contains(&aEndpoint) &&
15090 &aEndpoint != autoList.LastElement();
15093 MOZ_ASSERT((&aEndpoint)->IsElement() &&
15094 (&aEndpoint)->AsElement()->IsAutoPopover());
15095 bool repeatingHide = false;
15096 bool fireEvents = aFireEvents;
15097 do {
15098 RefPtr<const Element> lastToHide = nullptr;
15099 bool foundEndpoint = false;
15100 for (const Element* popover : AutoPopoverList()) {
15101 if (popover == &aEndpoint) {
15102 foundEndpoint = true;
15103 } else if (foundEndpoint) {
15104 lastToHide = popover;
15105 break;
15109 if (!foundEndpoint) {
15110 closeAllOpenPopovers();
15111 return;
15114 while (lastToHide && lastToHide->IsPopoverOpen()) {
15115 RefPtr<Element> topmost = GetTopmostAutoPopover();
15116 if (!topmost) {
15117 break;
15119 HidePopover(*topmost, aFocusPreviousElement, fireEvents, IgnoreErrors());
15122 repeatingHide = needRepeatingHide();
15123 if (repeatingHide) {
15124 fireEvents = false;
15126 } while (repeatingHide);
15129 MOZ_CAN_RUN_SCRIPT_BOUNDARY void
15130 Document::HideAllPopoversWithoutRunningScript() {
15131 return HideAllPopoversUntil(*this, false, false);
15134 void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement,
15135 bool aFireEvents, ErrorResult& aRv) {
15136 RefPtr<nsGenericHTMLElement> popoverHTMLEl =
15137 nsGenericHTMLElement::FromNode(aPopover);
15138 NS_ASSERTION(popoverHTMLEl, "Not a HTML element");
15140 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15141 nullptr, aRv)) {
15142 return;
15145 bool wasShowingOrHiding =
15146 popoverHTMLEl->GetPopoverData()->IsShowingOrHiding();
15147 popoverHTMLEl->GetPopoverData()->SetIsShowingOrHiding(true);
15148 const bool fireEvents = aFireEvents && !wasShowingOrHiding;
15149 auto cleanupHidingFlag = MakeScopeExit([&]() {
15150 if (auto* popoverData = popoverHTMLEl->GetPopoverData()) {
15151 popoverData->SetIsShowingOrHiding(wasShowingOrHiding);
15155 if (popoverHTMLEl->IsAutoPopover()) {
15156 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, fireEvents);
15157 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15158 nullptr, aRv)) {
15159 return;
15161 // TODO: we can't always guarantee:
15162 // The last item in document's auto popover list is popoverHTMLEl.
15163 // See, https://github.com/whatwg/html/issues/9197
15164 // If popoverHTMLEl is not on top, hide popovers again without firing
15165 // events.
15166 if (NS_WARN_IF(GetTopmostAutoPopover() != popoverHTMLEl)) {
15167 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
15168 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15169 nullptr, aRv)) {
15170 return;
15172 MOZ_ASSERT(GetTopmostAutoPopover() == popoverHTMLEl,
15173 "popoverHTMLEl should be on top of auto popover list");
15177 auto* data = popoverHTMLEl->GetPopoverData();
15178 MOZ_ASSERT(data, "Should have popover data");
15179 data->SetInvoker(nullptr);
15181 // Fire beforetoggle event and re-check popover validity.
15182 if (fireEvents) {
15183 // Intentionally ignore the return value here as only on open event for
15184 // beforetoggle the cancelable attribute is initialized to true.
15185 popoverHTMLEl->FireToggleEvent(PopoverVisibilityState::Showing,
15186 PopoverVisibilityState::Hidden,
15187 u"beforetoggle"_ns);
15188 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15189 nullptr, aRv)) {
15190 return;
15194 RemovePopoverFromTopLayer(aPopover);
15196 popoverHTMLEl->PopoverPseudoStateUpdate(false, true);
15197 popoverHTMLEl->GetPopoverData()->SetPopoverVisibilityState(
15198 PopoverVisibilityState::Hidden);
15200 // Queue popover toggle event task.
15201 if (fireEvents) {
15202 popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing);
15205 if (aFocusPreviousElement) {
15206 popoverHTMLEl->FocusPreviousElementAfterHidingPopover();
15207 } else {
15208 popoverHTMLEl->ForgetPreviouslyFocusedElementAfterHidingPopover();
15212 nsTArray<Element*> Document::AutoPopoverList() const {
15213 nsTArray<Element*> elements;
15214 for (const nsWeakPtr& ptr : mTopLayer) {
15215 if (nsCOMPtr<Element> element = do_QueryReferent(ptr)) {
15216 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15217 elements.AppendElement(element);
15221 return elements;
15224 Element* Document::GetTopmostAutoPopover() const {
15225 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15226 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15227 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15228 return element;
15231 return nullptr;
15234 void Document::AddToAutoPopoverList(Element& aElement) {
15235 MOZ_ASSERT(aElement.IsAutoPopover());
15236 TopLayerPush(aElement);
15239 void Document::RemoveFromAutoPopoverList(Element& aElement) {
15240 MOZ_ASSERT(aElement.IsAutoPopover());
15241 TopLayerPop(aElement);
15244 void Document::AddPopoverToTopLayer(Element& aElement) {
15245 MOZ_ASSERT(aElement.GetPopoverData());
15246 TopLayerPush(aElement);
15249 void Document::RemovePopoverFromTopLayer(Element& aElement) {
15250 MOZ_ASSERT(aElement.GetPopoverData());
15251 TopLayerPop(aElement);
15254 // Returns true if aDoc browsing context is focused.
15255 bool IsInFocusedTab(Document* aDoc) {
15256 BrowsingContext* bc = aDoc->GetBrowsingContext();
15257 if (!bc) {
15258 return false;
15261 nsFocusManager* fm = nsFocusManager::GetFocusManager();
15262 if (!fm) {
15263 return false;
15266 if (XRE_IsParentProcess()) {
15267 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
15268 // by retaining the old code path for the parent process.
15269 nsIDocShell* docshell = aDoc->GetDocShell();
15270 if (!docshell) {
15271 return false;
15273 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15274 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15275 if (!rootItem) {
15276 return false;
15278 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
15279 if (!rootWin) {
15280 return false;
15283 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
15284 activeWindow = fm->GetActiveWindow();
15285 if (!activeWindow) {
15286 return false;
15289 return activeWindow == rootWin;
15292 return fm->GetActiveBrowsingContext() == bc->Top();
15295 // Returns true if aDoc browsing context is focused and is also active.
15296 bool IsInActiveTab(Document* aDoc) {
15297 if (!IsInFocusedTab(aDoc)) {
15298 return false;
15301 BrowsingContext* bc = aDoc->GetBrowsingContext();
15302 MOZ_ASSERT(bc, "With no BrowsingContext, we should have failed earlier.");
15303 return bc->IsActive();
15306 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
15307 // Ensure the frame element is the fullscreen element in this document.
15308 // If the frame element is already the fullscreen element in this document,
15309 // this has no effect.
15310 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
15311 RequestFullscreen(std::move(request), XRE_IsContentProcess());
15314 void Document::RemoteFrameFullscreenReverted() {
15315 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
15316 RestorePreviousFullscreenState(std::move(exit));
15319 static bool HasFullscreenSubDocument(Document& aDoc) {
15320 uint32_t count = CountFullscreenSubDocuments(aDoc);
15321 NS_ASSERTION(count <= 1,
15322 "Fullscreen docs should have at most 1 fullscreen child!");
15323 return count >= 1;
15326 // Returns nullptr if a request for Fullscreen API is currently enabled
15327 // in the given document. Returns a static string indicates the reason
15328 // why it is not enabled otherwise.
15329 const char* Document::GetFullscreenError(CallerType aCallerType) {
15330 if (!StaticPrefs::full_screen_api_enabled()) {
15331 return "FullscreenDeniedDisabled";
15334 if (aCallerType == CallerType::System) {
15335 // Chrome code can always use the fullscreen API, provided it's not
15336 // explicitly disabled.
15337 return nullptr;
15340 if (!IsVisible()) {
15341 return "FullscreenDeniedHidden";
15344 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
15345 return "FullscreenDeniedFeaturePolicy";
15348 // Ensure that all containing elements are <iframe> and have allowfullscreen
15349 // attribute set.
15350 BrowsingContext* bc = GetBrowsingContext();
15351 if (!bc || !bc->FullscreenAllowed()) {
15352 return "FullscreenDeniedContainerNotAllowed";
15355 return nullptr;
15358 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
15359 Element* elem = aRequest.Element();
15360 // Strictly speaking, this isn't part of the fullscreen element ready
15361 // check in the spec, but per steps in the spec, when an element which
15362 // is already the fullscreen element requests fullscreen, nothing
15363 // should change and no event should be dispatched, but we still need
15364 // to resolve the returned promise.
15365 Element* fullscreenElement = GetUnretargetedFullscreenElement();
15366 if (elem == fullscreenElement) {
15367 aRequest.MayResolvePromise();
15368 return false;
15370 if (!elem->IsInComposedDoc()) {
15371 aRequest.Reject("FullscreenDeniedNotInDocument");
15372 return false;
15374 if (elem->IsPopoverOpen()) {
15375 aRequest.Reject("FullscreenDeniedPopoverOpen");
15376 return false;
15378 if (elem->OwnerDoc() != this) {
15379 aRequest.Reject("FullscreenDeniedMovedDocument");
15380 return false;
15382 if (!GetWindow()) {
15383 aRequest.Reject("FullscreenDeniedLostWindow");
15384 return false;
15386 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
15387 aRequest.Reject(msg);
15388 return false;
15390 if (HasFullscreenSubDocument(*this)) {
15391 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
15392 return false;
15394 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
15395 aRequest.Reject("FullscreenDeniedHTMLDialog");
15396 return false;
15398 if (!nsContentUtils::IsChromeDoc(this) && !IsInFocusedTab(this)) {
15399 aRequest.Reject("FullscreenDeniedNotFocusedTab");
15400 return false;
15402 return true;
15405 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
15406 MOZ_ASSERT(XRE_IsParentProcess());
15407 nsIDocShell* docShell = aDoc->GetDocShell();
15408 if (!docShell) {
15409 return nullptr;
15411 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15412 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15413 return rootItem ? rootItem->GetWindow() : nullptr;
15416 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
15417 nsPIDOMWindowOuter* aRootWin) {
15418 MOZ_ASSERT(XRE_IsParentProcess());
15419 // If we are in the chrome process, and the window has not been in
15420 // fullscreen, we certainly need to make that fullscreen first.
15421 if (!aRootWin->GetFullScreen()) {
15422 return false;
15424 // The iterator not being at end indicates there is still some
15425 // pending fullscreen request relates to this document. We have to
15426 // push the request to the pending queue so requests are handled
15427 // in the correct order.
15428 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15429 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15430 if (!iter.AtEnd()) {
15431 return false;
15434 // Same thing for exits. If we have any pending, we have to push
15435 // to the pending queue.
15436 PendingFullscreenChangeList::Iterator<FullscreenExit> iterExit(
15437 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15438 if (!iterExit.AtEnd()) {
15439 return false;
15442 // We have to apply the fullscreen state directly in this case,
15443 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
15444 // if it is already in fullscreen. If we do not apply the state but
15445 // instead add it to the queue and wait for the window as normal,
15446 // we would get stuck.
15447 return true;
15450 static bool CheckFullscreenAllowedElementType(const Element* elem) {
15451 // Per spec only HTML, <svg>, and <math> should be allowed, but
15452 // we also need to allow XUL elements right now.
15453 return elem->IsHTMLElement() || elem->IsXULElement() ||
15454 elem->IsSVGElement(nsGkAtoms::svg) ||
15455 elem->IsMathMLElement(nsGkAtoms::math);
15458 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
15459 bool aApplyFullscreenDirectly) {
15460 if (XRE_IsContentProcess()) {
15461 RequestFullscreenInContentProcess(std::move(aRequest),
15462 aApplyFullscreenDirectly);
15463 } else {
15464 RequestFullscreenInParentProcess(std::move(aRequest),
15465 aApplyFullscreenDirectly);
15469 void Document::RequestFullscreenInContentProcess(
15470 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15471 MOZ_ASSERT(XRE_IsContentProcess());
15473 // If we are in the content process, we can apply the fullscreen
15474 // state directly only if we have been in DOM fullscreen, because
15475 // otherwise we always need to notify the chrome.
15476 if (aApplyFullscreenDirectly ||
15477 nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) {
15478 ApplyFullscreen(std::move(aRequest));
15479 return;
15482 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15483 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15484 return;
15487 // We don't need to check element ready before this point, because
15488 // if we called ApplyFullscreen, it would check that for us.
15489 if (!FullscreenElementReadyCheck(*aRequest)) {
15490 return;
15493 PendingFullscreenChangeList::Add(std::move(aRequest));
15494 // If we are not the top level process, dispatch an event to make
15495 // our parent process go fullscreen first.
15496 Dispatch(
15497 TaskCategory::Other,
15498 NS_NewRunnableFunction(
15499 "Document::RequestFullscreenInContentProcess", [self = RefPtr{this}] {
15500 if (!self->HasPendingFullscreenRequests()) {
15501 return;
15503 nsContentUtils::DispatchEventOnlyToChrome(
15504 self, self, u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
15505 Cancelable::eNo, /* DefaultAction */ nullptr);
15506 }));
15509 void Document::RequestFullscreenInParentProcess(
15510 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15511 MOZ_ASSERT(XRE_IsParentProcess());
15512 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
15513 if (!rootWin) {
15514 aRequest->MayRejectPromise("No active window");
15515 return;
15518 if (aApplyFullscreenDirectly ||
15519 ShouldApplyFullscreenDirectly(this, rootWin)) {
15520 ApplyFullscreen(std::move(aRequest));
15521 return;
15524 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15525 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15526 return;
15529 // See if we're waiting on an exit. If so, just make this one pending.
15530 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
15531 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15532 if (!iter.AtEnd()) {
15533 PendingFullscreenChangeList::Add(std::move(aRequest));
15534 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15535 return;
15538 // We don't need to check element ready before this point, because
15539 // if we called ApplyFullscreen, it would check that for us.
15540 if (!FullscreenElementReadyCheck(*aRequest)) {
15541 return;
15544 PendingFullscreenChangeList::Add(std::move(aRequest));
15545 // Make the window fullscreen.
15546 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15549 /* static */
15550 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
15551 bool handled = false;
15552 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15553 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15554 while (!iter.AtEnd()) {
15555 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15556 Document* doc = request->Document();
15557 if (doc->ApplyFullscreen(std::move(request))) {
15558 handled = true;
15561 return handled;
15564 /* static */
15565 void Document::ClearPendingFullscreenRequests(Document* aDoc) {
15566 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15567 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
15568 while (!iter.AtEnd()) {
15569 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15570 request->MayRejectPromise("Fullscreen request aborted");
15574 bool Document::HasPendingFullscreenRequests() {
15575 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15576 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15577 return !iter.AtEnd();
15580 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
15581 if (!FullscreenElementReadyCheck(*aRequest)) {
15582 return false;
15585 RefPtr<Document> doc = aRequest->Document();
15586 doc->HideAllPopoversWithoutRunningScript();
15588 // Stash a reference to any existing fullscreen doc, we'll use this later
15589 // to detect if the origin which is fullscreen has changed.
15590 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
15592 // Stores a list of documents which we must dispatch "fullscreenchange"
15593 // too. We're required by the spec to dispatch the events in root-to-leaf
15594 // order, but we traverse the doctree in a leaf-to-root order, so we save
15595 // references to the documents we must dispatch to so that we get the order
15596 // as specified.
15597 AutoTArray<Document*, 8> changed;
15599 // Remember the root document, so that if a fullscreen document is hidden
15600 // we can reset fullscreen state in the remaining visible fullscreen
15601 // documents.
15602 Document* fullScreenRootDoc =
15603 nsContentUtils::GetInProcessSubtreeRootDocument(this);
15605 // If a document is already in fullscreen, then unlock the mouse pointer
15606 // before setting a new document to fullscreen
15607 PointerLockManager::Unlock();
15609 // Set the fullscreen element. This sets the fullscreen style on the
15610 // element, and the fullscreen-ancestor styles on ancestors of the element
15611 // in this document.
15612 Element* elem = aRequest->Element();
15613 SetFullscreenElement(*elem);
15614 // Set the iframe fullscreen flag.
15615 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
15616 iframe->SetFullscreenFlag(true);
15618 changed.AppendElement(this);
15620 // Propagate up the document hierarchy, setting the fullscreen element as
15621 // the element's container in ancestor documents. This also sets the
15622 // appropriate css styles as well. Note we don't propagate down the
15623 // document hierarchy, the fullscreen element (or its container) is not
15624 // visible there. Stop when we reach the root document.
15625 Document* child = this;
15626 while (true) {
15627 child->SetFullscreenRoot(fullScreenRootDoc);
15629 // When entering fullscreen, reset the RCD's resolution to the intrinsic
15630 // resolution, otherwise the fullscreen content could be sized larger than
15631 // the screen (since fullscreen is implemented using position:fixed and
15632 // fixed elements are sized to the layout viewport).
15633 // This also ensures that things like video controls aren't zoomed in
15634 // when in fullscreen mode.
15635 if (PresShell* presShell = child->GetPresShell()) {
15636 if (RefPtr<MobileViewportManager> manager =
15637 presShell->GetMobileViewportManager()) {
15638 // Save the previous resolution so it can be restored.
15639 child->mSavedResolution = presShell->GetResolution();
15640 presShell->SetResolutionAndScaleTo(
15641 manager->ComputeIntrinsicResolution(),
15642 ResolutionChangeOrigin::MainThreadRestore);
15646 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
15647 "Fullscreen root should be set!");
15648 if (child == fullScreenRootDoc) {
15649 break;
15652 Element* element = child->GetEmbedderElement();
15653 if (!element) {
15654 // We've reached the root.No more changes need to be made
15655 // to the top layer stacks of documents further up the tree.
15656 break;
15659 Document* parent = child->GetInProcessParentDocument();
15660 parent->SetFullscreenElement(*element);
15661 changed.AppendElement(parent);
15662 child = parent;
15665 FullscreenRoots::Add(this);
15667 // If it is the first entry of the fullscreen, trigger an event so
15668 // that the UI can response to this change, e.g. hide chrome, or
15669 // notifying parent process to enter fullscreen. Note that chrome
15670 // code may also want to listen to MozDOMFullscreen:NewOrigin event
15671 // to pop up warning UI.
15672 if (!previousFullscreenDoc) {
15673 nsContentUtils::DispatchEventOnlyToChrome(
15674 this, elem, u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
15675 Cancelable::eNo, /* DefaultAction */ nullptr);
15678 // The origin which is fullscreen gets changed. Trigger an event so
15679 // that the chrome knows to pop up a warning UI. Note that
15680 // previousFullscreenDoc == nullptr upon first entry, we show the warning UI
15681 // directly as soon as chrome document goes into fullscreen state. Also note
15682 // that, in a multi-process browser, the code in content process is
15683 // responsible for sending message with the origin to its parent, and the
15684 // parent shouldn't rely on this event itself.
15685 if (aRequest->mShouldNotifyNewOrigin && previousFullscreenDoc &&
15686 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
15687 DispatchFullscreenNewOriginEvent(this);
15690 // Dispatch "fullscreenchange" events. Note that the loop order is
15691 // reversed so that events are dispatched in the tree order as
15692 // indicated in the spec.
15693 for (Document* d : Reversed(changed)) {
15694 DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement());
15696 aRequest->MayResolvePromise();
15697 return true;
15700 void Document::ClearOrientationPendingPromise() {
15701 mOrientationPendingPromise = nullptr;
15704 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
15705 if (mIsGoingAway) {
15706 return false;
15709 mOrientationPendingPromise = aPromise;
15710 return true;
15713 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
15714 dom::VisibilityState oldState = mVisibilityState;
15715 mVisibilityState = ComputeVisibilityState();
15716 if (oldState != mVisibilityState) {
15717 if (aDispatchEvent == DispatchVisibilityChange::Yes) {
15718 nsContentUtils::DispatchTrustedEvent(this, this, u"visibilitychange"_ns,
15719 CanBubble::eYes, Cancelable::eNo);
15721 NotifyActivityChanged();
15722 if (mVisibilityState == dom::VisibilityState::Visible) {
15723 MaybeActiveMediaComponents();
15726 bool visible = !Hidden();
15727 for (auto* listener : mWorkerListeners) {
15728 listener->OnVisible(visible);
15733 void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
15734 mWorkerListeners.Insert(aListener);
15735 aListener->OnVisible(!Hidden());
15738 void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
15739 mWorkerListeners.Remove(aListener);
15742 VisibilityState Document::ComputeVisibilityState() const {
15743 // We have to check a few pieces of information here:
15744 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
15745 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
15746 // want to use GetWindow here because it does weird groveling for windows
15747 // in some cases.
15748 // 3) Is our outer window background? If so, we're hidden.
15749 // Otherwise, we're visible.
15750 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
15751 mWindow->GetOuterWindow()->IsBackground()) {
15752 return dom::VisibilityState::Hidden;
15755 return dom::VisibilityState::Visible;
15758 void Document::PostVisibilityUpdateEvent() {
15759 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
15760 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
15761 DispatchVisibilityChange::Yes);
15762 Dispatch(TaskCategory::Other, event.forget());
15765 void Document::MaybeActiveMediaComponents() {
15766 auto* window = GetWindow();
15767 if (!window || !window->ShouldDelayMediaFromStart()) {
15768 return;
15770 window->ActivateMediaComponents();
15773 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
15774 nsINode::AddSizeOfExcludingThis(aWindowSizes,
15775 &aWindowSizes.mDOMSizes.mDOMOtherSize);
15777 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
15778 AddSizeOfNodeTree(*kid, aWindowSizes);
15781 // IMPORTANT: for our ComputedValues measurements, we want to measure
15782 // ComputedValues accessible from DOM elements before ComputedValues not
15783 // accessible from DOM elements (i.e. accessible only from the frame tree).
15785 // Therefore, the measurement of the Document superclass must happen after
15786 // the measurement of DOM nodes (above), because Document contains the
15787 // PresShell, which contains the frame tree.
15788 if (mPresShell) {
15789 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
15792 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
15794 aWindowSizes.mPropertyTablesSize +=
15795 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15797 if (EventListenerManager* elm = GetExistingListenerManager()) {
15798 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15801 if (mNodeInfoManager) {
15802 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
15805 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15806 mDOMMediaQueryLists.sizeOfExcludingThis(
15807 aWindowSizes.mState.mMallocSizeOf);
15809 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
15810 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15811 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15814 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
15816 for (auto& sheetArray : mAdditionalSheets) {
15817 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
15819 // Lumping in the loader with the style-sheets size is not ideal,
15820 // but most of the things in there are in fact stylesheets, so it
15821 // doesn't seem worthwhile to separate it out.
15822 // This can be null if we've already been unlinked.
15823 if (mCSSLoader) {
15824 aWindowSizes.mLayoutStyleSheetsSize +=
15825 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
15828 if (mResizeObserverController) {
15829 mResizeObserverController->AddSizeOfIncludingThis(aWindowSizes);
15832 if (mAttributeStyles) {
15833 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15834 mAttributeStyles->DOMSizeOfIncludingThis(
15835 aWindowSizes.mState.mMallocSizeOf);
15838 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15839 mStyledLinks.ShallowSizeOfExcludingThis(
15840 aWindowSizes.mState.mMallocSizeOf);
15842 // Measurement of the following members may be added later if DMD finds it
15843 // is worthwhile:
15844 // - mMidasCommandManager
15845 // - many!
15848 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
15849 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15850 aWindowSizes.mState.mMallocSizeOf(this);
15851 DocAddSizeOfExcludingThis(aWindowSizes);
15854 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
15855 size_t* aNodeSize) const {
15856 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
15857 // nsDocuments can only appear at the top of the DOM tree, and we use the
15858 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
15859 // be called.
15860 MOZ_CRASH();
15863 /* static */
15864 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
15865 size_t nodeSize = 0;
15866 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
15868 // This is where we transfer the nodeSize obtained from
15869 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
15870 switch (aNode.NodeType()) {
15871 case nsINode::ELEMENT_NODE:
15872 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
15873 break;
15874 case nsINode::TEXT_NODE:
15875 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
15876 break;
15877 case nsINode::CDATA_SECTION_NODE:
15878 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
15879 break;
15880 case nsINode::COMMENT_NODE:
15881 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
15882 break;
15883 default:
15884 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
15885 break;
15888 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
15889 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15892 if (aNode.IsContent()) {
15893 nsTArray<nsIContent*> anonKids;
15894 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
15895 nsIContent::eAllChildren);
15896 for (nsIContent* anonKid : anonKids) {
15897 AddSizeOfNodeTree(*anonKid, aWindowSizes);
15900 if (auto* element = Element::FromNode(aNode)) {
15901 if (ShadowRoot* shadow = element->GetShadowRoot()) {
15902 AddSizeOfNodeTree(*shadow, aWindowSizes);
15907 // NOTE(emilio): If you feel smart and want to change this function to use
15908 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
15909 // sane way, and kids of <content> won't point to the parent, so we'd never
15910 // find the root node where we should stop at.
15911 for (nsIContent* kid = aNode.GetFirstChild(); kid;
15912 kid = kid->GetNextSibling()) {
15913 AddSizeOfNodeTree(*kid, aWindowSizes);
15917 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
15918 ErrorResult& rv) {
15919 nsCOMPtr<nsIScriptGlobalObject> global =
15920 do_QueryInterface(aGlobal.GetAsSupports());
15921 if (!global) {
15922 rv.Throw(NS_ERROR_UNEXPECTED);
15923 return nullptr;
15926 nsCOMPtr<nsIScriptObjectPrincipal> prin =
15927 do_QueryInterface(aGlobal.GetAsSupports());
15928 if (!prin) {
15929 rv.Throw(NS_ERROR_UNEXPECTED);
15930 return nullptr;
15933 nsCOMPtr<nsIURI> uri;
15934 NS_NewURI(getter_AddRefs(uri), "about:blank");
15935 if (!uri) {
15936 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
15937 return nullptr;
15940 nsCOMPtr<Document> doc;
15941 nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
15942 nullptr, uri, uri, prin->GetPrincipal(),
15943 true, global, DocumentFlavorPlain);
15944 if (NS_FAILED(res)) {
15945 rv.Throw(res);
15946 return nullptr;
15949 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
15951 return doc.forget();
15954 XPathExpression* Document::CreateExpression(const nsAString& aExpression,
15955 XPathNSResolver* aResolver,
15956 ErrorResult& rv) {
15957 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
15960 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
15961 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
15964 already_AddRefed<XPathResult> Document::Evaluate(
15965 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
15966 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
15967 ErrorResult& rv) {
15968 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
15969 aType, aResult, rv);
15972 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
15973 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
15974 if (!item) {
15975 return nullptr;
15977 nsCOMPtr<nsIDocShellTreeOwner> owner;
15978 item->GetTreeOwner(getter_AddRefs(owner));
15979 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
15980 if (!appWin) {
15981 return nullptr;
15983 nsCOMPtr<nsIDocShell> appWinShell;
15984 appWin->GetDocShell(getter_AddRefs(appWinShell));
15985 if (!SameCOMIdentity(appWinShell, item)) {
15986 return nullptr;
15988 return appWin.forget();
15991 WindowContext* Document::GetTopLevelWindowContext() const {
15992 WindowContext* windowContext = GetWindowContext();
15993 return windowContext ? windowContext->TopWindowContext() : nullptr;
15996 Document* Document::GetTopLevelContentDocumentIfSameProcess() {
15997 Document* parent;
15999 if (!mLoadedAsData) {
16000 parent = this;
16001 } else {
16002 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
16003 if (!window) {
16004 return nullptr;
16007 parent = window->GetExtantDoc();
16008 if (!parent) {
16009 return nullptr;
16013 do {
16014 if (parent->IsTopLevelContentDocument()) {
16015 break;
16018 // If we ever have a non-content parent before we hit a toplevel content
16019 // parent, then we're never going to find one. Just bail.
16020 if (!parent->IsContentDocument()) {
16021 return nullptr;
16024 parent = parent->GetInProcessParentDocument();
16025 } while (parent);
16027 return parent;
16030 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
16031 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
16034 void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
16035 MOZ_ASSERT(IsBeingUsedAsImage());
16036 MOZ_ASSERT(aReferencingDocument);
16038 if (!aReferencingDocument->mShouldReportUseCounters) {
16039 // No need to propagate use counters to a document that itself won't report
16040 // use counters.
16041 return;
16044 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16045 ("PropagateImageUseCounters from %s to %s",
16046 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
16047 nsContentUtils::TruncatedURLForDisplay(
16048 aReferencingDocument->mDocumentURI)
16049 .get()));
16051 if (aReferencingDocument->IsBeingUsedAsImage()) {
16052 NS_WARNING(
16053 "Page use counters from nested image documents may not "
16054 "propagate to the top-level document (bug 1657805)");
16057 SetCssUseCounterBits();
16058 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
16059 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
16062 bool Document::HasScriptsBlockedBySandbox() const {
16063 return mSandboxFlags & SANDBOXED_SCRIPTS;
16066 // Some use-counter sanity-checking.
16067 static_assert(size_t(eUseCounter_EndCSSProperties) -
16068 size_t(eUseCounter_FirstCSSProperty) ==
16069 size_t(eCSSProperty_COUNT_with_aliases),
16070 "We should have the right amount of CSS property use counters");
16071 static_assert(size_t(eUseCounter_Count) -
16072 size_t(eUseCounter_FirstCountedUnknownProperty) ==
16073 size_t(CountedUnknownProperty::Count),
16074 "We should have the right amount of counted unknown properties"
16075 " use counters");
16076 static_assert(size_t(eUseCounter_Count) * 2 ==
16077 size_t(Telemetry::HistogramUseCounterCount),
16078 "There should be two histograms (document and page)"
16079 " for each use counter");
16081 #define ASSERT_CSS_COUNTER(id_, method_) \
16082 static_assert(size_t(eUseCounter_property_##method_) - \
16083 size_t(eUseCounter_FirstCSSProperty) == \
16084 size_t(id_), \
16085 "Order for CSS counters and CSS property id should match");
16086 #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
16087 #define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
16088 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
16089 #define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
16090 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
16091 #define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
16092 ASSERT_CSS_COUNTER(eCSSPropertyAlias_##aliasid_, method_)
16093 #include "mozilla/ServoCSSPropList.h"
16094 #undef CSS_PROP_ALIAS
16095 #undef CSS_PROP_SHORTHAND
16096 #undef CSS_PROP_LONGHAND
16097 #undef CSS_PROP_PUBLIC_OR_PRIVATE
16098 #undef ASSERT_CSS_COUNTER
16100 void Document::SetCssUseCounterBits() {
16101 if (StaticPrefs::layout_css_use_counters_enabled()) {
16102 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
16103 auto id = nsCSSPropertyID(i);
16104 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
16105 SetUseCounter(nsCSSProps::UseCounterFor(id));
16110 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
16111 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
16112 auto id = CountedUnknownProperty(i);
16113 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
16114 id)) {
16115 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
16121 void Document::InitUseCounters() {
16122 // We can be called more than once, e.g. when session history navigation shows
16123 // us a second time.
16124 if (mUseCountersInitialized) {
16125 return;
16127 mUseCountersInitialized = true;
16129 static_assert(Telemetry::HistogramUseCounterCount > 0);
16131 if (!ShouldIncludeInTelemetry(/* aAllowExtensionURIs = */ true)) {
16132 return;
16135 // Now we know for sure that we should report use counters from this document.
16136 mShouldReportUseCounters = true;
16138 WindowContext* top = GetWindowContextForPageUseCounters();
16139 if (!top) {
16140 // This is the case for SVG image documents. They are not displayed in a
16141 // window, but we still do want to record document use counters for them.
16143 // Page use counter propagation is handled in PropagateImageUseCounters,
16144 // so there is no need to use the cross-process machinery to send them.
16145 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16146 ("InitUseCounters for a non-displayed document [%s]",
16147 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16148 return;
16151 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16152 if (!wgc) {
16153 return;
16156 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16157 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
16158 " [from %s]",
16159 wgc->InnerWindowId(), top->Id(),
16160 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16162 // Inform the parent process that we will send it page use counters later on.
16163 wgc->SendExpectPageUseCounters(top);
16164 mShouldSendPageUseCounters = true;
16167 // We keep separate counts for individual documents and top-level
16168 // pages to more accurately track how many web pages might break if
16169 // certain features were removed. Consider the case of a single
16170 // HTML document with several SVG images and/or iframes with
16171 // sub-documents of their own. If we maintained a single set of use
16172 // counters and all the sub-documents use a particular feature, then
16173 // telemetry would indicate that we would be breaking N documents if
16174 // that feature were removed. Whereas with a document/top-level
16175 // page split, we can see that N documents would be affected, but
16176 // only a single web page would be affected.
16178 // The difference between the values of these two histograms and the
16179 // related use counters below tell us how many pages did *not* use
16180 // the feature in question. For instance, if we see that a given
16181 // session has destroyed 30 content documents, but a particular use
16182 // counter shows only a count of 5, we can infer that the use
16183 // counter was *not* used in 25 of those 30 documents.
16185 // We do things this way, rather than accumulating a boolean flag
16186 // for each use counter, to avoid sending histograms for features
16187 // that don't get widely used. Doing things in this fashion means
16188 // smaller telemetry payloads and faster processing on the server
16189 // side.
16190 void Document::ReportDocumentUseCounters() {
16191 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
16192 return;
16195 mReportedDocumentUseCounters = true;
16197 // Note that a document is being destroyed. See the comment above for how
16198 // use counter histograms are interpreted relative to this measurement.
16199 // TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED is recorded in
16200 // WindowGlobalParent::FinishAccumulatingPageUseCounters.
16201 Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
16203 // Ask all of our resource documents to report their own document use
16204 // counters.
16205 EnumerateExternalResources([](Document& aDoc) {
16206 aDoc.ReportDocumentUseCounters();
16207 return CallState::Continue;
16210 // Copy StyleUseCounters into our document use counters.
16211 SetCssUseCounterBits();
16213 Maybe<nsCString> urlForLogging;
16214 const bool dumpCounters = StaticPrefs::dom_use_counters_dump_document();
16215 if (dumpCounters) {
16216 urlForLogging.emplace(
16217 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()));
16220 // Report our per-document use counters.
16221 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
16222 auto uc = static_cast<UseCounter>(c);
16223 if (!mUseCounters[uc]) {
16224 continue;
16227 auto id = static_cast<Telemetry::HistogramID>(
16228 Telemetry::HistogramFirstUseCounter + uc * 2);
16229 if (dumpCounters) {
16230 printf_stderr("USE_COUNTER_DOCUMENT: %s - %s\n",
16231 Telemetry::GetHistogramName(id), urlForLogging->get());
16233 Telemetry::Accumulate(id, 1);
16237 void Document::SendPageUseCounters() {
16238 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
16239 return;
16242 // Ask all of our resource documents to send their own document use
16243 // counters to the parent process to be counted as page use counters.
16244 EnumerateExternalResources([](Document& aDoc) {
16245 aDoc.SendPageUseCounters();
16246 return CallState::Continue;
16249 // Send our use counters to the parent process to accumulate them towards the
16250 // page use counters for the top-level document.
16252 // We take our own document use counters (those in mUseCounters) and any child
16253 // document use counters (those in mChildDocumentUseCounters) that have been
16254 // explicitly propagated up to us, which includes resource documents, static
16255 // clones, and SVG images.
16256 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16257 if (!wgc) {
16258 MOZ_ASSERT_UNREACHABLE(
16259 "SendPageUseCounters should be called while we still have access "
16260 "to our WindowContext");
16261 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16262 (" > too late to send page use counters"));
16263 return;
16266 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16267 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
16268 wgc->WindowContext()->Id(),
16269 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
16271 // Copy StyleUseCounters into our document use counters.
16272 SetCssUseCounterBits();
16274 UseCounters counters = mUseCounters | mChildDocumentUseCounters;
16275 wgc->SendAccumulatePageUseCounters(counters);
16278 bool Document::RecomputeResistFingerprinting() {
16279 const bool previous = mShouldResistFingerprinting;
16281 RefPtr<BrowsingContext> opener =
16282 GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
16283 // If we have a parent or opener document, defer to it only when we have a
16284 // null principal (e.g. a sandboxed iframe or a data: uri) or when the
16285 // document's principal matches. This means we will defer about:blank,
16286 // about:srcdoc, blob and same-origin iframes/popups to the parent/opener,
16287 // but not cross-origin ones. Cross-origin iframes/popups may inherit a
16288 // CookieJarSettings.mShouldRFP = false bit however, which will be respected.
16289 auto shouldInheritFrom = [this](Document* aDoc) {
16290 return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
16291 this->NodePrincipal()->GetIsNullPrincipal());
16294 if (shouldInheritFrom(mParentDocument)) {
16295 MOZ_LOG(
16296 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16297 ("Inside RecomputeResistFingerprinting with URI %s and deferring "
16298 "to parent document %s",
16299 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16300 mParentDocument->GetDocumentURI()->GetSpecOrDefault().get()));
16301 mShouldResistFingerprinting = mParentDocument->ShouldResistFingerprinting(
16302 RFPTarget::IsAlwaysEnabledForPrecompute);
16303 } else if (opener && shouldInheritFrom(opener->GetDocument())) {
16304 MOZ_LOG(
16305 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16306 ("Inside RecomputeResistFingerprinting with URI %s and deferring to "
16307 "opener document %s",
16308 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16309 opener->GetDocument()->GetDocumentURI()->GetSpecOrDefault().get()));
16310 mShouldResistFingerprinting =
16311 opener->GetDocument()->ShouldResistFingerprinting(
16312 RFPTarget::IsAlwaysEnabledForPrecompute);
16313 } else {
16314 bool chromeDoc = nsContentUtils::IsChromeDoc(this);
16315 MOZ_LOG(
16316 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16317 ("Inside RecomputeResistFingerprinting with URI %s ChromeDoc:%x",
16318 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16319 chromeDoc));
16320 mShouldResistFingerprinting =
16321 !chromeDoc && nsContentUtils::ShouldResistFingerprinting(
16322 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16325 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16326 ("Finished RecomputeResistFingerprinting with result %x",
16327 mShouldResistFingerprinting));
16329 return previous != mShouldResistFingerprinting;
16332 bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const {
16333 return mShouldResistFingerprinting && nsRFPService::IsRFPEnabledFor(aTarget);
16336 WindowContext* Document::GetWindowContextForPageUseCounters() const {
16337 if (mDisplayDocument) {
16338 // If we are a resource document, then go through it to find the
16339 // top-level document.
16340 return mDisplayDocument->GetWindowContextForPageUseCounters();
16343 if (mOriginalDocument) {
16344 // For static clones (print preview documents), contribute page use counters
16345 // towards the original document.
16346 return mOriginalDocument->GetWindowContextForPageUseCounters();
16349 WindowContext* wc = GetTopLevelWindowContext();
16350 if (!wc || !wc->GetBrowsingContext()->IsContent()) {
16351 return nullptr;
16354 return wc;
16357 void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
16358 if (mIntersectionObservers.IsEmpty()) {
16359 return;
16362 DOMHighResTimeStamp time = 0;
16363 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
16364 if (Performance* perf = win->GetPerformance()) {
16365 time = perf->TimeStampToDOMHighResForRendering(aNowTime);
16369 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16370 mIntersectionObservers);
16371 for (const auto& observer : observers) {
16372 if (observer) {
16373 observer->Update(*this, time);
16378 void Document::ScheduleIntersectionObserverNotification() {
16379 if (mIntersectionObservers.IsEmpty()) {
16380 return;
16382 MOZ_RELEASE_ASSERT(NS_IsMainThread());
16383 nsCOMPtr<nsIRunnable> notification =
16384 NewRunnableMethod("Document::NotifyIntersectionObservers", this,
16385 &Document::NotifyIntersectionObservers);
16386 Dispatch(TaskCategory::Other, notification.forget());
16389 void Document::NotifyIntersectionObservers() {
16390 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16391 mIntersectionObservers);
16392 for (const auto& observer : observers) {
16393 if (observer) {
16394 // MOZ_KnownLive because the 'observers' array guarantees to keep it
16395 // alive.
16396 MOZ_KnownLive(observer)->Notify();
16401 DOMIntersectionObserver& Document::EnsureLazyLoadImageObserver() {
16402 if (!mLazyLoadImageObserver) {
16403 mLazyLoadImageObserver =
16404 DOMIntersectionObserver::CreateLazyLoadObserver(*this);
16406 return *mLazyLoadImageObserver;
16409 DOMIntersectionObserver& Document::EnsureContentVisibilityObserver() {
16410 if (!mContentVisibilityObserver) {
16411 mContentVisibilityObserver =
16412 DOMIntersectionObserver::CreateContentVisibilityObserver(*this);
16414 return *mContentVisibilityObserver;
16417 void Document::ObserveForContentVisibility(Element& aElement) {
16418 EnsureContentVisibilityObserver().Observe(aElement);
16421 void Document::UnobserveForContentVisibility(Element& aElement) {
16422 if (mContentVisibilityObserver) {
16423 mContentVisibilityObserver->Unobserve(aElement);
16427 ResizeObserver& Document::EnsureLastRememberedSizeObserver() {
16428 if (!mLastRememberedSizeObserver) {
16429 mLastRememberedSizeObserver =
16430 ResizeObserver::CreateLastRememberedSizeObserver(*this);
16432 return *mLastRememberedSizeObserver;
16435 void Document::ObserveForLastRememberedSize(Element& aElement) {
16436 if (NS_WARN_IF(!IsActive())) {
16437 return;
16439 // Options are initialized with ResizeObserverBoxOptions::Content_box by
16440 // default, which is what we want.
16441 static ResizeObserverOptions options;
16442 EnsureLastRememberedSizeObserver().Observe(aElement, options);
16445 void Document::UnobserveForLastRememberedSize(Element& aElement) {
16446 if (mLastRememberedSizeObserver) {
16447 mLastRememberedSizeObserver->Unobserve(aElement);
16451 void Document::NotifyLayerManagerRecreated() {
16452 NotifyActivityChanged();
16453 EnumerateSubDocuments([](Document& aSubDoc) {
16454 aSubDoc.NotifyLayerManagerRecreated();
16455 return CallState::Continue;
16459 XPathEvaluator* Document::XPathEvaluator() {
16460 if (!mXPathEvaluator) {
16461 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
16463 return mXPathEvaluator.get();
16466 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
16467 return mCachedEncoder.forget();
16470 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
16471 mCachedEncoder = aEncoder;
16474 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
16476 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
16478 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
16479 mStateObjectContainer = scContainer;
16480 mCachedStateObject = JS::UndefinedValue();
16481 mCachedStateObjectValid = false;
16484 bool Document::ComputeDocumentLWTheme() const {
16485 if (!NodePrincipal()->IsSystemPrincipal()) {
16486 return false;
16489 Element* element = GetRootElement();
16490 return element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
16491 nsGkAtoms::_true, eCaseMatters);
16494 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
16495 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
16496 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
16497 ELEMENT_NODE);
16498 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
16500 nsCOMPtr<Element> element;
16501 DebugOnly<nsresult> rv =
16502 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
16503 mozilla::dom::NOT_FROM_PARSER);
16505 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
16506 return element.forget();
16509 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
16510 BrowsingContext* aContext) {
16511 aContext->PreOrderWalk([&](BrowsingContext* aBC) {
16512 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
16513 if (RefPtr<Document> doc = win->GetExtantDoc()) {
16514 SuppressDocument(doc);
16515 mDocuments.AppendElement(doc);
16521 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
16522 BrowsingContextGroup* aGroup) {
16523 for (const auto& bc : aGroup->Toplevels()) {
16524 SuppressBrowsingContext(bc);
16528 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
16529 SyncOperationBehavior aSyncBehavior)
16530 : mSyncBehavior(aSyncBehavior) {
16531 mMicroTaskLevel = 0;
16532 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16533 mMicroTaskLevel = ccjs->MicroTaskLevel();
16534 ccjs->SetMicroTaskLevel(0);
16536 if (aDoc) {
16537 mBrowsingContext = aDoc->GetBrowsingContext();
16538 if (InputTaskManager::CanSuspendInputEvent()) {
16539 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
16540 SuppressBrowsingContextGroup(bcg);
16542 } else if (mBrowsingContext) {
16543 SuppressBrowsingContext(mBrowsingContext->Top());
16545 if (mBrowsingContext &&
16546 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16547 InputTaskManager::CanSuspendInputEvent()) {
16548 mBrowsingContext->Group()->IncInputEventSuspensionLevel();
16553 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
16554 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16555 win->TimeoutManager().BeginSyncOperation();
16557 aDoc->SetIsInSyncOperation(true);
16560 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
16561 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16562 win->TimeoutManager().EndSyncOperation();
16564 aDoc->SetIsInSyncOperation(false);
16567 nsAutoSyncOperation::~nsAutoSyncOperation() {
16568 UnsuppressDocuments();
16569 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
16570 if (ccjs) {
16571 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
16573 if (mBrowsingContext &&
16574 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16575 InputTaskManager::CanSuspendInputEvent()) {
16576 mBrowsingContext->Group()->DecInputEventSuspensionLevel();
16580 void Document::SetIsInSyncOperation(bool aSync) {
16581 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16582 ccjs->UpdateMicroTaskSuppressionGeneration();
16585 if (aSync) {
16586 ++mInSyncOperationCount;
16587 } else {
16588 --mInSyncOperationCount;
16592 gfxUserFontSet* Document::GetUserFontSet() {
16593 if (!mFontFaceSet) {
16594 return nullptr;
16597 return mFontFaceSet->GetImpl();
16600 void Document::FlushUserFontSet() {
16601 if (!mFontFaceSetDirty) {
16602 return;
16605 mFontFaceSetDirty = false;
16607 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
16608 nsTArray<nsFontFaceRuleContainer> rules;
16609 RefPtr<PresShell> presShell = GetPresShell();
16610 if (presShell) {
16611 MOZ_ASSERT(mStyleSetFilled);
16612 mStyleSet->AppendFontFaceRules(rules);
16615 if (!mFontFaceSet && !rules.IsEmpty()) {
16616 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16619 bool changed = false;
16620 if (mFontFaceSet) {
16621 changed = mFontFaceSet->UpdateRules(rules);
16624 // We need to enqueue a style change reflow (for later) to
16625 // reflect that we're modifying @font-face rules. (However,
16626 // without a reflow, nothing will happen to start any downloads
16627 // that are needed.)
16628 if (changed && presShell) {
16629 if (nsPresContext* presContext = presShell->GetPresContext()) {
16630 presContext->UserFontSetUpdated();
16636 void Document::MarkUserFontSetDirty() {
16637 if (mFontFaceSetDirty) {
16638 return;
16640 mFontFaceSetDirty = true;
16641 if (PresShell* presShell = GetPresShell()) {
16642 presShell->EnsureStyleFlush();
16646 FontFaceSet* Document::Fonts() {
16647 if (!mFontFaceSet) {
16648 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16649 FlushUserFontSet();
16651 return mFontFaceSet;
16654 void Document::ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp) {
16655 MOZ_ASSERT(!aTimeStamp.IsNull());
16657 if (!mLastScrollLinkedEffectDetectionTime.IsNull() &&
16658 mLastScrollLinkedEffectDetectionTime >= aTimeStamp) {
16659 return;
16662 if (mLastScrollLinkedEffectDetectionTime.IsNull()) {
16663 // Report to console just once.
16664 nsContentUtils::ReportToConsole(
16665 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
16666 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
16669 mLastScrollLinkedEffectDetectionTime = aTimeStamp;
16672 bool Document::HasScrollLinkedEffect() const {
16673 if (nsPresContext* pc = GetPresContext()) {
16674 return mLastScrollLinkedEffectDetectionTime ==
16675 pc->RefreshDriver()->MostRecentRefresh();
16678 return false;
16681 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
16682 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16683 // Setting has user interction on a discarded browsing context has
16684 // no effect.
16685 Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
16689 bool Document::GetSHEntryHasUserInteraction() {
16690 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16691 return topWc->GetSHEntryHasUserInteraction();
16693 return false;
16696 void Document::SetUserHasInteracted() {
16697 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
16698 ("Document %p has been interacted by user.", this));
16700 // We maybe need to update the user-interaction permission.
16701 MaybeStoreUserInteractionAsPermission();
16703 // For purposes of reducing irrelevant session history entries on
16704 // the back button, we annotate entries with whether they had user
16705 // interaction. This is gated on its own flag on the WindowContext
16706 // (instead of mUserHasInteracted) to account for the fact that multiple
16707 // top-level SH entries can be associated with the same document.
16708 // Thus, whenever we create a new SH entry for this document,
16709 // this flag is reset.
16710 if (!GetSHEntryHasUserInteraction()) {
16711 nsIDocShell* docShell = GetDocShell();
16712 if (docShell) {
16713 nsCOMPtr<nsISHEntry> currentEntry;
16714 bool oshe;
16715 nsresult rv =
16716 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
16717 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
16718 currentEntry->SetHasUserInteraction(true);
16721 SetSHEntryHasUserInteraction(true);
16724 if (mUserHasInteracted) {
16725 return;
16728 mUserHasInteracted = true;
16730 if (mChannel) {
16731 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16732 loadInfo->SetDocumentHasUserInteracted(true);
16734 // Tell the parent process about user interaction
16735 if (auto* wgc = GetWindowGlobalChild()) {
16736 wgc->SendUpdateDocumentHasUserInteracted(true);
16739 MaybeAllowStorageForOpenerAfterUserInteraction();
16742 BrowsingContext* Document::GetBrowsingContext() const {
16743 nsCOMPtr<nsIDocShell> docshell(mDocumentContainer);
16744 return docshell ? docshell->GetBrowsingContext() : nullptr;
16747 void Document::NotifyUserGestureActivation() {
16748 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16749 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16750 WindowContext* windowContext = aBC->GetCurrentWindowContext();
16751 if (!windowContext) {
16752 return;
16755 nsIDocShell* docShell = aBC->GetDocShell();
16756 if (!docShell) {
16757 return;
16760 Document* document = docShell->GetDocument();
16761 if (!document) {
16762 return;
16765 // XXXedgar we probably could just check `IsInProcess()` after fission
16766 // enable.
16767 if (NodePrincipal()->Equals(document->NodePrincipal())) {
16768 windowContext->NotifyUserGestureActivation();
16772 for (bc = bc->GetParent(); bc; bc = bc->GetParent()) {
16773 if (WindowContext* windowContext = bc->GetCurrentWindowContext()) {
16774 windowContext->NotifyUserGestureActivation();
16780 bool Document::HasBeenUserGestureActivated() {
16781 RefPtr<WindowContext> wc = GetWindowContext();
16782 return wc && wc->HasBeenUserGestureActivated();
16785 DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
16786 if (RefPtr<WindowContext> wc = GetWindowContext()) {
16787 if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) {
16788 if (Performance* perf = innerWindow->GetPerformance()) {
16789 return perf->GetDOMTiming()->TimeStampToDOMHighRes(
16790 wc->GetUserGestureStart());
16795 NS_WARNING(
16796 "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
16797 return 0;
16800 void Document::ClearUserGestureActivation() {
16801 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16802 bc = bc->Top();
16803 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16804 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
16805 windowContext->NotifyResetUserGestureActivation();
16811 bool Document::HasValidTransientUserGestureActivation() const {
16812 RefPtr<WindowContext> wc = GetWindowContext();
16813 return wc && wc->HasValidTransientUserGestureActivation();
16816 bool Document::ConsumeTransientUserGestureActivation() {
16817 RefPtr<WindowContext> wc = GetWindowContext();
16818 return wc && wc->ConsumeTransientUserGestureActivation();
16821 void Document::SetDocTreeHadMedia() {
16822 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
16823 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
16824 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
16828 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
16829 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
16830 return;
16833 // This will probably change for project fission, but currently this document
16834 // and the opener are on the same process. In the future, we should make this
16835 // part async.
16836 nsPIDOMWindowInner* inner = GetInnerWindow();
16837 if (NS_WARN_IF(!inner)) {
16838 return;
16841 uint32_t cookieBehavior = CookieJarSettings()->GetCookieBehavior();
16842 if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
16843 cookieBehavior ==
16844 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
16845 // We care about first-party tracking resources only.
16846 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
16847 return;
16849 } else {
16850 MOZ_ASSERT(net::CookieJarSettings::IsRejectThirdPartyWithExceptions(
16851 cookieBehavior));
16854 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16855 if (NS_WARN_IF(!outer)) {
16856 return;
16859 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
16860 if (!openerBC) {
16861 // No opener.
16862 return;
16865 // We want to ensure the following check works for both fission mode and
16866 // non-fission mode:
16867 // "If the opener is not a 3rd party and if this window is not a 3rd party
16868 // with respect to the opener, we should not continue."
16870 // In non-fission mode, the opener and the opened window are in the same
16871 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
16872 // In fission mode, if this window is not a 3rd party with respect to the
16873 // opener, they must be in the same process, so we can still use
16874 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
16875 // party.
16876 if (openerBC->IsInProcess()) {
16877 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
16878 if (NS_WARN_IF(!outerOpener)) {
16879 return;
16882 nsCOMPtr<nsPIDOMWindowInner> openerInner =
16883 outerOpener->GetCurrentInnerWindow();
16884 if (NS_WARN_IF(!openerInner)) {
16885 return;
16888 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
16889 if (NS_WARN_IF(!openerDocument)) {
16890 return;
16893 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
16894 if (NS_WARN_IF(!openerURI)) {
16895 return;
16898 // If the opener is not a 3rd party and if this window is not
16899 // a 3rd party with respect to the opener, we should not continue.
16900 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
16901 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
16902 return;
16906 // We don't care when the asynchronous work finishes here.
16907 Unused << StorageAccessAPIHelper::AllowAccessFor(
16908 NodePrincipal(), openerBC,
16909 ContentBlockingNotifier::eOpenerAfterUserInteraction);
16912 namespace {
16914 // Documents can stay alive for days. We don't want to update the permission
16915 // value at any user-interaction, and, using a timer triggered any X seconds
16916 // should be good enough. 'X' is taken from
16917 // privacy.userInteraction.document.interval pref.
16918 // We also want to store the user-interaction before shutting down, and, for
16919 // this reason, this class implements nsIAsyncShutdownBlocker interface.
16920 class UserInteractionTimer final : public Runnable,
16921 public nsITimerCallback,
16922 public nsIAsyncShutdownBlocker {
16923 public:
16924 NS_DECL_ISUPPORTS_INHERITED
16926 explicit UserInteractionTimer(Document* aDocument)
16927 : Runnable("UserInteractionTimer"),
16928 mPrincipal(aDocument->NodePrincipal()),
16929 mDocument(do_GetWeakReference(aDocument)) {
16930 static int32_t userInteractionTimerId = 0;
16931 // Blocker names must be unique. Let's create it now because when needed,
16932 // the document could be already gone.
16933 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
16934 ++userInteractionTimerId, aDocument);
16937 // Runnable interface
16939 NS_IMETHOD
16940 Run() override {
16941 uint32_t interval =
16942 StaticPrefs::privacy_userInteraction_document_interval();
16943 if (!interval) {
16944 return NS_OK;
16947 RefPtr<UserInteractionTimer> self = this;
16948 auto raii =
16949 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
16951 nsresult rv = NS_NewTimerWithCallback(
16952 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
16953 NS_ENSURE_SUCCESS(rv, NS_OK);
16955 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16956 NS_ENSURE_TRUE(!!phase, NS_OK);
16958 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
16959 __LINE__, u"UserInteractionTimer shutdown"_ns);
16960 NS_ENSURE_SUCCESS(rv, NS_OK);
16962 raii.release();
16963 return NS_OK;
16966 // nsITimerCallback interface
16968 NS_IMETHOD
16969 Notify(nsITimer* aTimer) override {
16970 StoreUserInteraction();
16971 return NS_OK;
16974 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
16975 using nsINamed::GetName;
16976 #endif
16978 // nsIAsyncShutdownBlocker interface
16980 NS_IMETHOD
16981 GetName(nsAString& aName) override {
16982 aName = mBlockerName;
16983 return NS_OK;
16986 NS_IMETHOD
16987 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
16988 CancelTimerAndStoreUserInteraction();
16989 return NS_OK;
16992 NS_IMETHOD
16993 GetState(nsIPropertyBag**) override { return NS_OK; }
16995 private:
16996 ~UserInteractionTimer() = default;
16998 void StoreUserInteraction() {
16999 // Remove the shutting down blocker
17000 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
17001 if (phase) {
17002 phase->RemoveBlocker(this);
17005 // If the document is not gone, let's reset its timer flag.
17006 nsCOMPtr<Document> document = do_QueryReferent(mDocument);
17007 if (document) {
17008 ContentBlockingUserInteraction::Observe(mPrincipal);
17009 document->ResetUserInteractionTimer();
17013 void CancelTimerAndStoreUserInteraction() {
17014 if (mTimer) {
17015 mTimer->Cancel();
17016 mTimer = nullptr;
17019 StoreUserInteraction();
17022 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
17023 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
17024 NS_ENSURE_TRUE(!!svc, nullptr);
17026 nsCOMPtr<nsIAsyncShutdownClient> phase;
17027 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
17028 NS_ENSURE_SUCCESS(rv, nullptr);
17030 return phase.forget();
17033 nsCOMPtr<nsIPrincipal> mPrincipal;
17034 nsWeakPtr mDocument;
17036 nsCOMPtr<nsITimer> mTimer;
17038 nsString mBlockerName;
17041 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
17042 nsIAsyncShutdownBlocker)
17044 } // namespace
17046 void Document::MaybeStoreUserInteractionAsPermission() {
17047 // We care about user-interaction stored only for top-level documents
17048 // and documents with access to the Storage Access API
17049 if (!IsTopLevelContentDocument()) {
17050 bool hasSA;
17051 nsresult rv = HasStorageAccessSync(hasSA);
17052 if (NS_FAILED(rv) || !hasSA) {
17053 return;
17057 if (!mUserHasInteracted) {
17058 // First interaction, let's store this info now.
17059 ContentBlockingUserInteraction::Observe(NodePrincipal());
17060 return;
17063 if (mHasUserInteractionTimerScheduled) {
17064 return;
17067 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
17068 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
17069 EventQueuePriority::Idle);
17070 if (NS_WARN_IF(NS_FAILED(rv))) {
17071 return;
17074 // This value will be reset by the timer.
17075 mHasUserInteractionTimerScheduled = true;
17078 void Document::ResetUserInteractionTimer() {
17079 mHasUserInteractionTimerScheduled = false;
17082 bool Document::IsExtensionPage() const {
17083 return BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
17086 void Document::AddResizeObserver(ResizeObserver& aObserver) {
17087 if (!mResizeObserverController) {
17088 mResizeObserverController = MakeUnique<ResizeObserverController>(this);
17090 mResizeObserverController->AddResizeObserver(aObserver);
17093 void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
17094 MOZ_DIAGNOSTIC_ASSERT(mResizeObserverController, "No controller?");
17095 if (MOZ_UNLIKELY(!mResizeObserverController)) {
17096 return;
17098 mResizeObserverController->RemoveResizeObserver(aObserver);
17101 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
17102 if (!mPermissionDelegateHandler) {
17103 mPermissionDelegateHandler =
17104 mozilla::MakeAndAddRef<PermissionDelegateHandler>(this);
17107 if (!mPermissionDelegateHandler->Initialize()) {
17108 mPermissionDelegateHandler = nullptr;
17111 return mPermissionDelegateHandler;
17114 void Document::ScheduleResizeObserversNotification() const {
17115 if (!mResizeObserverController) {
17116 return;
17119 mResizeObserverController->ScheduleNotification();
17122 void Document::ClearStaleServoData() {
17123 DocumentStyleRootIterator iter(this);
17124 while (Element* root = iter.GetNextStyleRoot()) {
17125 RestyleManager::ClearServoDataFromSubtree(root);
17129 Selection* Document::GetSelection(ErrorResult& aRv) {
17130 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
17131 if (!window) {
17132 return nullptr;
17135 if (!window->IsCurrentInnerWindow()) {
17136 return nullptr;
17139 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
17142 void Document::MakeBrowsingContextNonSynthetic() {
17143 if (nsContentUtils::ShouldHideObjectOrEmbedImageDocument()) {
17144 if (BrowsingContext* bc = GetBrowsingContext()) {
17145 if (bc->GetSyntheticDocumentContainer()) {
17146 Unused << bc->SetSyntheticDocumentContainer(false);
17152 nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
17153 // Step 1: check if cookie permissions are available or denied to this
17154 // document's principal
17155 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17156 if (!inner) {
17157 aHasStorageAccess = false;
17158 return NS_OK;
17160 Maybe<bool> resultBecauseCookiesApproved =
17161 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17162 CookieJarSettings(), NodePrincipal());
17163 if (resultBecauseCookiesApproved.isSome()) {
17164 if (resultBecauseCookiesApproved.value()) {
17165 aHasStorageAccess = true;
17166 return NS_OK;
17167 } else {
17168 aHasStorageAccess = false;
17169 return NS_OK;
17173 // Step 2: Check if the browser settings determine whether or not this
17174 // document has access to its unpartitioned cookies.
17175 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17176 bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
17177 bool isOnThirdPartySkipList = false;
17178 if (mChannel) {
17179 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17180 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17181 nsILoadInfo::StoragePermissionAllowListed;
17183 bool isThirdPartyTracker =
17184 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17185 Maybe<bool> resultBecauseBrowserSettings =
17186 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17187 CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
17188 isOnThirdPartySkipList, isThirdPartyTracker);
17189 if (resultBecauseBrowserSettings.isSome()) {
17190 if (resultBecauseBrowserSettings.value()) {
17191 aHasStorageAccess = true;
17192 return NS_OK;
17193 } else {
17194 aHasStorageAccess = false;
17195 return NS_OK;
17199 // Step 3: Check if the location of this call (embedded, top level, same-site)
17200 // determines if cookies are permitted or not.
17201 Maybe<bool> resultBecauseCallContext =
17202 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17203 false);
17204 if (resultBecauseCallContext.isSome()) {
17205 if (resultBecauseCallContext.value()) {
17206 aHasStorageAccess = true;
17207 return NS_OK;
17208 } else {
17209 aHasStorageAccess = false;
17210 return NS_OK;
17214 // Step 4: Check if the permissions for this document determine if if has
17215 // access or is denied cookies.
17216 Maybe<bool> resultBecausePreviousPermission =
17217 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17218 this, false);
17219 if (resultBecausePreviousPermission.isSome()) {
17220 if (resultBecausePreviousPermission.value()) {
17221 aHasStorageAccess = true;
17222 return NS_OK;
17223 } else {
17224 aHasStorageAccess = false;
17225 return NS_OK;
17228 // If you get here, we default to not giving you permission.
17229 aHasStorageAccess = false;
17230 return NS_OK;
17233 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
17234 mozilla::ErrorResult& aRv) {
17235 nsIGlobalObject* global = GetScopeObject();
17236 if (!global) {
17237 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17238 return nullptr;
17241 RefPtr<Promise> promise =
17242 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
17243 if (aRv.Failed()) {
17244 return nullptr;
17247 bool hasStorageAccess;
17248 nsresult rv = HasStorageAccessSync(hasStorageAccess);
17249 if (NS_FAILED(rv)) {
17250 promise->MaybeRejectWithUndefined();
17251 } else {
17252 promise->MaybeResolve(hasStorageAccess);
17255 return promise.forget();
17258 RefPtr<Document::GetContentBlockingEventsPromise>
17259 Document::GetContentBlockingEvents() {
17260 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
17261 if (!wgc) {
17262 return nullptr;
17265 return wgc->SendGetContentBlockingEvents()->Then(
17266 GetCurrentSerialEventTarget(), __func__,
17267 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
17268 ResolveOrRejectValue& aValue) {
17269 if (aValue.IsResolve()) {
17270 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
17271 aValue.ResolveValue(), __func__);
17274 return Document::GetContentBlockingEventsPromise::CreateAndReject(
17275 false, __func__);
17279 StorageAccessAPIHelper::PerformPermissionGrant
17280 Document::CreatePermissionGrantPromise(
17281 nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal,
17282 bool aHasUserInteraction, const Maybe<nsCString>& aTopLevelBaseDomain,
17283 bool aFrameOnly) {
17284 MOZ_ASSERT(aInnerWindow);
17285 MOZ_ASSERT(aPrincipal);
17286 RefPtr<Document> self(this);
17287 RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
17288 RefPtr<nsIPrincipal> principal(aPrincipal);
17290 return [inner, self, principal, aHasUserInteraction, aTopLevelBaseDomain,
17291 aFrameOnly]() {
17292 RefPtr<StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::Private>
17293 p = new StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
17294 Private(__func__);
17296 RefPtr<PWindowGlobalChild::HasStorageAccessPermissionPromise> promise;
17297 // Test the permission
17298 MOZ_ASSERT(XRE_IsContentProcess());
17300 WindowGlobalChild* wgc = inner->GetWindowGlobalChild();
17301 MOZ_ASSERT(wgc);
17303 promise = wgc->SendHasStorageAccessPermission();
17304 MOZ_ASSERT(promise);
17305 promise->Then(
17306 GetCurrentSerialEventTarget(), __func__,
17307 [self, p, inner, principal, aHasUserInteraction, aTopLevelBaseDomain,
17308 aFrameOnly](bool aGranted) {
17309 if (aGranted) {
17310 p->Resolve(true, __func__);
17311 return;
17314 // Create the user prompt
17315 RefPtr<StorageAccessPermissionRequest> sapr =
17316 StorageAccessPermissionRequest::Create(
17317 inner, principal, aTopLevelBaseDomain, aFrameOnly,
17318 // Allow
17319 [p] {
17320 Telemetry::AccumulateCategorical(
17321 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
17322 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17324 // Block
17325 [p] {
17326 Telemetry::AccumulateCategorical(
17327 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
17328 p->Reject(false, __func__);
17331 using PromptResult = ContentPermissionRequestBase::PromptResult;
17332 PromptResult pr = sapr->CheckPromptPrefs();
17334 if (pr == PromptResult::Pending) {
17335 // We're about to show a prompt, record the request attempt
17336 Telemetry::AccumulateCategorical(
17337 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
17340 // Try to auto-grant the storage access so the user doesn't see the
17341 // prompt.
17342 self->AutomaticStorageAccessPermissionCanBeGranted(
17343 aHasUserInteraction)
17344 ->Then(
17345 GetCurrentSerialEventTarget(), __func__,
17346 // If the autogrant check didn't fail, call this function
17347 [p, pr, sapr,
17348 inner](const Document::
17349 AutomaticStorageAccessPermissionGrantPromise::
17350 ResolveOrRejectValue& aValue) -> void {
17351 // Make a copy because we can't modified copy-captured
17352 // lambda variables.
17353 PromptResult pr2 = pr;
17355 // If the user didn't already click "allow" and we can
17356 // autogrant, do that!
17357 bool storageAccessCanBeGrantedAutomatically =
17358 aValue.IsResolve() && aValue.ResolveValue();
17359 bool autoGrant = false;
17360 if (pr2 == PromptResult::Pending &&
17361 storageAccessCanBeGrantedAutomatically) {
17362 pr2 = PromptResult::Granted;
17363 autoGrant = true;
17365 Telemetry::AccumulateCategorical(
17366 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
17367 AllowAutomatically);
17370 // If we can complete the permission request, do so.
17371 if (pr2 != PromptResult::Pending) {
17372 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
17373 pr2 == PromptResult::Denied);
17374 if (pr2 == PromptResult::Granted) {
17375 StorageAccessAPIHelper::StorageAccessPromptChoices
17376 choice = StorageAccessAPIHelper::eAllow;
17377 if (autoGrant) {
17378 choice = StorageAccessAPIHelper::eAllowAutoGrant;
17380 if (!autoGrant) {
17381 p->Resolve(choice, __func__);
17382 } else {
17383 // We capture sapr here to prevent it from destructing
17384 // before the callbacks complete.
17385 sapr->MaybeDelayAutomaticGrants()->Then(
17386 GetCurrentSerialEventTarget(), __func__,
17387 [p, sapr, choice] {
17388 p->Resolve(choice, __func__);
17390 [p, sapr] { p->Reject(false, __func__); });
17392 return;
17394 p->Reject(false, __func__);
17395 return;
17398 // If we get here, the auto-decision failed and we need to
17399 // wait for the user prompt to complete.
17400 sapr->RequestDelayedTask(
17401 inner->EventTargetFor(TaskCategory::Other),
17402 ContentPermissionRequestBase::DelayedTaskType::Request);
17405 [p](mozilla::ipc::ResponseRejectReason aError) {
17406 p->Reject(false, __func__);
17407 return p;
17410 return p;
17414 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
17415 mozilla::ErrorResult& aRv) {
17416 nsIGlobalObject* global = GetScopeObject();
17417 if (!global) {
17418 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17419 return nullptr;
17422 RefPtr<Promise> promise = Promise::Create(global, aRv);
17423 if (aRv.Failed()) {
17424 return nullptr;
17427 // Step 0: Check that we have user activation before proceeding to prevent
17428 // rapid calls to the API to leak information.
17429 if (!HasValidTransientUserGestureActivation()) {
17430 // Report an error to the console for this case
17431 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17432 nsLiteralCString("requestStorageAccess"),
17433 this, nsContentUtils::eDOM_PROPERTIES,
17434 "RequestStorageAccessUserGesture");
17435 ConsumeTransientUserGestureActivation();
17436 promise->MaybeRejectWithNotAllowedError(
17437 "requestStorageAccess not allowed"_ns);
17438 return promise.forget();
17441 // Get a pointer to the inner window- We need this for convenience sake
17442 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17443 if (!inner) {
17444 ConsumeTransientUserGestureActivation();
17445 promise->MaybeRejectWithNotAllowedError(
17446 "requestStorageAccess not allowed"_ns);
17447 return promise.forget();
17450 // Step 1: Check if the principal calling this has a permission that lets
17451 // them use cookies or forbids them from using cookies.
17452 // This is outside of the spec of the StorageAccess API, but makes the return
17453 // values to have proper semantics.
17454 Maybe<bool> resultBecauseCookiesApproved =
17455 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17456 CookieJarSettings(), NodePrincipal());
17457 if (resultBecauseCookiesApproved.isSome()) {
17458 if (resultBecauseCookiesApproved.value()) {
17459 promise->MaybeResolveWithUndefined();
17460 return promise.forget();
17461 } else {
17462 ConsumeTransientUserGestureActivation();
17463 promise->MaybeRejectWithNotAllowedError(
17464 "requestStorageAccess not allowed"_ns);
17465 return promise.forget();
17469 // Step 2: Check if the browser settings always allow or deny cookies.
17470 // We should always return a resolved promise if the cookieBehavior is ACCEPT.
17471 // This is outside of the spec of the StorageAccess API, but makes the return
17472 // values to have proper semantics.
17473 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17474 bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
17475 bool isOnThirdPartySkipList = false;
17476 if (mChannel) {
17477 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17478 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17479 nsILoadInfo::StoragePermissionAllowListed;
17481 bool isThirdPartyTracker =
17482 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17483 Maybe<bool> resultBecauseBrowserSettings =
17484 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17485 CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
17486 isOnThirdPartySkipList, isThirdPartyTracker);
17487 if (resultBecauseBrowserSettings.isSome()) {
17488 if (resultBecauseBrowserSettings.value()) {
17489 promise->MaybeResolveWithUndefined();
17490 return promise.forget();
17491 } else {
17492 ConsumeTransientUserGestureActivation();
17493 promise->MaybeRejectWithNotAllowedError(
17494 "requestStorageAccess not allowed"_ns);
17495 return promise.forget();
17499 // Step 3: Check if the Document calling requestStorageAccess has anything to
17500 // gain from storage access. It should be embedded, non-null, etc.
17501 Maybe<bool> resultBecauseCallContext =
17502 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17503 true);
17504 if (resultBecauseCallContext.isSome()) {
17505 if (resultBecauseCallContext.value()) {
17506 promise->MaybeResolveWithUndefined();
17507 return promise.forget();
17508 } else {
17509 ConsumeTransientUserGestureActivation();
17510 promise->MaybeRejectWithNotAllowedError(
17511 "requestStorageAccess not allowed"_ns);
17512 return promise.forget();
17516 // Step 4: Check if we already allowed or denied storage access for this
17517 // document's storage key.
17518 Maybe<bool> resultBecausePreviousPermission =
17519 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17520 this, true);
17521 if (resultBecausePreviousPermission.isSome()) {
17522 if (resultBecausePreviousPermission.value()) {
17523 promise->MaybeResolveWithUndefined();
17524 return promise.forget();
17525 } else {
17526 ConsumeTransientUserGestureActivation();
17527 promise->MaybeRejectWithNotAllowedError(
17528 "requestStorageAccess not allowed"_ns);
17529 return promise.forget();
17533 // Get pointers to some objects that will be used in the async portion
17534 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17535 RefPtr<nsGlobalWindowOuter> outer =
17536 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17537 if (!outer) {
17538 ConsumeTransientUserGestureActivation();
17539 promise->MaybeRejectWithNotAllowedError(
17540 "requestStorageAccess not allowed"_ns);
17541 return promise.forget();
17543 RefPtr<Document> self(this);
17545 // Step 5. Start an async call to request storage access. This will either
17546 // perform an automatic decision or notify the user, then perform some follow
17547 // on work changing state to reflect the result of the API. If it resolves,
17548 // the request was granted. If it rejects it was denied.
17549 StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17550 this, inner, bc, NodePrincipal(), true, true,
17551 ContentBlockingNotifier::eStorageAccessAPI, true)
17552 ->Then(
17553 GetCurrentSerialEventTarget(), __func__,
17554 [inner, promise] {
17555 inner->SaveStorageAccessPermissionGranted();
17556 promise->MaybeResolveWithUndefined();
17558 [self, promise] {
17559 self->ConsumeTransientUserGestureActivation();
17560 promise->MaybeRejectWithNotAllowedError(
17561 "requestStorageAccess not allowed"_ns);
17564 return promise.forget();
17567 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
17568 const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
17569 mozilla::ErrorResult& aRv) {
17570 nsIGlobalObject* global = GetScopeObject();
17571 if (!global) {
17572 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17573 return nullptr;
17575 RefPtr<Promise> promise = Promise::Create(global, aRv);
17576 if (aRv.Failed()) {
17577 return nullptr;
17580 // Step 0: Check that we have user activation before proceeding to prevent
17581 // rapid calls to the API to leak information.
17582 if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) {
17583 // Report an error to the console for this case
17584 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17585 nsLiteralCString("requestStorageAccess"),
17586 this, nsContentUtils::eDOM_PROPERTIES,
17587 "RequestStorageAccessUserGesture");
17588 ConsumeTransientUserGestureActivation();
17589 promise->MaybeRejectWithNotAllowedError(
17590 "requestStorageAccess not allowed"_ns);
17591 return promise.forget();
17594 // Step 1: Check if the provided URI is different-site to this Document
17595 nsCOMPtr<nsIURI> thirdPartyURI;
17596 nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
17597 if (NS_WARN_IF(NS_FAILED(rv))) {
17598 aRv.Throw(rv);
17599 return nullptr;
17601 bool isThirdPartyDocument;
17602 rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument);
17603 if (NS_WARN_IF(NS_FAILED(rv))) {
17604 aRv.Throw(rv);
17605 return nullptr;
17607 bool isOnRejectForeignAllowList =
17608 RejectForeignAllowList::Check(thirdPartyURI);
17609 Maybe<bool> resultBecauseBrowserSettings =
17610 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17611 CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
17612 false, true);
17613 if (resultBecauseBrowserSettings.isSome()) {
17614 if (resultBecauseBrowserSettings.value()) {
17615 promise->MaybeResolveWithUndefined();
17616 return promise.forget();
17618 ConsumeTransientUserGestureActivation();
17619 promise->MaybeRejectWithNotAllowedError(
17620 "requestStorageAccess not allowed"_ns);
17621 return promise.forget();
17624 // Step 2: Check that this Document is same-site to the top, and check that
17625 // we have user activation if we require it.
17626 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17627 CheckSameSiteCallingContextDecidesStorageAccessAPI(
17628 this, aRequireUserActivation);
17629 if (resultBecauseCallContext.isSome()) {
17630 if (resultBecauseCallContext.value()) {
17631 promise->MaybeResolveWithUndefined();
17632 return promise.forget();
17634 ConsumeTransientUserGestureActivation();
17635 promise->MaybeRejectWithNotAllowedError(
17636 "requestStorageAccess not allowed"_ns);
17637 return promise.forget();
17640 // Step 3: Get some useful variables that can be captured by the lambda for
17641 // the asynchronous portion
17642 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17643 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17644 if (!inner) {
17645 ConsumeTransientUserGestureActivation();
17646 promise->MaybeRejectWithNotAllowedError(
17647 "requestStorageAccess not allowed"_ns);
17648 return promise.forget();
17650 RefPtr<nsGlobalWindowOuter> outer =
17651 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17652 if (!outer) {
17653 ConsumeTransientUserGestureActivation();
17654 promise->MaybeRejectWithNotAllowedError(
17655 "requestStorageAccess not allowed"_ns);
17656 return promise.forget();
17658 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17659 thirdPartyURI, NodePrincipal()->OriginAttributesRef());
17660 if (!principal) {
17661 ConsumeTransientUserGestureActivation();
17662 promise->MaybeRejectWithNotAllowedError(
17663 "requestStorageAccess not allowed"_ns);
17664 return promise.forget();
17667 RefPtr<Document> self(this);
17668 bool hasUserActivation = HasValidTransientUserGestureActivation();
17670 // Consume user activation before entering the async part of this method.
17671 // This prevents usage of other transient activation-gated APIs.
17672 ConsumeTransientUserGestureActivation();
17674 // Step 4a: Start the async part of this function. Check the cookie
17675 // permission, but this can't be done in this process. We needs the cookie
17676 // permission of the URL as if it were embedded on this page, so we need to
17677 // make this check in the ContentParent.
17678 StorageAccessAPIHelper::AsyncCheckCookiesPermittedDecidesStorageAccessAPI(
17679 GetBrowsingContext(), principal)
17680 ->Then(
17681 GetCurrentSerialEventTarget(), __func__,
17682 [inner, thirdPartyURI, bc, principal, hasUserActivation, self,
17683 promise](Maybe<bool> cookieResult) {
17684 // Handle the result of the cookie permission check that took place
17685 // in the ContentParent.
17686 if (cookieResult.isSome()) {
17687 if (cookieResult.value()) {
17688 return MozPromise<int, bool, true>::CreateAndResolve(true,
17689 __func__);
17691 return MozPromise<int, bool, true>::CreateAndReject(false,
17692 __func__);
17695 // Step 4b: Check for the existing storage access permission
17696 nsAutoCString type;
17697 bool ok =
17698 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
17699 if (!ok) {
17700 return MozPromise<int, bool, true>::CreateAndReject(false,
17701 __func__);
17703 if (AntiTrackingUtils::CheckStoragePermission(
17704 self->NodePrincipal(), type,
17705 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
17706 return MozPromise<int, bool, true>::CreateAndResolve(true,
17707 __func__);
17710 // Step 4c: Try to request storage access, either automatically or
17711 // with a user-prompt. This is the part that is async in the
17712 // typical requestStorageAccess function.
17713 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17714 self, inner, bc, principal, hasUserActivation, false,
17715 ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI,
17716 true);
17718 // If the IPC rejects, we should reject our promise here which will
17719 // cause a rejection of the promise we already returned
17720 [promise]() {
17721 return MozPromise<int, bool, true>::CreateAndReject(false,
17722 __func__);
17724 ->Then(
17725 GetCurrentSerialEventTarget(), __func__,
17726 // If the previous handlers resolved, we should reinstate user
17727 // activation and resolve the promise we returned in Step 5.
17728 [self, inner, promise] {
17729 inner->SaveStorageAccessPermissionGranted();
17730 self->NotifyUserGestureActivation();
17731 promise->MaybeResolveWithUndefined();
17733 // If the previous handler rejected, we should reject the promise
17734 // returned by this function.
17735 [promise] {
17736 promise->MaybeRejectWithNotAllowedError(
17737 "requestStorageAccess not allowed"_ns);
17740 // Step 5: While the async stuff is happening, we should return the promise so
17741 // our caller can continue executing.
17742 return promise.forget();
17745 already_AddRefed<Promise> Document::RequestStorageAccessUnderSite(
17746 const nsAString& aSerializedSite, ErrorResult& aRv) {
17747 nsIGlobalObject* global = GetScopeObject();
17748 if (!global) {
17749 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17750 return nullptr;
17752 RefPtr<Promise> promise = Promise::Create(global, aRv);
17753 if (aRv.Failed()) {
17754 return nullptr;
17757 // Check that we have user activation before proceeding to prevent
17758 // rapid calls to the API to leak information.
17759 if (!ConsumeTransientUserGestureActivation()) {
17760 // Report an error to the console for this case
17761 nsContentUtils::ReportToConsole(
17762 nsIScriptError::errorFlag, "requestStorageAccess"_ns, this,
17763 nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture");
17764 promise->MaybeRejectWithUndefined();
17765 return promise.forget();
17768 // Check if the provided URI is different-site to this Document
17769 nsCOMPtr<nsIURI> siteURI;
17770 nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite);
17771 if (NS_WARN_IF(NS_FAILED(rv))) {
17772 promise->MaybeRejectWithUndefined();
17773 return promise.forget();
17775 bool isCrossSiteArgument;
17776 rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument);
17777 if (NS_WARN_IF(NS_FAILED(rv))) {
17778 aRv.Throw(rv);
17779 return nullptr;
17781 if (!isCrossSiteArgument) {
17782 promise->MaybeRejectWithUndefined();
17783 return promise.forget();
17786 // Check if this party has broad cookie permissions.
17787 Maybe<bool> resultBecauseCookiesApproved =
17788 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17789 CookieJarSettings(), NodePrincipal());
17790 if (resultBecauseCookiesApproved.isSome()) {
17791 if (resultBecauseCookiesApproved.value()) {
17792 promise->MaybeResolveWithUndefined();
17793 return promise.forget();
17795 promise->MaybeRejectWithUndefined();
17796 return promise.forget();
17799 // Check if browser settings preclude this document getting storage
17800 // access under the provided site
17801 bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
17802 Maybe<bool> resultBecauseBrowserSettings =
17803 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17804 CookieJarSettings(), true, isOnRejectForeignAllowList, false, true);
17805 if (resultBecauseBrowserSettings.isSome()) {
17806 if (resultBecauseBrowserSettings.value()) {
17807 promise->MaybeResolveWithUndefined();
17808 return promise.forget();
17810 promise->MaybeRejectWithUndefined();
17811 return promise.forget();
17814 // Check that this Document is same-site to the top
17815 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17816 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
17817 if (resultBecauseCallContext.isSome()) {
17818 if (resultBecauseCallContext.value()) {
17819 promise->MaybeResolveWithUndefined();
17820 return promise.forget();
17822 promise->MaybeRejectWithUndefined();
17823 return promise.forget();
17826 nsCOMPtr<nsIPrincipal> principal(NodePrincipal());
17828 // Test if the permission this is requesting is already set
17829 nsCOMPtr<nsIPrincipal> argumentPrincipal =
17830 BasePrincipal::CreateContentPrincipal(
17831 siteURI, NodePrincipal()->OriginAttributesRef());
17832 if (!argumentPrincipal) {
17833 ConsumeTransientUserGestureActivation();
17834 promise->MaybeRejectWithUndefined();
17835 return promise.forget();
17837 nsCString originNoSuffix;
17838 rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix);
17839 if (NS_WARN_IF(NS_FAILED(rv))) {
17840 promise->MaybeRejectWithUndefined();
17841 return promise.forget();
17844 ContentChild* cc = ContentChild::GetSingleton();
17845 MOZ_ASSERT(cc);
17846 RefPtr<Document> self(this);
17847 cc->SendTestStorageAccessPermission(argumentPrincipal, originNoSuffix)
17848 ->Then(
17849 GetCurrentSerialEventTarget(), __func__,
17850 [promise, siteURI,
17851 self](const ContentChild::TestStorageAccessPermissionPromise::
17852 ResolveValueType& aResult) {
17853 if (aResult) {
17854 return StorageAccessAPIHelper::
17855 StorageAccessPermissionGrantPromise::CreateAndResolve(
17856 StorageAccessAPIHelper::eAllow, __func__);
17858 // Get a grant for the storage access permission that will be set
17859 // when this is completed in the embedding context
17860 nsCString serializedSite;
17861 RefPtr<nsEffectiveTLDService> etld =
17862 nsEffectiveTLDService::GetInstance();
17863 if (!etld) {
17864 return StorageAccessAPIHelper::
17865 StorageAccessPermissionGrantPromise::CreateAndReject(
17866 false, __func__);
17868 nsresult rv = etld->GetSite(siteURI, serializedSite);
17869 if (NS_FAILED(rv)) {
17870 return StorageAccessAPIHelper::
17871 StorageAccessPermissionGrantPromise::CreateAndReject(
17872 false, __func__);
17874 return self->CreatePermissionGrantPromise(
17875 self->GetInnerWindow(), self->NodePrincipal(), true,
17876 Some(serializedSite), false)();
17878 [](const ContentChild::TestStorageAccessPermissionPromise::
17879 RejectValueType& aResult) {
17880 return StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
17881 CreateAndReject(false, __func__);
17883 ->Then(
17884 GetCurrentSerialEventTarget(), __func__,
17885 [promise, principal, siteURI](int result) {
17886 ContentChild* cc = ContentChild::GetSingleton();
17887 if (!cc) {
17888 // TODO(bug 1778561): Make this work in non-content processes.
17889 promise->MaybeRejectWithUndefined();
17890 return;
17892 // Set a permission in the parent process that this document wants
17893 // storage access under the argument's site, resolving our returned
17894 // promise on success
17895 cc->SendSetAllowStorageAccessRequestFlag(principal, siteURI)
17896 ->Then(
17897 GetCurrentSerialEventTarget(), __func__,
17898 [promise](bool success) {
17899 if (success) {
17900 promise->MaybeResolveWithUndefined();
17901 } else {
17902 promise->MaybeRejectWithUndefined();
17905 [promise](mozilla::ipc::ResponseRejectReason reason) {
17906 promise->MaybeRejectWithUndefined();
17909 [promise](bool result) { promise->MaybeRejectWithUndefined(); });
17911 // Return the promise that is resolved in the async handler above
17912 return promise.forget();
17915 already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite(
17916 const nsAString& aSerializedOrigin, ErrorResult& aRv) {
17917 nsIGlobalObject* global = GetScopeObject();
17918 if (!global) {
17919 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17920 return nullptr;
17922 RefPtr<Promise> promise = Promise::Create(global, aRv);
17923 if (aRv.Failed()) {
17924 return nullptr;
17927 // Check that the provided URI is different-site to this Document
17928 nsCOMPtr<nsIURI> argumentURI;
17929 nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin);
17930 if (NS_WARN_IF(NS_FAILED(rv))) {
17931 promise->MaybeRejectWithUndefined();
17932 return promise.forget();
17934 bool isCrossSiteArgument;
17935 rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument);
17936 if (NS_WARN_IF(NS_FAILED(rv))) {
17937 aRv.Throw(rv);
17938 return nullptr;
17940 if (!isCrossSiteArgument) {
17941 promise->MaybeRejectWithUndefined();
17942 return promise.forget();
17945 // Check if browser settings preclude this document getting storage
17946 // access under the provided site
17947 bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(argumentURI);
17948 Maybe<bool> resultBecauseBrowserSettings =
17949 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17950 CookieJarSettings(), true, isOnRejectForeignAllowList, false, true);
17951 if (resultBecauseBrowserSettings.isSome()) {
17952 if (resultBecauseBrowserSettings.value()) {
17953 promise->MaybeResolveWithUndefined();
17954 return promise.forget();
17956 promise->MaybeRejectWithUndefined();
17957 return promise.forget();
17960 // Check that this Document is same-site to the top
17961 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17962 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
17963 if (resultBecauseCallContext.isSome()) {
17964 if (resultBecauseCallContext.value()) {
17965 promise->MaybeResolveWithUndefined();
17966 return promise.forget();
17968 promise->MaybeRejectWithUndefined();
17969 return promise.forget();
17972 // Create principal of the embedded site requesting storage access
17973 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17974 argumentURI, NodePrincipal()->OriginAttributesRef());
17975 if (!principal) {
17976 promise->MaybeRejectWithUndefined();
17977 return promise.forget();
17980 // Get versions of these objects that we can use in lambdas for callbacks
17981 RefPtr<Document> self(this);
17982 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17983 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17985 // Test that the permission was set by a call to RequestStorageAccessUnderSite
17986 // from a top level document that is same-site with the argument
17987 ContentChild* cc = ContentChild::GetSingleton();
17988 if (!cc) {
17989 // TODO(bug 1778561): Make this work in non-content processes.
17990 promise->MaybeRejectWithUndefined();
17991 return promise.forget();
17993 cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI)
17994 ->Then(
17995 GetCurrentSerialEventTarget(), __func__,
17996 [inner, bc, self, principal](bool success) {
17997 if (success) {
17998 // If that resolved with true, check that we don't already have a
17999 // permission that gives cookie access.
18000 return StorageAccessAPIHelper::
18001 AsyncCheckCookiesPermittedDecidesStorageAccessAPI(bc,
18002 principal);
18004 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18005 NS_ERROR_FAILURE, __func__);
18007 [](mozilla::ipc::ResponseRejectReason reason) {
18008 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18009 NS_ERROR_FAILURE, __func__);
18011 ->Then(
18012 GetCurrentSerialEventTarget(), __func__,
18013 [inner, bc, principal, self, promise](Maybe<bool> cookieResult) {
18014 // Handle the result of the cookie permission check that took place
18015 // in the ContentParent.
18016 if (cookieResult.isSome()) {
18017 if (cookieResult.value()) {
18018 return StorageAccessAPIHelper::
18019 StorageAccessPermissionGrantPromise::CreateAndResolve(
18020 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18022 return StorageAccessAPIHelper::
18023 StorageAccessPermissionGrantPromise::CreateAndReject(
18024 false, __func__);
18027 // Check for the existing storage access permission
18028 nsAutoCString type;
18029 bool ok =
18030 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
18031 if (!ok) {
18032 return StorageAccessAPIHelper::
18033 StorageAccessPermissionGrantPromise::CreateAndReject(
18034 false, __func__);
18036 if (AntiTrackingUtils::CheckStoragePermission(
18037 self->NodePrincipal(), type,
18038 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
18039 return StorageAccessAPIHelper::
18040 StorageAccessPermissionGrantPromise::CreateAndResolve(
18041 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18044 // Try to request storage access, ignoring the final checks.
18045 // We ignore the final checks because this is where the "grant"
18046 // either by prompt doorhanger or autogrant takes place. We already
18047 // gathered an equivalent grant in requestStorageAccessUnderSite.
18048 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
18049 self, inner, bc, principal, true, false,
18050 ContentBlockingNotifier::eStorageAccessAPI, false);
18052 // If the IPC rejects, we should reject our promise here which will
18053 // cause a rejection of the promise we already returned
18054 [promise]() {
18055 return MozPromise<int, bool, true>::CreateAndReject(false,
18056 __func__);
18058 ->Then(
18059 GetCurrentSerialEventTarget(), __func__,
18060 // If the previous handlers resolved, we should reinstate user
18061 // activation and resolve the promise we returned in Step 5.
18062 [self, inner, promise] {
18063 inner->SaveStorageAccessPermissionGranted();
18064 promise->MaybeResolveWithUndefined();
18066 // If the previous handler rejected, we should reject the promise
18067 // returned by this function.
18068 [promise] { promise->MaybeRejectWithUndefined(); });
18070 return promise.forget();
18073 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
18074 Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
18075 // requestStorageAccessForOrigin may not require user activation. If we don't
18076 // have user activation at this point we should always show the prompt.
18077 if (!hasUserActivation ||
18078 !StaticPrefs::privacy_antitracking_enableWebcompat()) {
18079 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18080 false, __func__);
18082 if (XRE_IsContentProcess()) {
18083 // In the content process, we need to ask the parent process to compute
18084 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
18085 // isn't accessible in the content process.
18086 ContentChild* cc = ContentChild::GetSingleton();
18087 MOZ_ASSERT(cc);
18089 return cc->SendAutomaticStorageAccessPermissionCanBeGranted(NodePrincipal())
18090 ->Then(GetCurrentSerialEventTarget(), __func__,
18091 [](const ContentChild::
18092 AutomaticStorageAccessPermissionCanBeGrantedPromise::
18093 ResolveOrRejectValue& aValue) {
18094 if (aValue.IsResolve()) {
18095 return AutomaticStorageAccessPermissionGrantPromise::
18096 CreateAndResolve(aValue.ResolveValue(), __func__);
18099 return AutomaticStorageAccessPermissionGrantPromise::
18100 CreateAndReject(false, __func__);
18104 if (XRE_IsParentProcess()) {
18105 // In the parent process, we can directly compute this.
18106 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18107 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
18108 __func__);
18111 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
18112 false, __func__);
18115 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
18116 nsIPrincipal* aPrincipal) {
18117 if (!StaticPrefs::dom_storage_access_auto_grants()) {
18118 return false;
18121 if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
18122 return false;
18125 nsCOMPtr<nsIBrowserUsage> bu = do_ImportESModule(
18126 "resource:///modules/BrowserUsageTelemetry.sys.mjs", fallible);
18127 if (NS_WARN_IF(!bu)) {
18128 return false;
18131 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
18132 nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours(
18133 &uniqueDomainsVisitedInPast24Hours);
18134 if (NS_WARN_IF(NS_FAILED(rv))) {
18135 return false;
18138 Maybe<size_t> maybeOriginsThirdPartyHasAccessTo =
18139 AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal);
18140 if (maybeOriginsThirdPartyHasAccessTo.isNothing()) {
18141 return false;
18143 size_t originsThirdPartyHasAccessTo =
18144 maybeOriginsThirdPartyHasAccessTo.value();
18146 // one percent of the number of top-levels origins visited in the current
18147 // session (but not to exceed 24 hours), or the value of the
18148 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
18149 // higher.
18150 size_t maxConcurrentAutomaticGrants = std::max(
18151 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
18152 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
18155 return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
18158 void Document::RecordNavigationTiming(ReadyState aReadyState) {
18159 if (!XRE_IsContentProcess()) {
18160 return;
18162 if (!IsTopLevelContentDocument()) {
18163 return;
18165 // If we dont have the timing yet (mostly because the doc is still loading),
18166 // get it from docshell.
18167 RefPtr<nsDOMNavigationTiming> timing = mTiming;
18168 if (!timing) {
18169 if (!mDocumentContainer) {
18170 return;
18172 timing = mDocumentContainer->GetNavigationTiming();
18173 if (!timing) {
18174 return;
18177 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
18178 switch (aReadyState) {
18179 case READYSTATE_LOADING:
18180 if (!mDOMLoadingSet) {
18181 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
18182 startTime);
18183 mDOMLoadingSet = true;
18185 break;
18186 case READYSTATE_INTERACTIVE:
18187 if (!mDOMInteractiveSet) {
18188 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
18189 startTime);
18190 mDOMInteractiveSet = true;
18192 break;
18193 case READYSTATE_COMPLETE:
18194 if (!mDOMCompleteSet) {
18195 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
18196 startTime);
18197 mDOMCompleteSet = true;
18199 break;
18200 default:
18201 NS_WARNING("Unexpected ReadyState value");
18202 break;
18206 bool Document::ImportMapsEnabled() const {
18207 return nsContentUtils::IsChromeDoc(this) ||
18208 StaticPrefs::dom_importMaps_enabled();
18211 void Document::ReportShadowDOMUsage() {
18212 nsPIDOMWindowInner* inner = GetInnerWindow();
18213 if (NS_WARN_IF(!inner)) {
18214 return;
18217 WindowContext* wc = inner->GetWindowContext();
18218 if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
18219 return;
18222 WindowContext* topWc = wc->TopWindowContext();
18223 if (topWc->GetHasReportedShadowDOMUsage()) {
18224 return;
18227 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
18230 // static
18231 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
18232 return StaticPrefs::dom_storage_access_enabled() &&
18233 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
18236 bool Document::StorageAccessSandboxed() const {
18237 return Document::StorageAccessSandboxed(GetSandboxFlags());
18240 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
18241 if (mCachedTabSizeGeneration == 0 ||
18242 GetGeneration() != mCachedTabSizeGeneration) {
18243 return false;
18245 aSizes->mDom += mCachedTabSizes.mDom;
18246 aSizes->mStyle += mCachedTabSizes.mStyle;
18247 aSizes->mOther += mCachedTabSizes.mOther;
18248 return true;
18251 void Document::SetCachedSizes(nsTabSizes* aSizes) {
18252 mCachedTabSizes.mDom = aSizes->mDom;
18253 mCachedTabSizes.mStyle = aSizes->mStyle;
18254 mCachedTabSizes.mOther = aSizes->mOther;
18255 mCachedTabSizeGeneration = GetGeneration();
18258 already_AddRefed<nsAtom> Document::GetContentLanguageAsAtomForStyle() const {
18259 nsAutoString contentLang;
18260 GetContentLanguage(contentLang);
18261 contentLang.StripWhitespace();
18263 // Content-Language may be a comma-separated list of language codes,
18264 // in which case the HTML5 spec says to treat it as unknown
18265 if (!contentLang.IsEmpty() && !contentLang.Contains(char16_t(','))) {
18266 return NS_Atomize(contentLang);
18269 return nullptr;
18272 already_AddRefed<nsAtom> Document::GetLanguageForStyle() const {
18273 RefPtr<nsAtom> lang = GetContentLanguageAsAtomForStyle();
18274 if (!lang) {
18275 lang = mLanguageFromCharset;
18277 return lang.forget();
18280 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
18281 nsAtom* aLanguage, bool* aNeedsToCache) const {
18282 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
18283 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
18286 void Document::DoCacheAllKnownLangPrefs() {
18287 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
18288 RefPtr<nsAtom> lang = GetLanguageForStyle();
18289 StaticPresData* data = StaticPresData::Get();
18290 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
18291 data->GetFontPrefsForLang(nsGkAtoms::x_math);
18292 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
18293 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
18294 for (const auto& key : mLanguagesUsed) {
18295 data->GetFontPrefsForLang(key);
18297 mMayNeedFontPrefsUpdate = false;
18300 void Document::RecomputeLanguageFromCharset() {
18301 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
18302 RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
18303 if (language == nsGkAtoms::Unicode) {
18304 language = service->GetLocaleLanguage();
18307 if (language == mLanguageFromCharset) {
18308 return;
18311 mMayNeedFontPrefsUpdate = true;
18312 mLanguageFromCharset = std::move(language);
18315 nsICookieJarSettings* Document::CookieJarSettings() {
18316 // If we are here, this is probably a javascript: URL document. In any case,
18317 // we must have a nsCookieJarSettings. Let's create it.
18318 if (!mCookieJarSettings) {
18319 Document* inProcessParent = GetInProcessParentDocument();
18321 if (inProcessParent) {
18322 mCookieJarSettings = net::CookieJarSettings::Create(
18323 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
18324 mozilla::net::CookieJarSettings::Cast(
18325 inProcessParent->CookieJarSettings())
18326 ->GetPartitionKey(),
18327 inProcessParent->CookieJarSettings()->GetIsFirstPartyIsolated(),
18328 inProcessParent->CookieJarSettings()
18329 ->GetIsOnContentBlockingAllowList(),
18330 inProcessParent->CookieJarSettings()
18331 ->GetShouldResistFingerprinting());
18333 // Inherit the fingerprinting random key from the parent.
18334 nsTArray<uint8_t> randomKey;
18335 nsresult rv = inProcessParent->CookieJarSettings()
18336 ->GetFingerprintingRandomizationKey(randomKey);
18338 if (NS_SUCCEEDED(rv)) {
18339 net::CookieJarSettings::Cast(mCookieJarSettings)
18340 ->SetFingerprintingRandomizationKey(randomKey);
18342 } else {
18343 mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
18346 if (auto* wgc = GetWindowGlobalChild()) {
18347 net::CookieJarSettingsArgs csArgs;
18348 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
18349 // Update cookie settings in the parent process
18350 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
18351 NS_WARNING(
18352 "Failed to update document's cookie jar settings on the "
18353 "WindowGlobalParent");
18358 return mCookieJarSettings;
18361 bool Document::UsingStorageAccess() {
18362 // The HasStoragePermission flag in LoadInfo remains fixed when
18363 // it is set in the parent process, so we need to check the cache
18364 // to see if the permission is granted afterwards.
18365 nsPIDOMWindowInner* inner = GetInnerWindow();
18366 if (inner && inner->UsingStorageAccess()) {
18367 return true;
18370 if (!mChannel) {
18371 return false;
18374 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18375 return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
18378 bool Document::HasStorageAccessPermissionGrantedByAllowList() {
18379 // We only care about if the document gets the storage permission via the
18380 // allow list here. So we don't check the storage access cache in the inner
18381 // window.
18383 if (!mChannel) {
18384 return false;
18387 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18388 return loadInfo->GetStoragePermission() ==
18389 nsILoadInfo::StoragePermissionAllowListed;
18392 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
18393 if (!StaticPrefs::
18394 privacy_partition_always_partition_third_party_non_cookie_storage()) {
18395 return EffectiveCookiePrincipal();
18398 nsPIDOMWindowInner* inner = GetInnerWindow();
18399 if (!inner) {
18400 return NodePrincipal();
18403 // Return our cached storage principal if one exists.
18404 if (mActiveStoragePrincipal) {
18405 return mActiveStoragePrincipal;
18408 // Calling StorageAllowedForDocument will notify the ContentBlockLog. This
18409 // loads TrackingDBService.jsm, which in turn pulls in osfile.jsm, making us
18410 // fail // browser/base/content/test/performance/browser_startup.js. To avoid
18411 // that, we short-circuit the check here by allowing storage access to system
18412 // and addon principles, avoiding the test-failure.
18413 nsIPrincipal* principal = NodePrincipal();
18414 if (principal && (principal->IsSystemPrincipal() ||
18415 principal->GetIsAddonOrExpandedAddonPrincipal())) {
18416 return mActiveStoragePrincipal = NodePrincipal();
18419 auto cookieJarSettings = const_cast<Document*>(this)->CookieJarSettings();
18420 if (cookieJarSettings->GetIsOnContentBlockingAllowList()) {
18421 return mActiveStoragePrincipal = NodePrincipal();
18424 StorageAccess storageAccess = StorageAllowedForDocument(this);
18425 if (!ShouldPartitionStorage(storageAccess) ||
18426 !StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
18427 return mActiveStoragePrincipal = NodePrincipal();
18430 Unused << NS_WARN_IF(NS_FAILED(StoragePrincipalHelper::GetPrincipal(
18431 nsGlobalWindowInner::Cast(inner),
18432 StoragePrincipalHelper::eForeignPartitionedPrincipal,
18433 getter_AddRefs(mActiveStoragePrincipal))));
18434 return mActiveStoragePrincipal;
18437 nsIPrincipal* Document::EffectiveCookiePrincipal() const {
18438 nsPIDOMWindowInner* inner = GetInnerWindow();
18439 if (!inner) {
18440 return NodePrincipal();
18443 // Return our cached storage principal if one exists.
18444 if (mActiveCookiePrincipal) {
18445 return mActiveCookiePrincipal;
18448 // We use the lower-level ContentBlocking API here to ensure this
18449 // check doesn't send notifications.
18450 uint32_t rejectedReason = 0;
18451 if (ShouldAllowAccessFor(inner, GetDocumentURI(), &rejectedReason)) {
18452 return mActiveCookiePrincipal = NodePrincipal();
18455 // Let's use the storage principal only if we need to partition the cookie
18456 // jar. When the permission is granted, access will be different and the
18457 // normal principal will be used.
18458 if (ShouldPartitionStorage(rejectedReason) &&
18459 !StoragePartitioningEnabled(
18460 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
18461 return mActiveCookiePrincipal = NodePrincipal();
18464 return mActiveCookiePrincipal = mPartitionedPrincipal;
18467 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
18468 // If the document is sandboxed document or data: document, we should
18469 // get URI of the parent document.
18470 for (const Document* document = this;
18471 document && document->IsContentDocument();
18472 document = document->GetInProcessParentDocument()) {
18473 // The document URI may be about:blank even if it comes from actual web
18474 // site. Therefore, we need to check the URI of its principal.
18475 nsIPrincipal* principal = document->NodePrincipal();
18476 if (principal->GetIsNullPrincipal()) {
18477 continue;
18479 return principal;
18481 return nullptr;
18484 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
18485 mIsInitialDocumentInWindow = aIsInitialDocument;
18487 // Asynchronously tell the parent process that we are, or are no longer, the
18488 // initial document. This happens async.
18489 if (auto* wgc = GetWindowGlobalChild()) {
18490 wgc->SendSetIsInitialDocument(aIsInitialDocument);
18494 // static
18495 void Document::AddToplevelLoadingDocument(Document* aDoc) {
18496 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18497 // Currently we're interested in foreground documents only, so bail out early.
18498 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
18499 return;
18502 if (!sLoadingForegroundTopLevelContentDocument) {
18503 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
18504 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18505 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18506 if (idleScheduler) {
18507 idleScheduler->SendRunningPrioritizedOperation();
18510 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
18511 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
18515 // static
18516 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
18517 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18518 if (sLoadingForegroundTopLevelContentDocument) {
18519 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
18520 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
18521 delete sLoadingForegroundTopLevelContentDocument;
18522 sLoadingForegroundTopLevelContentDocument = nullptr;
18524 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18525 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18526 if (idleScheduler) {
18527 idleScheduler->SendPrioritizedOperationDone();
18533 ColorScheme Document::DefaultColorScheme() const {
18534 return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
18537 ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
18538 if (ShouldResistFingerprinting(RFPTarget::CSSPrefersColorScheme) &&
18539 aIgnoreRFP == IgnoreRFP::No) {
18540 return ColorScheme::Light;
18543 if (nsPresContext* pc = GetPresContext()) {
18544 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
18545 return *scheme;
18549 // NOTE(emilio): We use IsInChromeDocShell rather than IsChromeDoc
18550 // intentionally, to make chrome documents in content docshells (like about
18551 // pages) use the content color scheme.
18552 if (IsInChromeDocShell()) {
18553 return LookAndFeel::ColorSchemeForChrome();
18555 return LookAndFeel::PreferredColorSchemeForContent();
18558 bool Document::HasRecentlyStartedForegroundLoads() {
18559 if (!sLoadingForegroundTopLevelContentDocument) {
18560 return false;
18563 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
18564 ++i) {
18565 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
18566 // A page loaded in foreground could be in background now.
18567 if (!doc->IsInBackgroundWindow()) {
18568 nsPIDOMWindowInner* win = doc->GetInnerWindow();
18569 if (win) {
18570 Performance* perf = win->GetPerformance();
18571 if (perf &&
18572 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
18573 return true;
18579 // Didn't find any loading foreground documents, just clear the array.
18580 delete sLoadingForegroundTopLevelContentDocument;
18581 sLoadingForegroundTopLevelContentDocument = nullptr;
18583 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18584 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18585 if (idleScheduler) {
18586 idleScheduler->SendPrioritizedOperationDone();
18588 return false;
18591 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
18592 nsFrameLoader* aStaticCloneOf) {
18593 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
18594 clone->mElement = aElement;
18595 clone->mStaticCloneOf = aStaticCloneOf;
18598 bool Document::ShouldAvoidNativeTheme() const {
18599 return StaticPrefs::widget_non_native_theme_enabled() &&
18600 (!IsInChromeDocShell() || XRE_IsContentProcess());
18603 bool Document::UseRegularPrincipal() const {
18604 return EffectiveStoragePrincipal() == NodePrincipal();
18607 bool Document::HasThirdPartyChannel() {
18608 nsCOMPtr<nsIChannel> channel = GetChannel();
18609 if (channel) {
18610 // We assume that the channel is a third-party by default.
18611 bool thirdParty = true;
18613 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
18614 components::ThirdPartyUtil::Service();
18615 if (!thirdPartyUtil) {
18616 return thirdParty;
18619 // Check that if the channel is a third-party to its parent.
18620 nsresult rv =
18621 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
18622 if (NS_FAILED(rv)) {
18623 // Assume third-party in case of failure
18624 thirdParty = true;
18627 return thirdParty;
18630 if (mParentDocument) {
18631 return mParentDocument->HasThirdPartyChannel();
18634 return false;
18637 bool Document::IsContentInaccessibleAboutBlank() const {
18638 if (!mDocumentURI || !NS_IsAboutBlank(mDocumentURI)) {
18639 return false;
18641 nsIPrincipal* prin = NodePrincipal();
18642 if (!prin->GetIsNullPrincipal()) {
18643 return false;
18645 nsCOMPtr<nsIPrincipal> prec = prin->GetPrecursorPrincipal();
18646 if (prec) {
18647 return false;
18649 // FIXME(emilio): This doesn't account for internal about:blank documents we
18650 // do on cross-process navigations in iframes. That makes our per-document
18651 // telemetry probes not really reliable but doesn't affect the correctness of
18652 // our page probes, so it's not too terrible.
18653 BrowsingContext* bc = GetBrowsingContext();
18654 return bc && bc->IsTop() && bc->Group()->Toplevels().Length() == 1;
18657 bool Document::ShouldIncludeInTelemetry(bool aAllowExtensionURIs) {
18658 if (!IsContentDocument() && !IsResourceDoc()) {
18659 return false;
18662 if (IsContentInaccessibleAboutBlank()) {
18663 return false;
18666 nsIPrincipal* prin = NodePrincipal();
18667 if (!aAllowExtensionURIs && prin->GetIsAddonOrExpandedAddonPrincipal()) {
18668 return false;
18671 // TODO(emilio): Should this use GetIsContentPrincipal() +
18672 // GetPrecursorPrincipal() instead (accounting for add-ons separately)?
18673 if (prin->IsSystemPrincipal() || prin->SchemeIs("about") ||
18674 prin->SchemeIs("chrome") || prin->SchemeIs("resource")) {
18675 return false;
18678 return true;
18681 void Document::GetConnectedShadowRoots(
18682 nsTArray<RefPtr<ShadowRoot>>& aOut) const {
18683 AppendToArray(aOut, mComposedShadowRoots);
18686 bool Document::HasPictureInPictureChildElement() const {
18687 return mPictureInPictureChildElementCount > 0;
18690 void Document::EnableChildElementInPictureInPictureMode() {
18691 mPictureInPictureChildElementCount++;
18692 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
18695 void Document::DisableChildElementInPictureInPictureMode() {
18696 mPictureInPictureChildElementCount--;
18697 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
18700 void Document::AddMediaElementWithMSE() {
18701 if (mMediaElementWithMSECount++ == 0) {
18702 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18703 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18708 void Document::RemoveMediaElementWithMSE() {
18709 MOZ_ASSERT(mMediaElementWithMSECount > 0);
18710 if (--mMediaElementWithMSECount == 0) {
18711 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18712 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18717 void Document::UnregisterFromMemoryReportingForDataDocument() {
18718 if (!mAddedToMemoryReportingAsDataDocument) {
18719 return;
18721 mAddedToMemoryReportingAsDataDocument = false;
18722 nsIGlobalObject* global = GetScopeObject();
18723 if (global) {
18724 if (nsPIDOMWindowInner* win = global->AsInnerWindow()) {
18725 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
18726 this);
18730 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
18731 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
18732 mOOPChildrenLoading.AppendElement(aChild);
18733 if (mOOPChildrenLoading.Length() == 1) {
18734 // Let's block unload so that we're blocked from going into the BFCache
18735 // until the child has actually notified us that it has done loading.
18736 BlockOnload();
18740 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
18741 // aChild will not be in the list if nsDocLoader::Stop() was called, since
18742 // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
18743 // so we don't need to call DocLoaderIsEmpty in that case.
18744 if (mOOPChildrenLoading.RemoveElement(aChild)) {
18745 if (mOOPChildrenLoading.IsEmpty()) {
18746 UnblockOnload(false);
18748 RefPtr<nsDocLoader> docLoader(mDocumentContainer);
18749 if (docLoader) {
18750 docLoader->OOPChildrenLoadingIsEmpty();
18755 void Document::ClearOOPChildrenLoading() {
18756 nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
18757 mOOPChildrenLoading.SwapElements(oopChildrenLoading);
18758 if (!oopChildrenLoading.IsEmpty()) {
18759 UnblockOnload(false);
18763 bool Document::MayHaveDOMActivateListeners() const {
18764 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
18765 return inner->HasDOMActivateEventListeners();
18768 // If we can't get information from the window object, default to true.
18769 return true;
18772 HighlightRegistry& Document::HighlightRegistry() {
18773 if (!mHighlightRegistry) {
18774 mHighlightRegistry = MakeRefPtr<class HighlightRegistry>(this);
18776 return *mHighlightRegistry;
18779 } // namespace mozilla::dom