Bug 1881074. r=smaug
[gecko.git] / dom / base / Document.cpp
blob2078e286b3163e47341d2465a6b0904c8bfcd5e9
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/AntiTrackingUtils.h"
44 #include "mozilla/ArrayIterator.h"
45 #include "mozilla/ArrayUtils.h"
46 #include "mozilla/AsyncEventDispatcher.h"
47 #include "mozilla/Base64.h"
48 #include "mozilla/BasePrincipal.h"
49 #include "mozilla/CSSEnabledState.h"
50 #include "mozilla/ContentBlockingAllowList.h"
51 #include "mozilla/ContentBlockingNotifier.h"
52 #include "mozilla/ContentBlockingUserInteraction.h"
53 #include "mozilla/ContentPrincipal.h"
54 #include "mozilla/CycleCollectedJSContext.h"
55 #include "mozilla/DebugOnly.h"
56 #include "mozilla/ProfilerMarkers.h"
57 #include "mozilla/AttributeStyles.h"
58 #include "mozilla/DocumentStyleRootIterator.h"
59 #include "mozilla/EditorBase.h"
60 #include "mozilla/EditorCommands.h"
61 #include "mozilla/Encoding.h"
62 #include "mozilla/ErrorResult.h"
63 #include "mozilla/EventDispatcher.h"
64 #include "mozilla/EventListenerManager.h"
65 #include "mozilla/EventQueue.h"
66 #include "mozilla/EventStateManager.h"
67 #include "mozilla/ExtensionPolicyService.h"
68 #include "mozilla/FullscreenChange.h"
69 #include "mozilla/GlobalStyleSheetCache.h"
70 #include "mozilla/MappedDeclarationsBuilder.h"
71 #include "mozilla/HTMLEditor.h"
72 #include "mozilla/HoldDropJSObjects.h"
73 #include "mozilla/IdentifierMapEntry.h"
74 #include "mozilla/InputTaskManager.h"
75 #include "mozilla/IntegerRange.h"
76 #include "mozilla/InternalMutationEvent.h"
77 #include "mozilla/Likely.h"
78 #include "mozilla/Logging.h"
79 #include "mozilla/LookAndFeel.h"
80 #include "mozilla/MacroForEach.h"
81 #include "mozilla/Maybe.h"
82 #include "mozilla/MediaFeatureChange.h"
83 #include "mozilla/MediaManager.h"
84 #include "mozilla/MemoryReporting.h"
85 #include "mozilla/NullPrincipal.h"
86 #include "mozilla/OriginAttributes.h"
87 #include "mozilla/OwningNonNull.h"
88 #include "mozilla/PendingFullscreenEvent.h"
89 #include "mozilla/PermissionDelegateHandler.h"
90 #include "mozilla/PermissionManager.h"
91 #include "mozilla/Preferences.h"
92 #include "mozilla/PreloadHashKey.h"
93 #include "mozilla/PresShell.h"
94 #include "mozilla/PresShellForwards.h"
95 #include "mozilla/PresShellInlines.h"
96 #include "mozilla/PseudoStyleType.h"
97 #include "mozilla/RefCountType.h"
98 #include "mozilla/RelativeTo.h"
99 #include "mozilla/RestyleManager.h"
100 #include "mozilla/ReverseIterator.h"
101 #include "mozilla/SchedulerGroup.h"
102 #include "mozilla/ScrollTimelineAnimationTracker.h"
103 #include "mozilla/SMILAnimationController.h"
104 #include "mozilla/SMILTimeContainer.h"
105 #include "mozilla/ScopeExit.h"
106 #include "mozilla/Components.h"
107 #include "mozilla/ServoStyleConsts.h"
108 #include "mozilla/ServoTypes.h"
109 #include "mozilla/SizeOfState.h"
110 #include "mozilla/Span.h"
111 #include "mozilla/Sprintf.h"
112 #include "mozilla/StaticAnalysisFunctions.h"
113 #include "mozilla/StaticPrefs_apz.h"
114 #include "mozilla/StaticPrefs_browser.h"
115 #include "mozilla/StaticPrefs_docshell.h"
116 #include "mozilla/StaticPrefs_dom.h"
117 #include "mozilla/StaticPrefs_fission.h"
118 #include "mozilla/StaticPrefs_full_screen_api.h"
119 #include "mozilla/StaticPrefs_layout.h"
120 #include "mozilla/StaticPrefs_network.h"
121 #include "mozilla/StaticPrefs_page_load.h"
122 #include "mozilla/StaticPrefs_privacy.h"
123 #include "mozilla/StaticPrefs_security.h"
124 #include "mozilla/StaticPrefs_widget.h"
125 #include "mozilla/StaticPresData.h"
126 #include "mozilla/StorageAccess.h"
127 #include "mozilla/StoragePrincipalHelper.h"
128 #include "mozilla/StyleSheet.h"
129 #include "mozilla/Telemetry.h"
130 #include "mozilla/TelemetryScalarEnums.h"
131 #include "mozilla/TextControlElement.h"
132 #include "mozilla/TextEditor.h"
133 #include "mozilla/TypedEnumBits.h"
134 #include "mozilla/URLDecorationStripper.h"
135 #include "mozilla/URLExtraData.h"
136 #include "mozilla/Unused.h"
137 #include "mozilla/css/ImageLoader.h"
138 #include "mozilla/css/Loader.h"
139 #include "mozilla/css/Rule.h"
140 #include "mozilla/css/SheetParsingMode.h"
141 #include "mozilla/dom/AnonymousContent.h"
142 #include "mozilla/dom/BlobURLProtocolHandler.h"
143 #include "mozilla/dom/BrowserChild.h"
144 #include "mozilla/dom/BrowsingContext.h"
145 #include "mozilla/dom/BrowsingContextGroup.h"
146 #include "mozilla/dom/CanonicalBrowsingContext.h"
147 #include "mozilla/dom/CanvasRenderingContextHelper.h"
148 #include "mozilla/dom/CDATASection.h"
149 #include "mozilla/dom/CSPDictionariesBinding.h"
150 #include "mozilla/dom/ChromeObserver.h"
151 #include "mozilla/dom/ClientInfo.h"
152 #include "mozilla/dom/ClientState.h"
153 #include "mozilla/dom/Comment.h"
154 #include "mozilla/dom/ContentChild.h"
155 #include "mozilla/dom/CSSBinding.h"
156 #include "mozilla/dom/CSSCustomPropertyRegisteredEvent.h"
157 #include "mozilla/dom/DOMImplementation.h"
158 #include "mozilla/dom/DOMIntersectionObserver.h"
159 #include "mozilla/dom/DOMStringList.h"
160 #include "mozilla/dom/DocGroup.h"
161 #include "mozilla/dom/DocumentBinding.h"
162 #include "mozilla/dom/DocumentFragment.h"
163 #include "mozilla/dom/DocumentL10n.h"
164 #include "mozilla/dom/DocumentTimeline.h"
165 #include "mozilla/dom/DocumentType.h"
166 #include "mozilla/dom/ElementBinding.h"
167 #include "mozilla/dom/ErrorEvent.h"
168 #include "mozilla/dom/Event.h"
169 #include "mozilla/dom/EventListenerBinding.h"
170 #include "mozilla/dom/FailedCertSecurityInfoBinding.h"
171 #include "mozilla/dom/FeaturePolicy.h"
172 #include "mozilla/dom/FeaturePolicyUtils.h"
173 #include "mozilla/dom/FontFaceSet.h"
174 #include "mozilla/dom/FromParser.h"
175 #include "mozilla/dom/HighlightRegistry.h"
176 #include "mozilla/dom/HTMLAllCollection.h"
177 #include "mozilla/dom/HTMLBodyElement.h"
178 #include "mozilla/dom/HTMLCollectionBinding.h"
179 #include "mozilla/dom/HTMLDialogElement.h"
180 #include "mozilla/dom/HTMLFormElement.h"
181 #include "mozilla/dom/HTMLIFrameElement.h"
182 #include "mozilla/dom/HTMLImageElement.h"
183 #include "mozilla/dom/HTMLInputElement.h"
184 #include "mozilla/dom/HTMLLinkElement.h"
185 #include "mozilla/dom/HTMLMediaElement.h"
186 #include "mozilla/dom/HTMLMetaElement.h"
187 #include "mozilla/dom/HTMLSharedElement.h"
188 #include "mozilla/dom/HTMLTextAreaElement.h"
189 #include "mozilla/dom/ImageTracker.h"
190 #include "mozilla/dom/InspectorUtils.h"
191 #include "mozilla/dom/Link.h"
192 #include "mozilla/dom/MediaQueryList.h"
193 #include "mozilla/dom/MediaSource.h"
194 #include "mozilla/dom/MutationObservers.h"
195 #include "mozilla/dom/NameSpaceConstants.h"
196 #include "mozilla/dom/Navigator.h"
197 #include "mozilla/dom/NetErrorInfoBinding.h"
198 #include "mozilla/dom/NodeInfo.h"
199 #include "mozilla/dom/NodeIterator.h"
200 #include "mozilla/dom/PContentChild.h"
201 #include "mozilla/dom/PWindowGlobalChild.h"
202 #include "mozilla/dom/PageTransitionEvent.h"
203 #include "mozilla/dom/PageTransitionEventBinding.h"
204 #include "mozilla/dom/Performance.h"
205 #include "mozilla/dom/PermissionMessageUtils.h"
206 #include "mozilla/dom/PostMessageEvent.h"
207 #include "mozilla/dom/ProcessingInstruction.h"
208 #include "mozilla/dom/Promise.h"
209 #include "mozilla/dom/PromiseNativeHandler.h"
210 #include "mozilla/dom/ResizeObserver.h"
211 #include "mozilla/dom/RustTypes.h"
212 #include "mozilla/dom/SVGElement.h"
213 #include "mozilla/dom/SVGDocument.h"
214 #include "mozilla/dom/SVGSVGElement.h"
215 #include "mozilla/dom/SVGUseElement.h"
216 #include "mozilla/dom/ScriptLoader.h"
217 #include "mozilla/dom/ScriptSettings.h"
218 #include "mozilla/dom/Selection.h"
219 #include "mozilla/dom/ServiceWorkerContainer.h"
220 #include "mozilla/dom/ServiceWorkerDescriptor.h"
221 #include "mozilla/dom/ServiceWorkerManager.h"
222 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
223 #include "mozilla/dom/ShadowRoot.h"
224 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
225 #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
226 #include "mozilla/dom/StyleSheetList.h"
227 #include "mozilla/dom/StyleSheetRemovedEvent.h"
228 #include "mozilla/dom/StyleSheetRemovedEventBinding.h"
229 #include "mozilla/dom/TimeoutManager.h"
230 #include "mozilla/dom/ToggleEvent.h"
231 #include "mozilla/dom/Touch.h"
232 #include "mozilla/dom/TouchEvent.h"
233 #include "mozilla/dom/TreeOrderedArrayInlines.h"
234 #include "mozilla/dom/TreeWalker.h"
235 #include "mozilla/dom/URL.h"
236 #include "mozilla/dom/UseCounterMetrics.h"
237 #include "mozilla/dom/UserActivation.h"
238 #include "mozilla/dom/WakeLockJS.h"
239 #include "mozilla/dom/WakeLockSentinel.h"
240 #include "mozilla/dom/WindowBinding.h"
241 #include "mozilla/dom/WindowContext.h"
242 #include "mozilla/dom/WindowGlobalChild.h"
243 #include "mozilla/dom/WindowProxyHolder.h"
244 #include "mozilla/dom/WorkerDocumentListener.h"
245 #include "mozilla/dom/XPathEvaluator.h"
246 #include "mozilla/dom/XPathExpression.h"
247 #include "mozilla/dom/nsCSPContext.h"
248 #include "mozilla/dom/nsCSPUtils.h"
249 #include "mozilla/extensions/WebExtensionPolicy.h"
250 #include "mozilla/fallible.h"
251 #include "mozilla/gfx/BaseCoord.h"
252 #include "mozilla/gfx/BaseSize.h"
253 #include "mozilla/gfx/Coord.h"
254 #include "mozilla/gfx/Point.h"
255 #include "mozilla/gfx/ScaleFactor.h"
256 #include "mozilla/glean/GleanMetrics.h"
257 #include "mozilla/intl/LocaleService.h"
258 #include "mozilla/ipc/IdleSchedulerChild.h"
259 #include "mozilla/ipc/MessageChannel.h"
260 #include "mozilla/net/ChannelEventQueue.h"
261 #include "mozilla/net/CookieJarSettings.h"
262 #include "mozilla/net/NeckoChannelParams.h"
263 #include "mozilla/net/RequestContextService.h"
264 #include "nsAboutProtocolUtils.h"
265 #include "nsAlgorithm.h"
266 #include "nsAttrValue.h"
267 #include "nsAttrValueInlines.h"
268 #include "nsBaseHashtable.h"
269 #include "nsBidiUtils.h"
270 #include "nsCRT.h"
271 #include "nsCSSPropertyID.h"
272 #include "nsCSSProps.h"
273 #include "nsCSSPseudoElements.h"
274 #include "nsCSSRendering.h"
275 #include "nsCanvasFrame.h"
276 #include "nsCaseTreatment.h"
277 #include "nsCharsetSource.h"
278 #include "nsCommandManager.h"
279 #include "nsCommandParams.h"
280 #include "nsComponentManagerUtils.h"
281 #include "nsContentCreatorFunctions.h"
282 #include "nsContentList.h"
283 #include "nsContentPermissionHelper.h"
284 #include "nsContentSecurityUtils.h"
285 #include "nsContentUtils.h"
286 #include "nsCoord.h"
287 #include "nsCycleCollectionNoteChild.h"
288 #include "nsCycleCollectionTraversalCallback.h"
289 #include "nsDOMAttributeMap.h"
290 #include "nsDOMCaretPosition.h"
291 #include "nsDOMNavigationTiming.h"
292 #include "nsDOMString.h"
293 #include "nsDeviceContext.h"
294 #include "nsDocShell.h"
295 #include "nsDocShellLoadTypes.h"
296 #include "nsEffectiveTLDService.h"
297 #include "nsError.h"
298 #include "nsEscape.h"
299 #include "nsFocusManager.h"
300 #include "nsFrameLoader.h"
301 #include "nsFrameLoaderOwner.h"
302 #include "nsGenericHTMLElement.h"
303 #include "nsGlobalWindowInner.h"
304 #include "nsGlobalWindowOuter.h"
305 #include "nsHTMLDocument.h"
306 #include "nsHtml5Module.h"
307 #include "nsHtml5Parser.h"
308 #include "nsHtml5TreeOpExecutor.h"
309 #include "nsIAsyncShutdown.h"
310 #include "nsIAuthPrompt.h"
311 #include "nsIAuthPrompt2.h"
312 #include "nsIBFCacheEntry.h"
313 #include "nsIBaseWindow.h"
314 #include "nsIBrowserChild.h"
315 #include "nsIBrowserUsage.h"
316 #include "nsICSSLoaderObserver.h"
317 #include "nsICategoryManager.h"
318 #include "nsICertOverrideService.h"
319 #include "nsIContent.h"
320 #include "nsIContentInlines.h"
321 #include "nsIContentPolicy.h"
322 #include "nsIContentSecurityPolicy.h"
323 #include "nsIContentSink.h"
324 #include "nsICookieJarSettings.h"
325 #include "nsICookieService.h"
326 #include "nsIDOMXULCommandDispatcher.h"
327 #include "nsIDocShell.h"
328 #include "nsIDocShellTreeItem.h"
329 #include "nsIDocumentActivity.h"
330 #include "nsIDocumentEncoder.h"
331 #include "nsIDocumentLoader.h"
332 #include "nsIDocumentLoaderFactory.h"
333 #include "nsIDocumentObserver.h"
334 #include "nsIDNSService.h"
335 #include "nsIEditingSession.h"
336 #include "nsIEditor.h"
337 #include "nsIEffectiveTLDService.h"
338 #include "nsIFile.h"
339 #include "nsIFileChannel.h"
340 #include "nsIFrame.h"
341 #include "nsIGlobalObject.h"
342 #include "nsIHTMLCollection.h"
343 #include "nsIHttpChannel.h"
344 #include "nsIHttpChannelInternal.h"
345 #include "nsIIOService.h"
346 #include "nsIImageLoadingContent.h"
347 #include "nsIInlineSpellChecker.h"
348 #include "nsIInputStreamChannel.h"
349 #include "nsIInterfaceRequestorUtils.h"
350 #include "nsILayoutHistoryState.h"
351 #include "nsIMultiPartChannel.h"
352 #include "nsIMutationObserver.h"
353 #include "nsINSSErrorsService.h"
354 #include "nsINamed.h"
355 #include "nsINodeList.h"
356 #include "nsIObjectLoadingContent.h"
357 #include "nsIObserverService.h"
358 #include "nsIPermission.h"
359 #include "nsIPrompt.h"
360 #include "nsIPropertyBag2.h"
361 #include "nsIPublicKeyPinningService.h"
362 #include "nsIReferrerInfo.h"
363 #include "nsIRefreshURI.h"
364 #include "nsIRequest.h"
365 #include "nsIRequestContext.h"
366 #include "nsIRunnable.h"
367 #include "nsISHEntry.h"
368 #include "nsIScriptElement.h"
369 #include "nsIScriptError.h"
370 #include "nsIScriptGlobalObject.h"
371 #include "nsIScriptSecurityManager.h"
372 #include "nsISecurityConsoleMessage.h"
373 #include "nsISelectionController.h"
374 #include "nsISerialEventTarget.h"
375 #include "nsISimpleEnumerator.h"
376 #include "nsISiteSecurityService.h"
377 #include "nsISocketProvider.h"
378 #include "nsISpeculativeConnect.h"
379 #include "nsIStructuredCloneContainer.h"
380 #include "nsIThread.h"
381 #include "nsITimedChannel.h"
382 #include "nsITimer.h"
383 #include "nsITransportSecurityInfo.h"
384 #include "nsIURIMutator.h"
385 #include "nsIVariant.h"
386 #include "nsIWeakReference.h"
387 #include "nsIWebNavigation.h"
388 #include "nsIWidget.h"
389 #include "nsIX509Cert.h"
390 #include "nsIX509CertValidity.h"
391 #include "nsIXMLContentSink.h"
392 #include "nsIHTMLContentSink.h"
393 #include "nsIXULRuntime.h"
394 #include "nsImageLoadingContent.h"
395 #include "nsImportModule.h"
396 #include "nsLanguageAtomService.h"
397 #include "nsLayoutUtils.h"
398 #include "nsMimeTypes.h"
399 #include "nsNetCID.h"
400 #include "nsNetUtil.h"
401 #include "nsNodeInfoManager.h"
402 #include "nsObjectLoadingContent.h"
403 #include "nsPIDOMWindowInlines.h"
404 #include "nsPIWindowRoot.h"
405 #include "nsPoint.h"
406 #include "nsPointerHashKeys.h"
407 #include "nsPresContext.h"
408 #include "nsQueryFrame.h"
409 #include "nsQueryObject.h"
410 #include "nsRange.h"
411 #include "nsRect.h"
412 #include "nsRefreshDriver.h"
413 #include "nsSandboxFlags.h"
414 #include "nsSerializationHelper.h"
415 #include "nsServiceManagerUtils.h"
416 #include "nsStringFlags.h"
417 #include "nsStyleUtil.h"
418 #include "nsStringIterator.h"
419 #include "nsStyleSheetService.h"
420 #include "nsStyleStruct.h"
421 #include "nsTextNode.h"
422 #include "nsUnicharUtils.h"
423 #include "nsWrapperCache.h"
424 #include "nsWrapperCacheInlines.h"
425 #include "nsXPCOMCID.h"
426 #include "nsXULAppAPI.h"
427 #include "prthread.h"
428 #include "prtime.h"
429 #include "prtypes.h"
430 #include "xpcpublic.h"
432 // XXX Must be included after mozilla/Encoding.h
433 #include "encoding_rs.h"
435 #include "mozilla/dom/XULBroadcastManager.h"
436 #include "mozilla/dom/XULPersist.h"
437 #include "nsIAppWindow.h"
438 #include "nsXULPrototypeDocument.h"
439 #include "nsXULCommandDispatcher.h"
440 #include "nsXULPopupManager.h"
441 #include "nsIDocShellTreeOwner.h"
443 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
444 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
445 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
446 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
448 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
450 mozilla::LazyLogModule gPageCacheLog("PageCache");
451 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
452 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
453 mozilla::LazyLogModule gUseCountersLog("UseCounters");
455 namespace mozilla {
456 namespace dom {
458 class Document::HeaderData {
459 public:
460 HeaderData(nsAtom* aField, const nsAString& aData)
461 : mField(aField), mData(aData) {}
463 ~HeaderData() {
464 // Delete iteratively to avoid blowing up the stack, though it shouldn't
465 // happen in practice.
466 UniquePtr<HeaderData> next = std::move(mNext);
467 while (next) {
468 next = std::move(next->mNext);
472 RefPtr<nsAtom> mField;
473 nsString mData;
474 UniquePtr<HeaderData> mNext;
477 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
478 nullptr;
480 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
481 static LazyLogModule gCspPRLog("CSP");
482 LazyLogModule gUserInteractionPRLog("UserInteraction");
484 static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
485 nsIHttpChannel** aHttpChannel) {
486 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
487 if (httpChannel) {
488 httpChannel.forget(aHttpChannel);
489 return NS_OK;
492 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
493 if (!multipart) {
494 *aHttpChannel = nullptr;
495 return NS_OK;
498 nsCOMPtr<nsIChannel> baseChannel;
499 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
500 if (NS_WARN_IF(NS_FAILED(rv))) {
501 return rv;
504 httpChannel = do_QueryInterface(baseChannel);
505 httpChannel.forget(aHttpChannel);
507 return NS_OK;
510 } // namespace dom
512 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
514 IdentifierMapEntry::IdentifierMapEntry(
515 const IdentifierMapEntry::DependentAtomOrString* aKey)
516 : mKey(aKey ? *aKey : nullptr) {}
518 void IdentifierMapEntry::Traverse(
519 nsCycleCollectionTraversalCallback* aCallback) {
520 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
521 "mIdentifierMap mNameContentList");
522 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
524 if (mImageElement) {
525 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
526 "mIdentifierMap mImageElement element");
527 nsIContent* imageElement = mImageElement;
528 aCallback->NoteXPCOMChild(imageElement);
532 bool IdentifierMapEntry::IsEmpty() {
533 return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
534 !mImageElement;
537 bool IdentifierMapEntry::HasNameElement() const {
538 return mNameContentList && mNameContentList->Length() != 0;
541 void IdentifierMapEntry::AddContentChangeCallback(
542 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
543 if (!mChangeCallbacks) {
544 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
547 ChangeCallback cc = {aCallback, aData, aForImage};
548 mChangeCallbacks->PutEntry(cc);
551 void IdentifierMapEntry::RemoveContentChangeCallback(
552 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
553 if (!mChangeCallbacks) return;
554 ChangeCallback cc = {aCallback, aData, aForImage};
555 mChangeCallbacks->RemoveEntry(cc);
556 if (mChangeCallbacks->Count() == 0) {
557 mChangeCallbacks = nullptr;
561 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
562 Element* aNewElement,
563 bool aImageOnly) {
564 if (!mChangeCallbacks) return;
566 for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) {
567 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
568 // Don't fire image changes for non-image observers, and don't fire element
569 // changes for image observers when an image override is active.
570 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
571 continue;
574 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
575 iter.Remove();
580 void IdentifierMapEntry::AddIdElement(Element* aElement) {
581 MOZ_ASSERT(aElement, "Must have element");
582 MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
584 size_t index = mIdContentList.Insert(*aElement);
585 if (index == 0) {
586 Element* oldElement = mIdContentList->SafeElementAt(1);
587 FireChangeCallbacks(oldElement, aElement);
591 void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
592 MOZ_ASSERT(aElement, "Missing element");
594 // This should only be called while the document is in an update.
595 // Assertions near the call to this method guarantee this.
597 // This could fire in OOM situations
598 // Only assert this in HTML documents for now as XUL does all sorts of weird
599 // crap.
600 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
601 mIdContentList->Contains(aElement),
602 "Removing id entry that doesn't exist");
604 // XXXbz should this ever Compact() I guess when all the content is gone
605 // we'll just get cleaned up in the natural order of things...
606 Element* currentElement = mIdContentList->SafeElementAt(0);
607 mIdContentList.RemoveElement(*aElement);
608 if (currentElement == aElement) {
609 FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
613 void IdentifierMapEntry::SetImageElement(Element* aElement) {
614 Element* oldElement = GetImageIdElement();
615 mImageElement = aElement;
616 Element* newElement = GetImageIdElement();
617 if (oldElement != newElement) {
618 FireChangeCallbacks(oldElement, newElement, true);
622 void IdentifierMapEntry::ClearAndNotify() {
623 Element* currentElement = mIdContentList->SafeElementAt(0);
624 mIdContentList.Clear();
625 if (currentElement) {
626 FireChangeCallbacks(currentElement, nullptr);
628 mNameContentList = nullptr;
629 if (mImageElement) {
630 SetImageElement(nullptr);
632 mChangeCallbacks = nullptr;
635 namespace dom {
637 class SimpleHTMLCollection final : public nsSimpleContentList,
638 public nsIHTMLCollection {
639 public:
640 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
642 NS_DECL_ISUPPORTS_INHERITED
644 virtual nsINode* GetParentObject() override {
645 return nsSimpleContentList::GetParentObject();
647 virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
648 virtual Element* GetElementAt(uint32_t aIndex) override {
649 return mElements.SafeElementAt(aIndex)->AsElement();
652 virtual Element* GetFirstNamedElement(const nsAString& aName,
653 bool& aFound) override {
654 aFound = false;
655 RefPtr<nsAtom> name = NS_Atomize(aName);
656 for (uint32_t i = 0; i < mElements.Length(); i++) {
657 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
658 Element* element = mElements[i]->AsElement();
659 if (element->GetID() == name ||
660 (element->HasName() &&
661 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
662 aFound = true;
663 return element;
666 return nullptr;
669 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
670 AutoTArray<nsAtom*, 8> atoms;
671 for (uint32_t i = 0; i < mElements.Length(); i++) {
672 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
673 Element* element = mElements[i]->AsElement();
675 nsAtom* id = element->GetID();
676 MOZ_ASSERT(id != nsGkAtoms::_empty);
677 if (id && !atoms.Contains(id)) {
678 atoms.AppendElement(id);
681 if (element->HasName()) {
682 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
683 MOZ_ASSERT(name && name != nsGkAtoms::_empty);
684 if (name && !atoms.Contains(name)) {
685 atoms.AppendElement(name);
690 nsString* names = aNames.AppendElements(atoms.Length());
691 for (uint32_t i = 0; i < atoms.Length(); i++) {
692 atoms[i]->ToString(names[i]);
696 virtual JSObject* GetWrapperPreserveColorInternal() override {
697 return nsWrapperCache::GetWrapperPreserveColor();
699 virtual void PreserveWrapperInternal(
700 nsISupports* aScriptObjectHolder) override {
701 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
703 virtual JSObject* WrapObject(JSContext* aCx,
704 JS::Handle<JSObject*> aGivenProto) override {
705 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
708 using nsBaseContentList::Item;
710 private:
711 virtual ~SimpleHTMLCollection() = default;
714 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
715 nsIHTMLCollection)
717 } // namespace dom
719 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
720 if (!mNameContentList) {
721 mNameContentList = new dom::SimpleHTMLCollection(aNode);
724 mNameContentList->AppendElement(aElement);
727 void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
728 if (mNameContentList) {
729 mNameContentList->RemoveElement(aElement);
733 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const {
734 Element* idElement = GetIdElement();
735 return idElement &&
736 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
739 size_t IdentifierMapEntry::SizeOfExcludingThis(
740 MallocSizeOf aMallocSizeOf) const {
741 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
744 // Helper structs for the content->subdoc map
746 class SubDocMapEntry : public PLDHashEntryHdr {
747 public:
748 // Both of these are strong references
749 dom::Element* mKey; // must be first, to look like PLDHashEntryStub
750 dom::Document* mSubDocument;
753 class OnloadBlocker final : public nsIRequest {
754 public:
755 OnloadBlocker() = default;
757 NS_DECL_ISUPPORTS
758 NS_DECL_NSIREQUEST
760 private:
761 ~OnloadBlocker() = default;
764 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
766 NS_IMETHODIMP
767 OnloadBlocker::GetName(nsACString& aResult) {
768 aResult.AssignLiteral("about:document-onload-blocker");
769 return NS_OK;
772 NS_IMETHODIMP
773 OnloadBlocker::IsPending(bool* _retval) {
774 *_retval = true;
775 return NS_OK;
778 NS_IMETHODIMP
779 OnloadBlocker::GetStatus(nsresult* status) {
780 *status = NS_OK;
781 return NS_OK;
784 NS_IMETHODIMP OnloadBlocker::SetCanceledReason(const nsACString& aReason) {
785 return SetCanceledReasonImpl(aReason);
788 NS_IMETHODIMP OnloadBlocker::GetCanceledReason(nsACString& aReason) {
789 return GetCanceledReasonImpl(aReason);
792 NS_IMETHODIMP OnloadBlocker::CancelWithReason(nsresult aStatus,
793 const nsACString& aReason) {
794 return CancelWithReasonImpl(aStatus, aReason);
796 NS_IMETHODIMP
797 OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
798 NS_IMETHODIMP
799 OnloadBlocker::Suspend(void) { return NS_OK; }
800 NS_IMETHODIMP
801 OnloadBlocker::Resume(void) { return NS_OK; }
803 NS_IMETHODIMP
804 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
805 *aLoadGroup = nullptr;
806 return NS_OK;
809 NS_IMETHODIMP
810 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
812 NS_IMETHODIMP
813 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
814 *aLoadFlags = nsIRequest::LOAD_NORMAL;
815 return NS_OK;
818 NS_IMETHODIMP
819 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
820 return GetTRRModeImpl(aTRRMode);
823 NS_IMETHODIMP
824 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
825 return SetTRRModeImpl(aTRRMode);
828 NS_IMETHODIMP
829 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
831 // ==================================================================
833 namespace dom {
835 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
837 Document* ExternalResourceMap::RequestResource(
838 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
839 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
840 // If we ever start allowing non-same-origin loads here, we might need to do
841 // something interesting with aRequestingPrincipal even for the hashtable
842 // gets.
843 MOZ_ASSERT(aURI, "Must have a URI");
844 MOZ_ASSERT(aRequestingNode, "Must have a node");
845 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
846 *aPendingLoad = nullptr;
847 if (mHaveShutDown) {
848 return nullptr;
851 // First, make sure we strip the ref from aURI.
852 nsCOMPtr<nsIURI> clone;
853 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
854 if (NS_FAILED(rv) || !clone) {
855 return nullptr;
858 ExternalResource* resource;
859 mMap.Get(clone, &resource);
860 if (resource) {
861 return resource->mDocument;
864 bool loadStartSucceeded =
865 mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
866 if (!loadEntry) {
867 loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
869 if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
870 aRequestingNode))) {
871 return false;
875 RefPtr<PendingLoad> load(loadEntry.Data());
876 load.forget(aPendingLoad);
877 return true;
879 if (!loadStartSucceeded) {
880 // Make sure we don't thrash things by trying this load again, since
881 // chances are it failed for good reasons (security check, etc).
882 // This must be done outside the WithEntryHandle functor, as it accesses
883 // mPendingLoads.
884 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
887 return nullptr;
890 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
891 nsTArray<RefPtr<Document>> docs(mMap.Count());
892 for (const auto& entry : mMap.Values()) {
893 if (Document* doc = entry->mDocument) {
894 docs.AppendElement(doc);
898 for (auto& doc : docs) {
899 if (aCallback(*doc) == CallState::Stop) {
900 return;
905 void ExternalResourceMap::Traverse(
906 nsCycleCollectionTraversalCallback* aCallback) const {
907 // mPendingLoads will get cleared out as the requests complete, so
908 // no need to worry about those here.
909 for (const auto& entry : mMap) {
910 ExternalResourceMap::ExternalResource* resource = entry.GetWeak();
912 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
913 "mExternalResourceMap.mMap entry"
914 "->mDocument");
915 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
917 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
918 "mExternalResourceMap.mMap entry"
919 "->mViewer");
920 aCallback->NoteXPCOMChild(resource->mViewer);
922 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
923 "mExternalResourceMap.mMap entry"
924 "->mLoadGroup");
925 aCallback->NoteXPCOMChild(resource->mLoadGroup);
929 void ExternalResourceMap::HideViewers() {
930 for (const auto& entry : mMap) {
931 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
932 if (viewer) {
933 viewer->Hide();
938 void ExternalResourceMap::ShowViewers() {
939 for (const auto& entry : mMap) {
940 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
941 if (viewer) {
942 viewer->Show();
947 void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
948 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
950 if (aFromDoc->IsShowing()) {
951 aToDoc->OnPageShow(true, nullptr);
955 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
956 nsIDocumentViewer* aViewer,
957 nsILoadGroup* aLoadGroup,
958 Document* aDisplayDocument) {
959 MOZ_ASSERT(aURI, "Unexpected call");
960 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
961 "Must have both or neither");
963 RefPtr<PendingLoad> load;
964 mPendingLoads.Remove(aURI, getter_AddRefs(load));
966 nsresult rv = NS_OK;
968 nsCOMPtr<Document> doc;
969 if (aViewer) {
970 doc = aViewer->GetDocument();
971 NS_ASSERTION(doc, "Must have a document");
973 doc->SetDisplayDocument(aDisplayDocument);
975 // Make sure that hiding our viewer will tear down its presentation.
976 aViewer->SetSticky(false);
978 rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
979 if (NS_SUCCEEDED(rv)) {
980 rv = aViewer->Open(nullptr, nullptr);
983 if (NS_FAILED(rv)) {
984 doc = nullptr;
985 aViewer = nullptr;
986 aLoadGroup = nullptr;
990 ExternalResource* newResource =
991 mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get();
993 newResource->mDocument = doc;
994 newResource->mViewer = aViewer;
995 newResource->mLoadGroup = aLoadGroup;
996 if (doc) {
997 if (nsPresContext* pc = doc->GetPresContext()) {
998 pc->RecomputeBrowsingContextDependentData();
1000 TransferShowingState(aDisplayDocument, doc);
1003 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
1004 for (uint32_t i = 0; i < obs.Length(); ++i) {
1005 obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
1006 nullptr);
1009 return rv;
1012 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
1013 nsIRequestObserver)
1015 NS_IMETHODIMP
1016 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
1017 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
1018 if (map.HaveShutDown()) {
1019 return NS_BINDING_ABORTED;
1022 nsCOMPtr<nsIDocumentViewer> viewer;
1023 nsCOMPtr<nsILoadGroup> loadGroup;
1024 nsresult rv =
1025 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
1027 // Make sure to do this no matter what
1028 nsresult rv2 =
1029 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
1030 if (NS_FAILED(rv)) {
1031 return rv;
1033 if (NS_FAILED(rv2)) {
1034 mTargetListener = nullptr;
1035 return rv2;
1038 return mTargetListener->OnStartRequest(aRequest);
1041 nsresult ExternalResourceMap::PendingLoad::SetupViewer(
1042 nsIRequest* aRequest, nsIDocumentViewer** aViewer,
1043 nsILoadGroup** aLoadGroup) {
1044 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
1045 *aViewer = nullptr;
1046 *aLoadGroup = nullptr;
1048 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
1049 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
1051 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
1052 if (httpChannel) {
1053 bool requestSucceeded;
1054 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
1055 !requestSucceeded) {
1056 // Bail out on this load, since it looks like we have an HTTP error page
1057 return NS_BINDING_ABORTED;
1061 nsAutoCString type;
1062 chan->GetContentType(type);
1064 nsCOMPtr<nsILoadGroup> loadGroup;
1065 chan->GetLoadGroup(getter_AddRefs(loadGroup));
1067 // Give this document its own loadgroup
1068 nsCOMPtr<nsILoadGroup> newLoadGroup =
1069 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1070 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
1071 newLoadGroup->SetLoadGroup(loadGroup);
1073 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1074 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1076 nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
1077 new LoadgroupCallbacks(callbacks);
1078 newLoadGroup->SetNotificationCallbacks(newCallbacks);
1080 // This is some serious hackery cribbed from docshell
1081 nsCOMPtr<nsICategoryManager> catMan =
1082 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
1083 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
1084 nsCString contractId;
1085 nsresult rv =
1086 catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
1087 NS_ENSURE_SUCCESS(rv, rv);
1088 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
1089 do_GetService(contractId.get());
1090 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
1092 nsCOMPtr<nsIDocumentViewer> viewer;
1093 nsCOMPtr<nsIStreamListener> listener;
1094 rv = docLoaderFactory->CreateInstance(
1095 "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
1096 getter_AddRefs(listener), getter_AddRefs(viewer));
1097 NS_ENSURE_SUCCESS(rv, rv);
1098 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
1100 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
1101 if (!parser) {
1102 /// We don't want to deal with the various fake documents yet
1103 return NS_ERROR_NOT_IMPLEMENTED;
1106 // We can't handle HTML and other weird things here yet.
1107 nsIContentSink* sink = parser->GetContentSink();
1108 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
1109 if (!xmlSink) {
1110 return NS_ERROR_NOT_IMPLEMENTED;
1113 listener.swap(mTargetListener);
1114 viewer.forget(aViewer);
1115 newLoadGroup.forget(aLoadGroup);
1116 return NS_OK;
1119 NS_IMETHODIMP
1120 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
1121 nsIInputStream* aStream,
1122 uint64_t aOffset,
1123 uint32_t aCount) {
1124 // mTargetListener might be null if SetupViewer or AddExternalResource failed.
1125 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
1126 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
1127 return NS_BINDING_ABORTED;
1129 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1132 NS_IMETHODIMP
1133 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
1134 nsresult aStatus) {
1135 // mTargetListener might be null if SetupViewer or AddExternalResource failed
1136 if (mTargetListener) {
1137 nsCOMPtr<nsIStreamListener> listener;
1138 mTargetListener.swap(listener);
1139 return listener->OnStopRequest(aRequest, aStatus);
1142 return NS_OK;
1145 nsresult ExternalResourceMap::PendingLoad::StartLoad(
1146 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
1147 MOZ_ASSERT(aURI, "Must have a URI");
1148 MOZ_ASSERT(aRequestingNode, "Must have a node");
1149 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
1151 nsCOMPtr<nsILoadGroup> loadGroup =
1152 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
1154 nsresult rv = NS_OK;
1155 nsCOMPtr<nsIChannel> channel;
1156 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
1157 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1158 nsIContentPolicy::TYPE_OTHER,
1159 nullptr, // aPerformanceStorage
1160 loadGroup);
1161 NS_ENSURE_SUCCESS(rv, rv);
1163 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1164 if (httpChannel) {
1165 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
1166 Unused << NS_WARN_IF(NS_FAILED(rv));
1169 mURI = aURI;
1171 return channel->AsyncOpen(this);
1174 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
1175 nsIInterfaceRequestor)
1177 #define IMPL_SHIM(_i) \
1178 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
1180 IMPL_SHIM(nsILoadContext)
1181 IMPL_SHIM(nsIProgressEventSink)
1182 IMPL_SHIM(nsIChannelEventSink)
1184 #undef IMPL_SHIM
1186 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
1188 #define TRY_SHIM(_i) \
1189 PR_BEGIN_MACRO \
1190 if (IID_IS(_i)) { \
1191 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
1192 if (!real) { \
1193 return NS_NOINTERFACE; \
1195 nsCOMPtr<_i> shim = new _i##Shim(this, real); \
1196 shim.forget(aSink); \
1197 return NS_OK; \
1199 PR_END_MACRO
1201 NS_IMETHODIMP
1202 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
1203 void** aSink) {
1204 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
1205 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
1206 return mCallbacks->GetInterface(aIID, aSink);
1209 *aSink = nullptr;
1211 TRY_SHIM(nsILoadContext);
1212 TRY_SHIM(nsIProgressEventSink);
1213 TRY_SHIM(nsIChannelEventSink);
1215 return NS_NOINTERFACE;
1218 #undef TRY_SHIM
1219 #undef IID_IS
1221 ExternalResourceMap::ExternalResource::~ExternalResource() {
1222 if (mViewer) {
1223 mViewer->Close(nullptr);
1224 mViewer->Destroy();
1228 // ==================================================================
1229 // =
1230 // ==================================================================
1232 // If we ever have an nsIDocumentObserver notification for stylesheet title
1233 // changes we should update the list from that instead of overriding
1234 // EnsureFresh.
1235 class DOMStyleSheetSetList final : public DOMStringList {
1236 public:
1237 explicit DOMStyleSheetSetList(Document* aDocument);
1239 void Disconnect() { mDocument = nullptr; }
1241 virtual void EnsureFresh() override;
1243 protected:
1244 Document* mDocument; // Our document; weak ref. It'll let us know if it
1245 // dies.
1248 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
1249 : mDocument(aDocument) {
1250 NS_ASSERTION(mDocument, "Must have document!");
1253 void DOMStyleSheetSetList::EnsureFresh() {
1254 MOZ_ASSERT(NS_IsMainThread());
1256 mNames.Clear();
1258 if (!mDocument) {
1259 return; // Spec says "no exceptions", and we have no style sets if we have
1260 // no document, for sure
1263 size_t count = mDocument->SheetCount();
1264 nsAutoString title;
1265 for (size_t index = 0; index < count; index++) {
1266 StyleSheet* sheet = mDocument->SheetAt(index);
1267 NS_ASSERTION(sheet, "Null sheet in sheet list!");
1268 sheet->GetTitle(title);
1269 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
1270 return;
1275 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1277 // ==================================================================
1278 // =
1279 // ==================================================================
1281 Document::InternalCommandDataHashtable*
1282 Document::sInternalCommandDataHashtable = nullptr;
1284 // static
1285 void Document::Shutdown() {
1286 if (sInternalCommandDataHashtable) {
1287 sInternalCommandDataHashtable->Clear();
1288 delete sInternalCommandDataHashtable;
1289 sInternalCommandDataHashtable = nullptr;
1293 Document::Document(const char* aContentType)
1294 : nsINode(nullptr),
1295 DocumentOrShadowRoot(this),
1296 mCharacterSet(WINDOWS_1252_ENCODING),
1297 mCharacterSetSource(0),
1298 mParentDocument(nullptr),
1299 mCachedRootElement(nullptr),
1300 mNodeInfoManager(nullptr),
1301 #ifdef DEBUG
1302 mStyledLinksCleared(false),
1303 #endif
1304 mCachedStateObjectValid(false),
1305 mBlockAllMixedContent(false),
1306 mBlockAllMixedContentPreloads(false),
1307 mUpgradeInsecureRequests(false),
1308 mUpgradeInsecurePreloads(false),
1309 mDevToolsWatchingDOMMutations(false),
1310 mBidiEnabled(false),
1311 mMayNeedFontPrefsUpdate(true),
1312 mMathMLEnabled(false),
1313 mIsInitialDocumentInWindow(false),
1314 mIsEverInitialDocumentInWindow(false),
1315 mIgnoreDocGroupMismatches(false),
1316 mLoadedAsData(false),
1317 mAddedToMemoryReportingAsDataDocument(false),
1318 mMayStartLayout(true),
1319 mHaveFiredTitleChange(false),
1320 mIsShowing(false),
1321 mVisible(true),
1322 mRemovedFromDocShell(false),
1323 // mAllowDNSPrefetch starts true, so that we can always reliably && it
1324 // with various values that might disable it. Since we never prefetch
1325 // unless we get a window, and in that case the docshell value will get
1326 // &&-ed in, this is safe.
1327 mAllowDNSPrefetch(true),
1328 mIsStaticDocument(false),
1329 mCreatingStaticClone(false),
1330 mHasPrintCallbacks(false),
1331 mInUnlinkOrDeletion(false),
1332 mHasHadScriptHandlingObject(false),
1333 mIsBeingUsedAsImage(false),
1334 mChromeRulesEnabled(false),
1335 mInChromeDocShell(false),
1336 mIsSyntheticDocument(false),
1337 mHasLinksToUpdateRunnable(false),
1338 mFlushingPendingLinkUpdates(false),
1339 mMayHaveDOMMutationObservers(false),
1340 mMayHaveAnimationObservers(false),
1341 mHasCSPDeliveredThroughHeader(false),
1342 mBFCacheDisallowed(false),
1343 mHasHadDefaultView(false),
1344 mStyleSheetChangeEventsEnabled(false),
1345 mDevToolsAnonymousAndShadowEventsEnabled(false),
1346 mIsSrcdocDocument(false),
1347 mHasDisplayDocument(false),
1348 mFontFaceSetDirty(true),
1349 mDidFireDOMContentLoaded(true),
1350 mFrameRequestCallbacksScheduled(false),
1351 mIsTopLevelContentDocument(false),
1352 mIsContentDocument(false),
1353 mDidCallBeginLoad(false),
1354 mEncodingMenuDisabled(false),
1355 mLinksEnabled(true),
1356 mIsSVGGlyphsDocument(false),
1357 mInDestructor(false),
1358 mIsGoingAway(false),
1359 mStyleSetFilled(false),
1360 mQuirkSheetAdded(false),
1361 mContentEditableSheetAdded(false),
1362 mDesignModeSheetAdded(false),
1363 mMayHaveTitleElement(false),
1364 mDOMLoadingSet(false),
1365 mDOMInteractiveSet(false),
1366 mDOMCompleteSet(false),
1367 mAutoFocusFired(false),
1368 mScrolledToRefAlready(false),
1369 mChangeScrollPosWhenScrollingToRef(false),
1370 mDelayFrameLoaderInitialization(false),
1371 mSynchronousDOMContentLoaded(false),
1372 mMaybeServiceWorkerControlled(false),
1373 mAllowZoom(false),
1374 mValidScaleFloat(false),
1375 mValidMinScale(false),
1376 mValidMaxScale(false),
1377 mWidthStrEmpty(false),
1378 mParserAborted(false),
1379 mReportedDocumentUseCounters(false),
1380 mHasReportedShadowDOMUsage(false),
1381 mHasDelayedRefreshEvent(false),
1382 mLoadEventFiring(false),
1383 mSkipLoadEventAfterClose(false),
1384 mDisableCookieAccess(false),
1385 mDisableDocWrite(false),
1386 mTooDeepWriteRecursion(false),
1387 mPendingMaybeEditingStateChanged(false),
1388 mHasBeenEditable(false),
1389 mHasWarnedAboutZoom(false),
1390 mIsRunningExecCommandByContent(false),
1391 mIsRunningExecCommandByChromeOrAddon(false),
1392 mSetCompleteAfterDOMContentLoaded(false),
1393 mDidHitCompleteSheetCache(false),
1394 mUseCountersInitialized(false),
1395 mShouldReportUseCounters(false),
1396 mShouldSendPageUseCounters(false),
1397 mUserHasInteracted(false),
1398 mHasUserInteractionTimerScheduled(false),
1399 mShouldResistFingerprinting(false),
1400 mCloningForSVGUse(false),
1401 mAllowDeclarativeShadowRoots(false),
1402 mXMLDeclarationBits(0),
1403 mOnloadBlockCount(0),
1404 mWriteLevel(0),
1405 mContentEditableCount(0),
1406 mEditingState(EditingState::eOff),
1407 mCompatMode(eCompatibility_FullStandards),
1408 mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
1409 mAncestorIsLoading(false),
1410 mVisibilityState(dom::VisibilityState::Hidden),
1411 mType(eUnknown),
1412 mDefaultElementType(0),
1413 mAllowXULXBL(eTriUnset),
1414 mSkipDTDSecurityChecks(false),
1415 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
1416 mSandboxFlags(0),
1417 mPartID(0),
1418 mMarkedCCGeneration(0),
1419 mPresShell(nullptr),
1420 mSubtreeModifiedDepth(0),
1421 mPreloadPictureDepth(0),
1422 mEventsSuppressed(0),
1423 mIgnoreDestructiveWritesCounter(0),
1424 mStaticCloneCount(0),
1425 mWindow(nullptr),
1426 mBFCacheEntry(nullptr),
1427 mInSyncOperationCount(0),
1428 mBlockDOMContentLoaded(0),
1429 mUpdateNestLevel(0),
1430 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
1431 mViewportType(Unknown),
1432 mViewportFit(ViewportFitType::Auto),
1433 mSubDocuments(nullptr),
1434 mHeaderData(nullptr),
1435 mServoRestyleRootDirtyBits(0),
1436 mThrowOnDynamicMarkupInsertionCounter(0),
1437 mIgnoreOpensDuringUnloadCounter(0),
1438 mSavedResolution(1.0f),
1439 mSavedResolutionBeforeMVM(1.0f),
1440 mGeneration(0),
1441 mCachedTabSizeGeneration(0),
1442 mNextFormNumber(0),
1443 mNextControlNumber(0),
1444 mPreloadService(this),
1445 mShouldNotifyFetchSuccess(false),
1446 mShouldNotifyFormOrPasswordRemoved(false) {
1447 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
1449 SetIsInDocument();
1450 SetIsConnected(true);
1452 // Create these unconditionally, they will be used to warn about the `zoom`
1453 // property, even if use counters are disabled.
1454 mStyleUseCounters.reset(Servo_UseCounters_Create());
1456 SetContentType(nsDependentCString(aContentType));
1458 // Start out mLastStyleSheetSet as null, per spec
1459 SetDOMStringToNull(mLastStyleSheetSet);
1461 // void state used to differentiate an empty source from an unselected source
1462 mPreloadPictureFoundSource.SetIsVoid(true);
1464 RecomputeLanguageFromCharset();
1466 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
1467 mReferrerInfo = new dom::ReferrerInfo(nullptr);
1470 #ifndef ANDROID
1471 // unused by GeckoView
1472 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
1473 if (NS_WARN_IF(!aWin)) {
1474 return false;
1477 nsIURI* uri = aWin->GetDocumentURI();
1478 if (NS_WARN_IF(!uri)) {
1479 return false;
1481 // getSpec is an expensive operation, hence we first check the scheme
1482 // to see if the caller is actually an about: page.
1483 if (!uri->SchemeIs("about")) {
1484 return false;
1487 nsAutoCString aboutSpec;
1488 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
1489 NS_ENSURE_SUCCESS(rv, false);
1491 return aboutSpec.EqualsASCII(aSpec);
1493 #endif
1495 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
1496 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1497 #ifdef ANDROID
1498 // GeckoView uses data URLs for error pages, so for now just check for any
1499 // error page
1500 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1501 #else
1502 return win && IsAboutErrorPage(win, "neterror");
1503 #endif
1506 bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
1507 JSObject* aObject) {
1508 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1509 #ifdef ANDROID
1510 // GeckoView uses data URLs for error pages, so for now just check for any
1511 // error page
1512 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1513 #else
1514 return win && IsAboutErrorPage(win, "httpsonlyerror");
1515 #endif
1518 already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
1519 bool aIsTemporary, ErrorResult& aError) {
1520 RefPtr<Promise> promise = Promise::Create(GetScopeObject(), aError,
1521 Promise::ePropagateUserInteraction);
1522 if (aError.Failed()) {
1523 return nullptr;
1526 nsresult rv = NS_OK;
1527 if (NS_WARN_IF(!mFailedChannel)) {
1528 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1529 return promise.forget();
1532 nsCOMPtr<nsIURI> failedChannelURI;
1533 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
1534 if (!failedChannelURI) {
1535 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1536 return promise.forget();
1539 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI);
1540 if (!innerURI) {
1541 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1542 return promise.forget();
1545 nsAutoCString host;
1546 innerURI->GetAsciiHost(host);
1547 int32_t port;
1548 innerURI->GetPort(&port);
1550 nsCOMPtr<nsITransportSecurityInfo> tsi;
1551 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1552 if (NS_WARN_IF(NS_FAILED(rv))) {
1553 promise->MaybeReject(rv);
1554 return promise.forget();
1556 if (NS_WARN_IF(!tsi)) {
1557 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1558 return promise.forget();
1561 nsCOMPtr<nsIX509Cert> cert;
1562 rv = tsi->GetServerCert(getter_AddRefs(cert));
1563 if (NS_WARN_IF(NS_FAILED(rv))) {
1564 promise->MaybeReject(rv);
1565 return promise.forget();
1567 if (NS_WARN_IF(!cert)) {
1568 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1569 return promise.forget();
1572 if (XRE_IsContentProcess()) {
1573 ContentChild* cc = ContentChild::GetSingleton();
1574 MOZ_ASSERT(cc);
1575 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1576 cc->SendAddCertException(cert, host, port, attrs, aIsTemporary)
1577 ->Then(GetCurrentSerialEventTarget(), __func__,
1578 [promise](const mozilla::MozPromise<
1579 nsresult, mozilla::ipc::ResponseRejectReason,
1580 true>::ResolveOrRejectValue& aValue) {
1581 if (aValue.IsResolve()) {
1582 promise->MaybeResolve(aValue.ResolveValue());
1583 } else {
1584 promise->MaybeRejectWithUndefined();
1587 return promise.forget();
1590 if (XRE_IsParentProcess()) {
1591 nsCOMPtr<nsICertOverrideService> overrideService =
1592 do_GetService(NS_CERTOVERRIDE_CONTRACTID);
1593 if (!overrideService) {
1594 promise->MaybeReject(NS_ERROR_FAILURE);
1595 return promise.forget();
1598 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1599 rv = overrideService->RememberValidityOverride(host, port, attrs, cert,
1600 aIsTemporary);
1601 if (NS_WARN_IF(NS_FAILED(rv))) {
1602 promise->MaybeReject(rv);
1603 return promise.forget();
1606 promise->MaybeResolveWithUndefined();
1607 return promise.forget();
1610 promise->MaybeReject(NS_ERROR_FAILURE);
1611 return promise.forget();
1614 void Document::ReloadWithHttpsOnlyException() {
1615 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
1616 wgc->SendReloadWithHttpsOnlyException();
1620 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
1621 nsresult rv = NS_OK;
1622 if (NS_WARN_IF(!mFailedChannel)) {
1623 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1624 return;
1627 nsCOMPtr<nsITransportSecurityInfo> tsi;
1628 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1629 if (NS_WARN_IF(NS_FAILED(rv))) {
1630 aRv.Throw(rv);
1631 return;
1633 if (NS_WARN_IF(!tsi)) {
1634 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1635 return;
1638 nsAutoString errorCodeString;
1639 rv = tsi->GetErrorCodeString(errorCodeString);
1640 if (NS_WARN_IF(NS_FAILED(rv))) {
1641 aRv.Throw(rv);
1642 return;
1644 aInfo.mErrorCodeString.Assign(errorCodeString);
1647 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
1648 JSObject* aObject) {
1649 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1650 #ifdef ANDROID
1651 // GeckoView uses data URLs for error pages, so for now just check for any
1652 // error page
1653 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1654 #else
1655 return win && IsAboutErrorPage(win, "certerror");
1656 #endif
1659 bool Document::CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject) {
1660 RefPtr<BasePrincipal> principal =
1661 BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx));
1663 if (!principal) {
1664 return false;
1667 // We allow the privilege SSA to be called from system principal.
1668 if (principal->IsSystemPrincipal()) {
1669 return true;
1672 // We only allow calling the privilege SSA from the content script of the
1673 // webcompat extension.
1674 if (auto* policy = principal->ContentScriptAddonPolicy()) {
1675 nsAutoString addonID;
1676 policy->GetId(addonID);
1678 return addonID.EqualsLiteral("webcompat@mozilla.org");
1681 return false;
1684 bool Document::IsErrorPage() const {
1685 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
1686 return loadInfo && loadInfo->GetLoadErrorPage();
1689 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
1690 ErrorResult& aRv) {
1691 nsresult rv = NS_OK;
1692 if (NS_WARN_IF(!mFailedChannel)) {
1693 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1694 return;
1697 nsCOMPtr<nsITransportSecurityInfo> tsi;
1698 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1699 if (NS_WARN_IF(NS_FAILED(rv))) {
1700 aRv.Throw(rv);
1701 return;
1703 if (NS_WARN_IF(!tsi)) {
1704 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1705 return;
1708 nsAutoString errorCodeString;
1709 rv = tsi->GetErrorCodeString(errorCodeString);
1710 if (NS_WARN_IF(NS_FAILED(rv))) {
1711 aRv.Throw(rv);
1712 return;
1714 aInfo.mErrorCodeString.Assign(errorCodeString);
1716 nsITransportSecurityInfo::OverridableErrorCategory errorCategory;
1717 rv = tsi->GetOverridableErrorCategory(&errorCategory);
1718 if (NS_WARN_IF(NS_FAILED(rv))) {
1719 aRv.Throw(rv);
1720 return;
1722 switch (errorCategory) {
1723 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST:
1724 aInfo.mOverridableErrorCategory =
1725 dom::OverridableErrorCategory::Trust_error;
1726 break;
1727 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN:
1728 aInfo.mOverridableErrorCategory =
1729 dom::OverridableErrorCategory::Domain_mismatch;
1730 break;
1731 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME:
1732 aInfo.mOverridableErrorCategory =
1733 dom::OverridableErrorCategory::Expired_or_not_yet_valid;
1734 break;
1735 default:
1736 aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Unset;
1737 break;
1740 nsCOMPtr<nsIX509Cert> cert;
1741 nsCOMPtr<nsIX509CertValidity> validity;
1742 rv = tsi->GetServerCert(getter_AddRefs(cert));
1743 if (NS_WARN_IF(NS_FAILED(rv))) {
1744 aRv.Throw(rv);
1745 return;
1747 if (NS_WARN_IF(!cert)) {
1748 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1749 return;
1752 rv = cert->GetValidity(getter_AddRefs(validity));
1753 if (NS_WARN_IF(NS_FAILED(rv))) {
1754 aRv.Throw(rv);
1755 return;
1757 if (NS_WARN_IF(!validity)) {
1758 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1759 return;
1762 PRTime validityResult;
1763 rv = validity->GetNotBefore(&validityResult);
1764 if (NS_WARN_IF(NS_FAILED(rv))) {
1765 aRv.Throw(rv);
1766 return;
1768 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1770 rv = validity->GetNotAfter(&validityResult);
1771 if (NS_WARN_IF(NS_FAILED(rv))) {
1772 aRv.Throw(rv);
1773 return;
1775 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1777 nsAutoString issuerCommonName;
1778 nsAutoString certChainPEMString;
1779 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
1780 int64_t maxValidity = std::numeric_limits<int64_t>::max();
1781 int64_t minValidity = 0;
1782 PRTime notBefore, notAfter;
1783 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
1784 rv = tsi->GetFailedCertChain(failedCertArray);
1785 if (NS_WARN_IF(NS_FAILED(rv))) {
1786 aRv.Throw(rv);
1787 return;
1790 if (NS_WARN_IF(failedCertArray.IsEmpty())) {
1791 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1792 return;
1795 for (const auto& certificate : failedCertArray) {
1796 rv = certificate->GetIssuerCommonName(issuerCommonName);
1797 if (NS_WARN_IF(NS_FAILED(rv))) {
1798 aRv.Throw(rv);
1799 return;
1802 rv = certificate->GetValidity(getter_AddRefs(validity));
1803 if (NS_WARN_IF(NS_FAILED(rv))) {
1804 aRv.Throw(rv);
1805 return;
1807 if (NS_WARN_IF(!validity)) {
1808 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1809 return;
1812 rv = validity->GetNotBefore(&notBefore);
1813 if (NS_WARN_IF(NS_FAILED(rv))) {
1814 aRv.Throw(rv);
1815 return;
1818 rv = validity->GetNotAfter(&notAfter);
1819 if (NS_WARN_IF(NS_FAILED(rv))) {
1820 aRv.Throw(rv);
1821 return;
1824 notBefore = std::max(minValidity, notBefore);
1825 notAfter = std::min(maxValidity, notAfter);
1826 nsTArray<uint8_t> certArray;
1827 rv = certificate->GetRawDER(certArray);
1828 if (NS_WARN_IF(NS_FAILED(rv))) {
1829 aRv.Throw(rv);
1830 return;
1833 nsAutoString der64;
1834 rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()),
1835 certArray.Length(), der64);
1836 if (NS_WARN_IF(NS_FAILED(rv))) {
1837 aRv.Throw(rv);
1838 return;
1840 if (!certChainStrings.AppendElement(der64, fallible)) {
1841 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1842 return;
1846 aInfo.mIssuerCommonName.Assign(issuerCommonName);
1847 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
1848 aInfo.mCertValidityRangeNotBefore =
1849 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
1851 int32_t errorCode;
1852 rv = tsi->GetErrorCode(&errorCode);
1853 if (NS_WARN_IF(NS_FAILED(rv))) {
1854 aRv.Throw(rv);
1855 return;
1858 nsCOMPtr<nsINSSErrorsService> nsserr =
1859 do_GetService("@mozilla.org/nss_errors_service;1");
1860 if (NS_WARN_IF(!nsserr)) {
1861 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1862 return;
1864 nsresult res;
1865 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
1866 if (NS_WARN_IF(NS_FAILED(rv))) {
1867 aRv.Throw(rv);
1868 return;
1870 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
1871 if (NS_WARN_IF(NS_FAILED(rv))) {
1872 aRv.Throw(rv);
1873 return;
1876 OriginAttributes attrs;
1877 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1878 nsCOMPtr<nsIURI> aURI;
1879 mFailedChannel->GetURI(getter_AddRefs(aURI));
1880 if (XRE_IsContentProcess()) {
1881 ContentChild* cc = ContentChild::GetSingleton();
1882 MOZ_ASSERT(cc);
1883 cc->SendIsSecureURI(aURI, attrs, &aInfo.mHasHSTS);
1884 } else {
1885 nsCOMPtr<nsISiteSecurityService> sss =
1886 do_GetService(NS_SSSERVICE_CONTRACTID);
1887 if (NS_WARN_IF(!sss)) {
1888 return;
1890 Unused << NS_WARN_IF(
1891 NS_FAILED(sss->IsSecureURI(aURI, attrs, &aInfo.mHasHSTS)));
1893 nsCOMPtr<nsIPublicKeyPinningService> pkps =
1894 do_GetService(NS_PKPSERVICE_CONTRACTID);
1895 if (NS_WARN_IF(!pkps)) {
1896 return;
1898 Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP)));
1901 bool Document::IsAboutPage() const {
1902 return NodePrincipal()->SchemeIs("about");
1905 void Document::ConstructUbiNode(void* storage) {
1906 JS::ubi::Concrete<Document>::construct(storage, this);
1909 void Document::LoadEventFired() {
1910 // Object used to collect some telemetry data so we don't need to query for it
1911 // twice.
1912 glean::perf::PageLoadExtra pageLoadEventData;
1914 // Accumulate timing data located in each document's realm and report to
1915 // telemetry.
1916 AccumulateJSTelemetry(pageLoadEventData);
1918 // Collect page load timings
1919 AccumulatePageLoadTelemetry(pageLoadEventData);
1921 // Record page load event
1922 RecordPageLoadEventTelemetry(pageLoadEventData);
1924 // Release the JS bytecode cache from its wait on the load event, and
1925 // potentially dispatch the encoding of the bytecode.
1926 if (ScriptLoader()) {
1927 ScriptLoader()->LoadEventFired();
1931 void Document::RecordPageLoadEventTelemetry(
1932 glean::perf::PageLoadExtra& aEventTelemetryData) {
1933 // If the page load time is empty, then the content wasn't something we want
1934 // to report (i.e. not a top level document).
1935 if (!aEventTelemetryData.loadTime) {
1936 return;
1938 MOZ_ASSERT(IsTopLevelContentDocument());
1940 nsPIDOMWindowOuter* window = GetWindow();
1941 if (!window) {
1942 return;
1945 nsIDocShell* docshell = window->GetDocShell();
1946 if (!docshell) {
1947 return;
1950 nsAutoCString loadTypeStr;
1951 switch (docshell->GetLoadType()) {
1952 case LOAD_NORMAL:
1953 case LOAD_NORMAL_REPLACE:
1954 case LOAD_NORMAL_BYPASS_CACHE:
1955 case LOAD_NORMAL_BYPASS_PROXY:
1956 case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
1957 loadTypeStr.Append("NORMAL");
1958 break;
1959 case LOAD_HISTORY:
1960 loadTypeStr.Append("HISTORY");
1961 break;
1962 case LOAD_RELOAD_NORMAL:
1963 case LOAD_RELOAD_BYPASS_CACHE:
1964 case LOAD_RELOAD_BYPASS_PROXY:
1965 case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
1966 case LOAD_REFRESH:
1967 case LOAD_REFRESH_REPLACE:
1968 case LOAD_RELOAD_CHARSET_CHANGE:
1969 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
1970 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
1971 loadTypeStr.Append("RELOAD");
1972 break;
1973 case LOAD_LINK:
1974 loadTypeStr.Append("LINK");
1975 break;
1976 case LOAD_STOP_CONTENT:
1977 case LOAD_STOP_CONTENT_AND_REPLACE:
1978 loadTypeStr.Append("STOP");
1979 break;
1980 case LOAD_ERROR_PAGE:
1981 loadTypeStr.Append("ERROR");
1982 break;
1983 default:
1984 loadTypeStr.Append("OTHER");
1985 break;
1988 nsCOMPtr<nsIEffectiveTLDService> tldService =
1989 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
1990 if (tldService && mReferrerInfo &&
1991 (docshell->GetLoadType() & nsIDocShell::LOAD_CMD_NORMAL)) {
1992 nsAutoCString currentBaseDomain, referrerBaseDomain;
1993 nsCOMPtr<nsIURI> referrerURI = mReferrerInfo->GetComputedReferrer();
1994 if (referrerURI) {
1995 auto result = NS_SUCCEEDED(
1996 tldService->GetBaseDomain(referrerURI, 0, referrerBaseDomain));
1997 if (result) {
1998 bool sameOrigin = false;
1999 NodePrincipal()->IsSameOrigin(referrerURI, &sameOrigin);
2000 aEventTelemetryData.sameOriginNav = mozilla::Some(sameOrigin);
2005 aEventTelemetryData.loadType = mozilla::Some(loadTypeStr);
2007 // Sending a glean ping must be done on the parent process.
2008 if (ContentChild* cc = ContentChild::GetSingleton()) {
2009 cc->SendRecordPageLoadEvent(aEventTelemetryData);
2013 void Document::AccumulatePageLoadTelemetry(
2014 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2015 // Interested only in top level documents for real websites that are in the
2016 // foreground.
2017 if (!ShouldIncludeInTelemetry() || !IsTopLevelContentDocument() ||
2018 !GetNavigationTiming() ||
2019 !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
2020 return;
2023 if (!GetChannel()) {
2024 return;
2027 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
2028 if (!timedChannel) {
2029 return;
2032 // Default duration is 0, use this to check for bogus negative values.
2033 const TimeDuration zeroDuration;
2035 TimeStamp responseStart;
2036 timedChannel->GetResponseStart(&responseStart);
2038 TimeStamp redirectStart, redirectEnd;
2039 timedChannel->GetRedirectStart(&redirectStart);
2040 timedChannel->GetRedirectEnd(&redirectEnd);
2042 uint8_t redirectCount;
2043 timedChannel->GetRedirectCount(&redirectCount);
2044 if (redirectCount) {
2045 aEventTelemetryDataOut.redirectCount =
2046 mozilla::Some(static_cast<uint32_t>(redirectCount));
2049 if (!redirectStart.IsNull() && !redirectEnd.IsNull()) {
2050 TimeDuration redirectTime = redirectEnd - redirectStart;
2051 if (redirectTime > zeroDuration) {
2052 aEventTelemetryDataOut.redirectTime =
2053 mozilla::Some(static_cast<uint32_t>(redirectTime.ToMilliseconds()));
2057 TimeStamp dnsLookupStart, dnsLookupEnd;
2058 timedChannel->GetDomainLookupStart(&dnsLookupStart);
2059 timedChannel->GetDomainLookupEnd(&dnsLookupEnd);
2061 if (!dnsLookupStart.IsNull() && !dnsLookupEnd.IsNull()) {
2062 TimeDuration dnsLookupTime = dnsLookupEnd - dnsLookupStart;
2063 if (dnsLookupTime > zeroDuration) {
2064 aEventTelemetryDataOut.dnsLookupTime =
2065 mozilla::Some(static_cast<uint32_t>(dnsLookupTime.ToMilliseconds()));
2069 TimeStamp navigationStart =
2070 GetNavigationTiming()->GetNavigationStartTimeStamp();
2072 if (!responseStart || !navigationStart) {
2073 return;
2076 nsAutoCString dnsKey("Native");
2077 nsAutoCString http3Key;
2078 nsAutoCString http3WithPriorityKey;
2079 nsAutoCString earlyHintKey;
2080 nsCOMPtr<nsIHttpChannelInternal> httpChannel =
2081 do_QueryInterface(GetChannel());
2082 if (httpChannel) {
2083 bool resolvedByTRR = false;
2084 Unused << httpChannel->GetIsResolvedByTRR(&resolvedByTRR);
2085 if (resolvedByTRR) {
2086 if (nsCOMPtr<nsIDNSService> dns =
2087 do_GetService(NS_DNSSERVICE_CONTRACTID)) {
2088 dns->GetTRRDomainKey(dnsKey);
2089 } else {
2090 // Failed to get the DNS service.
2091 dnsKey = "(fail)"_ns;
2093 aEventTelemetryDataOut.trrDomain = mozilla::Some(dnsKey);
2096 uint32_t major;
2097 uint32_t minor;
2098 if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
2099 if (major == 3) {
2100 http3Key = "http3"_ns;
2101 nsCOMPtr<nsIHttpChannel> httpChannel2 = do_QueryInterface(GetChannel());
2102 nsCString header;
2103 if (httpChannel2 &&
2104 NS_SUCCEEDED(
2105 httpChannel2->GetResponseHeader("priority"_ns, header)) &&
2106 !header.IsEmpty()) {
2107 http3WithPriorityKey = "with_priority"_ns;
2108 } else {
2109 http3WithPriorityKey = "without_priority"_ns;
2111 } else if (major == 2) {
2112 bool supportHttp3 = false;
2113 if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
2114 supportHttp3 = false;
2116 if (supportHttp3) {
2117 http3Key = "supports_http3"_ns;
2121 aEventTelemetryDataOut.httpVer = mozilla::Some(major);
2124 uint32_t earlyHintType = 0;
2125 Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
2126 if (earlyHintType & LinkStyle::ePRECONNECT) {
2127 earlyHintKey.Append("preconnect_"_ns);
2129 if (earlyHintType & LinkStyle::ePRELOAD) {
2130 earlyHintKey.Append("preload_"_ns);
2131 earlyHintKey.Append(mPreloadService.GetEarlyHintUsed() ? "1"_ns : "0"_ns);
2135 TimeStamp asyncOpen;
2136 timedChannel->GetAsyncOpen(&asyncOpen);
2137 if (asyncOpen) {
2138 Telemetry::AccumulateTimeDelta(Telemetry::DNS_PERF_FIRST_BYTE_MS, dnsKey,
2139 asyncOpen, responseStart);
2142 // First Contentful Composite
2143 if (TimeStamp firstContentfulComposite =
2144 GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) {
2145 glean::performance_pageload::fcp.AccumulateRawDuration(
2146 firstContentfulComposite - navigationStart);
2148 if (!http3Key.IsEmpty()) {
2149 Telemetry::AccumulateTimeDelta(
2150 Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key,
2151 navigationStart, firstContentfulComposite);
2154 if (!http3WithPriorityKey.IsEmpty()) {
2155 Telemetry::AccumulateTimeDelta(
2156 Telemetry::H3P_PERF_FIRST_CONTENTFUL_PAINT_MS, http3WithPriorityKey,
2157 navigationStart, firstContentfulComposite);
2160 if (!earlyHintKey.IsEmpty()) {
2161 Telemetry::AccumulateTimeDelta(
2162 Telemetry::EH_PERF_FIRST_CONTENTFUL_PAINT_MS, earlyHintKey,
2163 navigationStart, firstContentfulComposite);
2166 Telemetry::AccumulateTimeDelta(
2167 Telemetry::DNS_PERF_FIRST_CONTENTFUL_PAINT_MS, dnsKey, navigationStart,
2168 firstContentfulComposite);
2170 glean::performance_pageload::fcp_responsestart.AccumulateRawDuration(
2171 firstContentfulComposite - responseStart);
2173 TimeDuration fcpTime = firstContentfulComposite - navigationStart;
2174 if (fcpTime > zeroDuration) {
2175 aEventTelemetryDataOut.fcpTime =
2176 mozilla::Some(static_cast<uint32_t>(fcpTime.ToMilliseconds()));
2180 // Report the most up to date LCP time. For our histogram we actually report
2181 // this on page unload.
2182 if (TimeStamp lcpTime =
2183 GetNavigationTiming()->GetLargestContentfulRenderTimeStamp()) {
2184 aEventTelemetryDataOut.lcpTime = mozilla::Some(
2185 static_cast<uint32_t>((lcpTime - navigationStart).ToMilliseconds()));
2188 // Load event
2189 if (TimeStamp loadEventStart =
2190 GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
2191 glean::performance_pageload::load_time.AccumulateRawDuration(
2192 loadEventStart - navigationStart);
2193 if (!http3Key.IsEmpty()) {
2194 Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
2195 http3Key, navigationStart, loadEventStart);
2198 if (!http3WithPriorityKey.IsEmpty()) {
2199 Telemetry::AccumulateTimeDelta(Telemetry::H3P_PERF_PAGE_LOAD_TIME_MS,
2200 http3WithPriorityKey, navigationStart,
2201 loadEventStart);
2204 if (!earlyHintKey.IsEmpty()) {
2205 Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_PAGE_LOAD_TIME_MS,
2206 earlyHintKey, navigationStart,
2207 loadEventStart);
2210 glean::performance_pageload::load_time_responsestart.AccumulateRawDuration(
2211 loadEventStart - responseStart);
2213 TimeDuration responseTime = responseStart - navigationStart;
2214 if (responseTime > zeroDuration) {
2215 aEventTelemetryDataOut.responseTime =
2216 mozilla::Some(static_cast<uint32_t>(responseTime.ToMilliseconds()));
2219 TimeDuration loadTime = loadEventStart - navigationStart;
2220 if (loadTime > zeroDuration) {
2221 aEventTelemetryDataOut.loadTime =
2222 mozilla::Some(static_cast<uint32_t>(loadTime.ToMilliseconds()));
2227 void Document::AccumulateJSTelemetry(
2228 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2229 if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry()) {
2230 return;
2233 if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
2234 return;
2237 AutoJSContext cx;
2238 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
2239 JSAutoRealm ar(cx, globalObject);
2240 JS::JSTimers timers = JS::GetJSTimers(cx);
2242 if (!timers.executionTime.IsZero()) {
2243 glean::javascript_pageload::execution_time.AccumulateRawDuration(
2244 timers.executionTime);
2245 aEventTelemetryDataOut.jsExecTime = mozilla::Some(
2246 static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
2249 if (!timers.delazificationTime.IsZero()) {
2250 glean::javascript_pageload::delazification_time.AccumulateRawDuration(
2251 timers.delazificationTime);
2254 if (!timers.xdrEncodingTime.IsZero()) {
2255 glean::javascript_pageload::xdr_encode_time.AccumulateRawDuration(
2256 timers.xdrEncodingTime);
2259 if (!timers.baselineCompileTime.IsZero()) {
2260 glean::javascript_pageload::baseline_compile_time.AccumulateRawDuration(
2261 timers.baselineCompileTime);
2264 if (!timers.gcTime.IsZero()) {
2265 glean::javascript_pageload::gc_time.AccumulateRawDuration(timers.gcTime);
2268 if (!timers.protectTime.IsZero()) {
2269 glean::javascript_pageload::protect_time.AccumulateRawDuration(
2270 timers.protectTime);
2274 Document::~Document() {
2275 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
2276 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
2277 "Can't be top-level and a resource doc at the same time");
2279 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
2281 if (IsTopLevelContentDocument()) {
2282 RemoveToplevelLoadingDocument(this);
2284 // don't report for about: pages
2285 if (!IsAboutPage()) {
2286 if (MOZ_UNLIKELY(mMathMLEnabled)) {
2287 ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
2290 if (IsHTMLDocument()) {
2291 switch (GetCompatibilityMode()) {
2292 case eCompatibility_FullStandards:
2293 Telemetry::AccumulateCategorical(
2294 Telemetry::LABELS_QUIRKS_MODE::FullStandards);
2295 break;
2296 case eCompatibility_AlmostStandards:
2297 Telemetry::AccumulateCategorical(
2298 Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
2299 break;
2300 case eCompatibility_NavQuirks:
2301 Telemetry::AccumulateCategorical(
2302 Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
2303 break;
2304 default:
2305 MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
2306 break;
2312 mInDestructor = true;
2313 mInUnlinkOrDeletion = true;
2315 mozilla::DropJSObjects(this);
2317 // Clear mObservers to keep it in sync with the mutationobserver list
2318 mObservers.Clear();
2320 mIntersectionObservers.Clear();
2322 if (mStyleSheetSetList) {
2323 mStyleSheetSetList->Disconnect();
2326 if (mAnimationController) {
2327 mAnimationController->Disconnect();
2330 MOZ_ASSERT(mTimelines.isEmpty());
2332 mParentDocument = nullptr;
2334 // Kill the subdocument map, doing this will release its strong
2335 // references, if any.
2336 delete mSubDocuments;
2337 mSubDocuments = nullptr;
2339 nsAutoScriptBlocker scriptBlocker;
2341 // Destroy link map now so we don't waste time removing
2342 // links one by one
2343 DestroyElementMaps();
2345 // Invalidate cached array of child nodes
2346 InvalidateChildNodes();
2348 // We should not have child nodes when destructor is called,
2349 // since child nodes keep their owner document alive.
2350 MOZ_ASSERT(!HasChildren());
2352 mCachedRootElement = nullptr;
2354 for (auto& sheets : mAdditionalSheets) {
2355 UnlinkStyleSheets(sheets);
2358 if (mAttributeStyles) {
2359 mAttributeStyles->SetOwningDocument(nullptr);
2362 if (mListenerManager) {
2363 mListenerManager->Disconnect();
2364 UnsetFlags(NODE_HAS_LISTENERMANAGER);
2367 if (mScriptLoader) {
2368 mScriptLoader->DropDocumentReference();
2371 if (mCSSLoader) {
2372 // Could be null here if Init() failed or if we have been unlinked.
2373 mCSSLoader->DropDocumentReference();
2376 if (mStyleImageLoader) {
2377 mStyleImageLoader->DropDocumentReference();
2380 if (mXULBroadcastManager) {
2381 mXULBroadcastManager->DropDocumentReference();
2384 if (mXULPersist) {
2385 mXULPersist->DropDocumentReference();
2388 if (mPermissionDelegateHandler) {
2389 mPermissionDelegateHandler->DropDocumentReference();
2392 mHeaderData = nullptr;
2394 mPendingTitleChangeEvent.Revoke();
2396 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
2397 "must not have media query lists left");
2399 if (mNodeInfoManager) {
2400 mNodeInfoManager->DropDocumentReference();
2403 if (mDocGroup) {
2404 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
2405 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
2408 UnlinkOriginalDocumentIfStatic();
2410 UnregisterFromMemoryReportingForDataDocument();
2413 void Document::DropStyleSet() { mStyleSet = nullptr; }
2415 NS_INTERFACE_TABLE_HEAD(Document)
2416 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
2417 NS_INTERFACE_TABLE_BEGIN
2418 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
2419 NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
2420 NS_INTERFACE_TABLE_ENTRY(Document, Document)
2421 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
2422 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
2423 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
2424 NS_INTERFACE_TABLE_END
2425 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
2426 NS_INTERFACE_MAP_END
2428 NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
2429 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Document, LastRelease())
2431 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2432 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2433 EventListenerManager* elm = tmp->GetExistingListenerManager();
2434 if (elm) {
2435 elm->MarkForCC();
2437 return true;
2439 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2441 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2442 return Element::CanSkipInCC(tmp);
2443 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2445 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2446 return Element::CanSkipThis(tmp);
2447 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2449 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2450 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2451 char name[512];
2452 nsAutoCString loadedAsData;
2453 if (tmp->IsLoadedAsData()) {
2454 loadedAsData.AssignLiteral("data");
2455 } else {
2456 loadedAsData.AssignLiteral("normal");
2458 uint32_t nsid = tmp->GetDefaultNamespaceID();
2459 nsAutoCString uri;
2460 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2461 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2462 "(xhtml)", "(XLink)", "(XSLT)",
2463 "(MathML)", "(RDF)", "(XUL)"};
2464 if (nsid < ArrayLength(kNSURIs)) {
2465 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2466 kNSURIs[nsid], uri.get());
2467 } else {
2468 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2470 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2471 } else {
2472 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2475 if (!nsINode::Traverse(tmp, cb)) {
2476 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2479 tmp->mExternalResourceMap.Traverse(&cb);
2481 // Traverse all Document pointer members.
2482 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2483 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2484 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2485 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2486 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2487 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry)
2489 // Traverse all Document nsCOMPtrs.
2490 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2491 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2492 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2493 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2494 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2496 DocumentOrShadowRoot::Traverse(tmp, cb);
2498 if (tmp->mRadioGroupContainer) {
2499 RadioGroupContainer::Traverse(tmp->mRadioGroupContainer.get(), cb);
2502 for (auto& sheets : tmp->mAdditionalSheets) {
2503 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2506 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2507 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadObserver)
2508 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRememberedSizeObserver)
2509 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2510 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2511 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2512 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2513 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2514 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2515 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker)
2516 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2517 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2518 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2519 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2520 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2521 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2522 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2523 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2524 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2525 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2526 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2527 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2528 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2529 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2530 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2531 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2532 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2533 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
2535 // Traverse all our nsCOMArrays.
2536 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2538 // Traverse animation components
2539 if (tmp->mAnimationController) {
2540 tmp->mAnimationController->Traverse(&cb);
2543 if (tmp->mSubDocuments) {
2544 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2545 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2547 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2548 cb.NoteXPCOMChild(entry->mKey);
2549 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2550 "mSubDocuments entry->mSubDocument");
2551 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2555 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2557 // We own only the items in mDOMMediaQueryLists that have listeners;
2558 // this reference is managed by their AddListener and RemoveListener
2559 // methods.
2560 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2561 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2562 if (mql->HasListeners() &&
2563 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2564 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2565 cb.NoteXPCOMChild(static_cast<EventTarget*>(mql));
2569 // XXX: This should be not needed once bug 1569185 lands.
2570 for (const auto& entry : tmp->mL10nProtoElements) {
2571 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2572 cb.NoteXPCOMChild(entry.GetKey());
2573 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
2576 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2577 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2578 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2579 mPendingFrameStaticClones[i].mStaticCloneOf);
2582 for (auto& tableEntry : tmp->mActiveLocks) {
2583 ImplCycleCollectionTraverse(cb, *tableEntry.GetModifiableData(),
2584 "mActiveLocks entry", 0);
2586 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2588 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2590 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document)
2591 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
2592 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject)
2593 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2595 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2596 tmp->mInUnlinkOrDeletion = true;
2598 tmp->SetStateObject(nullptr);
2600 // Clear out our external resources
2601 tmp->mExternalResourceMap.Shutdown();
2603 nsAutoScriptBlocker scriptBlocker;
2605 nsINode::Unlink(tmp);
2607 while (tmp->HasChildren()) {
2608 // Hold a strong ref to the node when we remove it, because we may be
2609 // the last reference to it.
2610 // If this code changes, change the corresponding code in Document's
2611 // unlink impl and ContentUnbinder::UnbindSubtree.
2612 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2613 tmp->DisconnectChild(child);
2614 child->UnbindFromTree();
2617 tmp->UnlinkOriginalDocumentIfStatic();
2619 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2621 tmp->SetScriptGlobalObject(nullptr);
2623 for (auto& sheets : tmp->mAdditionalSheets) {
2624 tmp->UnlinkStyleSheets(sheets);
2627 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2628 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2629 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadObserver)
2630 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastRememberedSizeObserver)
2631 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2632 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2633 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2634 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry)
2635 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2636 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2637 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2638 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2639 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2640 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2641 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2642 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2643 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker)
2644 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2645 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2646 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2647 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2648 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2649 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2650 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2651 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2652 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2653 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2654 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2655 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2656 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2657 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2658 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2659 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2660 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2661 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2663 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
2664 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
2665 tmp->mDocGroup);
2667 tmp->mDocGroup = nullptr;
2669 if (tmp->IsTopLevelContentDocument()) {
2670 RemoveToplevelLoadingDocument(tmp);
2673 tmp->mParentDocument = nullptr;
2675 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2677 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2679 if (tmp->mListenerManager) {
2680 tmp->mListenerManager->Disconnect();
2681 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2682 tmp->mListenerManager = nullptr;
2685 if (tmp->mStyleSheetSetList) {
2686 tmp->mStyleSheetSetList->Disconnect();
2687 tmp->mStyleSheetSetList = nullptr;
2690 delete tmp->mSubDocuments;
2691 tmp->mSubDocuments = nullptr;
2693 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
2694 MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
2695 "How did we get here without our presshell going away "
2696 "first?");
2698 DocumentOrShadowRoot::Unlink(tmp);
2700 tmp->mRadioGroupContainer = nullptr;
2702 // Document has a pretty complex destructor, so we're going to
2703 // assume that *most* cycles you actually want to break somewhere
2704 // else, and not unlink an awful lot here.
2706 tmp->mExpandoAndGeneration.OwnerUnlinked();
2708 if (tmp->mAnimationController) {
2709 tmp->mAnimationController->Unlink();
2712 tmp->mPendingTitleChangeEvent.Revoke();
2714 if (tmp->mCSSLoader) {
2715 tmp->mCSSLoader->DropDocumentReference();
2716 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2719 // We own only the items in mDOMMediaQueryLists that have listeners;
2720 // this reference is managed by their AddListener and RemoveListener
2721 // methods.
2722 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2723 MediaQueryList* next =
2724 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2725 mql->Disconnect();
2726 mql = next;
2729 tmp->mPendingFrameStaticClones.Clear();
2731 tmp->mActiveLocks.Clear();
2733 tmp->mInUnlinkOrDeletion = false;
2735 tmp->UnregisterFromMemoryReportingForDataDocument();
2737 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2738 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2739 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2740 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2742 nsresult Document::Init(nsIPrincipal* aPrincipal,
2743 nsIPrincipal* aPartitionedPrincipal) {
2744 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2745 return NS_ERROR_ALREADY_INITIALIZED;
2748 // Force initialization.
2749 mOnloadBlocker = new OnloadBlocker();
2750 mStyleImageLoader = new css::ImageLoader(this);
2752 mNodeInfoManager = new nsNodeInfoManager(this, aPrincipal);
2754 // mNodeInfo keeps NodeInfoManager alive!
2755 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2756 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2757 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2758 "Bad NodeType in aNodeInfo");
2760 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2762 mCSSLoader = new css::Loader(this);
2763 // Assume we're not quirky, until we know otherwise
2764 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2766 // If after creation the owner js global is not set for a document
2767 // we use the default compartment for this document, instead of creating
2768 // wrapper in some random compartment when the document is exposed to js
2769 // via some events.
2770 nsCOMPtr<nsIGlobalObject> global =
2771 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2772 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2773 mScopeObject = do_GetWeakReference(global);
2774 MOZ_ASSERT(mScopeObject);
2776 mScriptLoader = new dom::ScriptLoader(this);
2778 // we need to create a policy here so getting the policy within
2779 // ::Policy() can *always* return a non null policy
2780 mFeaturePolicy = new dom::FeaturePolicy(this);
2781 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2783 if (aPrincipal) {
2784 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2785 } else {
2786 RecomputeResistFingerprinting();
2789 return NS_OK;
2792 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2794 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2795 PropertyTable().RemoveAllPropertiesFor(aNode);
2798 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2799 nsCOMPtr<nsIURI> uri;
2800 nsCOMPtr<nsIPrincipal> principal;
2801 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2802 if (aChannel) {
2803 mIsInPrivateBrowsing = NS_UsePrivateBrowsing(aChannel);
2805 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2806 // nsScriptSecurityManager::GetChannelResultPrincipals.
2807 // Note: this should match the uri used for the OnNewURI call in
2808 // nsDocShell::CreateDocumentViewer.
2809 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2811 nsIScriptSecurityManager* securityManager =
2812 nsContentUtils::GetSecurityManager();
2813 if (securityManager) {
2814 securityManager->GetChannelResultPrincipals(
2815 aChannel, getter_AddRefs(principal),
2816 getter_AddRefs(partitionedPrincipal));
2820 bool equal = principal->Equals(partitionedPrincipal);
2822 principal = MaybeDowngradePrincipal(principal);
2823 if (equal) {
2824 partitionedPrincipal = principal;
2825 } else {
2826 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2829 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2831 // Note that, since mTiming does not change during a reset, the
2832 // navigationStart time remains unchanged and therefore any future new
2833 // timeline will have the same global clock time as the old one.
2834 mDocumentTimeline = nullptr;
2836 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
2837 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
2838 mDocumentBaseURI = baseURI.forget();
2839 mChromeXHRDocBaseURI = nullptr;
2843 mChannel = aChannel;
2844 RecomputeResistFingerprinting();
2847 void Document::DisconnectNodeTree() {
2848 // Delete references to sub-documents and kill the subdocument map,
2849 // if any. This is not strictly needed, but makes the node tree
2850 // teardown a bit faster.
2851 delete mSubDocuments;
2852 mSubDocuments = nullptr;
2854 bool oldVal = mInUnlinkOrDeletion;
2855 mInUnlinkOrDeletion = true;
2856 { // Scope for update
2857 MOZ_AUTO_DOC_UPDATE(this, true);
2859 // Destroy link map now so we don't waste time removing
2860 // links one by one
2861 DestroyElementMaps();
2863 // Invalidate cached array of child nodes
2864 InvalidateChildNodes();
2866 while (HasChildren()) {
2867 nsMutationGuard::DidMutate();
2868 nsCOMPtr<nsIContent> content = GetLastChild();
2869 nsIContent* previousSibling = content->GetPreviousSibling();
2870 DisconnectChild(content);
2871 if (content == mCachedRootElement) {
2872 // Immediately clear mCachedRootElement, now that it's been removed
2873 // from mChildren, so that GetRootElement() will stop returning this
2874 // now-stale value.
2875 mCachedRootElement = nullptr;
2877 MutationObservers::NotifyContentRemoved(this, content, previousSibling);
2878 content->UnbindFromTree();
2880 MOZ_ASSERT(!mCachedRootElement,
2881 "After removing all children, there should be no root elem");
2883 mInUnlinkOrDeletion = oldVal;
2886 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2887 nsIPrincipal* aPrincipal,
2888 nsIPrincipal* aPartitionedPrincipal) {
2889 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2890 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2892 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2893 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2895 mSecurityInfo = nullptr;
2897 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2898 if (!aLoadGroup || group != aLoadGroup) {
2899 mDocumentLoadGroup = nullptr;
2902 DisconnectNodeTree();
2904 // Reset our stylesheets
2905 ResetStylesheetsToURI(aURI);
2907 // Release the listener manager
2908 if (mListenerManager) {
2909 mListenerManager->Disconnect();
2910 mListenerManager = nullptr;
2913 // Release the stylesheets list.
2914 mDOMStyleSheets = nullptr;
2916 // Release our principal after tearing down the document, rather than before.
2917 // This ensures that, during teardown, the document and the dying window
2918 // (which already nulled out its document pointer and cached the principal)
2919 // have matching principals.
2920 SetPrincipals(nullptr, nullptr);
2922 // Clear the original URI so SetDocumentURI sets it.
2923 mOriginalURI = nullptr;
2925 SetDocumentURI(aURI);
2926 mChromeXHRDocURI = nullptr;
2927 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
2928 // mDocumentURI.
2929 mDocumentBaseURI = nullptr;
2930 mChromeXHRDocBaseURI = nullptr;
2932 if (aLoadGroup) {
2933 nsCOMPtr<nsIInterfaceRequestor> callbacks;
2934 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
2935 if (callbacks) {
2936 nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
2937 if (loadContext) {
2938 // This is asserting that if we previously set mIsInPrivateBrowsing
2939 // to true from the channel in Document::Reset, that the loadContext
2940 // also believes it to be true.
2941 // MOZ_ASSERT(!mIsInPrivateBrowsing ||
2942 // mIsInPrivateBrowsing == loadContext->UsePrivateBrowsing());
2943 mIsInPrivateBrowsing = loadContext->UsePrivateBrowsing();
2947 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
2948 // there was an assertion here that aLoadGroup was not null. This
2949 // is no longer valid: nsDocShell::SetDocument does not create a
2950 // load group, and it works just fine
2952 // XXXbz what does "just fine" mean exactly? And given that there
2953 // is no nsDocShell::SetDocument, what is this talking about?
2955 if (IsContentDocument()) {
2956 // Inform the associated request context about this load start so
2957 // any of its internal load progress flags gets reset.
2958 nsCOMPtr<nsIRequestContextService> rcsvc =
2959 net::RequestContextService::GetOrCreate();
2960 if (rcsvc) {
2961 nsCOMPtr<nsIRequestContext> rc;
2962 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
2963 if (rc) {
2964 rc->BeginLoad();
2970 mLastModified.Truncate();
2971 // XXXbz I guess we're assuming that the caller will either pass in
2972 // a channel with a useful type or call SetContentType?
2973 SetContentType(""_ns);
2974 mContentLanguage = nullptr;
2975 mBaseTarget.Truncate();
2977 mXMLDeclarationBits = 0;
2979 // Now get our new principal
2980 if (aPrincipal) {
2981 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2982 } else {
2983 nsIScriptSecurityManager* securityManager =
2984 nsContentUtils::GetSecurityManager();
2985 if (securityManager) {
2986 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
2988 if (!loadContext && aLoadGroup) {
2989 nsCOMPtr<nsIInterfaceRequestor> cbs;
2990 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
2991 loadContext = do_GetInterface(cbs);
2994 MOZ_ASSERT(loadContext,
2995 "must have a load context or pass in an explicit principal");
2997 nsCOMPtr<nsIPrincipal> principal;
2998 nsresult rv = securityManager->GetLoadContextContentPrincipal(
2999 mDocumentURI, loadContext, getter_AddRefs(principal));
3000 if (NS_SUCCEEDED(rv)) {
3001 SetPrincipals(principal, principal);
3006 if (mFontFaceSet) {
3007 mFontFaceSet->RefreshStandardFontLoadPrincipal();
3010 // Refresh the principal on the realm.
3011 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
3012 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
3016 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
3017 nsIPrincipal* aPrincipal) {
3018 if (!aPrincipal) {
3019 return nullptr;
3022 // We can't load a document with an expanded principal. If we're given one,
3023 // automatically downgrade it to the last principal it subsumes (which is the
3024 // extension principal, in the case of extension content scripts).
3025 auto* basePrin = BasePrincipal::Cast(aPrincipal);
3026 if (basePrin->Is<ExpandedPrincipal>()) {
3027 MOZ_DIAGNOSTIC_ASSERT(false,
3028 "Should never try to create a document with "
3029 "an expanded principal");
3031 auto* expanded = basePrin->As<ExpandedPrincipal>();
3032 return do_AddRef(expanded->AllowList().LastElement());
3035 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
3036 // We basically want the parent document here, but because this is very
3037 // early in the load, GetInProcessParentDocument() returns null, so we use
3038 // the docshell hierarchy to get this information instead.
3039 if (RefPtr<BrowsingContext> parent =
3040 mDocumentContainer->GetBrowsingContext()->GetParent()) {
3041 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
3042 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
3043 nsCOMPtr<nsIPrincipal> nullPrincipal =
3044 NullPrincipal::CreateWithoutOriginAttributes();
3045 return nullPrincipal.forget();
3049 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
3050 return principal.forget();
3053 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
3054 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3055 ServoStyleSet& styleSet = EnsureStyleSet();
3057 // lowest index first
3058 int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
3060 size_t count = styleSet.SheetCount(StyleOrigin::Author);
3061 size_t index = 0;
3062 for (; index < count; index++) {
3063 auto* sheet = styleSet.SheetAt(StyleOrigin::Author, index);
3064 MOZ_ASSERT(sheet);
3065 int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
3066 if (sheetDocIndex > newDocIndex) {
3067 break;
3070 // If the sheet is not owned by the document it can be an author
3071 // sheet registered at nsStyleSheetService or an additional author
3072 // sheet on the document, which means the new
3073 // doc sheet should end up before it.
3074 if (sheetDocIndex < 0) {
3075 if (sheetService) {
3076 auto& authorSheets = *sheetService->AuthorStyleSheets();
3077 if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
3078 break;
3081 if (sheet == GetFirstAdditionalAuthorSheet()) {
3082 break;
3087 return index;
3090 void Document::ResetStylesheetsToURI(nsIURI* aURI) {
3091 MOZ_ASSERT(aURI);
3093 ClearAdoptedStyleSheets();
3094 ServoStyleSet& styleSet = EnsureStyleSet();
3096 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
3097 for (auto& sheet : Reversed(aSheetList)) {
3098 sheet->ClearAssociatedDocumentOrShadowRoot();
3099 if (mStyleSetFilled) {
3100 styleSet.RemoveStyleSheet(*sheet);
3103 aSheetList.Clear();
3105 ClearSheetList(mStyleSheets);
3106 for (auto& sheets : mAdditionalSheets) {
3107 ClearSheetList(sheets);
3109 if (mStyleSetFilled) {
3110 if (auto* ss = nsStyleSheetService::GetInstance()) {
3111 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
3112 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
3113 if (sheet->IsApplicable()) {
3114 styleSet.RemoveStyleSheet(*sheet);
3120 // Now reset our inline style and attribute sheets.
3121 if (mAttributeStyles) {
3122 mAttributeStyles->Reset();
3123 mAttributeStyles->SetOwningDocument(this);
3124 } else {
3125 mAttributeStyles = new AttributeStyles(this);
3128 if (mStyleSetFilled) {
3129 FillStyleSetDocumentSheets();
3131 if (styleSet.StyleSheetsHaveChanged()) {
3132 ApplicableStylesChanged();
3137 static void AppendSheetsToStyleSet(
3138 ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
3139 for (StyleSheet* sheet : Reversed(aSheets)) {
3140 aStyleSet->AppendStyleSheet(*sheet);
3144 void Document::FillStyleSetUserAndUASheets() {
3145 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
3146 // ordering.
3148 // The document will fill in the document sheets when we create the presshell
3149 auto* cache = GlobalStyleSheetCache::Singleton();
3151 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3152 MOZ_ASSERT(sheetService,
3153 "should never be creating a StyleSet after the style sheet "
3154 "service has gone");
3156 ServoStyleSet& styleSet = EnsureStyleSet();
3157 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
3158 styleSet.AppendStyleSheet(*sheet);
3161 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
3162 : cache->GetUserContentSheet();
3163 if (sheet) {
3164 styleSet.AppendStyleSheet(*sheet);
3167 styleSet.AppendStyleSheet(*cache->UASheet());
3169 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
3170 styleSet.AppendStyleSheet(*cache->MathMLSheet());
3173 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
3174 styleSet.AppendStyleSheet(*cache->SVGSheet());
3177 styleSet.AppendStyleSheet(*cache->HTMLSheet());
3179 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
3180 styleSet.AppendStyleSheet(*cache->NoFramesSheet());
3183 styleSet.AppendStyleSheet(*cache->CounterStylesSheet());
3185 // Only load the full XUL sheet if we'll need it.
3186 if (LoadsFullXULStyleSheetUpFront()) {
3187 styleSet.AppendStyleSheet(*cache->XULSheet());
3190 styleSet.AppendStyleSheet(*cache->FormsSheet());
3191 styleSet.AppendStyleSheet(*cache->ScrollbarsSheet());
3193 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
3194 styleSet.AppendStyleSheet(*sheet);
3197 MOZ_ASSERT(!mQuirkSheetAdded);
3198 if (NeedsQuirksSheet()) {
3199 styleSet.AppendStyleSheet(*cache->QuirkSheet());
3200 mQuirkSheetAdded = true;
3204 void Document::FillStyleSet() {
3205 MOZ_ASSERT(!mStyleSetFilled);
3206 FillStyleSetUserAndUASheets();
3207 FillStyleSetDocumentSheets();
3208 mStyleSetFilled = true;
3211 void Document::RemoveContentEditableStyleSheets() {
3212 MOZ_ASSERT(IsHTMLOrXHTML());
3214 ServoStyleSet& styleSet = EnsureStyleSet();
3215 auto* cache = GlobalStyleSheetCache::Singleton();
3216 bool changed = false;
3217 if (mDesignModeSheetAdded) {
3218 styleSet.RemoveStyleSheet(*cache->DesignModeSheet());
3219 mDesignModeSheetAdded = false;
3220 changed = true;
3222 if (mContentEditableSheetAdded) {
3223 styleSet.RemoveStyleSheet(*cache->ContentEditableSheet());
3224 mContentEditableSheetAdded = false;
3225 changed = true;
3227 if (changed) {
3228 MOZ_ASSERT(mStyleSetFilled);
3229 ApplicableStylesChanged();
3233 void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
3234 MOZ_ASSERT(IsHTMLOrXHTML());
3235 MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
3236 "Caller should ensure we're being rendered");
3238 ServoStyleSet& styleSet = EnsureStyleSet();
3239 auto* cache = GlobalStyleSheetCache::Singleton();
3240 bool changed = false;
3241 if (!mContentEditableSheetAdded) {
3242 styleSet.AppendStyleSheet(*cache->ContentEditableSheet());
3243 mContentEditableSheetAdded = true;
3244 changed = true;
3246 if (mDesignModeSheetAdded != aDesignMode) {
3247 if (mDesignModeSheetAdded) {
3248 styleSet.RemoveStyleSheet(*cache->DesignModeSheet());
3249 } else {
3250 styleSet.AppendStyleSheet(*cache->DesignModeSheet());
3252 mDesignModeSheetAdded = !mDesignModeSheetAdded;
3253 changed = true;
3255 if (changed) {
3256 ApplicableStylesChanged();
3260 void Document::FillStyleSetDocumentSheets() {
3261 ServoStyleSet& styleSet = EnsureStyleSet();
3262 MOZ_ASSERT(styleSet.SheetCount(StyleOrigin::Author) == 0,
3263 "Style set already has document sheets?");
3265 // Sheets are added in reverse order to avoid worst-case time complexity when
3266 // looking up the index of a sheet.
3268 // Note that usually appending is faster (rebuilds less stuff in the
3269 // styleset), but in this case it doesn't matter since we're filling the
3270 // styleset from scratch anyway.
3271 for (StyleSheet* sheet : Reversed(mStyleSheets)) {
3272 if (sheet->IsApplicable()) {
3273 styleSet.AddDocStyleSheet(*sheet);
3277 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
3278 if (aSheet.IsApplicable()) {
3279 styleSet.AddDocStyleSheet(aSheet);
3283 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3284 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
3285 styleSet.AppendStyleSheet(*sheet);
3288 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eAgentSheet]);
3289 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eUserSheet]);
3290 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eAuthorSheet]);
3293 void Document::CompatibilityModeChanged() {
3294 MOZ_ASSERT(IsHTMLOrXHTML());
3295 CSSLoader()->SetCompatibilityMode(mCompatMode);
3297 if (mStyleSet) {
3298 mStyleSet->CompatibilityModeChanged();
3300 if (!mStyleSetFilled) {
3301 MOZ_ASSERT(!mQuirkSheetAdded);
3302 return;
3305 MOZ_ASSERT(mStyleSet);
3306 if (PresShell* presShell = GetPresShell()) {
3307 // Selectors may have become case-sensitive / case-insensitive, the stylist
3308 // has already performed the relevant invalidation.
3309 presShell->EnsureStyleFlush();
3311 if (mQuirkSheetAdded == NeedsQuirksSheet()) {
3312 return;
3314 auto* cache = GlobalStyleSheetCache::Singleton();
3315 StyleSheet* sheet = cache->QuirkSheet();
3316 if (mQuirkSheetAdded) {
3317 mStyleSet->RemoveStyleSheet(*sheet);
3318 } else {
3319 mStyleSet->AppendStyleSheet(*sheet);
3321 mQuirkSheetAdded = !mQuirkSheetAdded;
3322 ApplicableStylesChanged();
3325 void Document::SetCompatibilityMode(nsCompatibility aMode) {
3326 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
3327 "Bad compat mode for XHTML document!");
3329 if (mCompatMode == aMode) {
3330 return;
3332 mCompatMode = aMode;
3333 CompatibilityModeChanged();
3334 // Trigger recomputation of the nsViewportInfo the next time it's queried.
3335 mViewportType = Unknown;
3338 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
3339 uint32_t aSandboxFlags,
3340 nsIChannel* aChannel) {
3341 // If the document permits allow-top-navigation and
3342 // allow-top-navigation-by-user-activation this will permit all top
3343 // navigation.
3344 if (aSandboxFlags != SANDBOXED_NONE &&
3345 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
3346 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
3347 nsContentUtils::ReportToConsole(
3348 nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
3349 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
3350 "BothAllowTopNavigationAndUserActivationPresent");
3352 // If the document is sandboxed (via the HTML5 iframe sandbox
3353 // attribute) and both the allow-scripts and allow-same-origin
3354 // keywords are supplied, the sandboxed document can call into its
3355 // parent document and remove its sandboxing entirely - we print a
3356 // warning to the web console in this case.
3357 if (aSandboxFlags & SANDBOXED_NAVIGATION &&
3358 !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
3359 !(aSandboxFlags & SANDBOXED_ORIGIN)) {
3360 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
3361 MOZ_ASSERT(bc->IsInProcess());
3363 RefPtr<BrowsingContext> parentBC = bc->GetParent();
3364 if (!parentBC || !parentBC->IsInProcess()) {
3365 // If parent document is not in process, then by construction it
3366 // cannot be same origin.
3367 return;
3370 // Don't warn if our parent is not the top-level document.
3371 if (!parentBC->IsTopContent()) {
3372 return;
3375 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
3376 MOZ_ASSERT(parentDocShell);
3378 nsCOMPtr<nsIChannel> parentChannel;
3379 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
3380 if (!parentChannel) {
3381 return;
3383 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
3384 if (NS_FAILED(rv)) {
3385 return;
3388 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
3389 nsCOMPtr<nsIURI> iframeUri;
3390 parentChannel->GetURI(getter_AddRefs(iframeUri));
3391 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3392 "Iframe Sandbox"_ns, parentDocument,
3393 nsContentUtils::eSECURITY_PROPERTIES,
3394 "BothAllowScriptsAndSameOriginPresent",
3395 nsTArray<nsString>(), iframeUri);
3399 bool Document::IsSynthesized() {
3400 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
3401 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
3404 // static
3405 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
3406 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
3407 return principal && (principal->IsSystemPrincipal() ||
3408 principal->GetIsAddonOrExpandedAddonPrincipal());
3411 static void CheckIsBadPolicy(nsILoadInfo::CrossOriginOpenerPolicy aPolicy,
3412 BrowsingContext* aContext, nsIChannel* aChannel) {
3413 #if defined(EARLY_BETA_OR_EARLIER)
3414 auto requireCORP =
3415 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
3417 if (aContext->GetOpenerPolicy() == aPolicy ||
3418 (aContext->GetOpenerPolicy() != requireCORP && aPolicy != requireCORP)) {
3419 return;
3422 nsCOMPtr<nsIURI> uri;
3423 bool hasURI = NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(uri)));
3425 bool isViewSource = hasURI && uri->SchemeIs("view-source");
3427 nsCString contentType;
3428 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3429 bool isPDFJS = bag &&
3430 NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3431 contentType)) &&
3432 contentType.EqualsLiteral(APPLICATION_PDF);
3434 MOZ_DIAGNOSTIC_ASSERT(!isViewSource,
3435 "Bug 1834864: Assert due to view-source.");
3436 MOZ_DIAGNOSTIC_ASSERT(!isPDFJS, "Bug 1834864: Assert due to pdfjs.");
3437 MOZ_DIAGNOSTIC_ASSERT(aPolicy == requireCORP,
3438 "Assert due to clearing REQUIRE_CORP.");
3439 MOZ_DIAGNOSTIC_ASSERT(aContext->GetOpenerPolicy() == requireCORP,
3440 "Assert due to setting REQUIRE_CORP.");
3441 #endif // defined(EARLY_BETA_OR_EARLIER)
3444 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
3445 nsILoadGroup* aLoadGroup,
3446 nsISupports* aContainer,
3447 nsIStreamListener** aDocListener,
3448 bool aReset) {
3449 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
3450 nsCOMPtr<nsIURI> uri;
3451 aChannel->GetURI(getter_AddRefs(uri));
3452 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
3453 ("DOCUMENT %p StartDocumentLoad %s", this,
3454 uri ? uri->GetSpecOrDefault().get() : ""));
3457 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
3458 "Bad readyState");
3459 SetReadyStateInternal(READYSTATE_LOADING);
3461 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
3462 mLoadedAsData = true;
3463 SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true);
3464 // We need to disable script & style loading in this case.
3465 // We leave them disabled even in EndLoad(), and let anyone
3466 // who puts the document on display to worry about enabling.
3468 // Do not load/process scripts when loading as data
3469 ScriptLoader()->SetEnabled(false);
3471 // styles
3472 CSSLoader()->SetEnabled(
3473 false); // Do not load/process styles when loading as data
3474 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
3475 // Allow CSS, but not scripts
3476 ScriptLoader()->SetEnabled(false);
3479 mMayStartLayout = false;
3480 MOZ_ASSERT(!mReadyForIdle,
3481 "We should never hit DOMContentLoaded before this point");
3483 if (aReset) {
3484 Reset(aChannel, aLoadGroup);
3487 nsAutoCString contentType;
3488 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3489 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3490 contentType))) ||
3491 NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
3492 // XXX this is only necessary for viewsource:
3493 nsACString::const_iterator start, end, semicolon;
3494 contentType.BeginReading(start);
3495 contentType.EndReading(end);
3496 semicolon = start;
3497 FindCharInReadable(';', semicolon, end);
3498 SetContentType(Substring(start, semicolon));
3501 RetrieveRelevantHeaders(aChannel);
3503 mChannel = aChannel;
3504 RecomputeResistFingerprinting();
3505 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
3506 if (inStrmChan) {
3507 bool isSrcdocChannel;
3508 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
3509 if (isSrcdocChannel) {
3510 mIsSrcdocDocument = true;
3514 if (mChannel) {
3515 nsLoadFlags loadFlags;
3516 mChannel->GetLoadFlags(&loadFlags);
3517 bool isDocument = false;
3518 mChannel->GetIsDocument(&isDocument);
3519 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
3520 IsSynthesized() && XRE_IsContentProcess()) {
3521 ContentChild::UpdateCookieStatus(mChannel);
3524 // Store the security info for future use.
3525 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
3528 // If this document is being loaded by a docshell, copy its sandbox flags
3529 // to the document, and store the fullscreen enabled flag. These are
3530 // immutable after being set here.
3531 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
3533 // If this is an error page, don't inherit sandbox flags
3534 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3535 if (docShell && !loadInfo->GetLoadErrorPage()) {
3536 mSandboxFlags = loadInfo->GetSandboxFlags();
3537 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
3540 // Set the opener policy for the top level content document.
3541 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
3542 nsILoadInfo::CrossOriginOpenerPolicy policy =
3543 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
3544 if (IsTopLevelContentDocument() && httpChan &&
3545 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
3546 docShell->GetBrowsingContext()) {
3547 CheckIsBadPolicy(policy, docShell->GetBrowsingContext(), aChannel);
3549 // Setting the opener policy on a discarded context has no effect.
3550 Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
3553 // The CSP directives upgrade-insecure-requests as well as
3554 // block-all-mixed-content not only apply to the toplevel document,
3555 // but also to nested documents. The loadInfo of a subdocument
3556 // load already holds the correct flag, so let's just set it here
3557 // on the document. Please note that we set the appropriate preload
3558 // bits just for the sake of completeness here, because the preloader
3559 // does not reach into subdocuments.
3560 mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
3561 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3562 mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
3563 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3565 // HTTPS-Only Mode flags
3566 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
3567 // sub-resources and sub-documents.
3568 mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
3570 nsresult rv = InitReferrerInfo(aChannel);
3571 NS_ENSURE_SUCCESS(rv, rv);
3573 rv = InitCOEP(aChannel);
3574 NS_ENSURE_SUCCESS(rv, rv);
3576 // HACK: Calling EnsureIPCPoliciesRead() here will parse the CSP using the
3577 // context's current mSelfURI (which is still the previous mSelfURI),
3578 // bypassing some internal bugs with 'self' and iframe inheritance.
3579 // Not calling it here results in the mSelfURI being the current mSelfURI and
3580 // not the previous which breaks said inheritance.
3581 // https://bugzilla.mozilla.org/show_bug.cgi?id=1793560#ch-8
3582 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3583 if (cspToInherit) {
3584 cspToInherit->EnsureIPCPoliciesRead();
3587 rv = InitCSP(aChannel);
3588 NS_ENSURE_SUCCESS(rv, rv);
3590 // Initialize FeaturePolicy
3591 rv = InitFeaturePolicy(aChannel);
3592 NS_ENSURE_SUCCESS(rv, rv);
3594 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3595 NS_ENSURE_SUCCESS(rv, rv);
3597 // Generally XFO and CSP frame-ancestors is handled within
3598 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3599 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3600 nsContentPolicyType internalContentType =
3601 loadInfo->InternalContentPolicyType();
3602 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3603 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3604 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3606 nsresult status;
3607 aChannel->GetStatus(&status);
3608 if (status == NS_ERROR_XFO_VIOLATION) {
3609 // stop! ERROR page!
3610 // But before we have to reset the principal of the document
3611 // because the onload() event fires before the error page
3612 // is displayed and we do not want the enclosing document
3613 // to access the contentDocument.
3614 RefPtr<NullPrincipal> nullPrincipal =
3615 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3616 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3617 // and also GetInnerWindow() is still null at this point, before
3618 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3619 // within Document.cpp
3620 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3621 SetPrincipals(nullPrincipal, nullPrincipal);
3625 return NS_OK;
3628 void Document::SetLoadedAsData(bool aLoadedAsData,
3629 bool aConsiderForMemoryReporting) {
3630 mLoadedAsData = aLoadedAsData;
3631 if (aConsiderForMemoryReporting) {
3632 nsIGlobalObject* global = GetScopeObject();
3633 if (global) {
3634 if (nsPIDOMWindowInner* window = global->GetAsInnerWindow()) {
3635 nsGlobalWindowInner::Cast(window)
3636 ->RegisterDataDocumentForMemoryReporting(this);
3642 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3644 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
3646 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3647 return mPreloadCSP;
3650 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3651 mPreloadCSP = aPreloadCSP;
3654 void Document::GetCspJSON(nsString& aJSON) {
3655 aJSON.Truncate();
3657 if (!mCSP) {
3658 dom::CSPPolicies jsonPolicies;
3659 jsonPolicies.ToJSON(aJSON);
3660 return;
3662 mCSP->ToJSON(aJSON);
3665 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3666 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3667 nsAutoString messageTag;
3668 aMessages[i]->GetTag(messageTag);
3670 nsAutoString category;
3671 aMessages[i]->GetCategory(category);
3673 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3674 NS_ConvertUTF16toUTF8(category), this,
3675 nsContentUtils::eSECURITY_PROPERTIES,
3676 NS_ConvertUTF16toUTF8(messageTag).get());
3680 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3681 nsresult rv = NS_OK;
3682 if (!aSpeculative) {
3683 // 1) apply settings from regular CSP
3684 if (mCSP) {
3685 // Set up 'block-all-mixed-content' if not already inherited
3686 // from the parent context or set by any other CSP.
3687 if (!mBlockAllMixedContent) {
3688 bool block = false;
3689 rv = mCSP->GetBlockAllMixedContent(&block);
3690 NS_ENSURE_SUCCESS_VOID(rv);
3691 mBlockAllMixedContent = block;
3693 if (!mBlockAllMixedContentPreloads) {
3694 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3697 // Set up 'upgrade-insecure-requests' if not already inherited
3698 // from the parent context or set by any other CSP.
3699 if (!mUpgradeInsecureRequests) {
3700 bool upgrade = false;
3701 rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
3702 NS_ENSURE_SUCCESS_VOID(rv);
3703 mUpgradeInsecureRequests = upgrade;
3705 if (!mUpgradeInsecurePreloads) {
3706 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3708 // Update csp settings in the parent process
3709 if (auto* wgc = GetWindowGlobalChild()) {
3710 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3711 mUpgradeInsecureRequests);
3714 return;
3717 // 2) apply settings from speculative csp
3718 if (mPreloadCSP) {
3719 if (!mBlockAllMixedContentPreloads) {
3720 bool block = false;
3721 rv = mPreloadCSP->GetBlockAllMixedContent(&block);
3722 NS_ENSURE_SUCCESS_VOID(rv);
3723 mBlockAllMixedContent = block;
3725 if (!mUpgradeInsecurePreloads) {
3726 bool upgrade = false;
3727 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
3728 NS_ENSURE_SUCCESS_VOID(rv);
3729 mUpgradeInsecurePreloads = upgrade;
3734 nsresult Document::InitCSP(nsIChannel* aChannel) {
3735 MOZ_ASSERT(!mScriptGlobalObject,
3736 "CSP must be initialized before mScriptGlobalObject is set!");
3738 // If this is a data document - no need to set CSP.
3739 if (mLoadedAsData) {
3740 return NS_OK;
3743 // If this is an image, no need to set a CSP. Otherwise SVG images
3744 // served with a CSP might block internally applied inline styles.
3745 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3746 if (loadInfo->GetExternalContentPolicyType() ==
3747 ExtContentPolicy::TYPE_IMAGE ||
3748 loadInfo->GetExternalContentPolicyType() ==
3749 ExtContentPolicy::TYPE_IMAGESET) {
3750 return NS_OK;
3753 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3755 // If there is a CSP that needs to be inherited from whatever
3756 // global is considered the client of the document fetch then
3757 // we query it here from the loadinfo in case the newly created
3758 // document needs to inherit the CSP. See:
3759 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3760 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
3761 if (inheritedCSP) {
3762 mCSP = loadInfo->GetCspToInherit();
3765 // If there is no CSP to inherit, then we create a new CSP here so
3766 // that history entries always have the right reference in case a
3767 // Meta CSP gets dynamically added after the history entry has
3768 // already been created.
3769 if (!mCSP) {
3770 mCSP = new nsCSPContext();
3773 // Always overwrite the requesting context of the CSP so that any new
3774 // 'self' keyword added to an inherited CSP translates correctly.
3775 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3776 if (NS_WARN_IF(NS_FAILED(rv))) {
3777 return rv;
3780 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3782 nsCOMPtr<nsIHttpChannel> httpChannel;
3783 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3784 if (NS_WARN_IF(NS_FAILED(rv))) {
3785 return rv;
3788 if (httpChannel) {
3789 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3790 tCspHeaderValue);
3792 Unused << httpChannel->GetResponseHeader(
3793 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3795 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3796 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3798 // Check if this is a document from a WebExtension.
3799 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3800 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3802 // If there's no CSP to apply, go ahead and return early
3803 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
3804 cspROHeaderValue.IsEmpty()) {
3805 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3806 nsCOMPtr<nsIURI> chanURI;
3807 aChannel->GetURI(getter_AddRefs(chanURI));
3808 nsAutoCString aspec;
3809 chanURI->GetAsciiSpec(aspec);
3810 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3811 ("no CSP for document, %s", aspec.get()));
3814 return NS_OK;
3817 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3818 ("Document is an add-on or CSP header specified %p", this));
3820 // ----- if the doc is an addon, apply its CSP.
3821 if (addonPolicy) {
3822 mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
3824 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3825 // Bug 1548468: Move CSP off ExpandedPrincipal
3826 // Currently the LoadInfo holds the source of truth for every resource load
3827 // because LoadInfo::GetCsp() queries the CSP from an ExpandedPrincipal
3828 // (and not from the Client) if the load was triggered by an extension.
3829 auto* basePrin = BasePrincipal::Cast(principal);
3830 if (basePrin->Is<ExpandedPrincipal>()) {
3831 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3835 // ----- if there's a full-strength CSP header, apply it.
3836 if (!cspHeaderValue.IsEmpty()) {
3837 mHasCSPDeliveredThroughHeader = true;
3838 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3839 NS_ENSURE_SUCCESS(rv, rv);
3842 // ----- if there's a report-only CSP header, apply it.
3843 if (!cspROHeaderValue.IsEmpty()) {
3844 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3845 NS_ENSURE_SUCCESS(rv, rv);
3848 // ----- Enforce sandbox policy if supplied in CSP header
3849 // The document may already have some sandbox flags set (e.g. if the document
3850 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3851 // directive, intersect the CSP sandbox flags with the existing flags. This
3852 // corresponds to the _least_ permissive policy.
3853 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3854 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3855 NS_ENSURE_SUCCESS(rv, rv);
3857 // Probably the iframe sandbox attribute already caused the creation of a
3858 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3859 // and no one has been created yet.
3860 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3861 !(mSandboxFlags & SANDBOXED_ORIGIN);
3863 mSandboxFlags |= cspSandboxFlags;
3865 if (needNewNullPrincipal) {
3866 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3867 // Skip setting the content blocking allowlist principal to NullPrincipal.
3868 // The principal is only used to enable/disable trackingprotection via
3869 // permission and can be shared with the top level sandboxed site.
3870 // See Bug 1654546.
3871 SetPrincipals(principal, principal);
3874 ApplySettingsFromCSP(false);
3875 return NS_OK;
3878 static Document* GetInProcessParentDocumentFrom(BrowsingContext* aContext) {
3879 BrowsingContext* parentContext = aContext->GetParent();
3880 if (!parentContext) {
3881 return nullptr;
3884 WindowContext* windowContext = parentContext->GetCurrentWindowContext();
3885 if (!windowContext) {
3886 return nullptr;
3889 return windowContext->GetDocument();
3892 already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
3893 BrowsingContext* browsingContext = GetBrowsingContext();
3894 if (!browsingContext) {
3895 return nullptr;
3897 if (!browsingContext->IsContentSubframe()) {
3898 return nullptr;
3901 HTMLIFrameElement* iframe =
3902 HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement());
3903 if (iframe) {
3904 return do_AddRef(iframe->FeaturePolicy());
3907 if (XRE_IsParentProcess()) {
3908 return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy());
3911 if (Document* parentDocument =
3912 GetInProcessParentDocumentFrom(browsingContext)) {
3913 return do_AddRef(parentDocument->FeaturePolicy());
3916 WindowContext* windowContext = browsingContext->GetCurrentWindowContext();
3917 if (!windowContext) {
3918 return nullptr;
3921 WindowGlobalChild* child = windowContext->GetWindowGlobalChild();
3922 if (!child) {
3923 return nullptr;
3926 return do_AddRef(child->GetContainerFeaturePolicy());
3929 void Document::InitFeaturePolicy() {
3930 MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created");
3932 mFeaturePolicy->ResetDeclaredPolicy();
3934 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
3936 RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
3937 if (parentPolicy) {
3938 // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
3939 mFeaturePolicy->InheritPolicy(parentPolicy);
3940 mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
3944 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
3945 InitFeaturePolicy();
3947 // We don't want to parse the http Feature-Policy header if this pref is off.
3948 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
3949 return NS_OK;
3952 nsCOMPtr<nsIHttpChannel> httpChannel;
3953 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3954 if (NS_WARN_IF(NS_FAILED(rv))) {
3955 return rv;
3958 if (!httpChannel) {
3959 return NS_OK;
3962 // query the policy from the header
3963 nsAutoCString value;
3964 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
3965 if (NS_SUCCEEDED(rv)) {
3966 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
3967 NodePrincipal(), nullptr);
3970 return NS_OK;
3973 void Document::EnsureNotEnteringAndExitFullscreen() {
3974 Document::ClearPendingFullscreenRequests(this);
3975 if (GetFullscreenElement()) {
3976 Document::AsyncExitFullscreen(this);
3980 void Document::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
3981 mReferrerInfo = aReferrerInfo;
3982 mCachedReferrerInfoForInternalCSSAndSVGResources = nullptr;
3983 mCachedURLData = nullptr;
3986 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
3987 MOZ_ASSERT(mReferrerInfo);
3988 MOZ_ASSERT(mPreloadReferrerInfo);
3990 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
3991 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
3992 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
3993 // they have an opaque origin.
3994 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
3995 if (BrowsingContext* bc = GetBrowsingContext()) {
3996 // At this point the document is not fully created and mParentDocument has
3997 // not been set yet,
3998 Document* parentDoc = bc->GetEmbedderElement()
3999 ? bc->GetEmbedderElement()->OwnerDoc()
4000 : nullptr;
4001 if (parentDoc) {
4002 SetReferrerInfo(parentDoc->GetReferrerInfo());
4003 mPreloadReferrerInfo = mReferrerInfo;
4004 return NS_OK;
4007 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
4008 "srcdoc without null principal as toplevel!");
4012 nsCOMPtr<nsIHttpChannel> httpChannel;
4013 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4014 if (NS_WARN_IF(NS_FAILED(rv))) {
4015 return rv;
4018 if (!httpChannel) {
4019 return NS_OK;
4022 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo()) {
4023 SetReferrerInfo(referrerInfo);
4026 // Override policy if we get one from Referrerr-Policy header
4027 mozilla::dom::ReferrerPolicy policy =
4028 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
4029 nsCOMPtr<nsIReferrerInfo> clone =
4030 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
4031 ->CloneWithNewPolicy(policy);
4032 SetReferrerInfo(clone);
4033 mPreloadReferrerInfo = mReferrerInfo;
4034 return NS_OK;
4037 nsresult Document::InitCOEP(nsIChannel* aChannel) {
4038 nsCOMPtr<nsIHttpChannel> httpChannel;
4039 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4040 if (NS_FAILED(rv)) {
4041 return NS_OK;
4044 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
4046 if (!intChannel) {
4047 return NS_OK;
4050 nsILoadInfo::CrossOriginEmbedderPolicy policy =
4051 nsILoadInfo::EMBEDDER_POLICY_NULL;
4052 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(
4053 mTrials.IsEnabled(OriginTrial::CoepCredentialless), &policy))) {
4054 mEmbedderPolicy = Some(policy);
4057 return NS_OK;
4060 void Document::StopDocumentLoad() {
4061 if (mParser) {
4062 mParserAborted = true;
4063 mParser->Terminate();
4067 void Document::SetDocumentURI(nsIURI* aURI) {
4068 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
4069 mDocumentURI = aURI;
4070 nsIURI* newBase = GetDocBaseURI();
4072 mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI);
4074 bool equalBases = false;
4075 // Changing just the ref of a URI does not change how relative URIs would
4076 // resolve wrt to it, so we can treat the bases as equal as long as they're
4077 // equal ignoring the ref.
4078 if (oldBase && newBase) {
4079 oldBase->EqualsExceptRef(newBase, &equalBases);
4080 } else {
4081 equalBases = !oldBase && !newBase;
4084 // If this is the first time we're setting the document's URI, set the
4085 // document's original URI.
4086 if (!mOriginalURI) mOriginalURI = mDocumentURI;
4088 // If changing the document's URI changed the base URI of the document, we
4089 // need to refresh the hrefs of all the links on the page.
4090 if (!equalBases) {
4091 mCachedURLData = nullptr;
4092 RefreshLinkHrefs();
4095 // Recalculate our base domain
4096 mBaseDomain.Truncate();
4097 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
4098 if (thirdPartyUtil) {
4099 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
4102 // Tell our WindowGlobalParent that the document's URI has been changed.
4103 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
4104 wgc->SetDocumentURI(mDocumentURI);
4108 static void GetFormattedTimeString(PRTime aTime,
4109 nsAString& aFormattedTimeString) {
4110 PRExplodedTime prtime;
4111 PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
4112 // "MM/DD/YYYY hh:mm:ss"
4113 char formatedTime[24];
4114 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
4115 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
4116 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
4117 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
4118 } else {
4119 // If we for whatever reason failed to find the last modified time
4120 // (or even the current time), fall back to what NS4.x returned.
4121 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
4125 void Document::GetLastModified(nsAString& aLastModified) const {
4126 if (!mLastModified.IsEmpty()) {
4127 aLastModified.Assign(mLastModified);
4128 } else {
4129 GetFormattedTimeString(PR_Now(), aLastModified);
4133 static void IncrementExpandoGeneration(Document& aDoc) {
4134 ++aDoc.mExpandoAndGeneration.generation;
4137 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
4138 MOZ_ASSERT(
4139 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
4140 "Only put elements that need to be exposed as document['name'] in "
4141 "the named table.");
4143 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
4145 // Null for out-of-memory
4146 if (entry) {
4147 if (!entry->HasNameElement() &&
4148 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4149 IncrementExpandoGeneration(*this);
4151 entry->AddNameElement(this, aElement);
4155 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
4156 // Speed up document teardown
4157 if (mIdentifierMap.Count() == 0) return;
4159 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
4160 if (!entry) // Could be false if the element was anonymous, hence never added
4161 return;
4163 entry->RemoveNameElement(aElement);
4164 if (!entry->HasNameElement() &&
4165 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4166 IncrementExpandoGeneration(*this);
4170 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
4171 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
4173 if (entry) { /* True except on OOM */
4174 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4175 !entry->HasNameElement() &&
4176 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4177 IncrementExpandoGeneration(*this);
4179 entry->AddIdElement(aElement);
4183 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
4184 NS_ASSERTION(aId, "huhwhatnow?");
4186 // Speed up document teardown
4187 if (mIdentifierMap.Count() == 0) {
4188 return;
4191 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
4192 if (!entry) // Can be null for XML elements with changing ids.
4193 return;
4195 entry->RemoveIdElement(aElement);
4196 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4197 !entry->HasNameElement() &&
4198 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4199 IncrementExpandoGeneration(*this);
4201 if (entry->IsEmpty()) {
4202 mIdentifierMap.RemoveEntry(entry);
4206 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
4207 bool aPreload) {
4208 ReferrerPolicyEnum policy =
4209 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
4210 // The empty string "" corresponds to no referrer policy, causing a fallback
4211 // to a referrer policy defined elsewhere.
4212 if (policy == ReferrerPolicy::_empty) {
4213 return;
4216 MOZ_ASSERT(mReferrerInfo);
4217 MOZ_ASSERT(mPreloadReferrerInfo);
4219 if (aPreload) {
4220 mPreloadReferrerInfo =
4221 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
4222 ->CloneWithNewPolicy(policy);
4223 } else {
4224 nsCOMPtr<nsIReferrerInfo> clone =
4225 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
4226 ->CloneWithNewPolicy(policy);
4227 SetReferrerInfo(clone);
4231 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
4232 nsIPrincipal* aNewPartitionedPrincipal) {
4233 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
4234 if (aNewPrincipal && mAllowDNSPrefetch &&
4235 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
4236 if (aNewPrincipal->SchemeIs("https")) {
4237 mAllowDNSPrefetch = false;
4241 mCSSLoader->DeregisterFromSheetCache();
4243 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
4244 mPartitionedPrincipal = aNewPartitionedPrincipal;
4246 mCachedURLData = nullptr;
4248 mCSSLoader->RegisterInSheetCache();
4250 RecomputeResistFingerprinting();
4252 #ifdef DEBUG
4253 // Validate that the docgroup is set correctly by calling its getter and
4254 // triggering its sanity check.
4256 // If we're setting the principal to null, we don't want to perform the check,
4257 // as the document is entering an intermediate state where it does not have a
4258 // principal. It will be given another real principal shortly which we will
4259 // check. It's not unsafe to have a document which has a null principal in the
4260 // same docgroup as another document, so this should not be a problem.
4261 if (aNewPrincipal) {
4262 GetDocGroup();
4264 #endif
4267 #ifdef DEBUG
4268 void Document::AssertDocGroupMatchesKey() const {
4269 // Sanity check that we have an up-to-date and accurate docgroup
4270 // We only check if the principal when we can get the browsing context.
4272 // Note that we can be invoked during cycle collection, so we need to handle
4273 // the browsingcontext being partially unlinked - normally you shouldn't
4274 // null-check `Group()` as it shouldn't return nullptr.
4275 if (!GetBrowsingContext() || !GetBrowsingContext()->Group()) {
4276 return;
4279 if (mDocGroup && mDocGroup->GetBrowsingContextGroup()) {
4280 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
4281 GetBrowsingContext()->Group());
4283 // GetKey() can fail, e.g. after the TLD service has shut down.
4284 nsAutoCString docGroupKey;
4285 nsresult rv = mozilla::dom::DocGroup::GetKey(
4286 NodePrincipal(),
4287 GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated(),
4288 docGroupKey);
4289 if (NS_SUCCEEDED(rv)) {
4290 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
4294 #endif
4296 nsresult Document::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) const {
4297 return SchedulerGroup::Dispatch(std::move(aRunnable));
4300 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
4301 bool aIsTracking) {
4302 if (aIsTracking) {
4303 mTrackingScripts.Insert(aURL);
4304 } else {
4305 MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
4309 bool Document::IsScriptTracking(JSContext* aCx) const {
4310 JS::AutoFilename filename;
4311 if (!JS::DescribeScriptedCaller(aCx, &filename)) {
4312 return false;
4314 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
4317 void Document::GetContentType(nsAString& aContentType) {
4318 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
4321 void Document::SetContentType(const nsACString& aContentType) {
4322 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
4323 aContentType.EqualsLiteral("application/xhtml+xml")) {
4324 mDefaultElementType = kNameSpaceID_XHTML;
4327 mCachedEncoder = nullptr;
4328 mContentType = aContentType;
4331 bool Document::HasPendingInitialTranslation() {
4332 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
4335 bool Document::HasPendingL10nMutations() const {
4336 return mDocumentL10n && mDocumentL10n->HasPendingMutations();
4339 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
4340 JS::Rooted<JSObject*> object(aCx, aObject);
4341 nsCOMPtr<nsIPrincipal> callerPrincipal =
4342 nsContentUtils::SubjectPrincipal(aCx);
4343 nsGlobalWindowInner* win = xpc::WindowOrNull(object);
4344 bool allowed = false;
4345 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
4346 &allowed);
4347 return allowed;
4350 void Document::LocalizationLinkAdded(Element* aLinkElement) {
4351 if (!AllowsL10n()) {
4352 return;
4355 nsAutoString href;
4356 aLinkElement->GetAttr(nsGkAtoms::href, href);
4358 if (!mDocumentL10n) {
4359 Element* elem = GetDocumentElement();
4360 MOZ_DIAGNOSTIC_ASSERT(elem);
4362 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
4363 mDocumentL10n = DocumentL10n::Create(this, isSync);
4364 if (NS_WARN_IF(!mDocumentL10n)) {
4365 return;
4369 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
4371 if (mReadyState >= READYSTATE_INTERACTIVE) {
4372 nsContentUtils::AddScriptRunner(NewRunnableMethod(
4373 "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
4374 &DocumentL10n::TriggerInitialTranslation));
4375 } else {
4376 if (!mDocumentL10n->mBlockingLayout) {
4377 // Our initial translation is going to block layout start. Make sure
4378 // we don't fire the load event until after that stops happening and
4379 // layout has a chance to start.
4380 BlockOnload();
4381 mDocumentL10n->mBlockingLayout = true;
4386 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
4387 if (!AllowsL10n()) {
4388 return;
4391 if (mDocumentL10n) {
4392 nsAutoString href;
4393 aLinkElement->GetAttr(nsGkAtoms::href, href);
4394 uint32_t remaining =
4395 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
4396 if (remaining == 0) {
4397 if (mDocumentL10n->mBlockingLayout) {
4398 mDocumentL10n->mBlockingLayout = false;
4399 UnblockOnload(/* aFireSync = */ false);
4401 mDocumentL10n = nullptr;
4407 * This method should be called once the end of the l10n
4408 * resource container has been parsed.
4410 * In XUL this is the end of the first </linkset>,
4411 * In XHTML/HTML this is the end of </head>.
4413 * This milestone is used to allow for batch
4414 * localization context I/O and building done
4415 * once when all resources in the document have been
4416 * collected.
4418 void Document::OnL10nResourceContainerParsed() {
4419 // XXX: This is a scaffolding for where we might inject prefetch
4420 // in bug 1717241.
4423 void Document::OnParsingCompleted() {
4424 // Let's call it again, in case the resource
4425 // container has not been closed, and only
4426 // now we're closing the document.
4427 OnL10nResourceContainerParsed();
4429 if (mDocumentL10n) {
4430 RefPtr<DocumentL10n> l10n = mDocumentL10n;
4431 l10n->TriggerInitialTranslation();
4435 void Document::InitialTranslationCompleted(bool aL10nCached) {
4436 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4437 // This means we blocked the load event in LocalizationLinkAdded. It's
4438 // important that the load blocker removal here be async, because our caller
4439 // will notify the content sink after us, and we want the content sync's
4440 // work to happen before the load event fires.
4441 mDocumentL10n->mBlockingLayout = false;
4442 UnblockOnload(/* aFireSync = */ false);
4445 mL10nProtoElements.Clear();
4447 nsXULPrototypeDocument* proto = GetPrototype();
4448 if (proto) {
4449 proto->SetIsL10nCached(aL10nCached);
4453 bool Document::AllowsL10n() const {
4454 if (IsStaticDocument()) {
4455 // We don't allow l10n on static documents, because the nodes are already
4456 // cloned translated, and static docs don't get parsed so we never
4457 // TriggerInitialTranslation, etc, so a load blocker would keep hanging
4458 // forever.
4459 return false;
4461 bool allowed = false;
4462 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4463 return allowed;
4466 bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
4467 JSObject* /*unused*/
4469 MOZ_ASSERT(NS_IsMainThread());
4471 return nsContentUtils::IsSystemCaller(aCx) ||
4472 StaticPrefs::dom_animations_api_timelines_enabled();
4475 DocumentTimeline* Document::Timeline() {
4476 if (!mDocumentTimeline) {
4477 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4480 return mDocumentTimeline;
4483 SVGSVGElement* Document::GetSVGRootElement() const {
4484 Element* root = GetRootElement();
4485 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4486 return nullptr;
4488 return static_cast<SVGSVGElement*>(root);
4491 /* Return true if the document is in the focused top-level window, and is an
4492 * ancestor of the focused DOMWindow. */
4493 bool Document::HasFocus(ErrorResult& rv) const {
4494 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4495 if (!fm) {
4496 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4497 return false;
4500 BrowsingContext* bc = GetBrowsingContext();
4501 if (!bc) {
4502 return false;
4505 if (!fm->IsInActiveWindow(bc)) {
4506 return false;
4509 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
4512 bool Document::ThisDocumentHasFocus() const {
4513 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4514 return fm && fm->GetFocusedWindow() &&
4515 fm->GetFocusedWindow()->GetExtantDoc() == this;
4518 void Document::GetDesignMode(nsAString& aDesignMode) {
4519 if (IsInDesignMode()) {
4520 aDesignMode.AssignLiteral("on");
4521 } else {
4522 aDesignMode.AssignLiteral("off");
4526 void Document::SetDesignMode(const nsAString& aDesignMode,
4527 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4528 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4531 static void NotifyEditableStateChange(Document& aDoc) {
4532 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4533 nsMutationGuard g;
4534 #endif
4535 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4536 node = node->GetNextNode(&aDoc)) {
4537 if (auto* element = Element::FromNode(node)) {
4538 element->UpdateEditableState(true);
4541 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4544 void Document::SetDesignMode(const nsAString& aDesignMode,
4545 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4546 ErrorResult& rv) {
4547 if (aSubjectPrincipal.isSome() &&
4548 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4549 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4550 return;
4552 const bool editableMode = IsInDesignMode();
4553 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4554 SetEditableFlag(!editableMode);
4555 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4556 // state of all descendant elements of it. Update that now.
4557 NotifyEditableStateChange(*this);
4558 rv = EditingStateChanged();
4562 nsCommandManager* Document::GetMidasCommandManager() {
4563 // check if we have it cached
4564 if (mMidasCommandManager) {
4565 return mMidasCommandManager;
4568 nsPIDOMWindowOuter* window = GetWindow();
4569 if (!window) {
4570 return nullptr;
4573 nsIDocShell* docshell = window->GetDocShell();
4574 if (!docshell) {
4575 return nullptr;
4578 mMidasCommandManager = docshell->GetCommandManager();
4579 return mMidasCommandManager;
4582 // static
4583 void Document::EnsureInitializeInternalCommandDataHashtable() {
4584 if (sInternalCommandDataHashtable) {
4585 return;
4587 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
4588 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4589 // clang-format off
4590 sInternalCommandDataHashtable->InsertOrUpdate(
4591 u"bold"_ns,
4592 InternalCommandData(
4593 "cmd_bold",
4594 Command::FormatBold,
4595 ExecCommandParam::Ignore,
4596 StyleUpdatingCommand::GetInstance,
4597 CommandOnTextEditor::Disabled));
4598 sInternalCommandDataHashtable->InsertOrUpdate(
4599 u"italic"_ns,
4600 InternalCommandData(
4601 "cmd_italic",
4602 Command::FormatItalic,
4603 ExecCommandParam::Ignore,
4604 StyleUpdatingCommand::GetInstance,
4605 CommandOnTextEditor::Disabled));
4606 sInternalCommandDataHashtable->InsertOrUpdate(
4607 u"underline"_ns,
4608 InternalCommandData(
4609 "cmd_underline",
4610 Command::FormatUnderline,
4611 ExecCommandParam::Ignore,
4612 StyleUpdatingCommand::GetInstance,
4613 CommandOnTextEditor::Disabled));
4614 sInternalCommandDataHashtable->InsertOrUpdate(
4615 u"strikethrough"_ns,
4616 InternalCommandData(
4617 "cmd_strikethrough",
4618 Command::FormatStrikeThrough,
4619 ExecCommandParam::Ignore,
4620 StyleUpdatingCommand::GetInstance,
4621 CommandOnTextEditor::Disabled));
4622 sInternalCommandDataHashtable->InsertOrUpdate(
4623 u"subscript"_ns,
4624 InternalCommandData(
4625 "cmd_subscript",
4626 Command::FormatSubscript,
4627 ExecCommandParam::Ignore,
4628 StyleUpdatingCommand::GetInstance,
4629 CommandOnTextEditor::Disabled));
4630 sInternalCommandDataHashtable->InsertOrUpdate(
4631 u"superscript"_ns,
4632 InternalCommandData(
4633 "cmd_superscript",
4634 Command::FormatSuperscript,
4635 ExecCommandParam::Ignore,
4636 StyleUpdatingCommand::GetInstance,
4637 CommandOnTextEditor::Disabled));
4638 sInternalCommandDataHashtable->InsertOrUpdate(
4639 u"cut"_ns,
4640 InternalCommandData(
4641 "cmd_cut",
4642 Command::Cut,
4643 ExecCommandParam::Ignore,
4644 CutCommand::GetInstance,
4645 CommandOnTextEditor::Enabled));
4646 sInternalCommandDataHashtable->InsertOrUpdate(
4647 u"copy"_ns,
4648 InternalCommandData(
4649 "cmd_copy",
4650 Command::Copy,
4651 ExecCommandParam::Ignore,
4652 CopyCommand::GetInstance,
4653 CommandOnTextEditor::Enabled));
4654 sInternalCommandDataHashtable->InsertOrUpdate(
4655 u"paste"_ns,
4656 InternalCommandData(
4657 "cmd_paste",
4658 Command::Paste,
4659 ExecCommandParam::Ignore,
4660 PasteCommand::GetInstance,
4661 CommandOnTextEditor::Enabled));
4662 sInternalCommandDataHashtable->InsertOrUpdate(
4663 u"delete"_ns,
4664 InternalCommandData(
4665 "cmd_deleteCharBackward",
4666 Command::DeleteCharBackward,
4667 ExecCommandParam::Ignore,
4668 DeleteCommand::GetInstance,
4669 CommandOnTextEditor::Enabled));
4670 sInternalCommandDataHashtable->InsertOrUpdate(
4671 u"forwarddelete"_ns,
4672 InternalCommandData(
4673 "cmd_deleteCharForward",
4674 Command::DeleteCharForward,
4675 ExecCommandParam::Ignore,
4676 DeleteCommand::GetInstance,
4677 CommandOnTextEditor::Enabled));
4678 sInternalCommandDataHashtable->InsertOrUpdate(
4679 u"selectall"_ns,
4680 InternalCommandData(
4681 "cmd_selectAll",
4682 Command::SelectAll,
4683 ExecCommandParam::Ignore,
4684 SelectAllCommand::GetInstance,
4685 CommandOnTextEditor::Enabled));
4686 sInternalCommandDataHashtable->InsertOrUpdate(
4687 u"undo"_ns,
4688 InternalCommandData(
4689 "cmd_undo",
4690 Command::HistoryUndo,
4691 ExecCommandParam::Ignore,
4692 UndoCommand::GetInstance,
4693 CommandOnTextEditor::Enabled));
4694 sInternalCommandDataHashtable->InsertOrUpdate(
4695 u"redo"_ns,
4696 InternalCommandData(
4697 "cmd_redo",
4698 Command::HistoryRedo,
4699 ExecCommandParam::Ignore,
4700 RedoCommand::GetInstance,
4701 CommandOnTextEditor::Enabled));
4702 sInternalCommandDataHashtable->InsertOrUpdate(
4703 u"indent"_ns,
4704 InternalCommandData("cmd_indent",
4705 Command::FormatIndent,
4706 ExecCommandParam::Ignore,
4707 IndentCommand::GetInstance,
4708 CommandOnTextEditor::Disabled));
4709 sInternalCommandDataHashtable->InsertOrUpdate(
4710 u"outdent"_ns,
4711 InternalCommandData(
4712 "cmd_outdent",
4713 Command::FormatOutdent,
4714 ExecCommandParam::Ignore,
4715 OutdentCommand::GetInstance,
4716 CommandOnTextEditor::Disabled));
4717 sInternalCommandDataHashtable->InsertOrUpdate(
4718 u"backcolor"_ns,
4719 InternalCommandData(
4720 "cmd_highlight",
4721 Command::FormatBackColor,
4722 ExecCommandParam::String,
4723 HighlightColorStateCommand::GetInstance,
4724 CommandOnTextEditor::Disabled));
4725 sInternalCommandDataHashtable->InsertOrUpdate(
4726 u"hilitecolor"_ns,
4727 InternalCommandData(
4728 "cmd_highlight",
4729 Command::FormatBackColor,
4730 ExecCommandParam::String,
4731 HighlightColorStateCommand::GetInstance,
4732 CommandOnTextEditor::Disabled));
4733 sInternalCommandDataHashtable->InsertOrUpdate(
4734 u"forecolor"_ns,
4735 InternalCommandData(
4736 "cmd_fontColor",
4737 Command::FormatFontColor,
4738 ExecCommandParam::String,
4739 FontColorStateCommand::GetInstance,
4740 CommandOnTextEditor::Disabled));
4741 sInternalCommandDataHashtable->InsertOrUpdate(
4742 u"fontname"_ns,
4743 InternalCommandData(
4744 "cmd_fontFace",
4745 Command::FormatFontName,
4746 ExecCommandParam::String,
4747 FontFaceStateCommand::GetInstance,
4748 CommandOnTextEditor::Disabled));
4749 sInternalCommandDataHashtable->InsertOrUpdate(
4750 u"fontsize"_ns,
4751 InternalCommandData(
4752 "cmd_fontSize",
4753 Command::FormatFontSize,
4754 ExecCommandParam::String,
4755 FontSizeStateCommand::GetInstance,
4756 CommandOnTextEditor::Disabled));
4757 sInternalCommandDataHashtable->InsertOrUpdate(
4758 u"inserthorizontalrule"_ns,
4759 InternalCommandData(
4760 "cmd_insertHR",
4761 Command::InsertHorizontalRule,
4762 ExecCommandParam::Ignore,
4763 InsertTagCommand::GetInstance,
4764 CommandOnTextEditor::Disabled));
4765 sInternalCommandDataHashtable->InsertOrUpdate(
4766 u"createlink"_ns,
4767 InternalCommandData(
4768 "cmd_insertLinkNoUI",
4769 Command::InsertLink,
4770 ExecCommandParam::String,
4771 InsertTagCommand::GetInstance,
4772 CommandOnTextEditor::Disabled));
4773 sInternalCommandDataHashtable->InsertOrUpdate(
4774 u"insertimage"_ns,
4775 InternalCommandData(
4776 "cmd_insertImageNoUI",
4777 Command::InsertImage,
4778 ExecCommandParam::String,
4779 InsertTagCommand::GetInstance,
4780 CommandOnTextEditor::Disabled));
4781 sInternalCommandDataHashtable->InsertOrUpdate(
4782 u"inserthtml"_ns,
4783 InternalCommandData(
4784 "cmd_insertHTML",
4785 Command::InsertHTML,
4786 ExecCommandParam::String,
4787 InsertHTMLCommand::GetInstance,
4788 // TODO: Chromium inserts text content of the document fragment
4789 // created from the param.
4790 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
4791 CommandOnTextEditor::Disabled));
4792 sInternalCommandDataHashtable->InsertOrUpdate(
4793 u"inserttext"_ns,
4794 InternalCommandData(
4795 "cmd_insertText",
4796 Command::InsertText,
4797 ExecCommandParam::String,
4798 InsertPlaintextCommand::GetInstance,
4799 CommandOnTextEditor::Enabled));
4800 sInternalCommandDataHashtable->InsertOrUpdate(
4801 u"justifyleft"_ns,
4802 InternalCommandData(
4803 "cmd_align",
4804 Command::FormatJustifyLeft,
4805 ExecCommandParam::Ignore, // Will be set to "left"
4806 AlignCommand::GetInstance,
4807 CommandOnTextEditor::Disabled));
4808 sInternalCommandDataHashtable->InsertOrUpdate(
4809 u"justifyright"_ns,
4810 InternalCommandData(
4811 "cmd_align",
4812 Command::FormatJustifyRight,
4813 ExecCommandParam::Ignore, // Will be set to "right"
4814 AlignCommand::GetInstance,
4815 CommandOnTextEditor::Disabled));
4816 sInternalCommandDataHashtable->InsertOrUpdate(
4817 u"justifycenter"_ns,
4818 InternalCommandData(
4819 "cmd_align",
4820 Command::FormatJustifyCenter,
4821 ExecCommandParam::Ignore, // Will be set to "center"
4822 AlignCommand::GetInstance,
4823 CommandOnTextEditor::Disabled));
4824 sInternalCommandDataHashtable->InsertOrUpdate(
4825 u"justifyfull"_ns,
4826 InternalCommandData(
4827 "cmd_align",
4828 Command::FormatJustifyFull,
4829 ExecCommandParam::Ignore, // Will be set to "justify"
4830 AlignCommand::GetInstance,
4831 CommandOnTextEditor::Disabled));
4832 sInternalCommandDataHashtable->InsertOrUpdate(
4833 u"removeformat"_ns,
4834 InternalCommandData(
4835 "cmd_removeStyles",
4836 Command::FormatRemove,
4837 ExecCommandParam::Ignore,
4838 RemoveStylesCommand::GetInstance,
4839 CommandOnTextEditor::Disabled));
4840 sInternalCommandDataHashtable->InsertOrUpdate(
4841 u"unlink"_ns,
4842 InternalCommandData(
4843 "cmd_removeLinks",
4844 Command::FormatRemoveLink,
4845 ExecCommandParam::Ignore,
4846 StyleUpdatingCommand::GetInstance,
4847 CommandOnTextEditor::Disabled));
4848 sInternalCommandDataHashtable->InsertOrUpdate(
4849 u"insertorderedlist"_ns,
4850 InternalCommandData(
4851 "cmd_ol",
4852 Command::InsertOrderedList,
4853 ExecCommandParam::Ignore,
4854 ListCommand::GetInstance,
4855 CommandOnTextEditor::Disabled));
4856 sInternalCommandDataHashtable->InsertOrUpdate(
4857 u"insertunorderedlist"_ns,
4858 InternalCommandData(
4859 "cmd_ul",
4860 Command::InsertUnorderedList,
4861 ExecCommandParam::Ignore,
4862 ListCommand::GetInstance,
4863 CommandOnTextEditor::Disabled));
4864 sInternalCommandDataHashtable->InsertOrUpdate(
4865 u"insertparagraph"_ns,
4866 InternalCommandData(
4867 "cmd_insertParagraph",
4868 Command::InsertParagraph,
4869 ExecCommandParam::Ignore,
4870 InsertParagraphCommand::GetInstance,
4871 CommandOnTextEditor::Enabled));
4872 sInternalCommandDataHashtable->InsertOrUpdate(
4873 u"insertlinebreak"_ns,
4874 InternalCommandData(
4875 "cmd_insertLineBreak",
4876 Command::InsertLineBreak,
4877 ExecCommandParam::Ignore,
4878 InsertLineBreakCommand::GetInstance,
4879 CommandOnTextEditor::Enabled));
4880 sInternalCommandDataHashtable->InsertOrUpdate(
4881 u"formatblock"_ns,
4882 InternalCommandData(
4883 "cmd_formatBlock",
4884 Command::FormatBlock,
4885 ExecCommandParam::String,
4886 FormatBlockStateCommand::GetInstance,
4887 CommandOnTextEditor::Disabled));
4888 sInternalCommandDataHashtable->InsertOrUpdate(
4889 u"styleWithCSS"_ns,
4890 InternalCommandData(
4891 "cmd_setDocumentUseCSS",
4892 Command::SetDocumentUseCSS,
4893 ExecCommandParam::Boolean,
4894 SetDocumentStateCommand::GetInstance,
4895 CommandOnTextEditor::FallThrough));
4896 sInternalCommandDataHashtable->InsertOrUpdate(
4897 u"usecss"_ns, // Legacy command
4898 InternalCommandData(
4899 "cmd_setDocumentUseCSS",
4900 Command::SetDocumentUseCSS,
4901 ExecCommandParam::InvertedBoolean,
4902 SetDocumentStateCommand::GetInstance,
4903 CommandOnTextEditor::FallThrough));
4904 sInternalCommandDataHashtable->InsertOrUpdate(
4905 u"contentReadOnly"_ns,
4906 InternalCommandData(
4907 "cmd_setDocumentReadOnly",
4908 Command::SetDocumentReadOnly,
4909 ExecCommandParam::Boolean,
4910 SetDocumentStateCommand::GetInstance,
4911 CommandOnTextEditor::Enabled));
4912 sInternalCommandDataHashtable->InsertOrUpdate(
4913 u"insertBrOnReturn"_ns,
4914 InternalCommandData(
4915 "cmd_insertBrOnReturn",
4916 Command::SetDocumentInsertBROnEnterKeyPress,
4917 ExecCommandParam::Boolean,
4918 SetDocumentStateCommand::GetInstance,
4919 CommandOnTextEditor::FallThrough));
4920 sInternalCommandDataHashtable->InsertOrUpdate(
4921 u"defaultParagraphSeparator"_ns,
4922 InternalCommandData(
4923 "cmd_defaultParagraphSeparator",
4924 Command::SetDocumentDefaultParagraphSeparator,
4925 ExecCommandParam::String,
4926 SetDocumentStateCommand::GetInstance,
4927 CommandOnTextEditor::FallThrough));
4928 sInternalCommandDataHashtable->InsertOrUpdate(
4929 u"enableObjectResizing"_ns,
4930 InternalCommandData(
4931 "cmd_enableObjectResizing",
4932 Command::ToggleObjectResizers,
4933 ExecCommandParam::Boolean,
4934 SetDocumentStateCommand::GetInstance,
4935 CommandOnTextEditor::FallThrough));
4936 sInternalCommandDataHashtable->InsertOrUpdate(
4937 u"enableInlineTableEditing"_ns,
4938 InternalCommandData(
4939 "cmd_enableInlineTableEditing",
4940 Command::ToggleInlineTableEditor,
4941 ExecCommandParam::Boolean,
4942 SetDocumentStateCommand::GetInstance,
4943 CommandOnTextEditor::FallThrough));
4944 sInternalCommandDataHashtable->InsertOrUpdate(
4945 u"enableAbsolutePositionEditing"_ns,
4946 InternalCommandData(
4947 "cmd_enableAbsolutePositionEditing",
4948 Command::ToggleAbsolutePositionEditor,
4949 ExecCommandParam::Boolean,
4950 SetDocumentStateCommand::GetInstance,
4951 CommandOnTextEditor::FallThrough));
4952 sInternalCommandDataHashtable->InsertOrUpdate(
4953 u"enableCompatibleJoinSplitDirection"_ns,
4954 InternalCommandData("cmd_enableCompatibleJoinSplitNodeDirection",
4955 Command::EnableCompatibleJoinSplitNodeDirection,
4956 ExecCommandParam::Boolean,
4957 SetDocumentStateCommand::GetInstance,
4958 CommandOnTextEditor::FallThrough));
4959 #if 0
4960 // with empty string
4961 sInternalCommandDataHashtable->InsertOrUpdate(
4962 u"justifynone"_ns,
4963 InternalCommandData(
4964 "cmd_align",
4965 Command::Undefined,
4966 ExecCommandParam::Ignore,
4967 nullptr,
4968 CommandOnTextEditor::Disabled)); // Not implemented yet.
4969 // REQUIRED SPECIAL REVIEW special review
4970 sInternalCommandDataHashtable->InsertOrUpdate(
4971 u"saveas"_ns,
4972 InternalCommandData(
4973 "cmd_saveAs",
4974 Command::Undefined,
4975 ExecCommandParam::Boolean,
4976 nullptr,
4977 CommandOnTextEditor::FallThrough)); // Not implemented yet.
4978 // REQUIRED SPECIAL REVIEW special review
4979 sInternalCommandDataHashtable->InsertOrUpdate(
4980 u"print"_ns,
4981 InternalCommandData(
4982 "cmd_print",
4983 Command::Undefined,
4984 ExecCommandParam::Boolean,
4985 nullptr,
4986 CommandOnTextEditor::FallThrough)); // Not implemented yet.
4987 #endif // #if 0
4988 // clang-format on
4991 Document::InternalCommandData Document::ConvertToInternalCommand(
4992 const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */,
4993 nsAString* aAdjustedValue /* = nullptr */) {
4994 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
4995 EnsureInitializeInternalCommandDataHashtable();
4996 InternalCommandData commandData;
4997 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
4998 return InternalCommandData();
5000 // Ignore if the command is disabled by a corresponding pref due to Gecko
5001 // specific.
5002 switch (commandData.mCommand) {
5003 case Command::SetDocumentReadOnly:
5004 if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() &&
5005 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) {
5006 return InternalCommandData();
5008 break;
5009 case Command::SetDocumentInsertBROnEnterKeyPress:
5010 MOZ_DIAGNOSTIC_ASSERT(
5011 aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn"));
5012 if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) {
5013 return InternalCommandData();
5015 break;
5016 default:
5017 break;
5019 if (!aAdjustedValue) {
5020 // No further work to do
5021 return commandData;
5023 switch (commandData.mExecCommandParam) {
5024 case ExecCommandParam::Ignore:
5025 // Just have to copy it, no checking
5026 switch (commandData.mCommand) {
5027 case Command::FormatJustifyLeft:
5028 aAdjustedValue->AssignLiteral("left");
5029 break;
5030 case Command::FormatJustifyRight:
5031 aAdjustedValue->AssignLiteral("right");
5032 break;
5033 case Command::FormatJustifyCenter:
5034 aAdjustedValue->AssignLiteral("center");
5035 break;
5036 case Command::FormatJustifyFull:
5037 aAdjustedValue->AssignLiteral("justify");
5038 break;
5039 default:
5040 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
5041 EditorCommandParamType::None);
5042 break;
5044 return commandData;
5046 case ExecCommandParam::Boolean:
5047 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5048 EditorCommandParamType::Bool));
5049 // If this is a boolean value and it's not explicitly false (e.g. no
5050 // value). We default to "true" (see bug 301490).
5051 if (!aValue.LowerCaseEqualsLiteral("false")) {
5052 aAdjustedValue->AssignLiteral("true");
5053 } else {
5054 aAdjustedValue->AssignLiteral("false");
5056 return commandData;
5058 case ExecCommandParam::InvertedBoolean:
5059 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5060 EditorCommandParamType::Bool));
5061 // For old backwards commands we invert the check.
5062 if (aValue.LowerCaseEqualsLiteral("false")) {
5063 aAdjustedValue->AssignLiteral("true");
5064 } else {
5065 aAdjustedValue->AssignLiteral("false");
5067 return commandData;
5069 case ExecCommandParam::String:
5070 MOZ_ASSERT(!!(
5071 EditorCommand::GetParamType(commandData.mCommand) &
5072 (EditorCommandParamType::String | EditorCommandParamType::CString)));
5073 switch (commandData.mCommand) {
5074 case Command::FormatBlock: {
5075 const char16_t* start = aValue.BeginReading();
5076 const char16_t* end = aValue.EndReading();
5077 if (start != end && *start == '<' && *(end - 1) == '>') {
5078 ++start;
5079 --end;
5081 // XXX Should we reorder this array with actual usage?
5082 static const nsStaticAtom* kFormattableBlockTags[] = {
5083 // clang-format off
5084 nsGkAtoms::address,
5085 nsGkAtoms::article,
5086 nsGkAtoms::aside,
5087 nsGkAtoms::blockquote,
5088 nsGkAtoms::dd,
5089 nsGkAtoms::div,
5090 nsGkAtoms::dl,
5091 nsGkAtoms::dt,
5092 nsGkAtoms::footer,
5093 nsGkAtoms::h1,
5094 nsGkAtoms::h2,
5095 nsGkAtoms::h3,
5096 nsGkAtoms::h4,
5097 nsGkAtoms::h5,
5098 nsGkAtoms::h6,
5099 nsGkAtoms::header,
5100 nsGkAtoms::hgroup,
5101 nsGkAtoms::main,
5102 nsGkAtoms::nav,
5103 nsGkAtoms::p,
5104 nsGkAtoms::pre,
5105 nsGkAtoms::section,
5106 // clang-format on
5108 nsAutoString value(nsDependentSubstring(start, end));
5109 ToLowerCase(value);
5110 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
5111 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
5112 if (valueAtom == kTag) {
5113 kTag->ToString(*aAdjustedValue);
5114 return commandData;
5117 return InternalCommandData();
5119 case Command::FormatFontSize: {
5120 // Per editing spec as of April 23, 2012, we need to reject the value
5121 // if it's not a valid floating-point number surrounded by optional
5122 // whitespace. Otherwise, we parse it as a legacy font size. For
5123 // now, we just parse as a legacy font size regardless (matching
5124 // WebKit) -- bug 747879.
5125 int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
5126 if (!size) {
5127 return InternalCommandData();
5129 MOZ_ASSERT(aAdjustedValue->IsEmpty());
5130 aAdjustedValue->AppendInt(size);
5131 return commandData;
5133 case Command::InsertImage:
5134 case Command::InsertLink:
5135 if (aValue.IsEmpty()) {
5136 // Invalid value, return false
5137 return InternalCommandData();
5139 aAdjustedValue->Assign(aValue);
5140 return commandData;
5141 case Command::SetDocumentDefaultParagraphSeparator:
5142 if (!aValue.LowerCaseEqualsLiteral("div") &&
5143 !aValue.LowerCaseEqualsLiteral("p") &&
5144 !aValue.LowerCaseEqualsLiteral("br")) {
5145 // Invalid value
5146 return InternalCommandData();
5148 aAdjustedValue->Assign(aValue);
5149 return commandData;
5150 default:
5151 aAdjustedValue->Assign(aValue);
5152 return commandData;
5155 default:
5156 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
5157 return InternalCommandData();
5161 Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
5162 Document& aDocument, const InternalCommandData& aCommandData)
5163 : mCommandData(aCommandData) {
5164 // We'll retrieve an editor with current DOM tree and layout information.
5165 // However, JS may have already hidden or remove exposed root content of
5166 // the editor. Therefore, we need the latest layout information here.
5167 aDocument.FlushPendingNotifications(FlushType::Layout);
5168 if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) {
5169 mDoNothing = true;
5170 return;
5173 if (nsPresContext* presContext = aDocument.GetPresContext()) {
5174 // Consider context of command handling which is automatically resolved
5175 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5176 // The order is:
5177 // 1. TextEditor if there is an active element and it has TextEditor like
5178 // <input type="text"> or <textarea>.
5179 // 2. HTMLEditor for the document, if there is.
5180 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
5181 if (aCommandData.IsCutOrCopyCommand()) {
5182 // Note that we used to use DocShell to handle `cut` and `copy` command
5183 // for dispatching corresponding events for making possible web apps to
5184 // implement their own editor without editable elements but supports
5185 // standard shortcut keys, etc. In this case, we prefer to use active
5186 // element's editor to keep same behavior.
5187 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5188 } else {
5189 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5190 mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
5191 if (!mActiveEditor) {
5192 mActiveEditor = mHTMLEditor;
5197 // Then, retrieve editor command class instance which should handle it
5198 // and can handle it now.
5199 if (!mActiveEditor) {
5200 // If the command is available without editor, we should redirect the
5201 // command to focused descendant with DocShell.
5202 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5203 mDoNothing = true;
5204 return;
5206 return;
5209 // Otherwise, we should use EditorCommand instance (which is singleton
5210 // instance) when it's enabled.
5211 mEditorCommand = aCommandData.mGetEditorCommandFunc
5212 ? aCommandData.mGetEditorCommandFunc()
5213 : nullptr;
5214 if (!mEditorCommand) {
5215 mDoNothing = true;
5216 mActiveEditor = nullptr;
5217 mHTMLEditor = nullptr;
5218 return;
5221 if (IsCommandEnabled()) {
5222 return;
5225 // If the EditorCommand instance is disabled, we should do nothing if
5226 // the command requires an editor.
5227 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5228 // Do nothing if editor specific commands is disabled (bug 760052).
5229 mDoNothing = true;
5230 return;
5233 // Otherwise, we should redirect it to focused descendant with DocShell.
5234 mEditorCommand = nullptr;
5235 mActiveEditor = nullptr;
5236 mHTMLEditor = nullptr;
5239 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
5240 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
5241 switch (mCommandData.mCommandOnTextEditor) {
5242 case CommandOnTextEditor::Enabled:
5243 return mActiveEditor;
5244 case CommandOnTextEditor::Disabled:
5245 return mActiveEditor && mActiveEditor->IsTextEditor()
5246 ? nullptr
5247 : mActiveEditor.get();
5248 case CommandOnTextEditor::FallThrough:
5249 return mHTMLEditor;
5251 return nullptr;
5254 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
5255 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
5256 // Make sure frames are up to date, since that can affect whether
5257 // we're editable.
5258 doc->FlushPendingNotifications(FlushType::Frames);
5260 EditorBase* targetEditor = GetTargetEditor();
5261 if (targetEditor && targetEditor->IsTextEditor()) {
5262 // FYI: When `disabled` attribute is set, `TextEditor` treats it as
5263 // "readonly" too.
5264 return !targetEditor->IsReadonly();
5266 return aDocument->IsEditingOn();
5269 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
5270 EditorBase* targetEditor = GetTargetEditor();
5271 if (!targetEditor) {
5272 return false;
5274 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5275 return MOZ_KnownLive(mEditorCommand)
5276 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
5279 nsresult Document::AutoEditorCommandTarget::DoCommand(
5280 nsIPrincipal* aPrincipal) const {
5281 MOZ_ASSERT(!DoNothing());
5282 MOZ_ASSERT(mEditorCommand);
5283 EditorBase* targetEditor = GetTargetEditor();
5284 if (!targetEditor) {
5285 return NS_SUCCESS_DOM_NO_OPERATION;
5287 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5288 return MOZ_KnownLive(mEditorCommand)
5289 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
5290 aPrincipal);
5293 template <typename ParamType>
5294 nsresult Document::AutoEditorCommandTarget::DoCommandParam(
5295 const ParamType& aParam, nsIPrincipal* aPrincipal) const {
5296 MOZ_ASSERT(!DoNothing());
5297 MOZ_ASSERT(mEditorCommand);
5298 EditorBase* targetEditor = GetTargetEditor();
5299 if (!targetEditor) {
5300 return NS_SUCCESS_DOM_NO_OPERATION;
5302 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5303 return MOZ_KnownLive(mEditorCommand)
5304 ->DoCommandParam(mCommandData.mCommand, aParam,
5305 MOZ_KnownLive(*targetEditor), aPrincipal);
5308 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
5309 nsCommandParams& aParams) const {
5310 MOZ_ASSERT(mEditorCommand);
5311 EditorBase* targetEditor = GetTargetEditor();
5312 if (!targetEditor) {
5313 return NS_OK;
5315 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5316 return MOZ_KnownLive(mEditorCommand)
5317 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
5318 MOZ_KnownLive(targetEditor), nullptr);
5321 Document::AutoRunningExecCommandMarker::AutoRunningExecCommandMarker(
5322 Document& aDocument, nsIPrincipal* aPrincipal)
5323 : mDocument(aDocument),
5324 mTreatAsUserInput(EditorBase::TreatAsUserInput(aPrincipal)),
5325 mHasBeenRunningByContent(aDocument.mIsRunningExecCommandByContent),
5326 mHasBeenRunningByChromeOrAddon(
5327 aDocument.mIsRunningExecCommandByChromeOrAddon) {
5328 if (mTreatAsUserInput) {
5329 aDocument.mIsRunningExecCommandByChromeOrAddon = true;
5330 } else {
5331 aDocument.mIsRunningExecCommandByContent = true;
5335 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
5336 const nsAString& aValue,
5337 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
5338 // Only allow on HTML documents.
5339 if (!IsHTMLOrXHTML()) {
5340 aRv.ThrowInvalidStateError(
5341 "execCommand is only supported on HTML documents");
5342 return false;
5344 // Otherwise, don't throw exception for compatibility with Chrome.
5346 // if they are requesting UI from us, let's fail since we have no UI
5347 if (aShowUI) {
5348 return false;
5351 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
5352 // this might add some ugly JS dependencies?
5354 nsAutoString adjustedValue;
5355 InternalCommandData commandData =
5356 ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
5357 switch (commandData.mCommand) {
5358 case Command::DoNothing:
5359 return false;
5360 case Command::SetDocumentReadOnly:
5361 SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly);
5362 break;
5363 case Command::EnableCompatibleJoinSplitNodeDirection:
5364 // We didn't allow to enable the legacy behavior once we've enabled the
5365 // new behavior by default. For keeping the behavior at supporting both
5366 // mode, we should keep returning `false` if the web app to enable the
5367 // legacy mode. Additionally, we don't support the legacy direction
5368 // anymore. Therefore, we can return `false` here even if the caller is
5369 // an addon or chrome script.
5370 if (!adjustedValue.EqualsLiteral("true")) {
5371 return false;
5373 break;
5374 default:
5375 break;
5378 AutoRunningExecCommandMarker markRunningExecCommand(*this,
5379 &aSubjectPrincipal);
5381 // If we're running an execCommand, we should just return false.
5382 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
5383 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
5384 !markRunningExecCommand.IsSafeToRun()) {
5385 return false;
5388 // Do security check first.
5389 if (commandData.IsCutOrCopyCommand()) {
5390 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
5391 // We have rejected the event due to it not being performed in an
5392 // input-driven context therefore, we report the error to the console.
5393 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
5394 this, nsContentUtils::eDOM_PROPERTIES,
5395 "ExecCommandCutCopyDeniedNotInputDriven");
5396 return false;
5398 } else if (commandData.IsPasteCommand()) {
5399 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
5400 nsGkAtoms::clipboardRead)) {
5401 return false;
5405 // Next, consider context of command handling which is automatically resolved
5406 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5407 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5408 if (commandData.IsAvailableOnlyWhenEditable() &&
5409 !editCommandTarget.IsEditable(this)) {
5410 return false;
5413 if (editCommandTarget.DoNothing()) {
5414 return false;
5417 // If we cannot use EditorCommand instance directly, we need to handle the
5418 // command with traditional path (i.e., with DocShell or nsCommandManager).
5419 if (!editCommandTarget.IsEditor()) {
5420 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
5422 // Special case clipboard write commands like Command::Cut and
5423 // Command::Copy. For such commands, we need the behaviour from
5424 // nsWindowRoot::GetControllers() which is to look at the focused element,
5425 // and defer to a focused textbox's controller. The code past taken by
5426 // other commands in ExecCommand() always uses the window directly, rather
5427 // than deferring to the textbox, which is desireable for most editor
5428 // commands, but not these commands (as those should allow copying out of
5429 // embedded editors). This behaviour is invoked if we call DoCommand()
5430 // directly on the docShell.
5431 // XXX This means that we allow web app to pick up selected content in
5432 // descendant document and write it into the clipboard when a
5433 // descendant document has focus. However, Chromium does not allow
5434 // this and this seems that it's not good behavior from point of view
5435 // of security. We should treat this issue in another bug.
5436 if (commandData.IsCutOrCopyCommand()) {
5437 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
5438 if (!docShell) {
5439 return false;
5441 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
5442 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
5443 return false;
5445 return NS_SUCCEEDED(rv);
5448 // Otherwise (currently, only clipboard read commands like Command::Paste),
5449 // we don't need to redirect the command to focused subdocument.
5450 // Therefore, we should handle it with nsCommandManager as used to be.
5451 // It may dispatch only preceding event of editing on non-editable element
5452 // to make web apps possible to handle standard shortcut key, etc in
5453 // their own editor.
5454 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5455 if (!commandManager) {
5456 return false;
5459 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5460 if (!window) {
5461 return false;
5464 // Return false for disabled commands (bug 760052)
5465 if (!commandManager->IsCommandEnabled(
5466 nsDependentCString(commandData.mXULCommandName), window)) {
5467 return false;
5470 MOZ_ASSERT(commandData.IsPasteCommand() ||
5471 commandData.mCommand == Command::SelectAll);
5472 nsresult rv =
5473 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
5474 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5477 // Now, our target is fixed to the editor. So, we can use EditorCommand
5478 // in EditorCommandTarget directly.
5480 EditorCommandParamType paramType =
5481 EditorCommand::GetParamType(commandData.mCommand);
5483 // If we don't have meaningful parameter or the EditorCommand does not
5484 // require additional parameter, we can use `DoCommand()`.
5485 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
5486 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
5487 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
5488 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5491 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
5492 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
5493 // a `bool` value.
5494 if (!!(paramType & EditorCommandParamType::Bool)) {
5495 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
5496 adjustedValue.EqualsLiteral("false"));
5497 nsresult rv = editCommandTarget.DoCommandParam(
5498 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
5499 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5502 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
5503 // in this case. However, `paramType` may contain both `String` and
5504 // `CString` but in such case, we should use `DoCommandParam()` which
5505 // takes `nsAString`. So, we should check whether `paramType` contains
5506 // `String` or not first.
5507 if (!!(paramType & EditorCommandParamType::String)) {
5508 MOZ_ASSERT(!adjustedValue.IsVoid());
5509 nsresult rv =
5510 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
5511 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5514 // Finally, `paramType` should have `CString`. We should use
5515 // `DoCommandParam()` which takes `nsACString`.
5516 if (!!(paramType & EditorCommandParamType::CString)) {
5517 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
5518 MOZ_ASSERT(!utf8Value.IsVoid());
5519 nsresult rv =
5520 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
5521 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5524 MOZ_ASSERT_UNREACHABLE(
5525 "Not yet implemented to handle new EditorCommandParamType");
5526 return false;
5529 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
5530 nsIPrincipal& aSubjectPrincipal,
5531 ErrorResult& aRv) {
5532 // Only allow on HTML documents.
5533 if (!IsHTMLOrXHTML()) {
5534 aRv.ThrowInvalidStateError(
5535 "queryCommandEnabled is only supported on HTML documents");
5536 return false;
5538 // Otherwise, don't throw exception for compatibility with Chrome.
5540 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5541 switch (commandData.mCommand) {
5542 case Command::DoNothing:
5543 return false;
5544 case Command::SetDocumentReadOnly:
5545 SetUseCounter(
5546 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5547 break;
5548 case Command::SetDocumentInsertBROnEnterKeyPress:
5549 SetUseCounter(
5550 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5551 break;
5552 default:
5553 break;
5556 // cut & copy are always allowed
5557 if (commandData.IsCutOrCopyCommand()) {
5558 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
5561 // Report false for restricted commands
5562 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
5563 return false;
5566 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5567 if (commandData.IsAvailableOnlyWhenEditable() &&
5568 !editCommandTarget.IsEditable(this)) {
5569 return false;
5572 if (editCommandTarget.IsEditor()) {
5573 return editCommandTarget.IsCommandEnabled();
5576 // get command manager and dispatch command to our window if it's acceptable
5577 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5578 if (!commandManager) {
5579 return false;
5582 nsPIDOMWindowOuter* window = GetWindow();
5583 if (!window) {
5584 return false;
5587 return commandManager->IsCommandEnabled(
5588 nsDependentCString(commandData.mXULCommandName), window);
5591 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5592 ErrorResult& aRv) {
5593 // Only allow on HTML documents.
5594 if (!IsHTMLOrXHTML()) {
5595 aRv.ThrowInvalidStateError(
5596 "queryCommandIndeterm is only supported on HTML documents");
5597 return false;
5599 // Otherwise, don't throw exception for compatibility with Chrome.
5601 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5602 if (commandData.mCommand == Command::DoNothing) {
5603 return false;
5606 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5607 if (commandData.IsAvailableOnlyWhenEditable() &&
5608 !editCommandTarget.IsEditable(this)) {
5609 return false;
5611 RefPtr<nsCommandParams> params = new nsCommandParams();
5612 if (editCommandTarget.IsEditor()) {
5613 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5614 return false;
5616 } else {
5617 // get command manager and dispatch command to our window if it's acceptable
5618 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5619 if (!commandManager) {
5620 return false;
5623 nsPIDOMWindowOuter* window = GetWindow();
5624 if (!window) {
5625 return false;
5628 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5629 window, params))) {
5630 return false;
5634 // If command does not have a state_mixed value, this call fails and sets
5635 // retval to false. This is fine -- we want to return false in that case
5636 // anyway (bug 738385), so we just don't throw regardless.
5637 return params->GetBool("state_mixed");
5640 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5641 ErrorResult& aRv) {
5642 // Only allow on HTML documents.
5643 if (!IsHTMLOrXHTML()) {
5644 aRv.ThrowInvalidStateError(
5645 "queryCommandState is only supported on HTML documents");
5646 return false;
5648 // Otherwise, don't throw exception for compatibility with Chrome.
5650 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5651 switch (commandData.mCommand) {
5652 case Command::DoNothing:
5653 return false;
5654 case Command::SetDocumentReadOnly:
5655 SetUseCounter(
5656 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5657 break;
5658 case Command::SetDocumentInsertBROnEnterKeyPress:
5659 SetUseCounter(
5660 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5661 break;
5662 default:
5663 break;
5666 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5667 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5668 // return false always.
5669 return false;
5672 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5673 if (commandData.IsAvailableOnlyWhenEditable() &&
5674 !editCommandTarget.IsEditable(this)) {
5675 return false;
5677 RefPtr<nsCommandParams> params = new nsCommandParams();
5678 if (editCommandTarget.IsEditor()) {
5679 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5680 return false;
5682 } else {
5683 // get command manager and dispatch command to our window if it's acceptable
5684 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5685 if (!commandManager) {
5686 return false;
5689 nsPIDOMWindowOuter* window = GetWindow();
5690 if (!window) {
5691 return false;
5694 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5695 window, params))) {
5696 return false;
5700 // handle alignment as a special case (possibly other commands too?)
5701 // Alignment is special because the external api is individual
5702 // commands but internally we use cmd_align with different
5703 // parameters. When getting the state of this command, we need to
5704 // return the boolean for this particular alignment rather than the
5705 // string of 'which alignment is this?'
5706 switch (commandData.mCommand) {
5707 case Command::FormatJustifyLeft: {
5708 nsAutoCString currentValue;
5709 nsresult rv = params->GetCString("state_attribute", currentValue);
5710 if (NS_FAILED(rv)) {
5711 return false;
5713 return currentValue.EqualsLiteral("left");
5715 case Command::FormatJustifyRight: {
5716 nsAutoCString currentValue;
5717 nsresult rv = params->GetCString("state_attribute", currentValue);
5718 if (NS_FAILED(rv)) {
5719 return false;
5721 return currentValue.EqualsLiteral("right");
5723 case Command::FormatJustifyCenter: {
5724 nsAutoCString currentValue;
5725 nsresult rv = params->GetCString("state_attribute", currentValue);
5726 if (NS_FAILED(rv)) {
5727 return false;
5729 return currentValue.EqualsLiteral("center");
5731 case Command::FormatJustifyFull: {
5732 nsAutoCString currentValue;
5733 nsresult rv = params->GetCString("state_attribute", currentValue);
5734 if (NS_FAILED(rv)) {
5735 return false;
5737 return currentValue.EqualsLiteral("justify");
5739 default:
5740 break;
5743 // If command does not have a state_all value, this call fails and sets
5744 // retval to false. This is fine -- we want to return false in that case
5745 // anyway (bug 738385), so we just succeed and return false regardless.
5746 return params->GetBool("state_all");
5749 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5750 CallerType aCallerType, ErrorResult& aRv) {
5751 // Only allow on HTML documents.
5752 if (!IsHTMLOrXHTML()) {
5753 aRv.ThrowInvalidStateError(
5754 "queryCommandSupported is only supported on HTML documents");
5755 return false;
5757 // Otherwise, don't throw exception for compatibility with Chrome.
5759 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5760 switch (commandData.mCommand) {
5761 case Command::DoNothing:
5762 return false;
5763 case Command::SetDocumentReadOnly:
5764 SetUseCounter(
5765 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5766 break;
5767 case Command::SetDocumentInsertBROnEnterKeyPress:
5768 SetUseCounter(
5769 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5770 break;
5771 default:
5772 break;
5775 // Gecko technically supports all the clipboard commands including
5776 // cut/copy/paste, but non-privileged content will be unable to call
5777 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5778 // may also be disallowed to be called from non-privileged content.
5779 // For that reason, we report the support status of corresponding
5780 // command accordingly.
5781 if (aCallerType != CallerType::System) {
5782 if (commandData.IsPasteCommand()) {
5783 return false;
5785 if (commandData.IsCutOrCopyCommand() &&
5786 !StaticPrefs::dom_allow_cut_copy()) {
5787 // XXXbz should we worry about correctly reporting "true" in the
5788 // "restricted, but we're an addon with clipboardWrite permissions" case?
5789 // See also nsContentUtils::IsCutCopyAllowed.
5790 return false;
5794 // aHTMLCommandName is supported if it can be converted to a Midas command
5795 return true;
5798 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5799 nsAString& aValue, ErrorResult& aRv) {
5800 aValue.Truncate();
5802 // Only allow on HTML documents.
5803 if (!IsHTMLOrXHTML()) {
5804 aRv.ThrowInvalidStateError(
5805 "queryCommandValue is only supported on HTML documents");
5806 return;
5808 // Otherwise, don't throw exception for compatibility with Chrome.
5810 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5811 switch (commandData.mCommand) {
5812 case Command::DoNothing:
5813 // Return empty string
5814 return;
5815 case Command::SetDocumentReadOnly:
5816 SetUseCounter(
5817 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5818 break;
5819 case Command::SetDocumentInsertBROnEnterKeyPress:
5820 SetUseCounter(
5821 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5822 break;
5823 default:
5824 break;
5827 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5828 if (commandData.IsAvailableOnlyWhenEditable() &&
5829 !editCommandTarget.IsEditable(this)) {
5830 return;
5832 RefPtr<nsCommandParams> params = new nsCommandParams();
5833 if (editCommandTarget.IsEditor()) {
5834 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5835 return;
5838 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5839 return;
5841 } else {
5842 // get command manager and dispatch command to our window if it's acceptable
5843 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5844 if (!commandManager) {
5845 return;
5848 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5849 if (!window) {
5850 return;
5853 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5854 return;
5857 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5858 window, params))) {
5859 return;
5863 // If command does not have a state_attribute value, this call fails, and
5864 // aValue will wind up being the empty string. This is fine -- we want to
5865 // return "" in that case anyway (bug 738385), so we just return NS_OK
5866 // regardless.
5867 nsAutoCString result;
5868 params->GetCString("state_attribute", result);
5869 CopyUTF8toUTF16(result, aValue);
5872 void Document::MaybeEditingStateChanged() {
5873 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
5874 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
5875 if (nsContentUtils::IsSafeToRunScript()) {
5876 EditingStateChanged();
5877 } else if (!mInDestructor) {
5878 nsContentUtils::AddScriptRunner(
5879 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
5880 &Document::MaybeEditingStateChanged));
5885 void Document::NotifyFetchOrXHRSuccess() {
5886 if (mShouldNotifyFetchSuccess) {
5887 nsContentUtils::DispatchEventOnlyToChrome(
5888 this, this, u"DOMDocFetchSuccess"_ns, CanBubble::eNo, Cancelable::eNo,
5889 /* DefaultAction */ nullptr);
5893 void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
5894 mShouldNotifyFetchSuccess = aShouldNotify;
5897 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
5898 mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
5901 void Document::TearingDownEditor() {
5902 if (IsEditingOn()) {
5903 mEditingState = EditingState::eTearingDown;
5904 if (IsHTMLOrXHTML()) {
5905 RemoveContentEditableStyleSheets();
5910 nsresult Document::TurnEditingOff() {
5911 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
5913 nsPIDOMWindowOuter* window = GetWindow();
5914 if (!window) {
5915 return NS_ERROR_FAILURE;
5918 nsIDocShell* docshell = window->GetDocShell();
5919 if (!docshell) {
5920 return NS_ERROR_FAILURE;
5923 bool isBeingDestroyed = false;
5924 docshell->IsBeingDestroyed(&isBeingDestroyed);
5925 if (isBeingDestroyed) {
5926 return NS_ERROR_FAILURE;
5929 nsCOMPtr<nsIEditingSession> editSession;
5930 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5931 NS_ENSURE_SUCCESS(rv, rv);
5933 // turn editing off
5934 rv = editSession->TearDownEditorOnWindow(window);
5935 NS_ENSURE_SUCCESS(rv, rv);
5937 mEditingState = EditingState::eOff;
5939 // Editor resets selection since it is being destroyed. But if focus is
5940 // still into editable control, we have to initialize selection again.
5941 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
5942 if (RefPtr<TextControlElement> textControlElement =
5943 TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
5944 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
5945 textEditor->ReinitializeSelection(*textControlElement);
5950 return NS_OK;
5953 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
5954 nsIDocShell* docShell = aWindow->GetDocShell();
5955 if (!docShell) {
5956 return false;
5958 return docShell->GetPresShell() != nullptr;
5961 HTMLEditor* Document::GetHTMLEditor() const {
5962 nsPIDOMWindowOuter* window = GetWindow();
5963 if (!window) {
5964 return nullptr;
5967 nsIDocShell* docshell = window->GetDocShell();
5968 if (!docshell) {
5969 return nullptr;
5972 return docshell->GetHTMLEditor();
5975 nsresult Document::EditingStateChanged() {
5976 if (mRemovedFromDocShell) {
5977 return NS_OK;
5980 if (mEditingState == EditingState::eSettingUp ||
5981 mEditingState == EditingState::eTearingDown) {
5982 // XXX We shouldn't recurse
5983 return NS_OK;
5986 const bool designMode = IsInDesignMode();
5987 EditingState newState =
5988 designMode ? EditingState::eDesignMode
5989 : (mContentEditableCount > 0 ? EditingState::eContentEditable
5990 : EditingState::eOff);
5991 if (mEditingState == newState) {
5992 // No changes in editing mode.
5993 return NS_OK;
5996 const bool thisDocumentHasFocus = ThisDocumentHasFocus();
5997 if (newState == EditingState::eOff) {
5998 // Editing is being turned off.
5999 nsAutoScriptBlocker scriptBlocker;
6000 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6001 NotifyEditableStateChange(*this);
6002 nsresult rv = TurnEditingOff();
6003 // If this document has focus and the editing state of this document
6004 // becomes "off", it means that HTMLEditor won't handle any inputs nor
6005 // modify the DOM tree. However, HTMLEditor may not receive `blur`
6006 // event for this state change since this may occur without focus change.
6007 // Therefore, let's notify HTMLEditor of this editing state change.
6008 // Note that even if focusedElement is an editable text control element,
6009 // it becomes not editable from HTMLEditor point of view since text
6010 // control elements are manged by TextEditor.
6011 RefPtr<Element> focusedElement =
6012 nsFocusManager::GetFocusManager()
6013 ? nsFocusManager::GetFocusManager()->GetFocusedElement()
6014 : nullptr;
6015 DebugOnly<nsresult> rvIgnored =
6016 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6017 htmlEditor, *this, focusedElement);
6018 NS_WARNING_ASSERTION(
6019 NS_SUCCEEDED(rvIgnored),
6020 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but "
6021 "ignored");
6022 return rv;
6025 // Flush out style changes on our _parent_ document, if any, so that
6026 // our check for a presshell won't get stale information.
6027 if (mParentDocument) {
6028 mParentDocument->FlushPendingNotifications(FlushType::Style);
6031 // get editing session, make sure this is a strong reference so the
6032 // window can't get deleted during the rest of this call.
6033 const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6034 if (!window) {
6035 return NS_ERROR_FAILURE;
6038 nsIDocShell* docshell = window->GetDocShell();
6039 if (!docshell) {
6040 return NS_ERROR_FAILURE;
6043 // FlushPendingNotifications might destroy our docshell.
6044 bool isBeingDestroyed = false;
6045 docshell->IsBeingDestroyed(&isBeingDestroyed);
6046 if (isBeingDestroyed) {
6047 return NS_ERROR_FAILURE;
6050 nsCOMPtr<nsIEditingSession> editSession;
6051 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6052 NS_ENSURE_SUCCESS(rv, rv);
6054 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
6055 if (htmlEditor) {
6056 // We might already have an editor if it was set up for mail, let's see
6057 // if this is actually the case.
6058 uint32_t flags = 0;
6059 htmlEditor->GetFlags(&flags);
6060 if (flags & nsIEditor::eEditorMailMask) {
6061 // We already have a mail editor, then we should not attempt to create
6062 // another one.
6063 return NS_OK;
6067 if (!HasPresShell(window)) {
6068 // We should not make the window editable or setup its editor.
6069 // It's probably style=display:none.
6070 return NS_OK;
6073 bool makeWindowEditable = mEditingState == EditingState::eOff;
6074 bool spellRecheckAll = false;
6075 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
6076 htmlEditor = nullptr;
6079 EditingState oldState = mEditingState;
6080 nsAutoEditingState push(this, EditingState::eSettingUp);
6082 RefPtr<PresShell> presShell = GetPresShell();
6083 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
6085 // If we're entering the design mode from non-editable state, put the
6086 // selection at the beginning of the document for compatibility reasons.
6087 bool collapseSelectionAtBeginningOfDocument =
6088 designMode && oldState == EditingState::eOff;
6089 // However, mEditingState may be eOff even if there is some
6090 // `contenteditable` area and selection has been initialized for it because
6091 // mEditingState for `contenteditable` may have been scheduled to modify
6092 // when safe. In such case, we should not reinitialize selection.
6093 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
6094 Selection* selection =
6095 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
6096 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
6097 if (selection && selection->RangeCount()) {
6098 // Perhaps, we don't need to check whether the selection is in
6099 // an editing host or not because all contents will be editable
6100 // in designMode. (And we don't want to make this code so complicated
6101 // because of legacy API.)
6102 collapseSelectionAtBeginningOfDocument = false;
6106 MOZ_ASSERT(mStyleSetFilled);
6108 // Before making this window editable, we need to modify UA style sheet
6109 // because new style may change whether focused element will be focusable
6110 // or not.
6111 if (IsHTMLOrXHTML()) {
6112 AddContentEditableStyleSheetsToStyleSet(designMode);
6115 if (designMode) {
6116 // designMode is being turned on (overrides contentEditable).
6117 spellRecheckAll = oldState == EditingState::eContentEditable;
6120 // Adjust focused element with new style but blur event shouldn't be fired
6121 // until mEditingState is modified with newState.
6122 nsAutoScriptBlocker scriptBlocker;
6123 if (designMode) {
6124 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6125 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6126 window, nsFocusManager::eOnlyCurrentWindow,
6127 getter_AddRefs(focusedWindow));
6128 if (focusedContent) {
6129 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
6130 bool clearFocus = focusedFrame
6131 ? !focusedFrame->IsFocusable()
6132 : !focusedContent->IsFocusableWithoutStyle();
6133 if (clearFocus) {
6134 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
6135 fm->ClearFocus(window);
6136 // If we need to dispatch blur event, we should put off after
6137 // modifying mEditingState since blur event handler may change
6138 // designMode state again.
6139 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
6145 if (makeWindowEditable) {
6146 // Editing is being turned on (through designMode or contentEditable)
6147 // Turn on editor.
6148 // XXX This can cause flushing which can change the editing state, so make
6149 // sure to avoid recursing.
6150 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
6151 NS_ENSURE_SUCCESS(rv, rv);
6154 // XXX Need to call TearDownEditorOnWindow for all failures.
6155 htmlEditor = docshell->GetHTMLEditor();
6156 if (!htmlEditor) {
6157 // Return NS_OK even though we've failed to create an editor here. This
6158 // is so that the setter of designMode on non-HTML documents does not
6159 // fail.
6160 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
6161 // would detect that we can't support the mimetype if appropriate and
6162 // would fall onto the eEditorErrorCantEditMimeType path.
6163 return NS_OK;
6166 if (collapseSelectionAtBeginningOfDocument) {
6167 htmlEditor->BeginningOfDocument();
6170 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6171 nsContentUtils::AddScriptBlocker();
6175 mEditingState = newState;
6176 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6177 nsContentUtils::RemoveScriptBlocker();
6178 // If mEditingState is overwritten by another call and already disabled
6179 // the editing, we shouldn't keep making window editable.
6180 if (mEditingState == EditingState::eOff) {
6181 return NS_OK;
6185 if (makeWindowEditable) {
6186 // TODO: We should do this earlier in this method.
6187 // Previously, we called `ExecCommand` with `insertBrOnReturn` command
6188 // whose argument is false here. Then, if it returns error, we
6189 // stopped making it editable. However, after bug 1697078 fixed,
6190 // `ExecCommand` returns error only when the document is not XHTML's
6191 // nor HTML's. Therefore, we use same error handling for now.
6192 if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
6193 // Editor setup failed. Editing is not on after all.
6194 // XXX Should we reset the editable flag on nodes?
6195 editSession->TearDownEditorOnWindow(window);
6196 mEditingState = EditingState::eOff;
6197 return NS_ERROR_DOM_INVALID_STATE_ERR;
6199 // Set the editor to not insert <br> elements on return when in <p> elements
6200 // by default.
6201 htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
6204 // Resync the editor's spellcheck state.
6205 if (spellRecheckAll) {
6206 nsCOMPtr<nsISelectionController> selectionController =
6207 htmlEditor->GetSelectionController();
6208 if (NS_WARN_IF(!selectionController)) {
6209 return NS_ERROR_FAILURE;
6212 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
6213 nsISelectionController::SELECTION_SPELLCHECK);
6214 if (spellCheckSelection) {
6215 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
6218 htmlEditor->SyncRealTimeSpell();
6220 MaybeDispatchCheckKeyPressEventModelEvent();
6222 // If this document keeps having focus and the HTMLEditor is in the design
6223 // mode, it may not receive `focus` event for this editing state change since
6224 // this may occur without a focus change. Therefore, let's notify HTMLEditor
6225 // of this editing state change.
6226 if (thisDocumentHasFocus && htmlEditor->IsInDesignMode() &&
6227 ThisDocumentHasFocus()) {
6228 DebugOnly<nsresult> rvIgnored =
6229 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, nullptr);
6230 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6231 "HTMLEditor::FocusedElementOrDocumentBecomesEditable()"
6232 " failed, but ignored");
6235 return NS_OK;
6238 // Helper class, used below in ChangeContentEditableCount().
6239 class DeferredContentEditableCountChangeEvent : public Runnable {
6240 public:
6241 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
6242 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
6243 mDoc(aDoc),
6244 mElement(aElement) {}
6246 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
6247 if (mElement && mElement->OwnerDoc() == mDoc) {
6248 RefPtr<Document> doc = std::move(mDoc);
6249 RefPtr<Element> element = std::move(mElement);
6250 doc->DeferredContentEditableCountChange(element);
6252 return NS_OK;
6255 private:
6256 RefPtr<Document> mDoc;
6257 RefPtr<Element> mElement;
6260 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
6261 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
6262 "Trying to decrement too much.");
6264 mContentEditableCount += aChange;
6266 if (aElement) {
6267 nsContentUtils::AddScriptRunner(
6268 new DeferredContentEditableCountChangeEvent(this, aElement));
6272 void Document::DeferredContentEditableCountChange(Element* aElement) {
6273 const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
6274 const bool elementHasFocus =
6275 aElement && fm && fm->GetFocusedElement() == aElement;
6276 if (elementHasFocus) {
6277 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
6278 // When contenteditable of aElement is changed and HTMLEditor works with it
6279 // or needs to start working with it, HTMLEditor may not receive `focus`
6280 // event nor `blur` event because this may occur without a focus change.
6281 // Therefore, we need to notify HTMLEditor of this contenteditable attribute
6282 // change.
6283 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6284 if (aElement->HasFlag(NODE_IS_EDITABLE)) {
6285 if (htmlEditor) {
6286 DebugOnly<nsresult> rvIgnored =
6287 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this,
6288 aElement);
6289 NS_WARNING_ASSERTION(
6290 NS_SUCCEEDED(rvIgnored),
6291 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6292 "ignored");
6294 } else {
6295 DebugOnly<nsresult> rvIgnored =
6296 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6297 htmlEditor, *this, aElement);
6298 NS_WARNING_ASSERTION(
6299 NS_SUCCEEDED(rvIgnored),
6300 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, "
6301 "but ignored");
6305 if (mParser ||
6306 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
6307 return;
6310 EditingState oldState = mEditingState;
6312 nsresult rv = EditingStateChanged();
6313 NS_ENSURE_SUCCESS_VOID(rv);
6315 if (oldState == mEditingState &&
6316 mEditingState == EditingState::eContentEditable) {
6317 // We just changed the contentEditable state of a node, we need to reset
6318 // the spellchecking state of that node.
6319 if (aElement) {
6320 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6321 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
6322 rv = htmlEditor->GetInlineSpellChecker(false,
6323 getter_AddRefs(spellChecker));
6324 NS_ENSURE_SUCCESS_VOID(rv);
6326 if (spellChecker &&
6327 aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) {
6328 RefPtr<nsRange> range = nsRange::Create(aElement);
6329 IgnoredErrorResult res;
6330 range->SelectNode(*aElement, res);
6331 if (res.Failed()) {
6332 // The node might be detached from the document at this point,
6333 // which would cause this call to fail. In this case, we can
6334 // safely ignore the contenteditable count change.
6335 return;
6338 rv = spellChecker->SpellCheckRange(range);
6339 NS_ENSURE_SUCCESS_VOID(rv);
6345 // aElement causes creating new HTMLEditor and the element had and keep
6346 // having focus, the HTMLEditor won't receive `focus` event. Therefore, we
6347 // need to notify HTMLEditor of it becomes editable.
6348 if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) &&
6349 fm->GetFocusedElement() == aElement) {
6350 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6351 DebugOnly<nsresult> rvIgnored =
6352 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement);
6353 NS_WARNING_ASSERTION(
6354 NS_SUCCEEDED(rvIgnored),
6355 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6356 "ignored");
6361 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
6362 // Currently, we need to check only when we're becoming editable for
6363 // contenteditable.
6364 if (mEditingState != EditingState::eContentEditable) {
6365 return;
6368 if (mHasBeenEditable) {
6369 return;
6371 mHasBeenEditable = true;
6373 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
6374 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
6375 // with proper keypress event for the active web app.
6376 WidgetEvent checkEvent(true, eUnidentifiedEvent);
6377 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
6378 checkEvent.mFlags.mCancelable = false;
6379 checkEvent.mFlags.mBubbles = false;
6380 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
6381 // Post the event rather than dispatching it synchronously because we need
6382 // a call of SetKeyPressEventModel() before first key input. Therefore, we
6383 // can avoid paying unnecessary runtime cost for most web apps.
6384 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
6387 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
6388 PresShell* presShell = GetPresShell();
6389 if (!presShell) {
6390 return;
6392 presShell->SetKeyPressEventModel(aKeyPressEventModel);
6395 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
6397 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
6398 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
6399 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
6400 aFocusTime >= mLastFocusTime);
6401 mLastFocusTime = aFocusTime;
6404 void Document::GetReferrer(nsAString& aReferrer) const {
6405 aReferrer.Truncate();
6406 if (!mReferrerInfo) {
6407 return;
6410 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
6411 if (!referrer) {
6412 return;
6415 nsAutoCString uri;
6416 nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
6417 if (NS_WARN_IF(NS_FAILED(rv))) {
6418 return;
6421 CopyUTF8toUTF16(uri, aReferrer);
6424 void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
6425 aCookie.Truncate(); // clear current cookie in case service fails;
6426 // no cookie isn't an error condition.
6428 if (mDisableCookieAccess) {
6429 return;
6432 // If the document's sandboxed origin flag is set, then reading cookies
6433 // is prohibited.
6434 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6435 aRv.ThrowSecurityError(
6436 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6437 "flag.");
6438 return;
6441 StorageAccess storageAccess = CookieAllowedForDocument(this);
6442 if (storageAccess == StorageAccess::eDeny) {
6443 return;
6446 if (ShouldPartitionStorage(storageAccess) &&
6447 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6448 return;
6451 // If the document is a cookie-averse Document... return the empty string.
6452 if (IsCookieAverse()) {
6453 return;
6456 // not having a cookie service isn't an error
6457 nsCOMPtr<nsICookieService> service =
6458 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6459 if (service) {
6460 nsAutoCString cookie;
6461 service->GetCookieStringFromDocument(this, cookie);
6462 // CopyUTF8toUTF16 doesn't handle error
6463 // because it assumes that the input is valid.
6464 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
6468 void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
6469 if (mDisableCookieAccess) {
6470 return;
6473 // If the document's sandboxed origin flag is set, then setting cookies
6474 // is prohibited.
6475 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6476 aRv.ThrowSecurityError(
6477 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6478 "flag.");
6479 return;
6482 StorageAccess storageAccess = CookieAllowedForDocument(this);
6483 if (storageAccess == StorageAccess::eDeny) {
6484 return;
6487 if (ShouldPartitionStorage(storageAccess) &&
6488 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6489 return;
6492 // If the document is a cookie-averse Document... do nothing.
6493 if (IsCookieAverse()) {
6494 return;
6497 if (!mDocumentURI) {
6498 return;
6501 // not having a cookie service isn't an error
6502 nsCOMPtr<nsICookieService> service =
6503 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6504 if (!service) {
6505 return;
6508 NS_ConvertUTF16toUTF8 cookie(aCookie);
6509 nsresult rv = service->SetCookieStringFromDocument(this, cookie);
6511 // No warning messages here.
6512 if (NS_FAILED(rv)) {
6513 return;
6516 nsCOMPtr<nsIObserverService> observerService =
6517 mozilla::services::GetObserverService();
6518 if (observerService) {
6519 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
6520 nsString(aCookie).get());
6524 ReferrerPolicy Document::GetReferrerPolicy() const {
6525 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
6526 : ReferrerPolicy::_empty;
6529 void Document::GetAlinkColor(nsAString& aAlinkColor) {
6530 aAlinkColor.Truncate();
6532 HTMLBodyElement* body = GetBodyElement();
6533 if (body) {
6534 body->GetALink(aAlinkColor);
6538 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
6539 HTMLBodyElement* body = GetBodyElement();
6540 if (body) {
6541 body->SetALink(aAlinkColor);
6545 void Document::GetLinkColor(nsAString& aLinkColor) {
6546 aLinkColor.Truncate();
6548 HTMLBodyElement* body = GetBodyElement();
6549 if (body) {
6550 body->GetLink(aLinkColor);
6554 void Document::SetLinkColor(const nsAString& aLinkColor) {
6555 HTMLBodyElement* body = GetBodyElement();
6556 if (body) {
6557 body->SetLink(aLinkColor);
6561 void Document::GetVlinkColor(nsAString& aVlinkColor) {
6562 aVlinkColor.Truncate();
6564 HTMLBodyElement* body = GetBodyElement();
6565 if (body) {
6566 body->GetVLink(aVlinkColor);
6570 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
6571 HTMLBodyElement* body = GetBodyElement();
6572 if (body) {
6573 body->SetVLink(aVlinkColor);
6577 void Document::GetBgColor(nsAString& aBgColor) {
6578 aBgColor.Truncate();
6580 HTMLBodyElement* body = GetBodyElement();
6581 if (body) {
6582 body->GetBgColor(aBgColor);
6586 void Document::SetBgColor(const nsAString& aBgColor) {
6587 HTMLBodyElement* body = GetBodyElement();
6588 if (body) {
6589 body->SetBgColor(aBgColor);
6593 void Document::GetFgColor(nsAString& aFgColor) {
6594 aFgColor.Truncate();
6596 HTMLBodyElement* body = GetBodyElement();
6597 if (body) {
6598 body->GetText(aFgColor);
6602 void Document::SetFgColor(const nsAString& aFgColor) {
6603 HTMLBodyElement* body = GetBodyElement();
6604 if (body) {
6605 body->SetText(aFgColor);
6609 void Document::CaptureEvents() {
6610 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
6613 void Document::ReleaseEvents() {
6614 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
6617 HTMLAllCollection* Document::All() {
6618 if (!mAll) {
6619 mAll = new HTMLAllCollection(this);
6621 return mAll;
6624 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
6625 if (mIsSrcdocDocument) {
6626 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
6627 if (inStrmChan) {
6628 return inStrmChan->GetSrcdocData(aSrcdocData);
6631 aSrcdocData = VoidString();
6632 return NS_OK;
6635 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
6636 nsPIDOMWindowOuter* win = GetWindow();
6637 if (!win) {
6638 return nullptr;
6640 return WindowProxyHolder(win->GetBrowsingContext());
6643 nsIContent* Document::GetUnretargetedFocusedContent(
6644 IncludeChromeOnly aIncludeChromeOnly) const {
6645 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6646 if (!window) {
6647 return nullptr;
6649 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6650 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6651 window, nsFocusManager::eOnlyCurrentWindow,
6652 getter_AddRefs(focusedWindow));
6653 if (!focusedContent) {
6654 return nullptr;
6656 // be safe and make sure the element is from this document
6657 if (focusedContent->OwnerDoc() != this) {
6658 return nullptr;
6660 if (focusedContent->ChromeOnlyAccess() &&
6661 aIncludeChromeOnly == IncludeChromeOnly::No) {
6662 return focusedContent->FindFirstNonChromeOnlyAccessContent();
6664 return focusedContent;
6667 Element* Document::GetActiveElement() {
6668 // Get the focused element.
6669 Element* focusedElement = GetRetargetedFocusedElement();
6670 if (focusedElement) {
6671 return focusedElement;
6674 // No focused element anywhere in this document. Try to get the BODY.
6675 if (IsHTMLOrXHTML()) {
6676 Element* bodyElement = AsHTMLDocument()->GetBody();
6677 if (bodyElement) {
6678 return bodyElement;
6680 // Special case to handle the transition to XHTML from XUL documents
6681 // where there currently isn't a body element, but we need to match the
6682 // XUL behavior. This should be removed when bug 1540278 is resolved.
6683 if (nsContentUtils::IsChromeDoc(this)) {
6684 Element* docElement = GetDocumentElement();
6685 if (docElement && docElement->IsXULElement()) {
6686 return docElement;
6689 // Because of IE compatibility, return null when html document doesn't have
6690 // a body.
6691 return nullptr;
6694 // If we couldn't get a BODY, return the root element.
6695 return GetDocumentElement();
6698 Element* Document::GetCurrentScript() {
6699 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
6700 return el;
6703 void Document::ReleaseCapture() const {
6704 // only release the capture if the caller can access it. This prevents a
6705 // page from stopping a scrollbar grab for example.
6706 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
6707 if (node && nsContentUtils::CanCallerAccess(node)) {
6708 PresShell::ReleaseCapturingContent();
6712 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
6713 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
6714 return mChromeXHRDocBaseURI;
6717 return GetDocBaseURI();
6720 void Document::SetBaseURI(nsIURI* aURI) {
6721 if (!aURI && !mDocumentBaseURI) {
6722 return;
6725 // Don't do anything if the URI wasn't actually changed.
6726 if (aURI && mDocumentBaseURI) {
6727 bool equalBases = false;
6728 mDocumentBaseURI->Equals(aURI, &equalBases);
6729 if (equalBases) {
6730 return;
6734 mDocumentBaseURI = aURI;
6735 mCachedURLData = nullptr;
6736 RefreshLinkHrefs();
6739 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
6740 const nsAString& aURI) {
6741 RefPtr<nsIURI> resolvedURI;
6742 MOZ_TRY(
6743 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
6744 return OwningNonNull<nsIURI>(std::move(resolvedURI));
6747 nsIReferrerInfo* Document::ReferrerInfoForInternalCSSAndSVGResources() {
6748 if (!mCachedReferrerInfoForInternalCSSAndSVGResources) {
6749 mCachedReferrerInfoForInternalCSSAndSVGResources =
6750 ReferrerInfo::CreateForInternalCSSAndSVGResources(this);
6752 return mCachedReferrerInfoForInternalCSSAndSVGResources;
6755 URLExtraData* Document::DefaultStyleAttrURLData() {
6756 MOZ_ASSERT(NS_IsMainThread());
6757 if (!mCachedURLData) {
6758 mCachedURLData = new URLExtraData(
6759 GetDocBaseURI(), ReferrerInfoForInternalCSSAndSVGResources(),
6760 NodePrincipal());
6762 return mCachedURLData;
6765 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
6766 if (mCharacterSet != aEncoding) {
6767 mCharacterSet = aEncoding;
6768 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
6769 RecomputeLanguageFromCharset();
6771 if (nsPresContext* context = GetPresContext()) {
6772 context->DocumentCharSetChanged(aEncoding);
6777 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
6778 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
6781 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
6782 aData.Truncate();
6783 const HeaderData* data = mHeaderData.get();
6784 while (data) {
6785 if (data->mField == aHeaderField) {
6786 aData = data->mData;
6787 break;
6789 data = data->mNext.get();
6793 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
6794 if (!aHeaderField) {
6795 NS_ERROR("null headerField");
6796 return;
6799 if (!mHeaderData) {
6800 if (!aData.IsEmpty()) { // don't bother storing empty string
6801 mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
6803 } else {
6804 HeaderData* data = mHeaderData.get();
6805 UniquePtr<HeaderData>* lastPtr = &mHeaderData;
6806 bool found = false;
6807 do { // look for existing and replace
6808 if (data->mField == aHeaderField) {
6809 if (!aData.IsEmpty()) {
6810 data->mData.Assign(aData);
6811 } else { // don't store empty string
6812 // Note that data->mNext is moved to a temporary before the old value
6813 // of *lastPtr is deleted.
6814 *lastPtr = std::move(data->mNext);
6816 found = true;
6818 break;
6820 lastPtr = &data->mNext;
6821 data = lastPtr->get();
6822 } while (data);
6824 if (!aData.IsEmpty() && !found) {
6825 // didn't find, append
6826 *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
6830 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
6831 if (aData.IsEmpty()) {
6832 mContentLanguage = nullptr;
6833 } else {
6834 mContentLanguage = NS_AtomizeMainThread(aData);
6836 mMayNeedFontPrefsUpdate = true;
6837 if (auto* presContext = GetPresContext()) {
6838 presContext->ContentLanguageChanged();
6842 if (aHeaderField == nsGkAtoms::origin_trial) {
6843 mTrials.UpdateFromToken(aData, NodePrincipal());
6844 if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) {
6845 InitCOEP(mChannel);
6847 // If we still don't have a WindowContext, WindowContext::OnNewDocument
6848 // will take care of this.
6849 if (WindowContext* ctx = GetWindowContext()) {
6850 if (mEmbedderPolicy) {
6851 Unused << ctx->SetEmbedderPolicy(mEmbedderPolicy.value());
6857 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
6858 SetPreferredStyleSheetSet(aData);
6861 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
6862 // We get into this code before we have a script global yet, so get to our
6863 // container via mDocumentContainer.
6864 if (mDocumentContainer) {
6865 // Note: using mDocumentURI instead of mBaseURI here, for consistency
6866 // (used to just use the current URI of our webnavigation, but that
6867 // should really be the same thing). Note that this code can run
6868 // before the current URI of the webnavigation has been updated, so we
6869 // can't assert equality here.
6870 mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
6874 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
6875 mAllowDNSPrefetch) {
6876 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
6877 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
6880 if (aHeaderField == nsGkAtoms::handheldFriendly) {
6881 mViewportType = Unknown;
6885 void Document::SetEarlyHints(
6886 nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) {
6887 mEarlyHints = std::move(aEarlyHints);
6890 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
6891 NotNull<const Encoding*>& aEncoding,
6892 nsHtml5TreeOpExecutor* aExecutor) {
6893 if (aChannel) {
6894 nsAutoCString charsetVal;
6895 nsresult rv = aChannel->GetContentCharset(charsetVal);
6896 if (NS_SUCCEEDED(rv)) {
6897 const Encoding* preferred = Encoding::ForLabel(charsetVal);
6898 if (preferred) {
6899 if (aExecutor && preferred == REPLACEMENT_ENCODING) {
6900 aExecutor->ComplainAboutBogusProtocolCharset(this, false);
6902 aEncoding = WrapNotNull(preferred);
6903 aCharsetSource = kCharsetFromChannel;
6904 return;
6905 } else if (aExecutor && !charsetVal.IsEmpty()) {
6906 aExecutor->ComplainAboutBogusProtocolCharset(this, true);
6912 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
6913 #ifdef DEBUG
6914 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
6915 const Element* element = Element::FromNode(node);
6916 if (!element) {
6917 continue;
6919 MOZ_ASSERT(!element->HasServoData());
6921 #endif
6924 already_AddRefed<PresShell> Document::CreatePresShell(
6925 nsPresContext* aContext, nsViewManager* aViewManager) {
6926 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
6928 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
6930 AssertNoStaleServoDataIn(*this);
6932 RefPtr<PresShell> presShell = new PresShell(this);
6933 // Note: we don't hold a ref to the shell (it holds a ref to us)
6934 mPresShell = presShell;
6936 if (!mStyleSetFilled) {
6937 FillStyleSet();
6940 presShell->Init(aContext, aViewManager);
6941 if (RefPtr<class HighlightRegistry> highlightRegistry = mHighlightRegistry) {
6942 highlightRegistry->AddHighlightSelectionsToFrameSelection();
6944 // Gaining a shell causes changes in how media queries are evaluated, so
6945 // invalidate that.
6946 aContext->MediaFeatureValuesChanged(
6947 {MediaFeatureChange::kAllChanges},
6948 MediaFeatureChangePropagation::JustThisDocument);
6950 // Make sure to never paint if we belong to an invisible DocShell.
6951 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
6952 if (docShell && docShell->IsInvisible()) {
6953 presShell->SetNeverPainting(true);
6956 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
6957 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
6958 presShell.get(), docShell.get()));
6960 mExternalResourceMap.ShowViewers();
6962 UpdateFrameRequestCallbackSchedulingState();
6964 if (mDocumentL10n) {
6965 // In case we already accumulated mutations,
6966 // we'll trigger the refresh driver now.
6967 mDocumentL10n->OnCreatePresShell();
6970 if (HasAutoFocusCandidates()) {
6971 ScheduleFlushAutoFocusCandidates();
6973 // Now that we have a shell, we might have @font-face rules (the presence of a
6974 // shell may change which rules apply to us). We don't need to do anything
6975 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
6976 // is ready to update we'll flush the font set.
6977 MarkUserFontSetDirty();
6979 // Take the author style disabled state from the top browsing cvontext.
6980 // (PageStyleChild.sys.mjs ensures this is up to date.)
6981 if (BrowsingContext* bc = GetBrowsingContext()) {
6982 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
6985 return presShell.forget();
6988 void Document::UpdateFrameRequestCallbackSchedulingState(
6989 PresShell* aOldPresShell) {
6990 // If this condition changes to depend on some other variable, make sure to
6991 // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
6992 // that variable can change. Also consider if you should change
6993 // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
6994 // condition.
6995 bool shouldBeScheduled =
6996 WouldScheduleFrameRequestCallbacks() && !mFrameRequestManager.IsEmpty();
6997 if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
6998 // nothing to do
6999 return;
7002 PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
7003 MOZ_RELEASE_ASSERT(presShell);
7005 nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
7006 if (shouldBeScheduled) {
7007 rd->ScheduleFrameRequestCallbacks(this);
7008 } else {
7009 rd->RevokeFrameRequestCallbacks(this);
7012 mFrameRequestCallbacksScheduled = shouldBeScheduled;
7015 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
7016 MOZ_ASSERT(aCallbacks.IsEmpty());
7017 mFrameRequestManager.Take(aCallbacks);
7018 // No need to manually remove ourselves from the refresh driver; it will
7019 // handle that part. But we do have to update our state.
7020 mFrameRequestCallbacksScheduled = false;
7023 bool Document::ShouldThrottleFrameRequests() const {
7024 if (mStaticCloneCount > 0) {
7025 // Even if we're not visible, a static clone may be, so run at full speed.
7026 return false;
7029 if (Hidden()) {
7030 // We're not visible (probably in a background tab or the bf cache).
7031 return true;
7034 if (!mPresShell) {
7035 // Can't do anything smarter. We don't run frame requests in documents
7036 // without a pres shell anyways.
7037 return false;
7040 if (!mPresShell->IsActive()) {
7041 // The pres shell is not active (we're an invisible OOP iframe or such), so
7042 // throttle.
7043 return true;
7046 if (mPresShell->IsPaintingSuppressed()) {
7047 // Historically we have throttled frame requests until we've painted at
7048 // least once, so keep doing that.
7049 return true;
7052 if (mPresShell->IsUnderHiddenEmbedderElement()) {
7053 // For display: none and visibility: hidden we always throttle, for
7054 // consistency with OOP iframes.
7055 return true;
7058 Element* el = GetEmbedderElement();
7059 if (!el) {
7060 // If we're not in-process, our refresh driver is throttled separately (via
7061 // PresShell::SetIsActive, so not much more we can do here.
7062 return false;
7065 if (!StaticPrefs::layout_throttle_in_process_iframes()) {
7066 return false;
7069 // Note that because we have to scroll this document into view at least once
7070 // to unthrottle it, we will drop one requestAnimationFrame frame when a
7071 // document that previously wasn't visible scrolls into view. This is
7072 // acceptable / unlikely to be human-perceivable, though we could improve on
7073 // it if needed by adding an intersection margin or something of that sort.
7074 const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
7075 *el->OwnerDoc(), /* aRoot = */ nullptr, /* aRootMargin = */ nullptr);
7076 const IntersectionOutput output =
7077 DOMIntersectionObserver::Intersect(input, *el);
7078 return !output.Intersects();
7081 void Document::DeletePresShell() {
7082 mExternalResourceMap.HideViewers();
7083 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
7084 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
7085 presContext->RefreshDriver()->CancelFlushAutoFocus(this);
7088 // When our shell goes away, request that all our images be immediately
7089 // discarded, so we don't carry around decoded image data for a document we
7090 // no longer intend to paint.
7091 ImageTracker()->RequestDiscardAll();
7093 // Now that we no longer have a shell, we need to forget about any FontFace
7094 // objects for @font-face rules that came from the style set. There's no need
7095 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
7096 // no point on it.
7097 MarkUserFontSetDirty();
7099 if (IsEditingOn()) {
7100 TurnEditingOff();
7103 PresShell* oldPresShell = mPresShell;
7104 mPresShell = nullptr;
7105 UpdateFrameRequestCallbackSchedulingState(oldPresShell);
7107 ClearStaleServoData();
7108 AssertNoStaleServoDataIn(*this);
7110 mStyleSet->ShellDetachedFromDocument();
7111 mStyleSetFilled = false;
7112 mQuirkSheetAdded = false;
7113 mContentEditableSheetAdded = false;
7114 mDesignModeSheetAdded = false;
7117 void Document::DisallowBFCaching(uint32_t aStatus) {
7118 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
7119 if (!mBFCacheDisallowed) {
7120 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
7121 wgc->SendUpdateBFCacheStatus(aStatus, 0);
7124 mBFCacheDisallowed = true;
7127 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
7128 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
7130 if (mPresShell) {
7131 if (aEntry) {
7132 mPresShell->StopObservingRefreshDriver();
7133 } else if (mBFCacheEntry) {
7134 mPresShell->StartObservingRefreshDriver();
7137 mBFCacheEntry = aEntry;
7140 bool Document::RemoveFromBFCacheSync() {
7141 bool removed = false;
7142 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
7143 entry->RemoveFromBFCacheSync();
7144 removed = true;
7145 } else if (!IsCurrentActiveDocument()) {
7146 // In the old bfcache implementation while the new page is loading, but
7147 // before nsIDocumentViewer.show() has been called, the previous page
7148 // doesn't yet have nsIBFCacheEntry. However, the previous page isn't the
7149 // current active document anymore.
7150 DisallowBFCaching();
7151 removed = true;
7154 if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
7155 if (BrowsingContext* bc = GetBrowsingContext()) {
7156 if (bc->IsInBFCache()) {
7157 ContentChild* cc = ContentChild::GetSingleton();
7158 // IPC is asynchronous but the caller is supposed to check the return
7159 // value. The reason for 'Sync' in the method name is that the old
7160 // implementation may run scripts. There is Async variant in
7161 // the old session history implementation for the cases where
7162 // synchronous operation isn't safe.
7163 cc->SendRemoveFromBFCache(bc->Top());
7164 removed = true;
7168 return removed;
7171 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
7172 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
7174 NS_RELEASE(e->mKey);
7175 if (e->mSubDocument) {
7176 e->mSubDocument->SetParentDocument(nullptr);
7177 NS_RELEASE(e->mSubDocument);
7181 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
7182 SubDocMapEntry* e =
7183 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
7185 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
7186 NS_ADDREF(e->mKey);
7188 e->mSubDocument = nullptr;
7191 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
7192 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
7194 if (!aSubDoc) {
7195 // aSubDoc is nullptr, remove the mapping
7197 if (mSubDocuments) {
7198 mSubDocuments->Remove(aElement);
7200 } else {
7201 if (!mSubDocuments) {
7202 // Create a new hashtable
7204 static const PLDHashTableOps hash_table_ops = {
7205 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
7206 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
7208 mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
7211 // Add a mapping to the hash table
7212 auto entry =
7213 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
7215 if (!entry) {
7216 return NS_ERROR_OUT_OF_MEMORY;
7219 if (entry->mSubDocument) {
7220 entry->mSubDocument->SetParentDocument(nullptr);
7222 // Release the old sub document
7223 NS_RELEASE(entry->mSubDocument);
7226 entry->mSubDocument = aSubDoc;
7227 NS_ADDREF(entry->mSubDocument);
7229 aSubDoc->SetParentDocument(this);
7232 return NS_OK;
7235 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
7236 if (mSubDocuments && aContent->IsElement()) {
7237 auto entry = static_cast<SubDocMapEntry*>(
7238 mSubDocuments->Search(aContent->AsElement()));
7240 if (entry) {
7241 return entry->mSubDocument;
7245 return nullptr;
7248 Element* Document::GetEmbedderElement() const {
7249 // We check if we're the active document in our BrowsingContext
7250 // by comparing against its document, rather than checking if the
7251 // WindowContext is cached, since mWindow may be null when we're
7252 // called (such as in nsPresContext::Init).
7253 if (BrowsingContext* bc = GetBrowsingContext()) {
7254 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
7257 return nullptr;
7260 Element* Document::GetRootElement() const {
7261 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
7262 ? mCachedRootElement
7263 : GetRootElementInternal();
7266 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
7268 Element* Document::GetRootElementInternal() const {
7269 // We invoke GetRootElement() immediately before the servo traversal, so we
7270 // should always have a cache hit from Servo.
7271 MOZ_ASSERT(NS_IsMainThread());
7273 // Loop backwards because any non-elements, such as doctypes and PIs
7274 // are likely to appear before the root element.
7275 for (nsIContent* child = GetLastChild(); child;
7276 child = child->GetPreviousSibling()) {
7277 if (Element* element = Element::FromNode(child)) {
7278 const_cast<Document*>(this)->mCachedRootElement = element;
7279 return element;
7283 const_cast<Document*>(this)->mCachedRootElement = nullptr;
7284 return nullptr;
7287 void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
7288 bool aNotify, ErrorResult& aRv) {
7289 if (aKid->IsElement() && GetRootElement()) {
7290 NS_WARNING("Inserting root element when we already have one");
7291 aRv.ThrowHierarchyRequestError("There is already a root element.");
7292 return;
7295 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
7298 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
7299 Maybe<mozAutoDocUpdate> updateBatch;
7300 if (aKid->IsElement()) {
7301 updateBatch.emplace(this, aNotify);
7302 // Destroy the link map up front before we mess with the child list.
7303 DestroyElementMaps();
7306 // Preemptively clear mCachedRootElement, since we may be about to remove it
7307 // from our child list, and we don't want to return this maybe-obsolete value
7308 // from any GetRootElement() calls that happen inside of RemoveChildNode().
7309 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
7310 // GetRootElement() calls until after it's removed the child from mChildren.
7311 // Any call before that point would restore this soon-to-be-obsolete cached
7312 // answer, and our clearing here would be fruitless.)
7313 mCachedRootElement = nullptr;
7314 nsINode::RemoveChildNode(aKid, aNotify);
7315 MOZ_ASSERT(mCachedRootElement != aKid,
7316 "Stale pointer in mCachedRootElement, after we tried to clear it "
7317 "(maybe somebody called GetRootElement() too early?)");
7320 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
7321 if (mStyleSetFilled) {
7322 EnsureStyleSet().AddDocStyleSheet(aSheet);
7323 ApplicableStylesChanged();
7327 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
7328 EnsureStyleSet().RecordShadowStyleChange(aShadowRoot);
7329 ApplicableStylesChanged(/* aKnownInShadowTree= */ true);
7332 void Document::ApplicableStylesChanged(bool aKnownInShadowTree) {
7333 // TODO(emilio): if we decide to resolve style in display: none iframes, then
7334 // we need to always track style changes and remove the mStyleSetFilled.
7335 if (!mStyleSetFilled) {
7336 return;
7338 if (!aKnownInShadowTree) {
7339 MarkUserFontSetDirty();
7341 PresShell* ps = GetPresShell();
7342 if (!ps) {
7343 return;
7346 ps->EnsureStyleFlush();
7347 nsPresContext* pc = ps->GetPresContext();
7348 if (!pc) {
7349 return;
7352 if (!aKnownInShadowTree) {
7353 pc->MarkCounterStylesDirty();
7354 pc->MarkFontFeatureValuesDirty();
7355 pc->MarkFontPaletteValuesDirty();
7357 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
7360 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
7361 if (mStyleSetFilled) {
7362 mStyleSet->RemoveStyleSheet(aSheet);
7363 ApplicableStylesChanged();
7367 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
7368 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
7370 if (aSheet.IsApplicable()) {
7371 AddStyleSheetToStyleSets(aSheet);
7375 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
7376 const bool applicable = aSheet.IsApplicable();
7377 // If we're actually in the document style sheet list
7378 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
7379 if (applicable) {
7380 AddStyleSheetToStyleSets(aSheet);
7381 } else {
7382 RemoveStyleSheetFromStyleSets(aSheet);
7387 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
7388 if (!StyleSheetChangeEventsEnabled()) {
7389 return;
7392 StyleSheetApplicableStateChangeEventInit init;
7393 init.mBubbles = true;
7394 init.mCancelable = true;
7395 init.mStylesheet = &aSheet;
7396 init.mApplicable = aSheet.IsApplicable();
7398 RefPtr<StyleSheetApplicableStateChangeEvent> event =
7399 StyleSheetApplicableStateChangeEvent::Constructor(
7400 this, u"StyleSheetApplicableStateChanged"_ns, init);
7401 event->SetTrusted(true);
7402 event->SetTarget(this);
7403 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7404 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7405 asyncDispatcher->PostDOMEvent();
7408 void Document::PostStyleSheetRemovedEvent(StyleSheet& aSheet) {
7409 if (!StyleSheetChangeEventsEnabled()) {
7410 return;
7413 StyleSheetRemovedEventInit init;
7414 init.mBubbles = true;
7415 init.mCancelable = false;
7416 init.mStylesheet = &aSheet;
7418 RefPtr<StyleSheetRemovedEvent> event =
7419 StyleSheetRemovedEvent::Constructor(this, u"StyleSheetRemoved"_ns, init);
7420 event->SetTrusted(true);
7421 event->SetTarget(this);
7422 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7423 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7424 asyncDispatcher->PostDOMEvent();
7427 void Document::PostCustomPropertyRegistered(
7428 const PropertyDefinition& aDefinition) {
7429 if (!StyleSheetChangeEventsEnabled()) {
7430 return;
7433 CSSCustomPropertyRegisteredEventInit init;
7434 init.mBubbles = true;
7435 init.mCancelable = false;
7437 InspectorCSSPropertyDefinition property;
7439 property.mName.Append(aDefinition.mName);
7440 property.mSyntax.Append(aDefinition.mSyntax);
7441 property.mInherits = aDefinition.mInherits;
7442 if (aDefinition.mInitialValue.WasPassed()) {
7443 property.mInitialValue.Append(aDefinition.mInitialValue.Value());
7444 } else {
7445 property.mInitialValue.SetIsVoid(true);
7447 property.mFromJS = true;
7448 init.mPropertyDefinition = property;
7450 RefPtr<CSSCustomPropertyRegisteredEvent> event =
7451 CSSCustomPropertyRegisteredEvent::Constructor(
7452 this, u"csscustompropertyregistered"_ns, init);
7453 event->SetTrusted(true);
7454 event->SetTarget(this);
7455 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7456 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7457 asyncDispatcher->PostDOMEvent();
7460 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
7461 nsIURI* aSheetURI) {
7462 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
7463 bool bEqual;
7464 nsIURI* uri = aSheets[i]->GetSheetURI();
7466 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
7467 return i;
7470 return -1;
7473 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
7474 nsIURI* aSheetURI) {
7475 MOZ_ASSERT(aSheetURI, "null arg");
7477 // Checking if we have loaded this one already.
7478 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
7479 return NS_ERROR_INVALID_ARG;
7481 // Loading the sheet sync.
7482 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
7484 css::SheetParsingMode parsingMode;
7485 switch (aType) {
7486 case Document::eAgentSheet:
7487 parsingMode = css::eAgentSheetFeatures;
7488 break;
7490 case Document::eUserSheet:
7491 parsingMode = css::eUserSheetFeatures;
7492 break;
7494 case Document::eAuthorSheet:
7495 parsingMode = css::eAuthorSheetFeatures;
7496 break;
7498 default:
7499 MOZ_CRASH("impossible value for aType");
7502 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
7503 css::Loader::UseSystemPrincipal::Yes);
7504 if (result.isErr()) {
7505 return result.unwrapErr();
7508 RefPtr<StyleSheet> sheet = result.unwrap();
7510 sheet->SetAssociatedDocumentOrShadowRoot(this);
7511 MOZ_ASSERT(sheet->IsApplicable());
7513 return AddAdditionalStyleSheet(aType, sheet);
7516 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
7517 StyleSheet* aSheet) {
7518 if (mAdditionalSheets[aType].Contains(aSheet)) {
7519 return NS_ERROR_INVALID_ARG;
7522 if (!aSheet->IsApplicable()) {
7523 return NS_ERROR_INVALID_ARG;
7526 mAdditionalSheets[aType].AppendElement(aSheet);
7528 if (mStyleSetFilled) {
7529 EnsureStyleSet().AppendStyleSheet(*aSheet);
7530 ApplicableStylesChanged();
7532 return NS_OK;
7535 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
7536 nsIURI* aSheetURI) {
7537 MOZ_ASSERT(aSheetURI);
7539 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
7541 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
7542 if (i >= 0) {
7543 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
7544 sheets.RemoveElementAt(i);
7546 if (!mIsGoingAway) {
7547 MOZ_ASSERT(sheetRef->IsApplicable());
7548 if (mStyleSetFilled) {
7549 EnsureStyleSet().RemoveStyleSheet(*sheetRef);
7550 ApplicableStylesChanged();
7553 sheetRef->ClearAssociatedDocumentOrShadowRoot();
7557 nsIGlobalObject* Document::GetScopeObject() const {
7558 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
7559 return scope;
7562 DocGroup* Document::GetDocGroupOrCreate() {
7563 if (!mDocGroup && GetBrowsingContext()) {
7564 BrowsingContextGroup* group = GetBrowsingContext()->Group();
7565 MOZ_ASSERT(group);
7567 nsAutoCString docGroupKey;
7568 nsresult rv = mozilla::dom::DocGroup::GetKey(
7569 NodePrincipal(), group->IsPotentiallyCrossOriginIsolated(),
7570 docGroupKey);
7571 if (NS_SUCCEEDED(rv)) {
7572 mDocGroup = group->AddDocument(docGroupKey, this);
7575 return mDocGroup;
7578 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
7579 mScopeObject = do_GetWeakReference(aGlobal);
7580 if (aGlobal) {
7581 mHasHadScriptHandlingObject = true;
7583 nsPIDOMWindowInner* window = aGlobal->GetAsInnerWindow();
7584 if (!window) {
7585 return;
7588 // Same origin data documents should have the same docGroup as their scope
7589 // window.
7590 if (mLoadedAsData && window->GetExtantDoc() &&
7591 window->GetExtantDoc() != this &&
7592 window->GetExtantDoc()->NodePrincipal() == NodePrincipal()) {
7593 DocGroup* docGroup = window->GetExtantDoc()->GetDocGroup();
7595 if (docGroup) {
7596 if (!mDocGroup) {
7597 mDocGroup = docGroup;
7598 mDocGroup->AddDocument(this);
7599 } else {
7600 MOZ_ASSERT(mDocGroup == docGroup,
7601 "Data document has a mismatched doc group?");
7603 #ifdef DEBUG
7604 AssertDocGroupMatchesKey();
7605 #endif
7606 return;
7609 MOZ_ASSERT_UNREACHABLE(
7610 "Scope window doesn't have DocGroup when creating data document?");
7611 // ... but fall through to be safe.
7614 BrowsingContextGroup* browsingContextGroup =
7615 window->GetBrowsingContextGroup();
7617 // We should already have the principal, and now that we have been added
7618 // to a window, we should be able to join a DocGroup!
7619 nsAutoCString docGroupKey;
7620 nsresult rv = mozilla::dom::DocGroup::GetKey(
7621 NodePrincipal(),
7622 browsingContextGroup->IsPotentiallyCrossOriginIsolated(), docGroupKey);
7623 if (mDocGroup) {
7624 if (NS_SUCCEEDED(rv)) {
7625 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
7627 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
7628 browsingContextGroup);
7629 } else {
7630 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
7632 MOZ_ASSERT(mDocGroup);
7635 MOZ_ASSERT_IF(
7636 mNodeInfoManager->GetArenaAllocator(),
7637 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
7641 bool Document::ContainsEMEContent() {
7642 nsPIDOMWindowInner* win = GetInnerWindow();
7643 // Note this case is different from checking just media elements in that
7644 // it covers when we've created MediaKeys but not associated them with a
7645 // media element.
7646 return win && win->HasActiveMediaKeysInstance();
7649 bool Document::ContainsMSEContent() {
7650 bool containsMSE = false;
7651 EnumerateActivityObservers([&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;
7660 return containsMSE;
7663 static void NotifyActivityChangedCallback(nsISupports* aSupports) {
7664 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7665 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7666 mediaElem->NotifyOwnerDocumentActivityChanged();
7668 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
7669 do_QueryInterface(aSupports));
7670 if (objectDocumentActivity) {
7671 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
7672 } else {
7673 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
7674 do_QueryInterface(aSupports));
7675 if (imageLoadingContent) {
7676 auto* ilc =
7677 static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
7678 ilc->NotifyOwnerDocumentActivityChanged();
7683 void Document::NotifyActivityChanged() {
7684 EnumerateActivityObservers(NotifyActivityChangedCallback);
7687 void Document::SetContainer(nsDocShell* aContainer) {
7688 if (aContainer) {
7689 mDocumentContainer = aContainer;
7690 } else {
7691 mDocumentContainer = WeakPtr<nsDocShell>();
7694 mInChromeDocShell =
7695 aContainer && aContainer->GetBrowsingContext()->IsChrome();
7697 NotifyActivityChanged();
7699 // IsTopLevelWindowInactive depends on the docshell, so
7700 // update the cached value now that it's available.
7701 UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false);
7702 if (!aContainer) {
7703 return;
7706 BrowsingContext* context = aContainer->GetBrowsingContext();
7707 MOZ_ASSERT_IF(context && mDocGroup,
7708 context->Group() == mDocGroup->GetBrowsingContextGroup());
7709 if (context && context->IsContent()) {
7710 SetIsTopLevelContentDocument(context->IsTopContent());
7711 SetIsContentDocument(true);
7712 } else {
7713 SetIsTopLevelContentDocument(false);
7714 SetIsContentDocument(false);
7718 nsISupports* Document::GetContainer() const {
7719 return static_cast<nsIDocShell*>(mDocumentContainer);
7722 void Document::SetScriptGlobalObject(
7723 nsIScriptGlobalObject* aScriptGlobalObject) {
7724 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
7725 mAnimationController->IsPausedByType(
7726 SMILTimeContainer::PAUSE_PAGEHIDE |
7727 SMILTimeContainer::PAUSE_BEGIN),
7728 "Clearing window pointer while animations are unpaused");
7730 if (mScriptGlobalObject && !aScriptGlobalObject) {
7731 // We're detaching from the window. We need to grab a pointer to
7732 // our layout history state now.
7733 mLayoutHistoryState = GetLayoutHistoryState();
7735 // Also make sure to remove our onload blocker now if we haven't done it yet
7736 if (mOnloadBlockCount != 0) {
7737 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
7738 if (loadGroup) {
7739 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
7743 if (GetController().isSome()) {
7744 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
7745 loader->ClearCacheForControlledDocument(this);
7748 // We may become controlled again if this document comes back out
7749 // of bfcache. Clear our state to allow that to happen. Only
7750 // clear this flag if we are actually controlled, though, so pages
7751 // that were force reloaded don't become controlled when they
7752 // come out of bfcache.
7753 mMaybeServiceWorkerControlled = false;
7756 if (GetWindowContext()) {
7757 // The document is about to lose its window, so this is a good time to
7758 // send our page use counters, while we still have access to our
7759 // WindowContext.
7761 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
7762 // catches some cases of documents losing their window that don't
7763 // get in here.)
7764 SendPageUseCounters();
7768 // BlockOnload() might be called before mScriptGlobalObject is set.
7769 // We may need to add the blocker once mScriptGlobalObject is set.
7770 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
7772 mScriptGlobalObject = aScriptGlobalObject;
7774 if (needOnloadBlocker) {
7775 EnsureOnloadBlocker();
7778 UpdateFrameRequestCallbackSchedulingState();
7780 if (aScriptGlobalObject) {
7781 // Go back to using the docshell for the layout history state
7782 mLayoutHistoryState = nullptr;
7783 SetScopeObject(aScriptGlobalObject);
7784 mHasHadDefaultView = true;
7786 if (mAllowDNSPrefetch) {
7787 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7788 if (docShell) {
7789 #ifdef DEBUG
7790 nsCOMPtr<nsIWebNavigation> webNav =
7791 do_GetInterface(aScriptGlobalObject);
7792 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
7793 "Unexpected container or script global?");
7794 #endif
7795 bool allowDNSPrefetch;
7796 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
7797 mAllowDNSPrefetch = allowDNSPrefetch;
7801 // If we are set in a window that is already focused we should remember this
7802 // as the time the document gained focus.
7803 if (HasFocus(IgnoreErrors())) {
7804 SetLastFocusTime(TimeStamp::Now());
7808 // Remember the pointer to our window (or lack there of), to avoid
7809 // having to QI every time it's asked for.
7810 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
7811 mWindow = window;
7813 // Now that we know what our window is, we can flush the CSP errors to the
7814 // Web Console. We are flushing all messages that occurred and were stored in
7815 // the queue prior to this point.
7816 if (mCSP) {
7817 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
7820 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
7821 do_QueryInterface(GetChannel());
7822 if (internalChannel) {
7823 nsCOMArray<nsISecurityConsoleMessage> messages;
7824 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
7825 MOZ_ASSERT(NS_SUCCEEDED(rv));
7826 SendToConsole(messages);
7829 // Set our visibility state, but do not fire the event. This is correct
7830 // because either we're coming out of bfcache (in which case IsVisible() will
7831 // still test false at this point and no state change will happen) or we're
7832 // doing the initial document load and don't want to fire the event for this
7833 // change.
7835 // When the visibility is changed, notify it to observers.
7836 // Some observers need the notification, for example HTMLMediaElement uses
7837 // it to update internal media resource allocation.
7838 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
7839 // creation are already done before Document::SetScriptGlobalObject() call.
7840 // MediaDecoder decides whether starting decoding is decided based on
7841 // document's visibility. When the MediaDecoder is created,
7842 // Document::SetScriptGlobalObject() is not yet called and document is
7843 // hidden state. Therefore the MediaDecoder decides that decoding is
7844 // not yet necessary. But soon after Document::SetScriptGlobalObject()
7845 // call, the document becomes not hidden. At the time, MediaDecoder needs
7846 // to know it and needs to start updating decoding.
7847 UpdateVisibilityState(DispatchVisibilityChange::No);
7849 // The global in the template contents owner document should be the same.
7850 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
7851 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
7854 // Tell the script loader about the new global object.
7855 if (mScriptLoader && !IsTemplateContentsOwner()) {
7856 mScriptLoader->SetGlobalObject(mScriptGlobalObject);
7859 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
7860 mScriptGlobalObject && GetChannel()) {
7861 // If we are shift-reloaded, don't associate with a ServiceWorker.
7862 if (mDocumentContainer->IsForceReloading()) {
7863 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
7864 return;
7867 mMaybeServiceWorkerControlled = true;
7871 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
7872 MOZ_ASSERT(!mScriptGlobalObject,
7873 "Do not call this when mScriptGlobalObject is set!");
7874 if (mHasHadDefaultView) {
7875 return nullptr;
7878 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
7879 do_QueryReferent(mScopeObject);
7880 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
7881 if (win) {
7882 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
7883 if (!outer || outer->GetCurrentInnerWindow() != win) {
7884 NS_WARNING("Wrong inner/outer window combination!");
7885 return nullptr;
7888 return scriptHandlingObject;
7890 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
7891 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
7892 "Wrong script object!");
7893 if (aScriptObject) {
7894 SetScopeObject(aScriptObject);
7895 mHasHadDefaultView = false;
7899 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
7900 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
7901 // Let's use mScriptGlobalObject. Even if the document is already removed from
7902 // the docshell, the outer window might be still obtainable from the it.
7903 nsCOMPtr<nsPIDOMWindowOuter> win;
7904 if (mRemovedFromDocShell) {
7905 // The docshell returns the outer window we are done.
7906 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
7907 if (kungFuDeathGrip) {
7908 win = kungFuDeathGrip->GetWindow();
7910 } else {
7911 if (nsCOMPtr<nsPIDOMWindowInner> inner =
7912 do_QueryInterface(mScriptGlobalObject)) {
7913 // mScriptGlobalObject is always the inner window, let's get the outer.
7914 win = inner->GetOuterWindow();
7918 return win;
7921 bool Document::InternalAllowXULXBL() {
7922 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
7923 mAllowXULXBL = eTriTrue;
7924 return true;
7927 mAllowXULXBL = eTriFalse;
7928 return false;
7931 // Note: We don't hold a reference to the document observer; we assume
7932 // that it has a live reference to the document.
7933 void Document::AddObserver(nsIDocumentObserver* aObserver) {
7934 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
7935 "Observer already in the list");
7936 mObservers.AppendElement(aObserver);
7937 AddMutationObserver(aObserver);
7940 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
7941 // If we're in the process of destroying the document (and we're
7942 // informing the observers of the destruction), don't remove the
7943 // observers from the list. This is not a big deal, since we
7944 // don't hold a live reference to the observers.
7945 if (!mInDestructor) {
7946 RemoveMutationObserver(aObserver);
7947 return mObservers.RemoveElement(aObserver);
7950 return mObservers.Contains(aObserver);
7953 void Document::BeginUpdate() {
7954 ++mUpdateNestLevel;
7955 nsContentUtils::AddScriptBlocker();
7956 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
7959 void Document::EndUpdate() {
7960 const bool reset = !mPendingMaybeEditingStateChanged;
7961 mPendingMaybeEditingStateChanged = true;
7963 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
7965 --mUpdateNestLevel;
7967 nsContentUtils::RemoveScriptBlocker();
7969 if (mXULBroadcastManager) {
7970 mXULBroadcastManager->MaybeBroadcast();
7973 if (reset) {
7974 mPendingMaybeEditingStateChanged = false;
7976 MaybeEditingStateChanged();
7979 void Document::BeginLoad() {
7980 if (IsEditingOn()) {
7981 // Reset() blows away all event listeners in the document, and our
7982 // editor relies heavily on those. Midas is turned on, to make it
7983 // work, re-initialize it to give it a chance to add its event
7984 // listeners again.
7986 TurnEditingOff();
7987 EditingStateChanged();
7990 MOZ_ASSERT(!mDidCallBeginLoad);
7991 mDidCallBeginLoad = true;
7993 // Block onload here to prevent having to deal with blocking and
7994 // unblocking it while we know the document is loading.
7995 BlockOnload();
7996 mDidFireDOMContentLoaded = false;
7997 BlockDOMContentLoaded();
7999 if (mScriptLoader) {
8000 mScriptLoader->BeginDeferringScripts();
8003 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
8006 void Document::MozSetImageElement(const nsAString& aImageElementId,
8007 Element* aElement) {
8008 if (aImageElementId.IsEmpty()) return;
8010 // Hold a script blocker while calling SetImageElement since that can call
8011 // out to id-observers
8012 nsAutoScriptBlocker scriptBlocker;
8014 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
8015 if (entry) {
8016 entry->SetImageElement(aElement);
8017 if (entry->IsEmpty()) {
8018 mIdentifierMap.RemoveEntry(entry);
8023 void Document::DispatchContentLoadedEvents() {
8024 // If you add early returns from this method, make sure you're
8025 // calling UnblockOnload properly.
8027 // Unpin references to preloaded images
8028 mPreloadingImages.Clear();
8030 // DOM manipulation after content loaded should not care if the element
8031 // came from the preloader.
8032 mPreloadedPreconnects.Clear();
8034 if (mTiming) {
8035 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
8038 // Dispatch observer notification to notify observers document is interactive.
8039 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
8040 if (os) {
8041 nsIPrincipal* principal = NodePrincipal();
8042 os->NotifyObservers(ToSupports(this),
8043 principal->IsSystemPrincipal()
8044 ? "chrome-document-interactive"
8045 : "content-document-interactive",
8046 nullptr);
8049 // Fire a DOM event notifying listeners that this document has been
8050 // loaded (excluding images and other loads initiated by this
8051 // document).
8052 nsContentUtils::DispatchTrustedEvent(this, this, u"DOMContentLoaded"_ns,
8053 CanBubble::eYes, Cancelable::eNo);
8055 if (auto* const window = GetInnerWindow()) {
8056 const RefPtr<ServiceWorkerContainer> serviceWorker =
8057 window->Navigator()->ServiceWorker();
8059 // This could cause queued messages from a service worker to get
8060 // dispatched on serviceWorker.
8061 serviceWorker->StartMessages();
8064 if (MayStartLayout()) {
8065 MaybeResolveReadyForIdle();
8068 if (mTiming) {
8069 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
8072 // If this document is a [i]frame, fire a DOMFrameContentLoaded
8073 // event on all parent documents notifying that the HTML (excluding
8074 // other external files such as images and stylesheets) in a frame
8075 // has finished loading.
8077 // target_frame is the [i]frame element that will be used as the
8078 // target for the event. It's the [i]frame whose content is done
8079 // loading.
8080 nsCOMPtr<Element> target_frame = GetEmbedderElement();
8082 if (target_frame && target_frame->IsInComposedDoc()) {
8083 nsCOMPtr<Document> parent = target_frame->OwnerDoc();
8084 while (parent) {
8085 RefPtr<Event> event;
8086 if (parent) {
8087 IgnoredErrorResult ignored;
8088 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
8091 if (event) {
8092 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
8094 event->SetTarget(target_frame);
8095 event->SetTrusted(true);
8097 // To dispatch this event we must manually call
8098 // EventDispatcher::Dispatch() on the ancestor document since the
8099 // target is not in the same document, so the event would never reach
8100 // the ancestor document if we used the normal event
8101 // dispatching code.
8103 WidgetEvent* innerEvent = event->WidgetEventPtr();
8104 if (innerEvent) {
8105 nsEventStatus status = nsEventStatus_eIgnore;
8107 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
8108 EventDispatcher::Dispatch(parent, context, innerEvent, event,
8109 &status);
8114 parent = parent->GetInProcessParentDocument();
8118 nsPIDOMWindowInner* inner = GetInnerWindow();
8119 if (inner) {
8120 inner->NoteDOMContentLoaded();
8123 // TODO
8124 if (mMaybeServiceWorkerControlled) {
8125 using mozilla::dom::ServiceWorkerManager;
8126 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
8127 if (swm) {
8128 Maybe<ClientInfo> clientInfo = GetClientInfo();
8129 if (clientInfo.isSome()) {
8130 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
8135 if (mSetCompleteAfterDOMContentLoaded) {
8136 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
8137 mSetCompleteAfterDOMContentLoaded = false;
8140 UnblockOnload(true);
8143 void Document::EndLoad() {
8144 bool turnOnEditing =
8145 mParser && (IsInDesignMode() || mContentEditableCount > 0);
8147 #if defined(DEBUG)
8148 // only assert if nothing stopped the load on purpose
8149 if (!mParserAborted) {
8150 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
8152 #endif
8154 // EndLoad may have been called without a matching call to BeginLoad, in the
8155 // case of a failed parse (for example, due to timeout). In such a case, we
8156 // still want to execute part of this code to do appropriate cleanup, but we
8157 // gate part of it because it is intended to match 1-for-1 with calls to
8158 // BeginLoad. We have an explicit flag bit for this purpose, since it's
8159 // complicated and error prone to derive this condition from other related
8160 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
8162 // Part 1: Code that always executes to cleanup end of parsing, whether
8163 // that parsing was successful or not.
8165 // Drop the ref to our parser, if any, but keep hold of the sink so that we
8166 // can flush it from FlushPendingNotifications as needed. We might have to
8167 // do that to get a StartLayout() to happen.
8168 if (mParser) {
8169 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
8170 mParser = nullptr;
8173 // Update the attributes on the PerformanceNavigationTiming before notifying
8174 // the onload observers.
8175 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
8176 if (RefPtr<Performance> performance = window->GetPerformance()) {
8177 performance->UpdateNavigationTimingEntry();
8181 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
8183 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
8185 if (!mDidCallBeginLoad) {
8186 return;
8188 mDidCallBeginLoad = false;
8190 UnblockDOMContentLoaded();
8192 if (turnOnEditing) {
8193 EditingStateChanged();
8196 if (!GetWindow()) {
8197 // This is a document that's not in a window. For example, this could be an
8198 // XMLHttpRequest responseXML document, or a document created via DOMParser
8199 // or DOMImplementation. We don't reach this code normally for such
8200 // documents (which is not obviously correct), but can reach it via
8201 // document.open()/document.close().
8203 // Such documents don't fire load events, but per spec should set their
8204 // readyState to "complete" when parsing and all loading of subresources is
8205 // done. Parsing is done now, and documents not in a window don't load
8206 // subresources, so just go ahead and mark ourselves as complete.
8207 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
8208 /* updateTimingInformation = */ false);
8210 // Reset mSkipLoadEventAfterClose just in case.
8211 mSkipLoadEventAfterClose = false;
8215 void Document::UnblockDOMContentLoaded() {
8216 MOZ_ASSERT(mBlockDOMContentLoaded);
8217 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
8218 return;
8221 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
8222 ("DOCUMENT %p UnblockDOMContentLoaded", this));
8224 mDidFireDOMContentLoaded = true;
8225 if (PresShell* presShell = GetPresShell()) {
8226 presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
8229 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
8230 if (!mSynchronousDOMContentLoaded) {
8231 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8232 nsCOMPtr<nsIRunnable> ev =
8233 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
8234 &Document::DispatchContentLoadedEvents);
8235 Dispatch(ev.forget());
8236 } else {
8237 DispatchContentLoadedEvents();
8241 void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) {
8242 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
8243 "Someone forgot a scriptblocker");
8244 NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged,
8245 (this, aElement, aStateMask));
8248 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
8249 StyleRuleChangeKind) {
8250 if (aSheet.IsApplicable()) {
8251 ApplicableStylesChanged();
8255 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
8256 if (aRule.IsIncompleteImportRule()) {
8257 return;
8260 if (aSheet.IsApplicable()) {
8261 ApplicableStylesChanged();
8265 void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
8266 if (aSheet.IsApplicable()) {
8267 ApplicableStylesChanged();
8271 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
8272 if (aSheet.IsApplicable()) {
8273 ApplicableStylesChanged();
8277 static Element* GetCustomContentContainer(PresShell* aPresShell) {
8278 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
8279 return nullptr;
8282 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
8285 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
8286 bool aForce, ErrorResult& aRv) {
8287 RefPtr<PresShell> shell = GetPresShell();
8288 if (aForce && !GetCustomContentContainer(shell)) {
8289 FlushPendingNotifications(FlushType::Layout);
8290 shell = GetPresShell();
8293 nsAutoScriptBlocker scriptBlocker;
8295 RefPtr<AnonymousContent> anonContent = AnonymousContent::Create(*this);
8296 if (!anonContent) {
8297 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
8298 return nullptr;
8301 mAnonymousContents.AppendElement(anonContent);
8303 if (RefPtr<Element> container = GetCustomContentContainer(shell)) {
8304 // If the container is empty and we have other anon content we should be
8305 // about to show all the other anonymous content nodes.
8306 if (container->HasChildren() || mAnonymousContents.Length() == 1) {
8307 container->AppendChildTo(anonContent->Host(), true, IgnoreErrors());
8308 if (auto* canvasFrame = shell->GetCanvasFrame()) {
8309 canvasFrame->ShowCustomContentContainer();
8314 return anonContent.forget();
8317 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
8318 PresShell* aPresShell) {
8319 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
8320 if (!container) {
8321 return;
8323 container->RemoveChild(*aAnonContent.Host(), IgnoreErrors());
8326 void Document::RemoveAnonymousContent(AnonymousContent& aContent) {
8327 nsAutoScriptBlocker scriptBlocker;
8329 auto index = mAnonymousContents.IndexOf(&aContent);
8330 if (index == mAnonymousContents.NoIndex) {
8331 return;
8334 mAnonymousContents.RemoveElementAt(index);
8335 RemoveAnonContentFromCanvas(aContent, GetPresShell());
8337 if (mAnonymousContents.IsEmpty() &&
8338 GetCustomContentContainer(GetPresShell())) {
8339 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
8343 Element* Document::GetAnonRootIfInAnonymousContentContainer(
8344 nsINode* aNode) const {
8345 if (!aNode->IsInNativeAnonymousSubtree()) {
8346 return nullptr;
8349 PresShell* presShell = GetPresShell();
8350 if (!presShell || !presShell->GetCanvasFrame()) {
8351 return nullptr;
8354 nsAutoScriptBlocker scriptBlocker;
8355 nsCOMPtr<Element> customContainer =
8356 presShell->GetCanvasFrame()->GetCustomContentContainer();
8357 if (!customContainer) {
8358 return nullptr;
8361 // An arbitrary number of elements can be inserted as children of the custom
8362 // container frame. We want the one that was added that contains aNode, so
8363 // we need to keep track of the last child separately using |child| here.
8364 nsINode* child = aNode;
8365 nsINode* parent = aNode->GetParentNode();
8366 while (parent && parent->IsInNativeAnonymousSubtree()) {
8367 if (parent == customContainer) {
8368 return Element::FromNode(child);
8370 child = parent;
8371 parent = child->GetParentNode();
8373 return nullptr;
8376 Maybe<ClientInfo> Document::GetClientInfo() const {
8377 if (const Document* orig = GetOriginalDocument()) {
8378 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
8379 return info;
8383 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8384 return inner->GetClientInfo();
8387 return Maybe<ClientInfo>();
8390 Maybe<ClientState> Document::GetClientState() const {
8391 if (const Document* orig = GetOriginalDocument()) {
8392 if (Maybe<ClientState> state = orig->GetClientState()) {
8393 return state;
8397 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8398 return inner->GetClientState();
8401 return Maybe<ClientState>();
8404 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
8405 if (const Document* orig = GetOriginalDocument()) {
8406 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
8407 return controller;
8411 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8412 return inner->GetController();
8415 return Maybe<ServiceWorkerDescriptor>();
8419 // Document interface
8421 DocumentType* Document::GetDoctype() const {
8422 for (nsIContent* child = GetFirstChild(); child;
8423 child = child->GetNextSibling()) {
8424 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
8425 return static_cast<DocumentType*>(child);
8428 return nullptr;
8431 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
8432 if (!mDOMImplementation) {
8433 nsCOMPtr<nsIURI> uri;
8434 NS_NewURI(getter_AddRefs(uri), "about:blank");
8435 if (!uri) {
8436 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
8437 return nullptr;
8439 bool hasHadScriptObject = true;
8440 nsIScriptGlobalObject* scriptObject =
8441 GetScriptHandlingObject(hasHadScriptObject);
8442 if (!scriptObject && hasHadScriptObject) {
8443 rv.Throw(NS_ERROR_UNEXPECTED);
8444 return nullptr;
8446 mDOMImplementation = new DOMImplementation(
8447 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
8450 return mDOMImplementation;
8453 bool IsLowercaseASCII(const nsAString& aValue) {
8454 int32_t len = aValue.Length();
8455 for (int32_t i = 0; i < len; ++i) {
8456 char16_t c = aValue[i];
8457 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
8458 return false;
8461 return true;
8464 already_AddRefed<Element> Document::CreateElement(
8465 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8466 ErrorResult& rv) {
8467 rv = nsContentUtils::CheckQName(aTagName, false);
8468 if (rv.Failed()) {
8469 return nullptr;
8472 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
8473 nsAutoString lcTagName;
8474 if (needsLowercase) {
8475 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
8478 const nsString* is = nullptr;
8479 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
8480 if (aOptions.IsElementCreationOptions()) {
8481 const ElementCreationOptions& options =
8482 aOptions.GetAsElementCreationOptions();
8484 if (options.mIs.WasPassed()) {
8485 is = &options.mIs.Value();
8488 // Check 'pseudo' and throw an exception if it's not one allowed
8489 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
8490 if (options.mPseudo.WasPassed()) {
8491 Maybe<PseudoStyleType> type =
8492 nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
8493 if (!type || *type == PseudoStyleType::NotPseudo ||
8494 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
8495 rv.ThrowNotSupportedError("Invalid pseudo-element");
8496 return nullptr;
8498 pseudoType = *type;
8502 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
8503 nullptr, mDefaultElementType, is);
8505 if (pseudoType != PseudoStyleType::NotPseudo) {
8506 elem->SetPseudoElementType(pseudoType);
8509 return elem.forget();
8512 already_AddRefed<Element> Document::CreateElementNS(
8513 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8514 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
8515 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8516 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8517 mNodeInfoManager, ELEMENT_NODE,
8518 getter_AddRefs(nodeInfo));
8519 if (rv.Failed()) {
8520 return nullptr;
8523 const nsString* is = nullptr;
8524 if (aOptions.IsElementCreationOptions()) {
8525 const ElementCreationOptions& options =
8526 aOptions.GetAsElementCreationOptions();
8527 if (options.mIs.WasPassed()) {
8528 is = &options.mIs.Value();
8532 nsCOMPtr<Element> element;
8533 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
8534 NOT_FROM_PARSER, is);
8535 if (rv.Failed()) {
8536 return nullptr;
8539 return element.forget();
8542 already_AddRefed<Element> Document::CreateXULElement(
8543 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8544 ErrorResult& aRv) {
8545 aRv = nsContentUtils::CheckQName(aTagName, false);
8546 if (aRv.Failed()) {
8547 return nullptr;
8550 const nsString* is = nullptr;
8551 if (aOptions.IsElementCreationOptions()) {
8552 const ElementCreationOptions& options =
8553 aOptions.GetAsElementCreationOptions();
8554 if (options.mIs.WasPassed()) {
8555 is = &options.mIs.Value();
8559 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
8560 if (!elem) {
8561 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
8562 return nullptr;
8564 return elem.forget();
8567 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
8568 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8569 return text.forget();
8572 already_AddRefed<nsTextNode> Document::CreateTextNode(
8573 const nsAString& aData) const {
8574 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8575 // Don't notify; this node is still being created.
8576 text->SetText(aData, false);
8577 return text.forget();
8580 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
8581 RefPtr<DocumentFragment> frag =
8582 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
8583 return frag.forget();
8586 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
8587 already_AddRefed<dom::Comment> Document::CreateComment(
8588 const nsAString& aData) const {
8589 RefPtr<dom::Comment> comment =
8590 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
8592 // Don't notify; this node is still being created.
8593 comment->SetText(aData, false);
8594 return comment.forget();
8597 already_AddRefed<CDATASection> Document::CreateCDATASection(
8598 const nsAString& aData, ErrorResult& rv) {
8599 if (IsHTMLDocument()) {
8600 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8601 return nullptr;
8604 if (FindInReadable(u"]]>"_ns, aData)) {
8605 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8606 return nullptr;
8609 RefPtr<CDATASection> cdata =
8610 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
8612 // Don't notify; this node is still being created.
8613 cdata->SetText(aData, false);
8615 return cdata.forget();
8618 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
8619 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
8620 nsresult res = nsContentUtils::CheckQName(aTarget, false);
8621 if (NS_FAILED(res)) {
8622 rv.Throw(res);
8623 return nullptr;
8626 if (FindInReadable(u"?>"_ns, aData)) {
8627 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8628 return nullptr;
8631 RefPtr<ProcessingInstruction> pi =
8632 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
8634 return pi.forget();
8637 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
8638 ErrorResult& rv) {
8639 if (!mNodeInfoManager) {
8640 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8641 return nullptr;
8644 nsresult res = nsContentUtils::CheckQName(aName, false);
8645 if (NS_FAILED(res)) {
8646 rv.Throw(res);
8647 return nullptr;
8650 nsAutoString name;
8651 if (IsHTMLDocument()) {
8652 nsContentUtils::ASCIIToLower(aName, name);
8653 } else {
8654 name = aName;
8657 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8658 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
8659 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
8660 if (NS_FAILED(res)) {
8661 rv.Throw(res);
8662 return nullptr;
8665 RefPtr<Attr> attribute =
8666 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8667 return attribute.forget();
8670 already_AddRefed<Attr> Document::CreateAttributeNS(
8671 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8672 ErrorResult& rv) {
8673 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8674 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8675 mNodeInfoManager, ATTRIBUTE_NODE,
8676 getter_AddRefs(nodeInfo));
8677 if (rv.Failed()) {
8678 return nullptr;
8681 RefPtr<Attr> attribute =
8682 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8683 return attribute.forget();
8686 void Document::ScheduleForPresAttrEvaluation(Element* aElement) {
8687 MOZ_ASSERT(aElement->IsInComposedDoc());
8688 DebugOnly<bool> inserted = mLazyPresElements.EnsureInserted(aElement);
8689 MOZ_ASSERT(inserted);
8690 if (aElement->HasServoData()) {
8691 // TODO(emilio): RESTYLE_SELF is too strong, there should be no need to
8692 // re-selector-match, but right now this is needed to pick up the new mapped
8693 // attributes. We need something like RESTYLE_STYLE_ATTRIBUTE but for mapped
8694 // attributes.
8695 nsLayoutUtils::PostRestyleEvent(aElement, RestyleHint::RESTYLE_SELF,
8696 nsChangeHint(0));
8700 void Document::UnscheduleForPresAttrEvaluation(Element* aElement) {
8701 mLazyPresElements.Remove(aElement);
8704 void Document::DoResolveScheduledPresAttrs() {
8705 MOZ_ASSERT(!mLazyPresElements.IsEmpty());
8706 for (Element* el : mLazyPresElements) {
8707 MOZ_ASSERT(el->IsInComposedDoc(),
8708 "Un-schedule when removing from the document");
8709 MOZ_ASSERT(el->IsPendingMappedAttributeEvaluation());
8710 if (auto* svg = SVGElement::FromNode(el)) {
8711 // SVG does its own (very similar) thing, for now at least.
8712 svg->UpdateMappedDeclarationBlock();
8713 } else {
8714 MappedDeclarationsBuilder builder(*el, *this,
8715 el->GetMappedAttributeStyle());
8716 auto function = el->GetAttributeMappingFunction();
8717 function(builder);
8718 el->SetMappedDeclarationBlock(builder.TakeDeclarationBlock());
8720 MOZ_ASSERT(!el->IsPendingMappedAttributeEvaluation());
8722 mLazyPresElements.Clear();
8725 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
8726 const {
8727 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
8729 for (const nsWeakPtr& weakNode : mBlockedNodesByClassifier) {
8730 if (nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode)) {
8731 // Consider only nodes to which we have managed to get strong references.
8732 // Coping with nullptrs since it's expected for nodes to disappear when
8733 // nobody else is referring to them.
8734 list->AppendElement(node);
8738 return list.forget();
8741 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
8742 aSheetSet.Truncate();
8744 // Look through our sheets, find the selected set title
8745 size_t count = SheetCount();
8746 nsAutoString title;
8747 for (size_t index = 0; index < count; index++) {
8748 StyleSheet* sheet = SheetAt(index);
8749 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8751 if (sheet->Disabled()) {
8752 // Disabled sheets don't affect the currently selected set
8753 continue;
8756 sheet->GetTitle(title);
8758 if (aSheetSet.IsEmpty()) {
8759 aSheetSet = title;
8760 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
8761 // Sheets from multiple sets enabled; return null string, per spec.
8762 SetDOMStringToNull(aSheetSet);
8763 return;
8768 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
8769 if (DOMStringIsNull(aSheetSet)) {
8770 return;
8773 // Must update mLastStyleSheetSet before doing anything else with stylesheets
8774 // or CSSLoaders.
8775 mLastStyleSheetSet = aSheetSet;
8776 EnableStyleSheetsForSetInternal(aSheetSet, true);
8779 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
8780 mPreferredStyleSheetSet = aSheetSet;
8781 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
8782 // spec.
8783 if (DOMStringIsNull(mLastStyleSheetSet)) {
8784 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
8785 // per spec. The idea here is that we're changing our preferred set and
8786 // that shouldn't change the value of lastStyleSheetSet. Also, we're
8787 // using the Internal version so we can update the CSSLoader and not have
8788 // to worry about null strings.
8789 EnableStyleSheetsForSetInternal(aSheetSet, true);
8793 DOMStringList* Document::StyleSheetSets() {
8794 if (!mStyleSheetSetList) {
8795 mStyleSheetSetList = new DOMStyleSheetSetList(this);
8797 return mStyleSheetSetList;
8800 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
8801 // Per spec, passing in null is a no-op.
8802 if (!DOMStringIsNull(aSheetSet)) {
8803 // Note: must make sure to not change the CSSLoader's preferred sheet --
8804 // that value should be equal to either our lastStyleSheetSet (if that's
8805 // non-null) or to our preferredStyleSheetSet. And this method doesn't
8806 // change either of those.
8807 EnableStyleSheetsForSetInternal(aSheetSet, false);
8811 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
8812 bool aUpdateCSSLoader) {
8813 size_t count = SheetCount();
8814 nsAutoString title;
8815 for (size_t index = 0; index < count; index++) {
8816 StyleSheet* sheet = SheetAt(index);
8817 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8819 sheet->GetTitle(title);
8820 if (!title.IsEmpty()) {
8821 sheet->SetEnabled(title.Equals(aSheetSet));
8824 if (aUpdateCSSLoader) {
8825 CSSLoader()->DocumentStyleSheetSetChanged();
8827 if (EnsureStyleSet().StyleSheetsHaveChanged()) {
8828 ApplicableStylesChanged();
8832 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
8833 nsAutoCString charset;
8834 GetDocumentCharacterSet()->Name(charset);
8835 CopyASCIItoUTF16(charset, aCharacterSet);
8838 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
8839 ErrorResult& rv) const {
8840 nsINode* imported = &aNode;
8842 switch (imported->NodeType()) {
8843 case DOCUMENT_NODE: {
8844 break;
8846 case DOCUMENT_FRAGMENT_NODE:
8847 case ATTRIBUTE_NODE:
8848 case ELEMENT_NODE:
8849 case PROCESSING_INSTRUCTION_NODE:
8850 case TEXT_NODE:
8851 case CDATA_SECTION_NODE:
8852 case COMMENT_NODE:
8853 case DOCUMENT_TYPE_NODE: {
8854 return imported->Clone(aDeep, mNodeInfoManager, rv);
8856 default: {
8857 NS_WARNING("Don't know how to clone this nodetype for importNode.");
8861 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8862 return nullptr;
8865 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
8866 return nsRange::Create(this, 0, this, 0, rv);
8869 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
8870 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
8871 ErrorResult& rv) const {
8872 RefPtr<NodeIterator> iterator =
8873 new NodeIterator(&aRoot, aWhatToShow, aFilter);
8874 return iterator.forget();
8877 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
8878 uint32_t aWhatToShow,
8879 NodeFilter* aFilter,
8880 ErrorResult& rv) const {
8881 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
8882 return walker.forget();
8885 already_AddRefed<Location> Document::GetLocation() const {
8886 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
8888 if (!w) {
8889 return nullptr;
8892 return do_AddRef(w->Location());
8895 already_AddRefed<nsIURI> Document::GetDomainURI() {
8896 nsIPrincipal* principal = NodePrincipal();
8898 nsCOMPtr<nsIURI> uri;
8899 principal->GetDomain(getter_AddRefs(uri));
8900 if (uri) {
8901 return uri.forget();
8903 auto* basePrin = BasePrincipal::Cast(principal);
8904 basePrin->GetURI(getter_AddRefs(uri));
8905 return uri.forget();
8908 void Document::GetDomain(nsAString& aDomain) {
8909 nsCOMPtr<nsIURI> uri = GetDomainURI();
8911 if (!uri) {
8912 aDomain.Truncate();
8913 return;
8916 nsAutoCString hostName;
8917 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
8918 if (NS_SUCCEEDED(rv)) {
8919 CopyUTF8toUTF16(hostName, aDomain);
8920 } else {
8921 // If we can't get the host from the URI (e.g. about:, javascript:,
8922 // etc), just return an empty string.
8923 aDomain.Truncate();
8927 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
8928 if (!GetBrowsingContext()) {
8929 // If our browsing context is null; disallow setting domain
8930 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8931 return;
8934 if (mSandboxFlags & SANDBOXED_DOMAIN) {
8935 // We're sandboxed; disallow setting domain
8936 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8937 return;
8940 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
8941 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8942 return;
8945 if (aDomain.IsEmpty()) {
8946 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8947 return;
8950 nsCOMPtr<nsIURI> uri = GetDomainURI();
8951 if (!uri) {
8952 rv.Throw(NS_ERROR_FAILURE);
8953 return;
8956 // Check new domain - must be a superdomain of the current host
8957 // For example, a page from foo.bar.com may set domain to bar.com,
8958 // but not to ar.com, baz.com, or fi.foo.bar.com.
8960 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
8961 if (!newURI) {
8962 // Error: illegal domain
8963 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8964 return;
8967 if (GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated()) {
8968 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
8969 return;
8972 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
8973 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
8974 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
8975 wgc->SendSetDocumentDomain(WrapNotNull(newURI));
8979 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
8980 const nsACString& aHostString) {
8981 if (aHostString.IsEmpty()) {
8982 return nullptr;
8985 // Create new URI
8986 nsCOMPtr<nsIURI> uri = GetDomainURI();
8987 if (!uri) {
8988 return nullptr;
8991 nsresult rv;
8992 rv = NS_MutateURI(uri)
8993 .SetUserPass(""_ns)
8994 .SetPort(-1) // we want to reset the port number if needed.
8995 .SetHostPort(aHostString)
8996 .Finalize(uri);
8997 if (NS_FAILED(rv)) {
8998 return nullptr;
9001 return uri.forget();
9004 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
9005 const nsAString& aNewDomain, nsIURI* aOrigHost) {
9006 if (NS_WARN_IF(!aOrigHost)) {
9007 return nullptr;
9010 nsCOMPtr<nsIURI> newURI =
9011 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
9012 if (!newURI) {
9013 // Error: failed to parse input domain
9014 return nullptr;
9017 if (!IsValidDomain(aOrigHost, newURI)) {
9018 // Error: illegal domain
9019 return nullptr;
9022 nsAutoCString domain;
9023 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
9024 return nullptr;
9027 return CreateInheritingURIForHost(domain);
9030 /* static */
9031 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
9032 // Check new domain - must be a superdomain of the current host
9033 // For example, a page from foo.bar.com may set domain to bar.com,
9034 // but not to ar.com, baz.com, or fi.foo.bar.com.
9035 nsAutoCString current;
9036 nsAutoCString domain;
9037 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
9038 current.Truncate();
9040 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
9041 domain.Truncate();
9044 bool ok = current.Equals(domain);
9045 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
9046 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
9047 // We're golden if the new domain is the current page's base domain or a
9048 // subdomain of it.
9049 nsCOMPtr<nsIEffectiveTLDService> tldService =
9050 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
9051 if (!tldService) {
9052 return false;
9055 nsAutoCString currentBaseDomain;
9056 ok = NS_SUCCEEDED(
9057 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
9058 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
9059 (domain.Length() >= currentBaseDomain.Length()),
9060 "uh-oh! slight optimization wasn't valid somehow!");
9061 ok = ok && domain.Length() >= currentBaseDomain.Length();
9064 return ok;
9067 Element* Document::GetHtmlElement() const {
9068 Element* rootElement = GetRootElement();
9069 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
9070 return rootElement;
9071 return nullptr;
9074 Element* Document::GetHtmlChildElement(nsAtom* aTag) {
9075 Element* html = GetHtmlElement();
9076 if (!html) return nullptr;
9078 // Look for the element with aTag inside html. This needs to run
9079 // forwards to find the first such element.
9080 for (nsIContent* child = html->GetFirstChild(); child;
9081 child = child->GetNextSibling()) {
9082 if (child->IsHTMLElement(aTag)) return child->AsElement();
9084 return nullptr;
9087 nsGenericHTMLElement* Document::GetBody() {
9088 Element* html = GetHtmlElement();
9089 if (!html) {
9090 return nullptr;
9093 for (nsIContent* child = html->GetFirstChild(); child;
9094 child = child->GetNextSibling()) {
9095 if (child->IsHTMLElement(nsGkAtoms::body) ||
9096 child->IsHTMLElement(nsGkAtoms::frameset)) {
9097 return static_cast<nsGenericHTMLElement*>(child);
9101 return nullptr;
9104 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
9105 nsCOMPtr<Element> root = GetRootElement();
9107 // The body element must be either a body tag or a frameset tag. And we must
9108 // have a root element to be able to add kids to it.
9109 if (!newBody ||
9110 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
9111 rv.ThrowHierarchyRequestError(
9112 "The new body must be either a body tag or frameset tag.");
9113 return;
9116 if (!root) {
9117 rv.ThrowHierarchyRequestError("No root element.");
9118 return;
9121 // Use DOM methods so that we pass through the appropriate security checks.
9122 nsCOMPtr<Element> currentBody = GetBody();
9123 if (currentBody) {
9124 root->ReplaceChild(*newBody, *currentBody, rv);
9125 } else {
9126 root->AppendChild(*newBody, rv);
9130 HTMLSharedElement* Document::GetHead() {
9131 return static_cast<HTMLSharedElement*>(GetHeadElement());
9134 Element* Document::GetTitleElement() {
9135 // mMayHaveTitleElement will have been set to true if any HTML or SVG
9136 // <title> element has been bound to this document. So if it's false,
9137 // we know there is nothing to do here. This avoids us having to search
9138 // the whole DOM if someone calls document.title on a large document
9139 // without a title.
9140 if (!mMayHaveTitleElement) {
9141 return nullptr;
9144 Element* root = GetRootElement();
9145 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
9146 // In SVG, the document's title must be a child
9147 for (nsIContent* child = root->GetFirstChild(); child;
9148 child = child->GetNextSibling()) {
9149 if (child->IsSVGElement(nsGkAtoms::title)) {
9150 return child->AsElement();
9153 return nullptr;
9156 // We check the HTML namespace even for non-HTML documents, except SVG. This
9157 // matches the spec and the behavior of all tested browsers.
9158 for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
9159 if (node->IsHTMLElement(nsGkAtoms::title)) {
9160 return node->AsElement();
9163 return nullptr;
9166 void Document::GetTitle(nsAString& aTitle) {
9167 aTitle.Truncate();
9169 Element* rootElement = GetRootElement();
9170 if (!rootElement) {
9171 return;
9174 if (rootElement->IsXULElement()) {
9175 rootElement->GetAttr(nsGkAtoms::title, aTitle);
9176 } else if (Element* title = GetTitleElement()) {
9177 nsContentUtils::GetNodeTextContent(title, false, aTitle);
9178 } else {
9179 return;
9182 aTitle.CompressWhitespace();
9185 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
9186 Element* rootElement = GetRootElement();
9187 if (!rootElement) {
9188 return;
9191 if (rootElement->IsXULElement()) {
9192 aRv =
9193 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
9194 return;
9197 Maybe<mozAutoDocUpdate> updateBatch;
9198 nsCOMPtr<Element> title = GetTitleElement();
9199 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
9200 if (!title) {
9201 // Batch updates so that mutation events don't change "the title
9202 // element" under us
9203 updateBatch.emplace(this, true);
9204 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
9205 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
9206 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
9207 NOT_FROM_PARSER);
9208 if (!title) {
9209 return;
9211 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
9212 IgnoreErrors());
9214 } else if (rootElement->IsHTMLElement()) {
9215 if (!title) {
9216 // Batch updates so that mutation events don't change "the title
9217 // element" under us
9218 updateBatch.emplace(this, true);
9219 Element* head = GetHeadElement();
9220 if (!head) {
9221 return;
9224 RefPtr<mozilla::dom::NodeInfo> titleInfo;
9225 titleInfo = mNodeInfoManager->GetNodeInfo(
9226 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
9227 title = NS_NewHTMLTitleElement(titleInfo.forget());
9228 if (!title) {
9229 return;
9232 head->AppendChildTo(title, true, IgnoreErrors());
9234 } else {
9235 return;
9238 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
9241 class Document::TitleChangeEvent final : public Runnable {
9242 public:
9243 explicit TitleChangeEvent(Document* aDoc)
9244 : Runnable("Document::TitleChangeEvent"),
9245 mDoc(aDoc),
9246 mBlockOnload(aDoc->IsInChromeDocShell()) {
9247 if (mBlockOnload) {
9248 mDoc->BlockOnload();
9252 NS_IMETHOD Run() final {
9253 if (!mDoc) {
9254 return NS_OK;
9256 const RefPtr<Document> doc = mDoc;
9257 const bool blockOnload = mBlockOnload;
9258 mDoc = nullptr;
9259 doc->DoNotifyPossibleTitleChange();
9260 if (blockOnload) {
9261 doc->UnblockOnload(/* aFireSync = */ true);
9263 return NS_OK;
9266 void Revoke() {
9267 if (mDoc) {
9268 if (mBlockOnload) {
9269 mDoc->UnblockOnload(/* aFireSync = */ false);
9271 mDoc = nullptr;
9275 private:
9276 // Weak, caller is responsible for calling Revoke() when needed.
9277 Document* mDoc;
9278 // title changes should block the load event on chrome docshells, so that the
9279 // window title is consistently set by the time the top window is displayed.
9280 // Otherwise, some window manager integrations don't work properly,
9281 // see bug 1874766.
9282 const bool mBlockOnload = false;
9285 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
9286 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
9287 "Setting a title while unlinking or destroying the element?");
9288 if (mInUnlinkOrDeletion) {
9289 return;
9292 if (aBoundTitleElement) {
9293 mMayHaveTitleElement = true;
9296 if (mPendingTitleChangeEvent.IsPending()) {
9297 return;
9300 MOZ_RELEASE_ASSERT(NS_IsMainThread());
9301 RefPtr<TitleChangeEvent> event = new TitleChangeEvent(this);
9302 if (NS_WARN_IF(NS_FAILED(Dispatch(do_AddRef(event))))) {
9303 event->Revoke();
9304 return;
9306 mPendingTitleChangeEvent = std::move(event);
9309 void Document::DoNotifyPossibleTitleChange() {
9310 if (!mPendingTitleChangeEvent.IsPending()) {
9311 return;
9313 // Make sure the pending runnable method is cleared.
9314 mPendingTitleChangeEvent.Revoke();
9315 mHaveFiredTitleChange = true;
9317 nsAutoString title;
9318 GetTitle(title);
9320 if (RefPtr<PresShell> presShell = GetPresShell()) {
9321 nsCOMPtr<nsISupports> container =
9322 presShell->GetPresContext()->GetContainerWeak();
9323 if (container) {
9324 if (nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container)) {
9325 docShellWin->SetTitle(title);
9330 if (WindowGlobalChild* child = GetWindowGlobalChild()) {
9331 child->SendUpdateDocumentTitle(title);
9334 // Fire a DOM event for the title change.
9335 nsContentUtils::DispatchChromeEvent(this, this, u"DOMTitleChanged"_ns,
9336 CanBubble::eYes, Cancelable::eYes);
9338 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
9339 if (obs) {
9340 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
9344 already_AddRefed<MediaQueryList> Document::MatchMedia(
9345 const nsACString& aMediaQueryList, CallerType aCallerType) {
9346 RefPtr<MediaQueryList> result =
9347 new MediaQueryList(this, aMediaQueryList, aCallerType);
9349 mDOMMediaQueryLists.insertBack(result);
9351 return result.forget();
9354 void Document::SetMayStartLayout(bool aMayStartLayout) {
9355 mMayStartLayout = aMayStartLayout;
9356 if (MayStartLayout()) {
9357 // Before starting layout, check whether we're a toplevel chrome
9358 // window. If we are, setup some state so that we don't have to restyle
9359 // the whole tree after StartLayout.
9360 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
9361 // We're the chrome document!
9362 win->BeforeStartLayout();
9364 ReadyState state = GetReadyStateEnum();
9365 if (state >= READYSTATE_INTERACTIVE) {
9366 // DOMContentLoaded has fired already.
9367 MaybeResolveReadyForIdle();
9371 MaybeEditingStateChanged();
9374 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
9375 mInitializableFrameLoaders.RemoveElement(aLoader);
9376 // Don't even try to initialize.
9377 if (mInDestructor) {
9378 NS_WARNING(
9379 "Trying to initialize a frame loader while"
9380 "document is being deleted");
9381 return NS_ERROR_FAILURE;
9384 mInitializableFrameLoaders.AppendElement(aLoader);
9385 if (!mFrameLoaderRunner) {
9386 mFrameLoaderRunner =
9387 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9388 &Document::MaybeInitializeFinalizeFrameLoaders);
9389 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9390 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9392 return NS_OK;
9395 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
9396 nsIRunnable* aFinalizer) {
9397 mInitializableFrameLoaders.RemoveElement(aLoader);
9398 if (mInDestructor) {
9399 return NS_ERROR_FAILURE;
9402 LogRunnable::LogDispatch(aFinalizer);
9403 mFrameLoaderFinalizers.AppendElement(aFinalizer);
9404 if (!mFrameLoaderRunner) {
9405 mFrameLoaderRunner =
9406 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9407 &Document::MaybeInitializeFinalizeFrameLoaders);
9408 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9409 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9411 return NS_OK;
9414 void Document::MaybeInitializeFinalizeFrameLoaders() {
9415 if (mDelayFrameLoaderInitialization) {
9416 // This method will be recalled when !mDelayFrameLoaderInitialization.
9417 mFrameLoaderRunner = nullptr;
9418 return;
9421 // We're not in an update, but it is not safe to run scripts, so
9422 // postpone frameloader initialization and finalization.
9423 if (!nsContentUtils::IsSafeToRunScript()) {
9424 if (!mInDestructor && !mFrameLoaderRunner &&
9425 (mInitializableFrameLoaders.Length() ||
9426 mFrameLoaderFinalizers.Length())) {
9427 mFrameLoaderRunner = NewRunnableMethod(
9428 "Document::MaybeInitializeFinalizeFrameLoaders", this,
9429 &Document::MaybeInitializeFinalizeFrameLoaders);
9430 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9432 return;
9434 mFrameLoaderRunner = nullptr;
9436 // Don't use a temporary array for mInitializableFrameLoaders, because
9437 // loading a frame may cause some other frameloader to be removed from the
9438 // array. But be careful to keep the loader alive when starting the load!
9439 while (mInitializableFrameLoaders.Length()) {
9440 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
9441 mInitializableFrameLoaders.RemoveElementAt(0);
9442 NS_ASSERTION(loader, "null frameloader in the array?");
9443 loader->ReallyStartLoading();
9446 uint32_t length = mFrameLoaderFinalizers.Length();
9447 if (length > 0) {
9448 nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
9449 std::move(mFrameLoaderFinalizers);
9450 for (uint32_t i = 0; i < length; ++i) {
9451 LogRunnable::Run run(finalizers[i]);
9452 finalizers[i]->Run();
9457 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
9458 uint32_t length = mInitializableFrameLoaders.Length();
9459 for (uint32_t i = 0; i < length; ++i) {
9460 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
9461 mInitializableFrameLoaders.RemoveElementAt(i);
9462 return;
9467 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
9468 mPrototypeDocument = aPrototype;
9469 mSynchronousDOMContentLoaded = true;
9472 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
9473 return GetPermissionDelegateHandler();
9476 Document* Document::RequestExternalResource(
9477 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
9478 ExternalResourceLoad** aPendingLoad) {
9479 MOZ_ASSERT(aURI, "Must have a URI");
9480 MOZ_ASSERT(aRequestingNode, "Must have a node");
9481 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
9482 if (mDisplayDocument) {
9483 return mDisplayDocument->RequestExternalResource(
9484 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
9487 return mExternalResourceMap.RequestResource(
9488 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
9491 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
9492 mExternalResourceMap.EnumerateResources(aCallback);
9495 SMILAnimationController* Document::GetAnimationController() {
9496 // We create the animation controller lazily because most documents won't want
9497 // one and only SVG documents and the like will call this
9498 if (mAnimationController) return mAnimationController;
9499 // Refuse to create an Animation Controller for data documents.
9500 if (mLoadedAsData) return nullptr;
9502 mAnimationController = new SMILAnimationController(this);
9504 // If there's a presContext then check the animation mode and pause if
9505 // necessary.
9506 nsPresContext* context = GetPresContext();
9507 if (mAnimationController && context &&
9508 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
9509 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
9512 // If we're hidden (or being hidden), notify the newly-created animation
9513 // controller. (Skip this check for SVG-as-an-image documents, though,
9514 // because they don't get OnPageShow / OnPageHide calls).
9515 if (!mIsShowing && !mIsBeingUsedAsImage) {
9516 mAnimationController->OnPageHide();
9519 return mAnimationController;
9522 ScrollTimelineAnimationTracker*
9523 Document::GetOrCreateScrollTimelineAnimationTracker() {
9524 if (!mScrollTimelineAnimationTracker) {
9525 mScrollTimelineAnimationTracker = new ScrollTimelineAnimationTracker(this);
9528 return mScrollTimelineAnimationTracker;
9532 * Retrieve the "direction" property of the document.
9534 * @lina 01/09/2001
9536 void Document::GetDir(nsAString& aDirection) const {
9537 aDirection.Truncate();
9538 Element* rootElement = GetHtmlElement();
9539 if (rootElement) {
9540 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
9545 * Set the "direction" property of the document.
9547 * @lina 01/09/2001
9549 void Document::SetDir(const nsAString& aDirection) {
9550 Element* rootElement = GetHtmlElement();
9551 if (rootElement) {
9552 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
9556 nsIHTMLCollection* Document::Images() {
9557 if (!mImages) {
9558 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
9559 nsGkAtoms::img);
9561 return mImages;
9564 nsIHTMLCollection* Document::Embeds() {
9565 if (!mEmbeds) {
9566 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
9567 nsGkAtoms::embed);
9569 return mEmbeds;
9572 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9573 void* aData) {
9574 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
9575 aElement->HasAttr(nsGkAtoms::href);
9578 nsIHTMLCollection* Document::Links() {
9579 if (!mLinks) {
9580 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
9582 return mLinks;
9585 nsIHTMLCollection* Document::Forms() {
9586 if (!mForms) {
9587 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
9588 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
9589 nsGkAtoms::form);
9592 return mForms;
9595 nsIHTMLCollection* Document::Scripts() {
9596 if (!mScripts) {
9597 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
9598 nsGkAtoms::script);
9600 return mScripts;
9603 nsIHTMLCollection* Document::Applets() {
9604 if (!mApplets) {
9605 mApplets = new nsEmptyContentList(this);
9607 return mApplets;
9610 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9611 void* aData) {
9612 return aElement->IsHTMLElement(nsGkAtoms::a) &&
9613 aElement->HasAttr(nsGkAtoms::name);
9616 nsIHTMLCollection* Document::Anchors() {
9617 if (!mAnchors) {
9618 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
9620 return mAnchors;
9623 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
9624 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
9625 ErrorResult& rv) {
9626 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9627 "XOW should have caught this!");
9629 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
9630 if (!window) {
9631 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
9632 return nullptr;
9634 nsCOMPtr<nsPIDOMWindowOuter> outer =
9635 nsPIDOMWindowOuter::GetFromCurrentInner(window);
9636 if (!outer) {
9637 rv.Throw(NS_ERROR_NOT_INITIALIZED);
9638 return nullptr;
9640 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
9641 RefPtr<BrowsingContext> newBC;
9642 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
9643 if (!newBC) {
9644 return nullptr;
9646 return WindowProxyHolder(std::move(newBC));
9649 Document* Document::Open(const Optional<nsAString>& /* unused */,
9650 const Optional<nsAString>& /* unused */,
9651 ErrorResult& aError) {
9652 // Implements
9653 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
9655 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9656 "XOW should have caught this!");
9658 // Step 1 -- throw if we're an XML document.
9659 if (!IsHTMLDocument() || mDisableDocWrite) {
9660 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9661 return nullptr;
9664 // Step 2 -- throw if dynamic markup insertion should throw.
9665 if (ShouldThrowOnDynamicMarkupInsertion()) {
9666 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9667 return nullptr;
9670 // Step 3 -- get the entry document, so we can use it for security checks.
9671 nsCOMPtr<Document> callerDoc = GetEntryDocument();
9672 if (!callerDoc) {
9673 // If we're called from C++ or in some other way without an originating
9674 // document we can't do a document.open w/o changing the principal of the
9675 // document to something like about:blank (as that's the only sane thing to
9676 // do when we don't know the origin of this call), and since we can't
9677 // change the principals of a document for security reasons we'll have to
9678 // refuse to go ahead with this call.
9680 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9681 return nullptr;
9684 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
9685 // the entry document.
9686 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
9687 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9688 return nullptr;
9691 // Step 5 -- if we have an active parser with a nonzero script nesting level,
9692 // just no-op.
9693 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
9694 return this;
9697 // Step 6 -- check for open() during unload. Per spec, this is just a check
9698 // of the ignore-opens-during-unload counter, but our unload event code
9699 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
9700 // do), so we check for unload directly.
9701 if (ShouldIgnoreOpens()) {
9702 return this;
9705 RefPtr<nsDocShell> shell(mDocumentContainer);
9706 if (shell) {
9707 bool inUnload;
9708 shell->GetIsInUnload(&inUnload);
9709 if (inUnload) {
9710 return this;
9714 // At this point we know this is a valid-enough document.open() call
9715 // and not a no-op. Increment our use counter.
9716 SetUseCounter(eUseCounter_custom_DocumentOpen);
9718 // Step 7 -- stop existing navigation of our browsing context (and all other
9719 // loads it's doing) if we're the active document of our browsing context.
9720 // Note that we do not want to stop anything if there is no existing
9721 // navigation.
9722 if (shell && IsCurrentActiveDocument() &&
9723 shell->GetIsAttemptingToNavigate()) {
9724 shell->Stop(nsIWebNavigation::STOP_NETWORK);
9726 // The Stop call may have cancelled the onload blocker request or
9727 // prevented it from getting added, so we need to make sure it gets added
9728 // to the document again otherwise the document could have a non-zero
9729 // onload block count without the onload blocker request being in the
9730 // loadgroup.
9731 EnsureOnloadBlocker();
9734 // Step 8 -- clear event listeners out of our DOM tree
9735 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
9736 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
9737 elm->RemoveAllListeners();
9741 // Step 9 -- clear event listeners from our window, if we have one.
9743 // Note that we explicitly want the inner window, and only if we're its
9744 // document. We want to do this (per spec) even when we're not the "active
9745 // document", so we can't go through GetWindow(), because it might forward to
9746 // the wrong inner.
9747 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
9748 if (win->GetExtantDoc() == this) {
9749 if (EventListenerManager* elm =
9750 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
9751 elm->RemoveAllListeners();
9756 // If we have a parser that has a zero script nesting level, we need to
9757 // properly terminate it. We do that after we've removed all the event
9758 // listeners (so termination won't trigger event listeners if it does
9759 // something to the DOM), but before we remove all elements from the document
9760 // (so if termination does modify the DOM in some way we will just blow it
9761 // away immediately. See the similar code in WriteCommon that handles the
9762 // !IsInsertionPointDefined() case and should stay in sync with this code.
9763 if (mParser) {
9764 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
9765 "Why didn't we take the early return?");
9766 // Make sure we don't re-enter.
9767 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9768 mParser->Terminate();
9769 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9772 // Step 10 -- remove all our DOM kids without firing any mutation events.
9774 // We want to ignore any recursive calls to Open() that happen while
9775 // disconnecting the node tree. The spec doesn't say to do this, but the
9776 // spec also doesn't envision unload events on subframes firing while we do
9777 // this, while all browsers fire them in practice. See
9778 // <https://github.com/whatwg/html/issues/4611>.
9779 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9780 DisconnectNodeTree();
9783 // Step 11 -- if we're the current document in our docshell, do the
9784 // equivalent of pushState() with the new URL we should have.
9785 if (shell && IsCurrentActiveDocument()) {
9786 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
9787 if (callerDoc != this) {
9788 nsCOMPtr<nsIURI> noFragmentURI;
9789 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
9790 if (NS_WARN_IF(NS_FAILED(rv))) {
9791 aError.Throw(rv);
9792 return nullptr;
9794 newURI = std::move(noFragmentURI);
9797 // UpdateURLAndHistory might do various member-setting, so make sure we're
9798 // holding strong refs to all the refcounted args on the stack. We can
9799 // assume that our caller is holding on to "this" already.
9800 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
9801 bool equalURIs;
9802 nsresult rv = currentURI->Equals(newURI, &equalURIs);
9803 if (NS_WARN_IF(NS_FAILED(rv))) {
9804 aError.Throw(rv);
9805 return nullptr;
9807 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
9808 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
9809 /* aReplace = */ true, currentURI,
9810 equalURIs);
9811 if (NS_WARN_IF(NS_FAILED(rv))) {
9812 aError.Throw(rv);
9813 return nullptr;
9816 // And use the security info of the caller document as well, since
9817 // it's the thing providing our data.
9818 mSecurityInfo = callerDoc->GetSecurityInfo();
9820 // This is not mentioned in the spec, but I think that's a spec bug. See
9821 // <https://github.com/whatwg/html/issues/4299>. In any case, since our
9822 // URL may be changing away from about:blank here, we really want to unset
9823 // this flag no matter what, since only about:blank can be an initial
9824 // document.
9825 SetIsInitialDocument(false);
9827 // And let our docloader know that it will need to track our load event.
9828 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
9831 // Per spec nothing happens with our URI in other cases, though note
9832 // <https://github.com/whatwg/html/issues/4286>.
9834 // Note that we don't need to do anything here with base URIs per spec.
9835 // That said, this might be assuming that we implement
9836 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
9837 // correctly, which we don't right now for the about:blank case.
9839 // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
9840 mSkipLoadEventAfterClose = mLoadEventFiring;
9842 // Preliminary to steps 13-16. Set our ready state to uninitialized before
9843 // we do anything else, so we can then proceed to later ready state levels.
9844 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
9845 /* updateTimingInformation = */ false);
9846 // Reset a flag that affects readyState behavior.
9847 mSetCompleteAfterDOMContentLoaded = false;
9849 // Step 13 -- set our compat mode to standards.
9850 SetCompatibilityMode(eCompatibility_FullStandards);
9852 // Step 14 -- create a new parser associated with document. This also does
9853 // step 16 implicitly.
9854 mParserAborted = false;
9855 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
9856 mParser = parser;
9857 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
9858 nsresult rv = parser->StartExecutor();
9859 if (NS_WARN_IF(NS_FAILED(rv))) {
9860 aError.Throw(rv);
9861 return nullptr;
9864 // Clear out our form control state, because the state of controls
9865 // in the pre-open() document should not affect the state of
9866 // controls that are now going to be written.
9867 mLayoutHistoryState = nullptr;
9869 if (shell) {
9870 // Prepare the docshell and the document viewer for the impending
9871 // out-of-band document.write()
9872 shell->PrepareForNewContentModel();
9874 nsCOMPtr<nsIDocumentViewer> viewer;
9875 shell->GetDocViewer(getter_AddRefs(viewer));
9876 if (viewer) {
9877 viewer->LoadStart(this);
9881 // Step 15.
9882 SetReadyStateInternal(Document::READYSTATE_LOADING,
9883 /* updateTimingInformation = */ false);
9885 // Step 16 happened with step 14 above.
9887 // Step 17.
9888 return this;
9891 void Document::Close(ErrorResult& rv) {
9892 if (!IsHTMLDocument()) {
9893 // No calling document.close() on XHTML!
9895 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9896 return;
9899 if (ShouldThrowOnDynamicMarkupInsertion()) {
9900 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9901 return;
9904 if (!mParser || !mParser->IsScriptCreated()) {
9905 return;
9908 ++mWriteLevel;
9909 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
9910 ->Parse(u""_ns, nullptr, true);
9911 --mWriteLevel;
9914 void Document::WriteCommon(const Sequence<nsString>& aText,
9915 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
9916 // Fast path the common case
9917 if (aText.Length() == 1) {
9918 WriteCommon(aText[0], aNewlineTerminate, rv);
9919 } else {
9920 // XXXbz it would be nice if we could pass all the strings to the parser
9921 // without having to do all this copying and then ask it to start
9922 // parsing....
9923 nsString text;
9924 for (size_t i = 0; i < aText.Length(); ++i) {
9925 text.Append(aText[i]);
9927 WriteCommon(text, aNewlineTerminate, rv);
9931 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
9932 ErrorResult& aRv) {
9933 #ifdef DEBUG
9935 // Assert that we do not use or accidentally introduce doc.write()
9936 // in system privileged context or in any of our about: pages.
9937 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
9938 bool isAboutOrPrivContext = principal->IsSystemPrincipal();
9939 if (!isAboutOrPrivContext) {
9940 if (principal->SchemeIs("about")) {
9941 // about:blank inherits the security contetext and this assertion
9942 // is only meant for actual about: pages.
9943 nsAutoCString host;
9944 principal->GetHost(host);
9945 isAboutOrPrivContext = !host.EqualsLiteral("blank");
9948 // Some automated tests use an empty string to kick off some parsing
9949 // mechansims, but they do not do any harm since they use an empty string.
9950 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
9951 "do not use doc.write in privileged context!");
9953 #endif
9955 mTooDeepWriteRecursion =
9956 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
9957 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
9958 aRv.Throw(NS_ERROR_UNEXPECTED);
9959 return;
9962 if (!IsHTMLDocument() || mDisableDocWrite) {
9963 // No calling document.write*() on XHTML!
9965 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9966 return;
9969 if (ShouldThrowOnDynamicMarkupInsertion()) {
9970 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9971 return;
9974 if (mParserAborted) {
9975 // Hixie says aborting the parser doesn't undefine the insertion point.
9976 // However, since we null out mParser in that case, we track the
9977 // theoretically defined insertion point using mParserAborted.
9978 return;
9981 // Implement Step 4.1 of:
9982 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
9983 if (ShouldIgnoreOpens()) {
9984 return;
9987 void* key = GenerateParserKey();
9988 if (mParser && !mParser->IsInsertionPointDefined()) {
9989 if (mIgnoreDestructiveWritesCounter) {
9990 // Instead of implying a call to document.open(), ignore the call.
9991 nsContentUtils::ReportToConsole(
9992 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9993 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9994 return;
9996 // The spec doesn't tell us to ignore opens from here, but we need to
9997 // ensure opens are ignored here. See similar code in Open() that handles
9998 // the case of an existing parser which is not currently running script and
9999 // should stay in sync with this code.
10000 IgnoreOpensDuringUnload ignoreOpenGuard(this);
10001 mParser->Terminate();
10002 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
10005 if (!mParser) {
10006 if (mIgnoreDestructiveWritesCounter) {
10007 // Instead of implying a call to document.open(), ignore the call.
10008 nsContentUtils::ReportToConsole(
10009 nsIScriptError::warningFlag, "DOM Events"_ns, this,
10010 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
10011 return;
10014 Open({}, {}, aRv);
10016 // If Open() fails, or if it didn't create a parser (as it won't
10017 // if the user chose to not discard the current document through
10018 // onbeforeunload), don't write anything.
10019 if (aRv.Failed() || !mParser) {
10020 return;
10024 static constexpr auto new_line = u"\n"_ns;
10026 ++mWriteLevel;
10028 // This could be done with less code, but for performance reasons it
10029 // makes sense to have the code for two separate Parse() calls here
10030 // since the concatenation of strings costs more than we like. And
10031 // why pay that price when we don't need to?
10032 if (aNewlineTerminate) {
10033 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
10034 ->Parse(aText + new_line, key, false);
10035 } else {
10036 aRv =
10037 (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
10040 --mWriteLevel;
10042 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
10045 void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
10046 WriteCommon(aText, false, rv);
10049 void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
10050 WriteCommon(aText, true, rv);
10053 void* Document::GenerateParserKey(void) {
10054 if (!mScriptLoader) {
10055 // If we don't have a script loader, then the parser probably isn't parsing
10056 // anything anyway, so just return null.
10057 return nullptr;
10060 // The script loader provides us with the currently executing script element,
10061 // which is guaranteed to be unique per script.
10062 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
10063 if (script && mParser && mParser->IsScriptCreated()) {
10064 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
10065 if (creatorParser != mParser) {
10066 // Make scripts that aren't inserted by the active parser of this document
10067 // participate in the context of the script that document.open()ed
10068 // this document.
10069 return nullptr;
10072 return script;
10075 /* static */
10076 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
10077 nsAtom* aAtom, void* aData) {
10078 MOZ_ASSERT(aElement, "Must have element to work with!");
10080 if (!aElement->HasName()) {
10081 return false;
10084 nsString* elementName = static_cast<nsString*>(aData);
10085 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
10086 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
10087 eCaseMatters);
10090 /* static */
10091 void* Document::UseExistingNameString(nsINode* aRootNode,
10092 const nsString* aName) {
10093 return const_cast<nsString*>(aName);
10096 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
10097 if (mDocumentURI) {
10098 nsAutoCString uri;
10099 nsresult rv = mDocumentURI->GetSpec(uri);
10100 NS_ENSURE_SUCCESS(rv, rv);
10102 CopyUTF8toUTF16(uri, aDocumentURI);
10103 } else {
10104 aDocumentURI.Truncate();
10107 return NS_OK;
10110 // Alias of above
10111 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
10113 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
10114 CallerType aCallerType,
10115 ErrorResult& aRv) const {
10116 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
10117 aRv = GetDocumentURI(aDocumentURI);
10118 return;
10121 nsAutoCString uri;
10122 nsresult res = mChromeXHRDocURI->GetSpec(uri);
10123 if (NS_FAILED(res)) {
10124 aRv.Throw(res);
10125 return;
10127 CopyUTF8toUTF16(uri, aDocumentURI);
10130 nsIURI* Document::GetDocumentURIObject() const {
10131 if (!mChromeXHRDocURI) {
10132 return GetDocumentURI();
10135 return mChromeXHRDocURI;
10138 void Document::GetCompatMode(nsString& aCompatMode) const {
10139 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
10140 mCompatMode == eCompatibility_AlmostStandards ||
10141 mCompatMode == eCompatibility_FullStandards,
10142 "mCompatMode is neither quirks nor strict for this document");
10144 if (mCompatMode == eCompatibility_NavQuirks) {
10145 aCompatMode.AssignLiteral("BackCompat");
10146 } else {
10147 aCompatMode.AssignLiteral("CSS1Compat");
10151 } // namespace dom
10152 } // namespace mozilla
10154 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
10155 if (Element* element = Element::FromNode(aNode)) {
10156 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
10157 while (true) {
10158 RefPtr<Attr> attr;
10160 // Use an iterator to get an arbitrary attribute from the
10161 // cache. The iterator must be destroyed before any other
10162 // operations on mAttributeCache, to avoid hash table
10163 // assertions.
10164 auto iter = map->mAttributeCache.ConstIter();
10165 if (iter.Done()) {
10166 break;
10168 attr = iter.UserData();
10171 BlastSubtreeToPieces(attr);
10173 mozilla::DebugOnly<nsresult> rv =
10174 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
10175 attr->NodeInfo()->NameAtom(), false);
10177 // XXX Should we abort here?
10178 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
10182 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
10183 BlastSubtreeToPieces(shadow);
10184 element->UnattachShadow();
10188 while (aNode->HasChildren()) {
10189 nsIContent* node = aNode->GetFirstChild();
10190 BlastSubtreeToPieces(node);
10191 aNode->RemoveChildNode(node, false);
10195 namespace mozilla::dom {
10197 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv,
10198 bool aAcceptShadowRoot) {
10199 OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
10200 if (adoptedNode->IsShadowRoot() && !aAcceptShadowRoot) {
10201 rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
10202 return nullptr;
10205 // Scope firing mutation events so that we don't carry any state that
10206 // might be stale
10208 if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
10209 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
10213 nsAutoScriptBlocker scriptBlocker;
10215 switch (adoptedNode->NodeType()) {
10216 case ATTRIBUTE_NODE: {
10217 // Remove from ownerElement.
10218 OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
10220 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement();
10221 if (rv.Failed()) {
10222 return nullptr;
10225 if (ownerElement) {
10226 OwningNonNull<Attr> newAttr =
10227 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
10228 if (rv.Failed()) {
10229 return nullptr;
10233 break;
10235 case DOCUMENT_FRAGMENT_NODE:
10236 case ELEMENT_NODE:
10237 case PROCESSING_INSTRUCTION_NODE:
10238 case TEXT_NODE:
10239 case CDATA_SECTION_NODE:
10240 case COMMENT_NODE:
10241 case DOCUMENT_TYPE_NODE: {
10242 // Don't allow adopting a node's anonymous subtree out from under it.
10243 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
10244 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10245 return nullptr;
10248 // We don't want to adopt an element into its own contentDocument or into
10249 // a descendant contentDocument, so we check if the frameElement of this
10250 // document or any of its parents is the adopted node or one of its
10251 // descendants.
10252 RefPtr<BrowsingContext> bc = GetBrowsingContext();
10253 while (bc) {
10254 nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
10255 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
10256 rv.ThrowHierarchyRequestError(
10257 "Trying to adopt a node into its own contentDocument or a "
10258 "descendant contentDocument.");
10259 return nullptr;
10262 if (XRE_IsParentProcess()) {
10263 bc = bc->Canonical()->GetParentCrossChromeBoundary();
10264 } else {
10265 bc = bc->GetParent();
10269 // Remove from parent.
10270 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
10271 if (parent) {
10272 parent->RemoveChildNode(adoptedNode->AsContent(), true);
10273 } else {
10274 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
10277 break;
10279 case DOCUMENT_NODE: {
10280 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10281 return nullptr;
10283 default: {
10284 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
10286 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10287 return nullptr;
10291 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
10292 bool sameDocument = oldDocument == this;
10294 AutoJSContext cx;
10295 JS::Rooted<JSObject*> newScope(cx, nullptr);
10296 if (!sameDocument) {
10297 newScope = GetWrapper();
10298 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
10299 // Make sure cx is in a semi-sane compartment before we call WrapNative.
10300 // It's kind of irrelevant, given that we're passing aAllowWrapping =
10301 // false, and documents should always insist on being wrapped in an
10302 // canonical scope. But we try to pass something sane anyway.
10303 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
10304 JSAutoRealm ar(cx, globalObject);
10305 JS::Rooted<JS::Value> v(cx);
10306 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
10307 /* aAllowWrapping = */ false);
10308 if (rv.Failed()) return nullptr;
10309 newScope = &v.toObject();
10313 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
10314 if (rv.Failed()) {
10315 // Disconnect all nodes from their parents, since some have the old document
10316 // as their ownerDocument and some have this as their ownerDocument.
10317 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
10318 return nullptr;
10321 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
10322 "Should still be in the document we just got adopted into");
10324 return adoptedNode;
10327 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
10329 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
10330 const nsString& aScaleString) {
10331 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
10332 if (aScaleString.EqualsLiteral("device-width") ||
10333 aScaleString.EqualsLiteral("device-height")) {
10334 return Some(LayoutDeviceToScreenScale(10.0f));
10335 } else if (aScaleString.EqualsLiteral("yes")) {
10336 return Some(LayoutDeviceToScreenScale(1.0f));
10337 } else if (aScaleString.EqualsLiteral("no")) {
10338 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10339 } else if (aScaleString.IsEmpty()) {
10340 return Nothing();
10343 nsresult scaleErrorCode;
10344 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
10345 if (NS_FAILED(scaleErrorCode)) {
10346 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10349 if (scale < 0) {
10350 return Nothing();
10352 return Some(clamped(LayoutDeviceToScreenScale(scale), ViewportMinScale(),
10353 ViewportMaxScale()));
10356 void Document::ParseScalesInViewportMetaData(
10357 const ViewportMetaData& aViewportMetaData) {
10358 Maybe<LayoutDeviceToScreenScale> scale;
10360 scale = ParseScaleString(aViewportMetaData.mInitialScale);
10361 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
10362 mValidScaleFloat = scale.isSome();
10364 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
10365 // Chrome uses '5' for the fallback value of maximum-scale, we might
10366 // consider matching it in future.
10367 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
10368 mScaleMaxFloat = scale.valueOr(ViewportMaxScale());
10369 mValidMaxScale = scale.isSome();
10371 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
10372 mScaleMinFloat = scale.valueOr(ViewportMinScale());
10373 mValidMinScale = scale.isSome();
10375 // Resolve min-zoom and max-zoom values.
10376 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
10377 if (mValidMaxScale && mValidMinScale) {
10378 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
10382 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
10383 const nsAString& aHeightString,
10384 bool aHasValidScale) {
10385 // The width and height properties
10386 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
10388 // The width and height viewport <META> properties are translated into width
10389 // and height descriptors, setting the min-width/min-height value to
10390 // extend-to-zoom and the max-width/max-height value to the length from the
10391 // viewport <META> property as follows:
10393 // 1. Non-negative number values are translated to pixel lengths, clamped to
10394 // the range: [1px, 10000px]
10395 // 2. Negative number values are dropped
10396 // 3. device-width and device-height translate to 100vw and 100vh respectively
10397 // 4. Other keywords and unknown values are also dropped
10398 mMinWidth = nsViewportInfo::kAuto;
10399 mMaxWidth = nsViewportInfo::kAuto;
10400 if (!aWidthString.IsEmpty()) {
10401 mMinWidth = nsViewportInfo::kExtendToZoom;
10402 if (aWidthString.EqualsLiteral("device-width")) {
10403 mMaxWidth = nsViewportInfo::kDeviceSize;
10404 } else {
10405 nsresult widthErrorCode;
10406 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
10407 if (NS_FAILED(widthErrorCode)) {
10408 mMaxWidth = nsViewportInfo::kAuto;
10409 } else if (mMaxWidth >= 0.0f) {
10410 mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
10411 } else {
10412 mMaxWidth = nsViewportInfo::kAuto;
10415 } else if (aHasValidScale) {
10416 if (aHeightString.IsEmpty()) {
10417 mMinWidth = nsViewportInfo::kExtendToZoom;
10418 mMaxWidth = nsViewportInfo::kExtendToZoom;
10420 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
10421 mMinWidth = nsViewportInfo::kExtendToZoom;
10422 mMaxWidth = nsViewportInfo::kDeviceSize;
10425 mMinHeight = nsViewportInfo::kAuto;
10426 mMaxHeight = nsViewportInfo::kAuto;
10427 if (!aHeightString.IsEmpty()) {
10428 mMinHeight = nsViewportInfo::kExtendToZoom;
10429 if (aHeightString.EqualsLiteral("device-height")) {
10430 mMaxHeight = nsViewportInfo::kDeviceSize;
10431 } else {
10432 nsresult heightErrorCode;
10433 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
10434 if (NS_FAILED(heightErrorCode)) {
10435 mMaxHeight = nsViewportInfo::kAuto;
10436 } else if (mMaxHeight >= 0.0f) {
10437 mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
10438 } else {
10439 mMaxHeight = nsViewportInfo::kAuto;
10445 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
10446 MOZ_ASSERT(mPresShell);
10448 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
10449 // widget scale and the full zoom.
10450 nsPresContext* context = mPresShell->GetPresContext();
10451 // When querying the full zoom, get it from the device context rather than
10452 // directly from the pres context, because the device context's value can
10453 // include an adjustment necessary to keep the number of app units per device
10454 // pixel an integer, and we want the adjusted value.
10455 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
10456 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
10457 CSSToLayoutDeviceScale layoutDeviceScale =
10458 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
10460 CSSToScreenScale defaultScale =
10461 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
10463 // Special behaviour for desktop mode, provided we are not on an about: page,
10464 // or fullscreen.
10465 const bool fullscreen = Fullscreen();
10466 auto* bc = GetBrowsingContext();
10467 if (bc && bc->ForceDesktopViewport() && !IsAboutPage() && !fullscreen) {
10468 CSSCoord viewportWidth =
10469 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
10470 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
10471 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
10472 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
10473 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
10474 return nsViewportInfo(fakeDesktopSize, scaleToFit,
10475 nsViewportInfo::ZoomFlag::AllowZoom,
10476 nsViewportInfo::ZoomBehaviour::Mobile,
10477 nsViewportInfo::AutoScaleFlag::AutoScale);
10480 // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
10481 if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
10482 return nsViewportInfo(aDisplaySize, defaultScale,
10483 nsLayoutUtils::AllowZoomingForDocument(this)
10484 ? nsViewportInfo::ZoomFlag::AllowZoom
10485 : nsViewportInfo::ZoomFlag::DisallowZoom,
10486 StaticPrefs::apz_allow_zooming_out()
10487 ? nsViewportInfo::ZoomBehaviour::Mobile
10488 : nsViewportInfo::ZoomBehaviour::Desktop);
10491 // In cases where the width of the CSS viewport is less than or equal to the
10492 // width of the display (i.e. width <= device-width) then we disable
10493 // double-tap-to-zoom behaviour. See bug 941995 for details.
10495 switch (mViewportType) {
10496 case DisplayWidthHeight:
10497 return nsViewportInfo(aDisplaySize, defaultScale,
10498 nsViewportInfo::ZoomFlag::AllowZoom,
10499 nsViewportInfo::ZoomBehaviour::Mobile);
10500 case Unknown: {
10501 // We might early exit if the viewport is empty. Even if we don't,
10502 // at the end of this case we'll note that it was empty. Later, when
10503 // we're using the cached values, this will trigger alternate code paths.
10504 if (!mLastModifiedViewportMetaData) {
10505 // If the docType specifies that we are on a site optimized for mobile,
10506 // then we want to return specially crafted defaults for the viewport
10507 // info.
10508 if (RefPtr<DocumentType> docType = GetDoctype()) {
10509 nsAutoString docId;
10510 docType->GetPublicId(docId);
10511 if ((docId.Find(u"WAP") != -1) || (docId.Find(u"Mobile") != -1) ||
10512 (docId.Find(u"WML") != -1)) {
10513 // We're making an assumption that the docType can't change here
10514 mViewportType = DisplayWidthHeight;
10515 return nsViewportInfo(aDisplaySize, defaultScale,
10516 nsViewportInfo::ZoomFlag::AllowZoom,
10517 nsViewportInfo::ZoomBehaviour::Mobile);
10521 nsAutoString handheldFriendly;
10522 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
10523 if (handheldFriendly.EqualsLiteral("true")) {
10524 mViewportType = DisplayWidthHeight;
10525 return nsViewportInfo(aDisplaySize, defaultScale,
10526 nsViewportInfo::ZoomFlag::AllowZoom,
10527 nsViewportInfo::ZoomBehaviour::Mobile);
10531 ViewportMetaData metaData = GetViewportMetaData();
10533 // Parse initial-scale, minimum-scale and maximum-scale.
10534 ParseScalesInViewportMetaData(metaData);
10536 // Parse width and height properties
10537 // This function sets m{Min,Max}{Width,Height}.
10538 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
10539 mValidScaleFloat);
10541 mAllowZoom = true;
10542 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
10543 (metaData.mUserScalable.EqualsLiteral("no")) ||
10544 (metaData.mUserScalable.EqualsLiteral("false"))) {
10545 mAllowZoom = false;
10548 // Resolve viewport-fit value.
10549 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
10550 mViewportFit = ViewportFitType::Auto;
10551 if (!metaData.mViewportFit.IsEmpty()) {
10552 if (metaData.mViewportFit.EqualsLiteral("contain")) {
10553 mViewportFit = ViewportFitType::Contain;
10554 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
10555 mViewportFit = ViewportFitType::Cover;
10559 mWidthStrEmpty = metaData.mWidth.IsEmpty();
10561 mViewportType = Specified;
10562 [[fallthrough]];
10564 case Specified:
10565 default:
10566 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
10567 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
10568 bool effectiveValidMaxScale = mValidMaxScale;
10570 nsViewportInfo::ZoomFlag effectiveZoomFlag =
10571 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
10572 : nsViewportInfo::ZoomFlag::DisallowZoom;
10573 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
10574 // If the pref to force user-scalable is enabled, we ignore the values
10575 // from the meta-viewport tag for these properties and just assume they
10576 // allow the page to be scalable. Note in particular that this code is
10577 // in the "Specified" branch of the enclosing switch statement, so that
10578 // calls to GetViewportInfo always use the latest value of the
10579 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
10580 // return nsViewportInfo instances are all consistent with
10581 // browser_ui_zoom_force_user_scalable() already.
10582 effectiveMinScale = ViewportMinScale();
10583 effectiveMaxScale = ViewportMaxScale();
10584 effectiveValidMaxScale = true;
10585 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
10588 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
10589 auto ComputeExtendZoom = [&]() -> float {
10590 if (mValidScaleFloat && effectiveValidMaxScale) {
10591 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
10593 if (mValidScaleFloat) {
10594 return mScaleFloat.scale;
10596 if (effectiveValidMaxScale) {
10597 return effectiveMaxScale.scale;
10599 return nsViewportInfo::kAuto;
10602 // Resolving 'extend-to-zoom'
10603 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
10604 float extendZoom = ComputeExtendZoom();
10606 CSSCoord minWidth = mMinWidth;
10607 CSSCoord maxWidth = mMaxWidth;
10608 CSSCoord minHeight = mMinHeight;
10609 CSSCoord maxHeight = mMaxHeight;
10611 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
10612 // viewport size. We need to use this scaled size for any clamping of
10613 // width or height.
10614 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
10616 // Our min and max width and height values are mostly as specified by
10617 // the viewport declaration, but we make an exception for max width.
10618 // Max width, if auto, and if there's no initial-scale, will be set
10619 // to a default size. This is to support legacy site design with no
10620 // viewport declaration, and to do that using the same scheme as
10621 // Chrome does, in order to maintain web compatibility. Since the
10622 // default size has a complicated calculation, we fixup the maxWidth
10623 // value after setting it, above.
10624 if (maxWidth == nsViewportInfo::kAuto && !mValidScaleFloat) {
10625 if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
10626 bc->InRDMPane()) {
10627 // If RDM and touch simulation are active, then use the simulated
10628 // screen width to accommodate for cases where the screen width is
10629 // larger than the desktop viewport default.
10630 maxWidth = nsViewportInfo::Max(
10631 displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
10632 } else {
10633 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
10635 // Divide by fullZoom to stretch CSS pixel size of viewport in order
10636 // to keep device pixel size unchanged after full zoom applied.
10637 // See bug 974242.
10638 maxWidth /= fullZoom;
10640 // We set minWidth to ExtendToZoom, which will cause our later width
10641 // calculation to expand to maxWidth, if scale restrictions allow it.
10642 minWidth = nsViewportInfo::kExtendToZoom;
10645 // Resolve device-width and device-height first.
10646 if (maxWidth == nsViewportInfo::kDeviceSize) {
10647 maxWidth = displaySize.width;
10649 if (maxHeight == nsViewportInfo::kDeviceSize) {
10650 maxHeight = displaySize.height;
10652 if (extendZoom == nsViewportInfo::kAuto) {
10653 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10654 maxWidth = nsViewportInfo::kAuto;
10656 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10657 maxHeight = nsViewportInfo::kAuto;
10659 if (minWidth == nsViewportInfo::kExtendToZoom) {
10660 minWidth = maxWidth;
10662 if (minHeight == nsViewportInfo::kExtendToZoom) {
10663 minHeight = maxHeight;
10665 } else {
10666 CSSSize extendSize = displaySize / extendZoom;
10667 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10668 maxWidth = extendSize.width;
10670 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10671 maxHeight = extendSize.height;
10673 if (minWidth == nsViewportInfo::kExtendToZoom) {
10674 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
10676 if (minHeight == nsViewportInfo::kExtendToZoom) {
10677 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
10681 // Resolve initial width and height from min/max descriptors
10682 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
10683 CSSCoord width = nsViewportInfo::kAuto;
10684 if (minWidth != nsViewportInfo::kAuto ||
10685 maxWidth != nsViewportInfo::kAuto) {
10686 width = nsViewportInfo::Max(
10687 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
10689 CSSCoord height = nsViewportInfo::kAuto;
10690 if (minHeight != nsViewportInfo::kAuto ||
10691 maxHeight != nsViewportInfo::kAuto) {
10692 height = nsViewportInfo::Max(
10693 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
10696 // Resolve width value
10697 // https://drafts.csswg.org/css-device-adapt/#resolve-width
10698 if (width == nsViewportInfo::kAuto) {
10699 if (height == nsViewportInfo::kAuto || aDisplaySize.height == 0) {
10700 width = displaySize.width;
10701 } else {
10702 width = height * aDisplaySize.width / aDisplaySize.height;
10706 // Resolve height value
10707 // https://drafts.csswg.org/css-device-adapt/#resolve-height
10708 if (height == nsViewportInfo::kAuto) {
10709 if (aDisplaySize.width == 0) {
10710 height = displaySize.height;
10711 } else {
10712 height = width * aDisplaySize.height / aDisplaySize.width;
10715 MOZ_ASSERT(width != nsViewportInfo::kAuto &&
10716 height != nsViewportInfo::kAuto);
10718 CSSSize size(width, height);
10720 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
10721 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
10722 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
10724 nsViewportInfo::AutoSizeFlag sizeFlag =
10725 nsViewportInfo::AutoSizeFlag::FixedSize;
10726 if (mMaxWidth == nsViewportInfo::kDeviceSize ||
10727 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::kDeviceSize ||
10728 mScaleFloat.scale == 1.0f)) ||
10729 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::kAuto &&
10730 mMaxHeight < 0)) {
10731 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
10734 // FIXME: Resolving width and height should be done above 'Resolve width
10735 // value' and 'Resolve height value'.
10736 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
10737 size = displaySize;
10740 // The purpose of clamping the viewport width to a minimum size is to
10741 // prevent page authors from setting it to a ridiculously small value.
10742 // If the page is actually being rendered in a very small area (as might
10743 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
10744 // prevent the viewport from taking on that size.
10745 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
10747 size.width = clamped(size.width, effectiveMinSize.width,
10748 float(kViewportMaxSize.width));
10750 // Also recalculate the default zoom, if it wasn't specified in the
10751 // metadata, and the width is specified.
10752 if (!mValidScaleFloat && !mWidthStrEmpty) {
10753 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
10754 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
10757 size.height = clamped(size.height, effectiveMinSize.height,
10758 float(kViewportMaxSize.height));
10760 // In cases of user-scalable=no, if we have a positive scale, clamp it to
10761 // min and max, and then use the clamped value for the scale, the min, and
10762 // the max. If we don't have a positive scale, assert that we are setting
10763 // the auto scale flag.
10764 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
10765 scaleFloat > CSSToScreenScale(0.0f)) {
10766 scaleFloat = scaleMinFloat = scaleMaxFloat =
10767 clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
10769 MOZ_ASSERT(
10770 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
10771 "If we don't have a positive scale, we should be using auto scale.");
10773 // We need to perform a conversion, but only if the initial or maximum
10774 // scale were set explicitly by the user.
10775 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
10776 scaleFloat <= scaleMaxFloat) {
10777 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
10778 size.width = std::max(size.width, displaySize.width);
10779 size.height = std::max(size.height, displaySize.height);
10780 } else if (effectiveValidMaxScale) {
10781 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
10782 size.width = std::max(size.width, displaySize.width);
10783 size.height = std::max(size.height, displaySize.height);
10786 return nsViewportInfo(
10787 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
10788 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
10789 : nsViewportInfo::AutoScaleFlag::AutoScale,
10790 effectiveZoomFlag, mViewportFit);
10794 ViewportMetaData Document::GetViewportMetaData() const {
10795 return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
10796 : ViewportMetaData();
10799 void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
10800 mLastModifiedViewportMetaData = std::move(aData);
10801 // Trigger recomputation of the nsViewportInfo the next time it's queried.
10802 mViewportType = Unknown;
10804 AsyncEventDispatcher::RunDOMEventWhenSafe(
10805 *this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes,
10806 ChromeOnlyDispatch::eYes);
10809 EventListenerManager* Document::GetOrCreateListenerManager() {
10810 if (!mListenerManager) {
10811 mListenerManager =
10812 new EventListenerManager(static_cast<EventTarget*>(this));
10813 SetFlags(NODE_HAS_LISTENERMANAGER);
10816 return mListenerManager;
10819 EventListenerManager* Document::GetExistingListenerManager() const {
10820 return mListenerManager;
10823 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
10824 aVisitor.mCanHandle = true;
10825 // FIXME! This is a hack to make middle mouse paste working also in Editor.
10826 // Bug 329119
10827 aVisitor.mForceContentDispatch = true;
10829 // Load events must not propagate to |window| object, see bug 335251.
10830 if (aVisitor.mEvent->mMessage != eLoad) {
10831 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
10832 aVisitor.SetParentTarget(
10833 window ? window->GetTargetForEventTargetChain() : nullptr, false);
10837 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
10838 CallerType aCallerType,
10839 ErrorResult& rv) const {
10840 nsPresContext* presContext = GetPresContext();
10842 // Create event even without presContext.
10843 RefPtr<Event> ev =
10844 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
10845 nullptr, aEventType, aCallerType);
10846 if (!ev) {
10847 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10848 return nullptr;
10850 WidgetEvent* e = ev->WidgetEventPtr();
10851 e->mFlags.mBubbles = false;
10852 e->mFlags.mCancelable = false;
10853 return ev.forget();
10856 void Document::FlushPendingNotifications(FlushType aType) {
10857 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
10858 FlushPendingNotifications(flush);
10861 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
10862 FlushType flushType = aFlush.mFlushType;
10864 RefPtr<Document> documentOnStack = this;
10866 // We need to flush the sink for non-HTML documents (because the XML
10867 // parser still does insertion with deferred notifications). We
10868 // also need to flush the sink if this is a layout-related flush, to
10869 // make sure that layout is started as needed. But we can skip that
10870 // part if we have no presshell or if it's already done an initial
10871 // reflow.
10872 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
10873 mPresShell && !mPresShell->DidInitialize())) &&
10874 (mParser || mWeakSink)) {
10875 nsCOMPtr<nsIContentSink> sink;
10876 if (mParser) {
10877 sink = mParser->GetContentSink();
10878 } else {
10879 sink = do_QueryReferent(mWeakSink);
10880 if (!sink) {
10881 mWeakSink = nullptr;
10884 // Determine if it is safe to flush the sink notifications
10885 // by determining if it safe to flush all the presshells.
10886 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
10887 sink->FlushPendingNotifications(flushType);
10891 // Should we be flushing pending binding constructors in here?
10893 if (flushType <= FlushType::ContentAndNotify) {
10894 // Nothing to do here
10895 return;
10898 // If we have a parent we must flush the parent too to ensure that our
10899 // container is reflowed if its size was changed.
10901 // We do it only if the subdocument and the parent can observe each other
10902 // synchronously (that is, if we're not cross-origin), to avoid work that is
10903 // not observable, and if the parent document has finished loading all its
10904 // render-blocking stylesheets and may start laying out the document, to avoid
10905 // unnecessary flashes of unstyled content on the parent document. Note that
10906 // this last bit means that size-dependent media queries in this document may
10907 // produce incorrect results temporarily.
10909 // But if it's not safe to flush ourselves, then don't flush the parent, since
10910 // that can cause things like resizes of our frame's widget, which we can't
10911 // handle while flushing is unsafe.
10912 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
10913 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
10914 ChangesToFlush parentFlush = aFlush;
10915 if (flushType >= FlushType::Style) {
10916 // Since media queries mean that a size change of our container can affect
10917 // style, we need to promote a style flush on ourself to a layout flush on
10918 // our parent, since we need our container to be the correct size to
10919 // determine the correct style.
10920 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
10922 mParentDocument->FlushPendingNotifications(parentFlush);
10925 if (RefPtr<PresShell> presShell = GetPresShell()) {
10926 presShell->FlushPendingNotifications(aFlush);
10930 void Document::FlushExternalResources(FlushType aType) {
10931 NS_ASSERTION(
10932 aType >= FlushType::Style,
10933 "should only need to flush for style or higher in external resources");
10934 if (GetDisplayDocument()) {
10935 return;
10938 EnumerateExternalResources([aType](Document& aDoc) {
10939 aDoc.FlushPendingNotifications(aType);
10940 return CallState::Continue;
10944 void Document::SetXMLDeclaration(const char16_t* aVersion,
10945 const char16_t* aEncoding,
10946 const int32_t aStandalone) {
10947 if (!aVersion || *aVersion == '\0') {
10948 mXMLDeclarationBits = 0;
10949 return;
10952 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
10954 if (aEncoding && *aEncoding != '\0') {
10955 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
10958 if (aStandalone == 1) {
10959 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
10960 XML_DECLARATION_BITS_STANDALONE_YES;
10961 } else if (aStandalone == 0) {
10962 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
10966 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
10967 nsAString& aStandalone) {
10968 aVersion.Truncate();
10969 aEncoding.Truncate();
10970 aStandalone.Truncate();
10972 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
10973 return;
10976 // always until we start supporting 1.1 etc.
10977 aVersion.AssignLiteral("1.0");
10979 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
10980 // This is what we have stored, not necessarily what was written
10981 // in the original
10982 GetCharacterSet(aEncoding);
10985 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
10986 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
10987 aStandalone.AssignLiteral("yes");
10988 } else {
10989 aStandalone.AssignLiteral("no");
10994 void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
10995 mColorSchemeMetaTags.Insert(aMeta);
10996 RecomputeColorScheme();
10999 void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
11000 mColorSchemeMetaTags.RemoveElement(aMeta);
11001 RecomputeColorScheme();
11004 void Document::RecomputeColorScheme() {
11005 auto oldColorScheme = mColorSchemeBits;
11006 mColorSchemeBits = 0;
11007 const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
11008 for (const HTMLMetaElement* el : elements) {
11009 nsAutoString content;
11010 if (!el->GetAttr(nsGkAtoms::content, content)) {
11011 continue;
11014 NS_ConvertUTF16toUTF8 contentU8(content);
11015 if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
11016 break;
11020 if (mColorSchemeBits == oldColorScheme) {
11021 return;
11024 if (nsPresContext* pc = GetPresContext()) {
11025 // This affects system colors, which are inherited, so we need to recascade.
11026 pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
11030 bool Document::IsScriptEnabled() const {
11031 // If this document is sandboxed without 'allow-scripts'
11032 // script is not enabled
11033 if (HasScriptsBlockedBySandbox()) {
11034 return false;
11037 nsCOMPtr<nsIScriptGlobalObject> globalObject =
11038 do_QueryInterface(GetInnerWindow());
11039 if (!globalObject || !globalObject->HasJSGlobal()) {
11040 return false;
11043 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
11044 .Allowed();
11047 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
11048 PRTime modDate = 0;
11049 nsresult rv;
11051 nsCOMPtr<nsIHttpChannel> httpChannel;
11052 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
11053 if (NS_WARN_IF(NS_FAILED(rv))) {
11054 return;
11057 if (httpChannel) {
11058 nsAutoCString tmp;
11059 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
11061 if (NS_SUCCEEDED(rv)) {
11062 PRTime time;
11063 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
11064 if (st == PR_SUCCESS) {
11065 modDate = time;
11069 static const char* const headers[] = {
11070 "default-style", "content-style-type", "content-language",
11071 "content-disposition", "refresh", "x-dns-prefetch-control",
11072 "x-frame-options", "origin-trial",
11073 // add more http headers if you need
11074 // XXXbz don't add content-location support without reading bug
11075 // 238654 and its dependencies/dups first.
11078 nsAutoCString headerVal;
11079 const char* const* name = headers;
11080 while (*name) {
11081 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
11082 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
11083 RefPtr<nsAtom> key = NS_Atomize(*name);
11084 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
11086 ++name;
11088 } else {
11089 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
11090 if (fileChannel) {
11091 nsCOMPtr<nsIFile> file;
11092 fileChannel->GetFile(getter_AddRefs(file));
11093 if (file) {
11094 PRTime msecs;
11095 rv = file->GetLastModifiedTime(&msecs);
11097 if (NS_SUCCEEDED(rv)) {
11098 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
11101 } else {
11102 nsAutoCString contentDisp;
11103 rv = aChannel->GetContentDispositionHeader(contentDisp);
11104 if (NS_SUCCEEDED(rv)) {
11105 SetHeaderData(nsGkAtoms::headerContentDisposition,
11106 NS_ConvertASCIItoUTF16(contentDisp));
11111 mLastModified.Truncate();
11112 if (modDate != 0) {
11113 GetFormattedTimeString(modDate, mLastModified);
11117 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
11118 // set any HTTP-EQUIV data into document's header data as well as url
11119 nsAutoString header;
11120 aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
11121 if (!header.IsEmpty()) {
11122 // Ignore META REFRESH when document is sandboxed from automatic features.
11123 nsContentUtils::ASCIIToLower(header);
11124 if (nsGkAtoms::refresh->Equals(header) &&
11125 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
11126 return;
11129 nsAutoString result;
11130 aMetaElement->GetAttr(nsGkAtoms::content, result);
11131 if (!result.IsEmpty()) {
11132 RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
11133 SetHeaderData(fieldAtom, result);
11137 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
11138 nsGkAtoms::handheldFriendly, eIgnoreCase)) {
11139 nsAutoString result;
11140 aMetaElement->GetAttr(nsGkAtoms::content, result);
11141 if (!result.IsEmpty()) {
11142 nsContentUtils::ASCIIToLower(result);
11143 SetHeaderData(nsGkAtoms::handheldFriendly, result);
11148 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
11149 nsAtom* aPrefix,
11150 int32_t aNamespaceID,
11151 const nsAString* aIs) {
11152 #ifdef DEBUG
11153 nsAutoString qName;
11154 if (aPrefix) {
11155 aPrefix->ToString(qName);
11156 qName.Append(':');
11158 qName.Append(aName);
11160 // Note: "a:b:c" is a valid name in non-namespaces XML, and
11161 // Document::CreateElement can call us with such a name and no prefix,
11162 // which would cause an error if we just used true here.
11163 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
11164 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
11165 "Don't pass invalid prefixes to Document::CreateElem, "
11166 "check caller.");
11167 #endif
11169 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
11170 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
11171 getter_AddRefs(nodeInfo));
11172 NS_ENSURE_TRUE(nodeInfo, nullptr);
11174 nsCOMPtr<Element> element;
11175 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
11176 NOT_FROM_PARSER, aIs);
11177 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
11180 bool Document::IsSafeToFlush() const {
11181 PresShell* presShell = GetPresShell();
11182 if (!presShell) {
11183 return true;
11185 return presShell->IsSafeToFlush();
11188 void Document::Sanitize() {
11189 // Sanitize the document by resetting all (current and former) password fields
11190 // and any form fields with autocomplete=off to their default values. We do
11191 // this now, instead of when the presentation is restored, to offer some
11192 // protection in case there is ever an exploit that allows a cached document
11193 // to be accessed from a different document.
11195 // First locate all input elements, regardless of whether they are
11196 // in a form, and reset the password and autocomplete=off elements.
11198 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
11200 nsAutoString value;
11202 uint32_t length = nodes->Length(true);
11203 for (uint32_t i = 0; i < length; ++i) {
11204 NS_ASSERTION(nodes->Item(i), "null item in node list!");
11206 RefPtr<HTMLInputElement> input =
11207 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
11208 if (!input) continue;
11210 input->GetAttr(nsGkAtoms::autocomplete, value);
11211 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
11212 input->Reset();
11216 // Now locate all _form_ elements that have autocomplete=off and reset them
11217 nodes = GetElementsByTagName(u"form"_ns);
11219 length = nodes->Length(true);
11220 for (uint32_t i = 0; i < length; ++i) {
11221 // Reset() may change the list dynamically.
11222 RefPtr<HTMLFormElement> form =
11223 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
11224 if (!form) continue;
11226 form->GetAttr(nsGkAtoms::autocomplete, value);
11227 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
11231 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
11232 if (!mSubDocuments) {
11233 return;
11236 // PLDHashTable::Iterator can't handle modifications while iterating so we
11237 // copy all entries to an array first before calling any callbacks.
11238 AutoTArray<RefPtr<Document>, 8> subdocs;
11239 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11240 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11241 if (Document* subdoc = entry->mSubDocument) {
11242 subdocs.AppendElement(subdoc);
11245 for (auto& subdoc : subdocs) {
11246 if (aCallback(*subdoc) == CallState::Stop) {
11247 break;
11252 void Document::CollectDescendantDocuments(
11253 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
11254 if (!mSubDocuments) {
11255 return;
11258 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11259 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11260 const Document* subdoc = entry->mSubDocument;
11261 if (subdoc) {
11262 if (aCallback(subdoc)) {
11263 aDescendants.AppendElement(entry->mSubDocument);
11265 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
11270 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
11271 uint32_t& aBFCacheCombo,
11272 bool aIncludeSubdocuments,
11273 bool aAllowUnloadListeners) {
11274 bool ret = true;
11276 if (!IsBFCachingAllowed()) {
11277 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
11278 ret = false;
11281 nsAutoCString uri;
11282 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11283 if (mDocumentURI) {
11284 mDocumentURI->GetSpec(uri);
11288 if (EventHandlingSuppressed()) {
11289 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11290 ("Save of %s blocked on event handling suppression", uri.get()));
11291 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
11292 ret = false;
11295 // Do not allow suspended windows to be placed in the
11296 // bfcache. This method is also used to verify a document
11297 // coming out of the bfcache is ok to restore, though. So
11298 // we only want to block suspend windows that aren't also
11299 // frozen.
11300 nsPIDOMWindowInner* win = GetInnerWindow();
11301 if (win && win->IsSuspended() && !win->IsFrozen()) {
11302 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11303 ("Save of %s blocked on suspended Window", uri.get()));
11304 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
11305 ret = false;
11308 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
11309 bool thirdParty = false;
11310 // Currently some other mobile browsers seem to bfcache only cross-domain
11311 // pages, but bfcache those also when there are unload event listeners, so
11312 // this is trying to match that behavior as much as possible.
11313 bool allowUnloadListeners =
11314 aAllowUnloadListeners &&
11315 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
11316 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
11317 channel, &thirdParty)) &&
11318 thirdParty));
11320 // Check our event listener manager for unload/beforeunload listeners.
11321 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
11322 if (!allowUnloadListeners && piTarget) {
11323 EventListenerManager* manager = piTarget->GetExistingListenerManager();
11324 if (manager) {
11325 if (manager->HasUnloadListeners()) {
11326 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11327 ("Save of %s blocked due to unload handlers", uri.get()));
11328 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
11329 ret = false;
11331 if (manager->HasBeforeUnloadListeners()) {
11332 if (!mozilla::SessionHistoryInParent() ||
11333 !StaticPrefs::
11334 docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
11335 MOZ_LOG(
11336 gPageCacheLog, mozilla::LogLevel::Verbose,
11337 ("Save of %s blocked due to beforeUnload handlers", uri.get()));
11338 aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
11339 ret = false;
11345 // Check if we have pending network requests
11346 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11347 if (loadGroup) {
11348 nsCOMPtr<nsISimpleEnumerator> requests;
11349 loadGroup->GetRequests(getter_AddRefs(requests));
11351 bool hasMore = false;
11353 // We want to bail out if we have any requests other than aNewRequest (or
11354 // in the case when aNewRequest is a part of a multipart response the base
11355 // channel the multipart response is coming in on).
11356 nsCOMPtr<nsIChannel> baseChannel;
11357 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
11358 if (part) {
11359 part->GetBaseChannel(getter_AddRefs(baseChannel));
11362 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11363 nsCOMPtr<nsISupports> elem;
11364 requests->GetNext(getter_AddRefs(elem));
11366 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11367 if (request && request != aNewRequest && request != baseChannel) {
11368 // Favicon loads don't need to block caching.
11369 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
11370 if (channel) {
11371 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
11372 if (li->InternalContentPolicyType() ==
11373 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
11374 continue;
11378 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11379 nsAutoCString requestName;
11380 request->GetName(requestName);
11381 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
11382 ("Save of %s blocked because document has request %s",
11383 uri.get(), requestName.get()));
11385 aBFCacheCombo |= BFCacheStatus::REQUEST;
11386 ret = false;
11391 // Check if we have active GetUserMedia use
11392 if (MediaManager::Exists() && win &&
11393 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
11394 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11395 ("Save of %s blocked due to GetUserMedia", uri.get()));
11396 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
11397 ret = false;
11400 #ifdef MOZ_WEBRTC
11401 // Check if we have active PeerConnections
11402 if (win && win->HasActivePeerConnections()) {
11403 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11404 ("Save of %s blocked due to PeerConnection", uri.get()));
11405 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
11406 ret = false;
11408 #endif // MOZ_WEBRTC
11410 // Don't save presentations for documents containing EME content, so that
11411 // CDMs reliably shutdown upon user navigation.
11412 if (ContainsEMEContent()) {
11413 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
11414 ret = false;
11417 // Don't save presentations for documents containing MSE content, to
11418 // reduce memory usage.
11419 if (ContainsMSEContent()) {
11420 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11421 ("Save of %s blocked due to MSE use", uri.get()));
11422 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
11423 ret = false;
11426 if (aIncludeSubdocuments && mSubDocuments) {
11427 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11428 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11429 Document* subdoc = entry->mSubDocument;
11431 uint32_t subDocBFCacheCombo = 0;
11432 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
11433 bool canCache =
11434 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
11435 true, allowUnloadListeners)
11436 : false;
11437 if (!canCache) {
11438 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11439 ("Save of %s blocked due to subdocument blocked", uri.get()));
11440 aBFCacheCombo |= subDocBFCacheCombo;
11441 ret = false;
11446 if (!mozilla::BFCacheInParent()) {
11447 // BFCache is currently not compatible with remote subframes (bug 1609324)
11448 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
11449 for (auto& child : browsingContext->Children()) {
11450 if (!child->IsInProcess()) {
11451 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
11452 ret = false;
11453 break;
11459 if (win) {
11460 auto* globalWindow = nsGlobalWindowInner::Cast(win);
11461 #ifdef MOZ_WEBSPEECH
11462 if (globalWindow->HasActiveSpeechSynthesis()) {
11463 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11464 ("Save of %s blocked due to Speech use", uri.get()));
11465 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
11466 ret = false;
11468 #endif
11469 if (globalWindow->HasUsedVR()) {
11470 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11471 ("Save of %s blocked due to having used VR", uri.get()));
11472 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
11473 ret = false;
11476 if (win->HasActiveLocks()) {
11477 MOZ_LOG(
11478 gPageCacheLog, mozilla::LogLevel::Verbose,
11479 ("Save of %s blocked due to having active lock requests", uri.get()));
11480 aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
11481 ret = false;
11484 if (win->HasActiveWebTransports()) {
11485 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11486 ("Save of %s blocked due to WebTransport", uri.get()));
11487 aBFCacheCombo |= BFCacheStatus::ACTIVE_WEBTRANSPORT;
11488 ret = false;
11492 return ret;
11495 void Document::Destroy() {
11496 // The ContentViewer wants to release the document now. So, tell our content
11497 // to drop any references to the document so that it can be destroyed.
11498 if (mIsGoingAway) {
11499 return;
11502 ReportDocumentUseCounters();
11503 ReportLCP();
11504 SetDevToolsWatchingDOMMutations(false);
11506 mIsGoingAway = true;
11508 ScriptLoader()->Destroy();
11509 SetScriptGlobalObject(nullptr);
11510 RemovedFromDocShell();
11512 bool oldVal = mInUnlinkOrDeletion;
11513 mInUnlinkOrDeletion = true;
11515 #ifdef DEBUG
11516 uint32_t oldChildCount = GetChildCount();
11517 #endif
11519 for (nsIContent* child = GetFirstChild(); child;
11520 child = child->GetNextSibling()) {
11521 child->DestroyContent();
11522 MOZ_ASSERT(child->GetParentNode() == this);
11524 MOZ_ASSERT(oldChildCount == GetChildCount());
11525 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
11527 mInUnlinkOrDeletion = oldVal;
11529 mLayoutHistoryState = nullptr;
11531 if (mOriginalDocument) {
11532 mOriginalDocument->mLatestStaticClone = nullptr;
11535 if (IsStaticDocument()) {
11536 RemoveProperty(nsGkAtoms::printisfocuseddoc);
11537 RemoveProperty(nsGkAtoms::printselectionranges);
11540 // Shut down our external resource map. We might not need this for
11541 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
11542 // tearing down all those frame trees right now is the right thing to do.
11543 mExternalResourceMap.Shutdown();
11545 // Manually break cycles via promise's global object pointer.
11546 mReadyForIdle = nullptr;
11547 mOrientationPendingPromise = nullptr;
11549 // To break cycles.
11550 mPreloadService.ClearAllPreloads();
11552 if (mDocumentL10n) {
11553 mDocumentL10n->Destroy();
11556 if (!mPresShell) {
11557 DropStyleSet();
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(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 EnumerateExternalResources([aPersisted](Document& aExternalResource) {
11809 aExternalResource.OnPageShow(aPersisted, nullptr);
11810 return CallState::Continue;
11813 if (mAnimationController) {
11814 mAnimationController->OnPageShow();
11817 if (!mIsBeingUsedAsImage) {
11818 // Dispatch observer notification to notify observers page is shown.
11819 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11820 if (os) {
11821 nsIPrincipal* principal = NodePrincipal();
11822 os->NotifyObservers(ToSupports(this),
11823 principal->IsSystemPrincipal() ? "chrome-page-shown"
11824 : "content-page-shown",
11825 nullptr);
11828 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11829 if (!target) {
11830 target = do_QueryInterface(GetWindow());
11832 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
11833 aPersisted, aOnlySystemGroup);
11837 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
11838 if (nsPresContext* presContext = aDocument.GetPresContext()) {
11839 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
11840 FullscreenEventType::Change, &aDocument, aTarget);
11841 presContext->RefreshDriver()->ScheduleFullscreenEvent(
11842 std::move(pendingEvent));
11846 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
11847 bool aOnlySystemGroup) {
11848 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11849 nsCString uri;
11850 if (GetDocumentURI()) {
11851 uri = GetDocumentURI()->GetSpecOrDefault();
11853 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11854 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
11857 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11858 MOZ_DIAGNOSTIC_ASSERT(
11859 inFrameLoaderSwap ==
11860 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11862 if (mAnimationController) {
11863 mAnimationController->OnPageHide();
11866 if (!inFrameLoaderSwap) {
11867 if (aPersisted) {
11868 // We do not stop the animations (bug 1024343) when the page is refreshing
11869 // while being dragged out.
11870 ImageTracker()->SetAnimatingState(false);
11873 // Set mIsShowing before firing events, in case those event handlers
11874 // move us around.
11875 mIsShowing = false;
11876 mVisible = false;
11879 ExitPointerLock();
11881 if (!mIsBeingUsedAsImage) {
11882 // Dispatch observer notification to notify observers page is hidden.
11883 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11884 if (os) {
11885 nsIPrincipal* principal = NodePrincipal();
11886 os->NotifyObservers(ToSupports(this),
11887 principal->IsSystemPrincipal()
11888 ? "chrome-page-hidden"
11889 : "content-page-hidden",
11890 nullptr);
11893 // Now send out a PageHide event.
11894 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11895 if (!target) {
11896 target = do_QueryInterface(GetWindow());
11899 PageUnloadingEventTimeStamp timeStamp(this);
11900 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
11901 aPersisted, aOnlySystemGroup);
11905 if (!inFrameLoaderSwap) {
11906 UpdateVisibilityState();
11909 EnumerateExternalResources([aPersisted](Document& aExternalResource) {
11910 aExternalResource.OnPageHide(aPersisted, nullptr);
11911 return CallState::Continue;
11913 NotifyActivityChanged();
11915 ClearPendingFullscreenRequests(this);
11916 if (Fullscreen()) {
11917 // If this document was fullscreen, we should exit fullscreen in this
11918 // doctree branch. This ensures that if the user navigates while in
11919 // fullscreen mode we don't leave its still visible ancestor documents
11920 // in fullscreen mode. So exit fullscreen in the document's fullscreen
11921 // root document, as this will exit fullscreen in all the root's
11922 // descendant documents. Note that documents are removed from the
11923 // doctree by the time OnPageHide() is called, so we must store a
11924 // reference to the root (in Document::mFullscreenRoot) since we can't
11925 // just traverse the doctree to get the root.
11926 Document::ExitFullscreenInDocTree(this);
11928 // Since the document is removed from the doctree before OnPageHide() is
11929 // called, ExitFullscreen() can't traverse from the root down to *this*
11930 // document, so we must manually call CleanupFullscreenState() below too.
11931 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
11932 // so we *must* call it after ExitFullscreen(), not before.
11933 // OnPageHide() is called in every hidden (i.e. descendant) document,
11934 // so calling CleanupFullscreenState() here will ensure all hidden
11935 // documents have their fullscreen state reset.
11936 CleanupFullscreenState();
11938 // The fullscreenchange event is to be queued in the refresh driver,
11939 // however a hidden page wouldn't trigger that again, so it makes no
11940 // sense to dispatch such event here.
11944 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
11945 NS_ASSERTION(
11946 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
11947 "mSubtreeModifiedTargets not cleared after dispatching?");
11948 ++mSubtreeModifiedDepth;
11949 if (aTarget) {
11950 // MayDispatchMutationEvent is often called just before this method,
11951 // so it has already appended the node to mSubtreeModifiedTargets.
11952 int32_t count = mSubtreeModifiedTargets.Count();
11953 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
11954 mSubtreeModifiedTargets.AppendObject(aTarget);
11959 void Document::MutationEventDispatched(nsINode* aTarget) {
11960 if (--mSubtreeModifiedDepth) {
11961 return;
11964 int32_t count = mSubtreeModifiedTargets.Count();
11965 if (!count) {
11966 return;
11969 nsPIDOMWindowInner* window = GetInnerWindow();
11970 if (window &&
11971 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
11972 mSubtreeModifiedTargets.Clear();
11973 return;
11976 nsCOMArray<nsINode> realTargets;
11977 for (nsINode* possibleTarget : mSubtreeModifiedTargets) {
11978 if (possibleTarget->ChromeOnlyAccess()) {
11979 continue;
11982 nsINode* commonAncestor = nullptr;
11983 int32_t realTargetCount = realTargets.Count();
11984 for (int32_t j = 0; j < realTargetCount; ++j) {
11985 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
11986 possibleTarget, realTargets[j]);
11987 if (commonAncestor) {
11988 realTargets.ReplaceObjectAt(commonAncestor, j);
11989 break;
11992 if (!commonAncestor) {
11993 realTargets.AppendObject(possibleTarget);
11997 mSubtreeModifiedTargets.Clear();
11999 for (const nsCOMPtr<nsINode>& target : realTargets) {
12000 InternalMutationEvent mutation(true, eLegacySubtreeModified);
12001 // MOZ_KnownLive due to bug 1620312
12002 AsyncEventDispatcher::RunDOMEventWhenSafe(MOZ_KnownLive(*target), mutation);
12006 void Document::DestroyElementMaps() {
12007 #ifdef DEBUG
12008 mStyledLinksCleared = true;
12009 #endif
12010 mStyledLinks.Clear();
12011 // Notify ID change listeners before clearing the identifier map.
12012 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
12013 iter.Get()->ClearAndNotify();
12015 mIdentifierMap.Clear();
12016 mComposedShadowRoots.Clear();
12017 mResponsiveContent.Clear();
12018 IncrementExpandoGeneration(*this);
12021 void Document::RefreshLinkHrefs() {
12022 // Get a list of all links we know about. We will reset them, which will
12023 // remove them from the document, so we need a copy of what is in the
12024 // hashtable.
12025 const nsTArray<Link*> linksToNotify = ToArray(mStyledLinks);
12027 // Reset all of our styled links.
12028 nsAutoScriptBlocker scriptBlocker;
12029 for (Link* link : linksToNotify) {
12030 link->ResetLinkState(true);
12034 nsresult Document::CloneDocHelper(Document* clone) const {
12035 clone->mIsStaticDocument = mCreatingStaticClone;
12037 // Init document
12038 nsresult rv = clone->Init(NodePrincipal(), mPartitionedPrincipal);
12039 NS_ENSURE_SUCCESS(rv, rv);
12041 if (mCreatingStaticClone) {
12042 if (mOriginalDocument) {
12043 clone->mOriginalDocument = mOriginalDocument;
12044 } else {
12045 clone->mOriginalDocument = const_cast<Document*>(this);
12047 clone->mOriginalDocument->mLatestStaticClone = clone;
12048 clone->mOriginalDocument->mStaticCloneCount++;
12050 nsCOMPtr<nsILoadGroup> loadGroup;
12052 // |mDocumentContainer| is the container of the document that is being
12053 // created and not the original container. See CreateStaticClone function().
12054 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
12055 if (docLoader) {
12056 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
12058 nsCOMPtr<nsIChannel> channel = GetChannel();
12059 nsCOMPtr<nsIURI> uri;
12060 if (channel) {
12061 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
12062 } else {
12063 uri = Document::GetDocumentURI();
12065 clone->mChannel = channel;
12066 clone->mShouldResistFingerprinting = mShouldResistFingerprinting;
12067 if (uri) {
12068 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
12071 clone->mIsSrcdocDocument = mIsSrcdocDocument;
12072 clone->SetContainer(mDocumentContainer);
12074 // Setup the navigation time. This will be needed by any animations in the
12075 // document, even if they are only paused.
12076 MOZ_ASSERT(!clone->GetNavigationTiming(),
12077 "Navigation time was already set?");
12078 if (mTiming) {
12079 RefPtr<nsDOMNavigationTiming> timing =
12080 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
12081 clone->SetNavigationTiming(timing);
12083 clone->SetCsp(mCSP);
12086 // Now ensure that our clone has the same URI, base URI, and principal as us.
12087 // We do this after the mCreatingStaticClone block above, because that block
12088 // can set the base URI to an incorrect value in cases when base URI
12089 // information came from the channel. So we override explicitly, and do it
12090 // for all these properties, in case ResetToURI messes with any of the rest of
12091 // them.
12092 clone->SetDocumentURI(Document::GetDocumentURI());
12093 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
12094 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
12095 clone->mActiveCookiePrincipal = mActiveCookiePrincipal;
12096 // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
12097 // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
12098 // when printed standalone via window.print() (where there won't be a parent
12099 // document to grab the URI from).
12100 clone->mDocumentBaseURI = GetDocBaseURI();
12101 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
12102 clone->mReferrerInfo =
12103 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
12104 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
12106 bool hasHadScriptObject = true;
12107 nsIScriptGlobalObject* scriptObject =
12108 GetScriptHandlingObject(hasHadScriptObject);
12109 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
12110 if (mCreatingStaticClone) {
12111 // If we're doing a static clone (print, print preview), then we're going to
12112 // be setting a scope object after the clone. It's better to set it only
12113 // once, so we don't do that here. However, we do want to act as if there is
12114 // a script handling object. So we set mHasHadScriptHandlingObject.
12115 clone->mHasHadScriptHandlingObject = true;
12116 } else if (scriptObject) {
12117 clone->SetScriptHandlingObject(scriptObject);
12118 } else {
12119 clone->SetScopeObject(GetScopeObject());
12121 // Make the clone a data document
12122 clone->SetLoadedAsData(
12123 true,
12124 /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
12126 // Misc state
12128 // State from Document
12129 clone->mCharacterSet = mCharacterSet;
12130 clone->mCharacterSetSource = mCharacterSetSource;
12131 clone->SetCompatibilityMode(mCompatMode);
12132 clone->mBidiOptions = mBidiOptions;
12133 clone->mContentLanguage = mContentLanguage;
12134 clone->SetContentType(GetContentTypeInternal());
12135 clone->mSecurityInfo = mSecurityInfo;
12137 // State from Document
12138 clone->mType = mType;
12139 clone->mXMLDeclarationBits = mXMLDeclarationBits;
12140 clone->mBaseTarget = mBaseTarget;
12142 return NS_OK;
12145 void Document::NotifyLoading(bool aNewParentIsLoading,
12146 const ReadyState& aCurrentState,
12147 ReadyState aNewState) {
12148 // Mirror the top-level loading state down to all subdocuments
12149 bool was_loading = mAncestorIsLoading ||
12150 aCurrentState == READYSTATE_LOADING ||
12151 aCurrentState == READYSTATE_INTERACTIVE;
12152 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
12153 aNewState == READYSTATE_INTERACTIVE; // new value for state
12154 bool set_load_state = was_loading != is_loading;
12156 MOZ_LOG(
12157 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12158 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
12159 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
12160 "set_load_state: %d",
12161 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
12162 (int)aNewState, was_loading, is_loading, set_load_state));
12164 mAncestorIsLoading = aNewParentIsLoading;
12165 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
12166 // Tell our innerwindow (and thus TimeoutManager)
12167 nsPIDOMWindowInner* inner = GetInnerWindow();
12168 if (inner) {
12169 inner->SetActiveLoadingState(is_loading);
12171 BrowsingContext* context = GetBrowsingContext();
12172 if (context) {
12173 // Don't use PreOrderWalk to mirror this down; go down one level as a
12174 // time so we can set mAncestorIsLoading and take into account the
12175 // readystates of the subdocument. In the child process it will call
12176 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
12177 // iterate it's children
12178 for (auto& child : context->Children()) {
12179 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12180 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
12181 // Setting ancestor loading on a discarded browsing context has no
12182 // effect.
12183 Unused << child->SetAncestorLoading(is_loading);
12189 void Document::SetReadyStateInternal(ReadyState aReadyState,
12190 bool aUpdateTimingInformation) {
12191 if (aReadyState == READYSTATE_UNINITIALIZED) {
12192 // Transition back to uninitialized happens only to keep assertions happy
12193 // right before readyState transitions to something else. Make this
12194 // transition undetectable by Web content.
12195 mReadyState = aReadyState;
12196 return;
12199 if (IsTopLevelContentDocument()) {
12200 if (aReadyState == READYSTATE_LOADING) {
12201 AddToplevelLoadingDocument(this);
12202 } else if (aReadyState == READYSTATE_COMPLETE) {
12203 RemoveToplevelLoadingDocument(this);
12207 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
12208 SetLoadingOrRestoredFromBFCacheTimeStampToNow();
12210 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
12211 mReadyState = aReadyState;
12212 if (aUpdateTimingInformation && mTiming) {
12213 switch (aReadyState) {
12214 case READYSTATE_LOADING:
12215 mTiming->NotifyDOMLoading(GetDocumentURI());
12216 break;
12217 case READYSTATE_INTERACTIVE:
12218 mTiming->NotifyDOMInteractive(GetDocumentURI());
12219 break;
12220 case READYSTATE_COMPLETE:
12221 mTiming->NotifyDOMComplete(GetDocumentURI());
12222 break;
12223 default:
12224 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
12225 break;
12228 // At the time of loading start, we don't have timing object, record time.
12230 if (READYSTATE_INTERACTIVE == aReadyState &&
12231 NodePrincipal()->IsSystemPrincipal()) {
12232 if (!mXULPersist && XRE_IsParentProcess()) {
12233 mXULPersist = new XULPersist(this);
12234 mXULPersist->Init();
12236 if (!mChromeObserver) {
12237 mChromeObserver = new ChromeObserver(this);
12238 mChromeObserver->Init();
12242 if (aUpdateTimingInformation) {
12243 RecordNavigationTiming(aReadyState);
12246 AsyncEventDispatcher::RunDOMEventWhenSafe(
12247 *this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
12250 void Document::GetReadyState(nsAString& aReadyState) const {
12251 switch (mReadyState) {
12252 case READYSTATE_LOADING:
12253 aReadyState.AssignLiteral(u"loading");
12254 break;
12255 case READYSTATE_INTERACTIVE:
12256 aReadyState.AssignLiteral(u"interactive");
12257 break;
12258 case READYSTATE_COMPLETE:
12259 aReadyState.AssignLiteral(u"complete");
12260 break;
12261 default:
12262 aReadyState.AssignLiteral(u"uninitialized");
12266 void Document::SuppressEventHandling(uint32_t aIncrease) {
12267 mEventsSuppressed += aIncrease;
12268 if (mEventsSuppressed == aIncrease) {
12269 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
12270 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12273 UpdateFrameRequestCallbackSchedulingState();
12274 for (uint32_t i = 0; i < aIncrease; ++i) {
12275 ScriptLoader()->AddExecuteBlocker();
12278 EnumerateSubDocuments([aIncrease](Document& aSubDoc) {
12279 aSubDoc.SuppressEventHandling(aIncrease);
12280 return CallState::Continue;
12284 void Document::NotifyAbortedLoad() {
12285 // If we still have outstanding work blocking DOMContentLoaded,
12286 // then don't try to change the readystate now, but wait until
12287 // they finish and then do so.
12288 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
12289 mSetCompleteAfterDOMContentLoaded = true;
12290 return;
12293 // Otherwise we're fully done at this point, so set the
12294 // readystate to complete.
12295 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
12296 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
12300 MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents(
12301 nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) {
12302 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
12303 if (MOZ_UNLIKELY(!fm)) {
12304 return;
12307 nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments);
12308 for (uint32_t i = 0; i < documents.Length(); ++i) {
12309 nsCOMPtr<Document> document = std::move(documents[i]);
12310 // NB: Don't bother trying to fire delayed events on documents that were
12311 // closed before this event ran.
12312 if (!document->EventHandlingSuppressed()) {
12313 fm->FireDelayedEvents(document);
12314 RefPtr<PresShell> presShell = document->GetPresShell();
12315 if (presShell) {
12316 // Only fire events for active documents.
12317 bool fire = aFireEvents && document->GetInnerWindow() &&
12318 document->GetInnerWindow()->IsCurrentInnerWindow();
12319 presShell->FireOrClearDelayedEvents(fire);
12321 document->FireOrClearPostMessageEvents(aFireEvents);
12326 void Document::PreloadPictureClosed() {
12327 MOZ_ASSERT(mPreloadPictureDepth > 0);
12328 mPreloadPictureDepth--;
12329 if (mPreloadPictureDepth == 0) {
12330 mPreloadPictureFoundSource.SetIsVoid(true);
12334 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
12335 const nsAString& aSizesAttr,
12336 const nsAString& aTypeAttr,
12337 const nsAString& aMediaAttr) {
12338 // Nested pictures are not valid syntax, so while we'll eventually load them,
12339 // it's not worth tracking sources mixed between nesting levels to preload
12340 // them effectively.
12341 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
12342 // <picture> selects the first matching source, so if this returns a URI we
12343 // needn't consider new sources until a new <picture> is encountered.
12344 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
12345 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
12346 aMediaAttr, mPreloadPictureFoundSource);
12347 if (found && mPreloadPictureFoundSource.IsVoid()) {
12348 // Found an empty source, which counts
12349 mPreloadPictureFoundSource.SetIsVoid(false);
12354 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
12355 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
12356 const nsAString& aSizesAttr, bool* aIsImgSet) {
12357 nsString sourceURL;
12358 bool isImgSet;
12359 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
12360 // We're in a <picture> element and found a URI from a source previous to
12361 // this image, use it.
12362 sourceURL = mPreloadPictureFoundSource;
12363 isImgSet = true;
12364 } else {
12365 // Otherwise try to use this <img> as a source
12366 HTMLImageElement::SelectSourceForTagWithAttrs(
12367 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
12368 VoidString(), sourceURL);
12369 isImgSet = !aSrcsetAttr.IsEmpty();
12372 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
12373 if (sourceURL.IsEmpty()) {
12374 return nullptr;
12377 // Construct into URI using passed baseURI (the parser may know of base URI
12378 // changes that have not reached us)
12379 nsresult rv;
12380 nsCOMPtr<nsIURI> uri;
12381 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
12382 this, aBaseURI);
12383 if (NS_FAILED(rv)) {
12384 return nullptr;
12387 *aIsImgSet = isImgSet;
12389 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
12390 // this this <picture> share the same <sources> (though this is not valid per
12391 // spec)
12392 return uri.forget();
12395 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
12396 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
12397 bool aLinkPreload, uint64_t aEarlyHintPreloaderId) {
12398 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
12399 nsContentUtils::CORSModeToLoadImageFlags(
12400 Element::StringToCORSMode(aCrossOriginAttr));
12402 nsContentPolicyType policyType =
12403 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
12404 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
12406 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12407 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12409 RefPtr<imgRequestProxy> request;
12411 nsLiteralString initiator = aEarlyHintPreloaderId
12412 ? u"early-hints"_ns
12413 : (aLinkPreload ? u"link"_ns : u"img"_ns);
12415 nsresult rv = nsContentUtils::LoadImage(
12416 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
12417 nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request),
12418 policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId);
12420 // Pin image-reference to avoid evicting it from the img-cache before
12421 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
12422 // unlink
12423 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
12424 mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
12428 void Document::MaybePreLoadImage(nsIURI* aUri,
12429 const nsAString& aCrossOriginAttr,
12430 ReferrerPolicyEnum aReferrerPolicy,
12431 bool aIsImgSet, bool aLinkPreload) {
12432 const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr);
12433 if (aLinkPreload) {
12434 // Check if the image was already preloaded in this document to avoid
12435 // duplicate preloading.
12436 PreloadHashKey key =
12437 PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode);
12438 if (!mPreloadService.PreloadExists(key)) {
12439 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12440 aLinkPreload, 0);
12442 return;
12445 // Early exit if the img is already present in the img-cache
12446 // which indicates that the "real" load has already started and
12447 // that we shouldn't preload it.
12448 if (nsContentUtils::IsImageAvailable(aUri, NodePrincipal(), corsMode, this)) {
12449 return;
12452 // Image not in cache - trigger preload
12453 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload,
12457 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
12458 if (!StaticPrefs::network_preconnect()) {
12459 return;
12462 NS_MutateURI mutator(aOrigURI);
12463 if (NS_FAILED(mutator.GetStatus())) {
12464 return;
12467 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
12468 // which ignores the path and uses only the origin. The other is for the
12469 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
12470 // non-Anonymous preconnects create different connections on the wire and
12471 // therefore should not be considred duplicates of each other and we
12472 // normalize the path before putting it in the hash to accomplish that.
12474 if (aCORSMode == CORS_ANONYMOUS) {
12475 mutator.SetPathQueryRef("/anonymous"_ns);
12476 } else {
12477 mutator.SetPathQueryRef("/"_ns);
12480 nsCOMPtr<nsIURI> uri;
12481 nsresult rv = mutator.Finalize(uri);
12482 if (NS_FAILED(rv)) {
12483 return;
12486 const bool existingEntryFound =
12487 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
12488 if (entry) {
12489 return true;
12491 entry.Insert(true);
12492 return false;
12494 if (existingEntryFound) {
12495 return;
12498 nsCOMPtr<nsISpeculativeConnect> speculator =
12499 mozilla::components::IO::Service();
12500 if (!speculator) {
12501 return;
12504 OriginAttributes oa;
12505 StoragePrincipalHelper::GetOriginAttributesForNetworkState(this, oa);
12506 speculator->SpeculativeConnectWithOriginAttributesNative(
12507 uri, std::move(oa), nullptr, aCORSMode == CORS_ANONYMOUS);
12510 void Document::ForgetImagePreload(nsIURI* aURI) {
12511 // Checking count is faster than hashing the URI in the common
12512 // case of empty table.
12513 if (mPreloadingImages.Count() != 0) {
12514 nsCOMPtr<imgIRequest> req;
12515 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
12516 if (req) {
12517 // Make sure to cancel the request so imagelib knows it's gone.
12518 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
12523 void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates,
12524 bool aNotify) {
12525 const DocumentState oldStates = mState;
12526 if (aMaybeChangedStates.HasAtLeastOneOfStates(
12527 DocumentState::ALL_LOCALEDIR_BITS)) {
12528 mState &= ~DocumentState::ALL_LOCALEDIR_BITS;
12529 if (IsDocumentRightToLeft()) {
12530 mState |= DocumentState::RTL_LOCALE;
12531 } else {
12532 mState |= DocumentState::LTR_LOCALE;
12536 if (aMaybeChangedStates.HasAtLeastOneOfStates(DocumentState::LWTHEME)) {
12537 if (ComputeDocumentLWTheme()) {
12538 mState |= DocumentState::LWTHEME;
12539 } else {
12540 mState &= ~DocumentState::LWTHEME;
12544 if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) {
12545 BrowsingContext* bc = GetBrowsingContext();
12546 if (!bc || !bc->GetIsActiveBrowserWindow()) {
12547 mState |= DocumentState::WINDOW_INACTIVE;
12548 } else {
12549 mState &= ~DocumentState::WINDOW_INACTIVE;
12553 const DocumentState changedStates = oldStates ^ mState;
12554 if (aNotify && !changedStates.IsEmpty()) {
12555 if (PresShell* ps = GetObservingPresShell()) {
12556 ps->DocumentStatesChanged(changedStates);
12561 namespace {
12564 * Stub for LoadSheet(), since all we want is to get the sheet into
12565 * the CSSLoader's style cache
12567 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
12568 ~StubCSSLoaderObserver() = default;
12570 public:
12571 NS_IMETHOD
12572 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
12573 NS_DECL_ISUPPORTS
12575 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
12577 } // namespace
12579 SheetPreloadStatus Document::PreloadStyle(
12580 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
12581 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aNonce,
12582 const nsAString& aIntegrity, css::StylePreloadKind aKind,
12583 uint64_t aEarlyHintPreloaderId, const nsAString& aFetchPriority) {
12584 MOZ_ASSERT(aKind != css::StylePreloadKind::None);
12586 // The CSSLoader will retain this object after we return.
12587 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
12589 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12590 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12592 // Charset names are always ASCII.
12593 auto result = CSSLoader()->LoadSheet(
12594 uri, aKind, aEncoding, referrerInfo, obs, aEarlyHintPreloaderId,
12595 Element::StringToCORSMode(aCrossOriginAttr), aNonce, aIntegrity,
12596 nsGenericHTMLElement::ToFetchPriority(aFetchPriority));
12597 if (result.isErr()) {
12598 return SheetPreloadStatus::Errored;
12600 RefPtr<StyleSheet> sheet = result.unwrap();
12601 if (sheet->IsComplete()) {
12602 return SheetPreloadStatus::AlreadyComplete;
12604 return SheetPreloadStatus::InProgress;
12607 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
12608 return CSSLoader()
12609 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
12610 .unwrapOr(nullptr);
12613 void Document::ResetDocumentDirection() {
12614 if (!nsContentUtils::IsChromeDoc(this)) {
12615 return;
12617 UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true);
12620 bool Document::IsDocumentRightToLeft() {
12621 if (!nsContentUtils::IsChromeDoc(this)) {
12622 return false;
12624 // setting the localedir attribute on the root element forces a
12625 // specific direction for the document.
12626 Element* element = GetRootElement();
12627 if (element) {
12628 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
12629 nullptr};
12630 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
12631 strings, eCaseMatters)) {
12632 case 0:
12633 return false;
12634 case 1:
12635 return true;
12636 default:
12637 break; // otherwise, not a valid value, so fall through
12641 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
12642 !mDocumentURI->SchemeIs("resource")) {
12643 return false;
12646 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
12649 class nsDelayedEventDispatcher : public Runnable {
12650 public:
12651 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
12652 : mozilla::Runnable("nsDelayedEventDispatcher"),
12653 mDocuments(std::move(aDocuments)) {}
12654 virtual ~nsDelayedEventDispatcher() = default;
12656 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
12657 // bug 1535398.
12658 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
12659 FireOrClearDelayedEvents(std::move(mDocuments), true);
12660 return NS_OK;
12663 private:
12664 nsTArray<nsCOMPtr<Document>> mDocuments;
12667 static void GetAndUnsuppressSubDocuments(
12668 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
12669 if (aDocument.EventHandlingSuppressed() > 0) {
12670 aDocument.DecreaseEventSuppression();
12671 aDocument.ScriptLoader()->RemoveExecuteBlocker();
12673 aDocuments.AppendElement(&aDocument);
12674 aDocument.EnumerateSubDocuments([&aDocuments](Document& aSubDoc) {
12675 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
12676 return CallState::Continue;
12680 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
12681 nsTArray<nsCOMPtr<Document>> documents;
12682 GetAndUnsuppressSubDocuments(*this, documents);
12684 for (nsCOMPtr<Document>& doc : documents) {
12685 if (!doc->EventHandlingSuppressed()) {
12686 if (WindowGlobalChild* wgc = doc->GetWindowGlobalChild()) {
12687 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12690 MOZ_ASSERT(NS_IsMainThread());
12691 nsTArray<RefPtr<net::ChannelEventQueue>> queues =
12692 std::move(doc->mSuspendedQueues);
12693 for (net::ChannelEventQueue* queue : queues) {
12694 queue->Resume();
12697 // If there have been any events driven by the refresh driver which were
12698 // delayed due to events being suppressed in this document, make sure
12699 // there is a refresh scheduled soon so the events will run.
12700 if (doc->mHasDelayedRefreshEvent) {
12701 doc->mHasDelayedRefreshEvent = false;
12703 if (doc->mPresShell) {
12704 nsRefreshDriver* rd =
12705 doc->mPresShell->GetPresContext()->RefreshDriver();
12706 rd->RunDelayedEventsSoon();
12712 if (aFireEvents) {
12713 MOZ_RELEASE_ASSERT(NS_IsMainThread());
12714 nsCOMPtr<nsIRunnable> ded =
12715 new nsDelayedEventDispatcher(std::move(documents));
12716 Dispatch(ded.forget());
12717 } else {
12718 FireOrClearDelayedEvents(std::move(documents), false);
12722 bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
12723 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
12726 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
12727 MOZ_ASSERT(NS_IsMainThread());
12728 MOZ_ASSERT(EventHandlingSuppressed());
12729 mSuspendedQueues.AppendElement(aQueue);
12732 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
12733 MOZ_ASSERT(NS_IsMainThread());
12735 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
12736 mSuspendedPostMessageEvents.AppendElement(aEvent);
12737 return true;
12739 return false;
12742 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
12743 nsTArray<RefPtr<PostMessageEvent>> events =
12744 std::move(mSuspendedPostMessageEvents);
12746 if (aFireEvents) {
12747 for (PostMessageEvent* event : events) {
12748 event->Run();
12753 void Document::SetSuppressedEventListener(EventListener* aListener) {
12754 mSuppressedEventListener = aListener;
12755 EnumerateSubDocuments([&](Document& aDocument) {
12756 aDocument.SetSuppressedEventListener(aListener);
12757 return CallState::Continue;
12761 bool Document::IsActive() const {
12762 return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
12763 !GetBrowsingContext()->IsInBFCache();
12766 nsISupports* Document::GetCurrentContentSink() {
12767 return mParser ? mParser->GetContentSink() : nullptr;
12770 Document* Document::GetTemplateContentsOwner() {
12771 if (!mTemplateContentsOwner) {
12772 bool hasHadScriptObject = true;
12773 nsIScriptGlobalObject* scriptObject =
12774 GetScriptHandlingObject(hasHadScriptObject);
12776 nsCOMPtr<Document> document;
12777 nsresult rv = NS_NewDOMDocument(
12778 getter_AddRefs(document),
12779 u""_ns, // aNamespaceURI
12780 u""_ns, // aQualifiedName
12781 nullptr, // aDoctype
12782 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
12783 true, // aLoadedAsData
12784 scriptObject, // aEventObject
12785 IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
12786 NS_ENSURE_SUCCESS(rv, nullptr);
12788 mTemplateContentsOwner = document;
12789 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
12791 if (!scriptObject) {
12792 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
12795 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
12797 // Set |mTemplateContentsOwner| as the template contents owner of itself so
12798 // that it is the template contents owner of nested template elements.
12799 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
12802 MOZ_ASSERT(mTemplateContentsOwner->IsTemplateContentsOwner());
12803 return mTemplateContentsOwner;
12806 // https://html.spec.whatwg.org/#the-autofocus-attribute
12807 void Document::ElementWithAutoFocusInserted(Element* aAutoFocusCandidate) {
12808 BrowsingContext* bc = GetBrowsingContext();
12809 if (!bc) {
12810 return;
12813 // If target is not fully active, then return.
12814 if (!IsCurrentActiveDocument()) {
12815 return;
12818 // If target's active sandboxing flag set has the sandboxed automatic features
12819 // browsing context flag, then return.
12820 if (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES) {
12821 return;
12824 // For each ancestorBC of target's browsing context's ancestor browsing
12825 // contexts: if ancestorBC's active document's origin is not same origin with
12826 // target's origin, then return.
12827 while (bc) {
12828 BrowsingContext* parent = bc->GetParent();
12829 if (!parent) {
12830 break;
12832 // AncestorBC is not the same site
12833 if (!parent->IsInProcess()) {
12834 return;
12837 Document* currentDocument = bc->GetDocument();
12838 if (!currentDocument) {
12839 return;
12842 Document* parentDocument = parent->GetDocument();
12843 if (!parentDocument) {
12844 return;
12847 // Not same origin
12848 if (!currentDocument->NodePrincipal()->Equals(
12849 parentDocument->NodePrincipal())) {
12850 return;
12853 bc = parent;
12855 MOZ_ASSERT(bc->IsTop());
12857 Document* topDocument = bc->GetDocument();
12858 MOZ_ASSERT(topDocument);
12859 topDocument->AppendAutoFocusCandidateToTopDocument(aAutoFocusCandidate);
12862 void Document::ScheduleFlushAutoFocusCandidates() {
12863 MOZ_ASSERT(mPresShell && mPresShell->DidInitialize());
12864 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12865 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
12866 rd->ScheduleAutoFocusFlush(this);
12870 void Document::AppendAutoFocusCandidateToTopDocument(
12871 Element* aAutoFocusCandidate) {
12872 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12873 if (mAutoFocusFired) {
12874 return;
12877 if (!HasAutoFocusCandidates()) {
12878 // PresShell may be initialized later
12879 if (mPresShell && mPresShell->DidInitialize()) {
12880 ScheduleFlushAutoFocusCandidates();
12884 nsWeakPtr element = do_GetWeakReference(aAutoFocusCandidate);
12885 mAutoFocusCandidates.RemoveElement(element);
12886 mAutoFocusCandidates.AppendElement(element);
12889 void Document::SetAutoFocusFired() {
12890 mAutoFocusCandidates.Clear();
12891 mAutoFocusFired = true;
12894 // https://html.spec.whatwg.org/#flush-autofocus-candidates
12895 void Document::FlushAutoFocusCandidates() {
12896 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12897 if (mAutoFocusFired) {
12898 return;
12901 if (!mPresShell) {
12902 return;
12905 MOZ_ASSERT(HasAutoFocusCandidates());
12906 MOZ_ASSERT(mPresShell->DidInitialize());
12908 nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetWindow();
12909 // We should be the top document
12910 if (!topWindow) {
12911 return;
12914 #ifdef DEBUG
12916 // Trying to find the top window (equivalent to window.top).
12917 nsCOMPtr<nsPIDOMWindowOuter> top = topWindow->GetInProcessTop();
12918 MOZ_ASSERT(topWindow == top);
12920 #endif
12922 // Don't steal the focus from the user
12923 if (topWindow->GetFocusedElement()) {
12924 SetAutoFocusFired();
12925 return;
12928 MOZ_ASSERT(mDocumentURI);
12929 nsAutoCString ref;
12930 // GetRef never fails
12931 nsresult rv = mDocumentURI->GetRef(ref);
12932 if (NS_SUCCEEDED(rv) &&
12933 nsContentUtils::GetTargetElement(this, NS_ConvertUTF8toUTF16(ref))) {
12934 SetAutoFocusFired();
12935 return;
12938 nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mAutoFocusCandidates);
12939 while (iter.HasMore()) {
12940 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(iter.GetNext());
12941 if (!autoFocusElement) {
12942 continue;
12944 RefPtr<Document> autoFocusElementDoc = autoFocusElement->OwnerDoc();
12945 // Get the latest info about the frame and allow scripts
12946 // to run which might affect the focusability of this element.
12947 autoFocusElementDoc->FlushPendingNotifications(FlushType::Frames);
12949 // Above layout flush may cause the PresShell to disappear.
12950 if (!mPresShell) {
12951 return;
12954 // Re-get the element because the ownerDoc() might have changed
12955 autoFocusElementDoc = autoFocusElement->OwnerDoc();
12956 BrowsingContext* bc = autoFocusElementDoc->GetBrowsingContext();
12957 if (!bc) {
12958 continue;
12961 // If doc is not fully active, then remove element from candidates, and
12962 // continue.
12963 if (!autoFocusElementDoc->IsCurrentActiveDocument()) {
12964 iter.Remove();
12965 continue;
12968 nsCOMPtr<nsIContentSink> sink =
12969 do_QueryInterface(autoFocusElementDoc->GetCurrentContentSink());
12970 if (sink) {
12971 nsHtml5TreeOpExecutor* executor =
12972 static_cast<nsHtml5TreeOpExecutor*>(sink->AsExecutor());
12973 if (executor) {
12974 // This is a HTML5 document
12975 MOZ_ASSERT(autoFocusElementDoc->IsHTMLDocument());
12976 // If doc's script-blocking style sheet counter is greater than 0, th
12977 // return.
12978 if (executor->WaitForPendingSheets()) {
12979 // In this case, element is the currently-best candidate, but doc is
12980 // not ready for autofocusing. We'll try again next time flush
12981 // autofocus candidates is called.
12982 ScheduleFlushAutoFocusCandidates();
12983 return;
12988 // The autofocus element could be moved to a different
12989 // top level BC.
12990 if (bc->Top()->GetDocument() != this) {
12991 continue;
12994 iter.Remove();
12996 // Let inclusiveAncestorDocuments be a list consisting of doc, plus the
12997 // active documents of each of doc's browsing context's ancestor browsing
12998 // contexts.
12999 // If any Document in inclusiveAncestorDocuments has non-null target
13000 // element, then continue.
13001 bool shouldFocus = true;
13002 while (bc) {
13003 Document* doc = bc->GetDocument();
13004 if (!doc) {
13005 shouldFocus = false;
13006 break;
13009 nsIURI* uri = doc->GetDocumentURI();
13010 if (!uri) {
13011 shouldFocus = false;
13012 break;
13015 nsAutoCString ref;
13016 nsresult rv = uri->GetRef(ref);
13017 // If there is an element in the document tree that has an ID equal to
13018 // fragment
13019 if (NS_SUCCEEDED(rv) &&
13020 nsContentUtils::GetTargetElement(doc, NS_ConvertUTF8toUTF16(ref))) {
13021 shouldFocus = false;
13022 break;
13024 bc = bc->GetParent();
13027 if (!shouldFocus) {
13028 continue;
13031 MOZ_ASSERT(topWindow);
13032 if (TryAutoFocusCandidate(*autoFocusElement)) {
13033 // We've successfully autofocused an element, don't
13034 // need to try to focus the rest.
13035 SetAutoFocusFired();
13036 break;
13040 if (HasAutoFocusCandidates()) {
13041 ScheduleFlushAutoFocusCandidates();
13045 bool Document::TryAutoFocusCandidate(Element& aElement) {
13046 const FocusOptions options;
13047 if (RefPtr<Element> target = nsFocusManager::GetTheFocusableArea(
13048 &aElement, nsFocusManager::ProgrammaticFocusFlags(options))) {
13049 target->Focus(options, CallerType::NonSystem, IgnoreErrors());
13050 return true;
13053 return false;
13056 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
13057 if (!aDocumentURI) {
13058 return;
13061 nsAutoCString ref;
13063 // Since all URI's that pass through here aren't URL's we can't
13064 // rely on the nsIURI implementation for providing a way for
13065 // finding the 'ref' part of the URI, we'll haveto revert to
13066 // string routines for finding the data past '#'
13068 nsresult rv = aDocumentURI->GetSpec(ref);
13069 if (NS_FAILED(rv)) {
13070 Unused << aDocumentURI->GetRef(mScrollToRef);
13071 return;
13074 nsReadingIterator<char> start, end;
13076 ref.BeginReading(start);
13077 ref.EndReading(end);
13079 if (FindCharInReadable('#', start, end)) {
13080 ++start; // Skip over the '#'
13082 mScrollToRef = Substring(start, end);
13086 // https://html.spec.whatwg.org/#scrolling-to-a-fragment
13087 void Document::ScrollToRef() {
13088 if (mScrolledToRefAlready) {
13089 RefPtr<PresShell> presShell = GetPresShell();
13090 if (presShell) {
13091 presShell->ScrollToAnchor();
13093 return;
13096 // 2. If fragment is the empty string, then return the special value top of
13097 // the document.
13098 if (mScrollToRef.IsEmpty()) {
13099 return;
13102 RefPtr<PresShell> presShell = GetPresShell();
13103 if (!presShell) {
13104 return;
13107 // 3. Let potentialIndicatedElement be the result of finding a potential
13108 // indicated element given document and fragment.
13109 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
13110 auto rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
13112 // 4. If potentialIndicatedElement is not null, then return
13113 // potentialIndicatedElement.
13114 if (NS_SUCCEEDED(rv)) {
13115 mScrolledToRefAlready = true;
13116 return;
13119 // 5. Let fragmentBytes be the result of percent-decoding fragment.
13120 nsAutoCString fragmentBytes;
13121 const bool unescaped =
13122 NS_UnescapeURL(mScrollToRef.Data(), mScrollToRef.Length(),
13123 /* aFlags = */ 0, fragmentBytes);
13125 if (!unescaped || fragmentBytes.IsEmpty()) {
13126 // Another attempt is only necessary if characters were unescaped.
13127 return;
13130 // 6. Let decodedFragment be the result of running UTF-8 decode without BOM on
13131 // fragmentBytes.
13132 nsAutoString decodedFragment;
13133 rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(fragmentBytes, decodedFragment);
13134 NS_ENSURE_SUCCESS_VOID(rv);
13136 // 7. Set potentialIndicatedElement to the result of finding a potential
13137 // indicated element given document and decodedFragment.
13138 rv = presShell->GoToAnchor(decodedFragment,
13139 mChangeScrollPosWhenScrollingToRef);
13140 if (NS_SUCCEEDED(rv)) {
13141 mScrolledToRefAlready = true;
13145 void Document::RegisterActivityObserver(nsISupports* aSupports) {
13146 if (!mActivityObservers) {
13147 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
13149 mActivityObservers->Insert(aSupports);
13152 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
13153 if (!mActivityObservers) {
13154 return false;
13156 return mActivityObservers->EnsureRemoved(aSupports);
13159 void Document::EnumerateActivityObservers(
13160 ActivityObserverEnumerator aEnumerator) {
13161 if (!mActivityObservers) {
13162 return;
13165 const auto keyArray =
13166 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
13167 for (auto& observer : keyArray) {
13168 aEnumerator(observer.get());
13172 void Document::RegisterPendingLinkUpdate(Link* aLink) {
13173 if (aLink->HasPendingLinkUpdate()) {
13174 return;
13177 aLink->SetHasPendingLinkUpdate();
13179 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
13180 nsCOMPtr<nsIRunnable> event =
13181 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
13182 &Document::FlushPendingLinkUpdates);
13183 // Do this work in a second in the worst case.
13184 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
13185 EventQueuePriority::Idle);
13186 if (NS_FAILED(rv)) {
13187 // If during shutdown posting a runnable doesn't succeed, we probably
13188 // don't need to update link states.
13189 return;
13191 mHasLinksToUpdateRunnable = true;
13194 mLinksToUpdate.InfallibleAppend(aLink);
13197 void Document::FlushPendingLinkUpdates() {
13198 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
13199 MOZ_ASSERT(mHasLinksToUpdateRunnable);
13200 mHasLinksToUpdateRunnable = false;
13202 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
13203 mFlushingPendingLinkUpdates = true;
13205 while (!mLinksToUpdate.IsEmpty()) {
13206 LinksToUpdateList links(std::move(mLinksToUpdate));
13207 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
13208 Link* link = iter.Get();
13209 Element* element = link->GetElement();
13210 if (element->OwnerDoc() == this) {
13211 link->ClearHasPendingLinkUpdate();
13212 if (element->IsInComposedDoc()) {
13213 link->TriggerLinkUpdate(/* aNotify = */ true);
13221 * Retrieves the node in a static-clone document that corresponds to aOrigNode,
13222 * which is a node in the original document from which aStaticClone was cloned.
13224 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
13225 Document& aStaticClone) {
13226 MOZ_ASSERT(aOrigNode);
13228 // Selections in anonymous subtrees aren't supported.
13229 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
13230 return nullptr;
13233 // If the node is disconnected, this is a bug in the selection code, but it
13234 // can happen with shadow DOM so handle it.
13235 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
13236 return nullptr;
13239 AutoTArray<Maybe<uint32_t>, 32> indexArray;
13240 const nsINode* current = aOrigNode;
13241 while (const nsINode* parent = current->GetParentNode()) {
13242 Maybe<uint32_t> index = parent->ComputeIndexOf(current);
13243 NS_ENSURE_TRUE(index.isSome(), nullptr);
13244 indexArray.AppendElement(std::move(index));
13245 current = parent;
13247 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
13248 nsINode* correspondingNode = [&]() -> nsINode* {
13249 if (current->IsDocument()) {
13250 return &aStaticClone;
13252 const auto* shadow = ShadowRoot::FromNode(*current);
13253 if (!shadow) {
13254 return nullptr;
13256 nsINode* correspondingHost =
13257 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
13258 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
13259 return nullptr;
13261 return correspondingHost->AsElement()->GetShadowRoot();
13262 }();
13264 if (NS_WARN_IF(!correspondingNode)) {
13265 return nullptr;
13267 for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
13268 correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
13269 NS_ENSURE_TRUE(correspondingNode, nullptr);
13271 return correspondingNode;
13275 * Caches the selection ranges from the source document onto the static clone in
13276 * case the "Print Selection Only" functionality is invoked.
13278 * Note that we cannot use the selection obtained from GetOriginalDocument()
13279 * since that selection may have mutated after the print was invoked.
13281 * Note also that because nsRange objects point into a specific document's
13282 * nodes, we cannot reuse an array of nsRange objects across multiple static
13283 * clone documents. For that reason we cache a new array of ranges on each
13284 * static clone that we create.
13286 * TODO(emilio): This can be simplified once we don't re-clone from static
13287 * documents.
13289 * @param aSourceDoc the document from which we are caching selection ranges
13290 * @param aStaticClone the document that will hold the cache
13291 * @return true if a selection range was cached
13293 static void CachePrintSelectionRanges(const Document& aSourceDoc,
13294 Document& aStaticClone) {
13295 MOZ_ASSERT(aStaticClone.IsStaticDocument());
13296 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc));
13297 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
13299 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
13301 // When the user opts to "Print Selection Only", the print code prefers any
13302 // selection in the static clone corresponding to the focused frame. If this
13303 // is that static clone, flag it for the printing code:
13304 const bool isFocusedDoc = [&] {
13305 if (sourceDocIsStatic) {
13306 return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc));
13308 nsPIDOMWindowOuter* window = aSourceDoc.GetWindow();
13309 if (!window) {
13310 return false;
13312 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
13313 if (!rootWindow) {
13314 return false;
13316 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
13317 nsFocusManager::GetFocusedDescendant(rootWindow,
13318 nsFocusManager::eIncludeAllDescendants,
13319 getter_AddRefs(focusedWindow));
13320 return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc;
13321 }();
13322 if (isFocusedDoc) {
13323 aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc,
13324 reinterpret_cast<void*>(true));
13327 const Selection* origSelection = nullptr;
13328 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
13330 if (sourceDocIsStatic) {
13331 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
13332 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
13333 } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
13334 origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
13337 if (!origSelection && !origRanges) {
13338 return;
13341 const uint32_t rangeCount =
13342 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
13343 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
13345 for (const uint32_t i : IntegerRange(rangeCount)) {
13346 MOZ_ASSERT_IF(!sourceDocIsStatic,
13347 origSelection->RangeCount() == rangeCount);
13348 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
13349 : origSelection->GetRangeAt(i);
13350 MOZ_ASSERT(range);
13351 nsINode* startContainer = range->GetStartContainer();
13352 nsINode* endContainer = range->GetEndContainer();
13354 if (!startContainer || !endContainer) {
13355 continue;
13358 nsINode* startNode =
13359 GetCorrespondingNodeInDocument(startContainer, aStaticClone);
13360 nsINode* endNode =
13361 GetCorrespondingNodeInDocument(endContainer, aStaticClone);
13363 if (NS_WARN_IF(!startNode || !endNode)) {
13364 continue;
13367 RefPtr<nsRange> clonedRange =
13368 nsRange::Create(startNode, range->StartOffset(), endNode,
13369 range->EndOffset(), IgnoreErrors());
13370 if (clonedRange && !clonedRange->Collapsed()) {
13371 printRanges->AppendElement(std::move(clonedRange));
13375 if (printRanges->IsEmpty()) {
13376 return;
13379 aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
13380 printRanges.release(),
13381 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
13384 already_AddRefed<Document> Document::CreateStaticClone(
13385 nsIDocShell* aCloneContainer, nsIDocumentViewer* aViewer,
13386 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
13387 MOZ_ASSERT(!mCreatingStaticClone);
13388 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
13389 MOZ_DIAGNOSTIC_ASSERT(aViewer);
13391 mCreatingStaticClone = true;
13392 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
13393 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
13395 auto raii = MakeScopeExit([&] {
13396 RemoveProperty(nsGkAtoms::adoptedsheetclones);
13397 mCreatingStaticClone = false;
13400 // Make document use different container during cloning.
13402 // FIXME(emilio): Why is this needed?
13403 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
13404 SetContainer(nsDocShell::Cast(aCloneContainer));
13405 IgnoredErrorResult rv;
13406 nsCOMPtr<nsINode> clonedNode = CloneNode(true, rv);
13407 SetContainer(originalShell);
13408 if (rv.Failed()) {
13409 return nullptr;
13412 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
13413 if (!clonedDoc) {
13414 return nullptr;
13417 size_t sheetsCount = SheetCount();
13418 for (size_t i = 0; i < sheetsCount; ++i) {
13419 RefPtr<StyleSheet> sheet = SheetAt(i);
13420 if (sheet) {
13421 if (sheet->IsApplicable()) {
13422 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13423 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13424 if (clonedSheet) {
13425 clonedDoc->AddStyleSheet(clonedSheet);
13430 clonedDoc->CloneAdoptedSheetsFrom(*this);
13432 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
13433 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
13434 for (StyleSheet* sheet : sheets) {
13435 if (sheet->IsApplicable()) {
13436 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13437 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13438 if (clonedSheet) {
13439 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
13440 clonedSheet);
13446 // Font faces created with the JS API will not be reflected in the
13447 // stylesheets and need to be copied over to the cloned document.
13448 if (const FontFaceSet* set = GetFonts()) {
13449 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
13452 clonedDoc->mReferrerInfo =
13453 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
13454 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
13455 CachePrintSelectionRanges(*this, *clonedDoc);
13457 // We're done with the clone, embed ourselves into the document viewer and
13458 // clone our children. The order here is pretty important, because our
13459 // document our document needs to have an owner global before we can create
13460 // the frame loaders for subdocuments.
13461 aViewer->SetDocument(clonedDoc);
13463 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
13465 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
13466 for (const auto& clone : pendingClones) {
13467 RefPtr<Element> element = do_QueryObject(clone.mElement);
13468 RefPtr<nsFrameLoader> frameLoader =
13469 nsFrameLoader::Create(element, /* aNetworkCreated */ false);
13471 if (NS_WARN_IF(!frameLoader)) {
13472 continue;
13475 clone.mElement->SetFrameLoader(frameLoader);
13477 nsresult rv = frameLoader->FinishStaticClone(
13478 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
13479 Unused << NS_WARN_IF(NS_FAILED(rv));
13482 return clonedDoc.forget();
13485 void Document::UnlinkOriginalDocumentIfStatic() {
13486 if (IsStaticDocument() && mOriginalDocument) {
13487 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
13488 mOriginalDocument->mStaticCloneCount--;
13489 mOriginalDocument = nullptr;
13491 MOZ_ASSERT(!mOriginalDocument);
13494 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
13495 int32_t* aHandle) {
13496 nsresult rv = mFrameRequestManager.Schedule(aCallback, aHandle);
13497 if (NS_FAILED(rv)) {
13498 return rv;
13501 UpdateFrameRequestCallbackSchedulingState();
13502 return NS_OK;
13505 void Document::CancelFrameRequestCallback(int32_t aHandle) {
13506 if (mFrameRequestManager.Cancel(aHandle)) {
13507 UpdateFrameRequestCallbackSchedulingState();
13511 bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
13512 return mFrameRequestManager.IsCanceled(aHandle);
13515 nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
13516 // Get the document's current state object. This is the object backing both
13517 // history.state and popStateEvent.state.
13519 // mStateObjectContainer may be null; this just means that there's no
13520 // current state object.
13522 if (!mCachedStateObjectValid) {
13523 if (mStateObjectContainer) {
13524 AutoJSAPI jsapi;
13525 // Init with null is "OK" in the sense that it will just fail.
13526 if (!jsapi.Init(GetScopeObject())) {
13527 return NS_ERROR_UNEXPECTED;
13529 JS::Rooted<JS::Value> value(jsapi.cx());
13530 nsresult rv =
13531 mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value);
13532 NS_ENSURE_SUCCESS(rv, rv);
13534 mCachedStateObject = value;
13535 if (!value.isNullOrUndefined()) {
13536 mozilla::HoldJSObjects(this);
13538 } else {
13539 mCachedStateObject = JS::NullValue();
13541 mCachedStateObjectValid = true;
13544 aState.set(mCachedStateObject);
13545 return NS_OK;
13548 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
13549 mTiming = aTiming;
13550 if (!mLoadingOrRestoredFromBFCacheTimeStamp.IsNull() && mTiming) {
13551 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(),
13552 mLoadingOrRestoredFromBFCacheTimeStamp);
13555 // If there's already the DocumentTimeline instance, tell it since the
13556 // DocumentTimeline is based on both the navigation start time stamp and the
13557 // refresh driver timestamp.
13558 if (mDocumentTimeline) {
13559 mDocumentTimeline->UpdateLastRefreshDriverTime();
13563 nsContentList* Document::ImageMapList() {
13564 if (!mImageMaps) {
13565 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
13566 nsGkAtoms::map);
13569 return mImageMaps;
13572 #define DEPRECATED_OPERATION(_op) #_op "Warning",
13573 static const char* kDeprecationWarnings[] = {
13574 #include "nsDeprecatedOperationList.h"
13575 nullptr};
13576 #undef DEPRECATED_OPERATION
13578 #define DOCUMENT_WARNING(_op) #_op "Warning",
13579 static const char* kDocumentWarnings[] = {
13580 #include "nsDocumentWarningList.h"
13581 nullptr};
13582 #undef DOCUMENT_WARNING
13584 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
13585 switch (aOperation) {
13586 #define DEPRECATED_OPERATION(_op) \
13587 case DeprecatedOperations::e##_op: \
13588 return eUseCounter_##_op;
13589 #include "nsDeprecatedOperationList.h"
13590 #undef DEPRECATED_OPERATION
13591 default:
13592 MOZ_CRASH();
13596 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
13597 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
13600 void Document::WarnOnceAbout(
13601 DeprecatedOperations aOperation, bool asError /* = false */,
13602 const nsTArray<nsString>& aParams /* = empty array */) const {
13603 MOZ_ASSERT(NS_IsMainThread());
13604 if (HasWarnedAbout(aOperation)) {
13605 return;
13607 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
13608 // Don't count deprecated operations for about pages since those pages
13609 // are almost in our control, and we always need to remove uses there
13610 // before we remove the operation itself anyway.
13611 if (!IsAboutPage()) {
13612 const_cast<Document*>(this)->SetUseCounter(
13613 OperationToUseCounter(aOperation));
13615 uint32_t flags =
13616 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13617 nsContentUtils::ReportToConsole(
13618 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
13619 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
13622 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
13623 return mDocWarningWarnedAbout[aWarning];
13626 void Document::WarnOnceAbout(
13627 DocumentWarnings aWarning, bool asError /* = false */,
13628 const nsTArray<nsString>& aParams /* = empty array */) const {
13629 MOZ_ASSERT(NS_IsMainThread());
13630 if (HasWarnedAbout(aWarning)) {
13631 return;
13633 mDocWarningWarnedAbout[aWarning] = true;
13634 uint32_t flags =
13635 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13636 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
13637 nsContentUtils::eDOM_PROPERTIES,
13638 kDocumentWarnings[aWarning], aParams);
13641 mozilla::dom::ImageTracker* Document::ImageTracker() {
13642 if (!mImageTracker) {
13643 mImageTracker = new mozilla::dom::ImageTracker;
13645 return mImageTracker;
13648 void Document::ScheduleSVGUseElementShadowTreeUpdate(
13649 SVGUseElement& aUseElement) {
13650 MOZ_ASSERT(aUseElement.IsInComposedDoc());
13652 if (MOZ_UNLIKELY(mIsStaticDocument)) {
13653 // Printing doesn't deal well with dynamic DOM mutations.
13654 return;
13657 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
13659 if (PresShell* presShell = GetPresShell()) {
13660 presShell->EnsureStyleFlush();
13664 void Document::DoUpdateSVGUseElementShadowTrees() {
13665 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13667 MOZ_ASSERT(!mCloningForSVGUse);
13668 mCloningForSVGUse = true;
13670 do {
13671 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
13672 mSVGUseElementsNeedingShadowTreeUpdate);
13673 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
13675 for (const auto& useElement : useElementsToUpdate) {
13676 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
13677 // The element was in another <use> shadow tree which we processed
13678 // already and also needed an update, and is removed from the document
13679 // now, so nothing to do here.
13680 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
13681 continue;
13683 useElement->UpdateShadowTree();
13685 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13687 mCloningForSVGUse = false;
13690 void Document::NotifyMediaFeatureValuesChanged() {
13691 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
13692 imageElement->MediaFeatureValuesChanged();
13696 already_AddRefed<Touch> Document::CreateTouch(
13697 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
13698 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
13699 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
13700 float aRotationAngle, float aForce) {
13701 RefPtr<Touch> touch =
13702 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
13703 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
13704 return touch.forget();
13707 already_AddRefed<TouchList> Document::CreateTouchList() {
13708 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13709 return retval.forget();
13712 already_AddRefed<TouchList> Document::CreateTouchList(
13713 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
13714 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13715 retval->Append(&aTouch);
13716 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13717 retval->Append(aTouches[i].get());
13719 return retval.forget();
13722 already_AddRefed<TouchList> Document::CreateTouchList(
13723 const Sequence<OwningNonNull<Touch>>& aTouches) {
13724 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13725 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13726 retval->Append(aTouches[i].get());
13728 return retval.forget();
13731 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
13732 float aX, float aY) {
13733 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
13735 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
13736 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
13737 nsPoint pt(x, y);
13739 FlushPendingNotifications(FlushType::Layout);
13741 PresShell* presShell = GetPresShell();
13742 if (!presShell) {
13743 return nullptr;
13746 nsIFrame* rootFrame = presShell->GetRootFrame();
13748 // XUL docs, unlike HTML, have no frame tree until everything's done loading
13749 if (!rootFrame) {
13750 return nullptr;
13753 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
13754 RelativeTo{rootFrame}, pt,
13755 {{FrameForPointOption::IgnorePaintSuppression,
13756 FrameForPointOption::IgnoreCrossDoc}});
13757 if (!ptFrame) {
13758 return nullptr;
13761 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
13762 nsPoint adjustedPoint = pt;
13763 if (nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
13764 adjustedPoint) !=
13765 nsLayoutUtils::TRANSFORM_SUCCEEDED) {
13766 return nullptr;
13769 nsIFrame::ContentOffsets offsets =
13770 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
13772 nsCOMPtr<nsIContent> node = offsets.content;
13773 uint32_t offset = offsets.offset;
13774 nsCOMPtr<nsIContent> anonNode = node;
13775 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
13776 if (nodeIsAnonymous) {
13777 node = ptFrame->GetContent();
13778 nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
13779 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
13780 nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
13781 if (textFrame) {
13782 // If the anonymous content node has a child, then we need to make sure
13783 // that we get the appropriate child, as otherwise the offset may not be
13784 // correct when we construct a range for it.
13785 nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
13786 if (firstChild) {
13787 anonNode = firstChild;
13790 if (textArea) {
13791 offset =
13792 nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
13795 node = nonanon;
13796 } else {
13797 node = nullptr;
13798 offset = 0;
13802 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
13803 if (nodeIsAnonymous) {
13804 aCaretPos->SetAnonymousContentNode(anonNode);
13806 return aCaretPos.forget();
13809 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
13810 // We rely on correct frame information here, so need to flush frames.
13811 FlushPendingNotifications(FlushType::Frames);
13813 // An element that is the HTML body element is potentially scrollable if all
13814 // of the following conditions are true:
13816 // The element has an associated CSS layout box.
13817 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
13818 if (!bodyFrame) {
13819 return false;
13822 // The element's parent element's computed value of the overflow-x and
13823 // overflow-y properties are visible.
13824 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
13825 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
13826 if (parentFrame &&
13827 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
13828 return false;
13831 // The element's computed value of the overflow-x or overflow-y properties is
13832 // not visible.
13833 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
13836 Element* Document::GetScrollingElement() {
13837 // Keep this in sync with IsScrollingElement.
13838 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
13839 RefPtr<HTMLBodyElement> body = GetBodyElement();
13840 if (body && !IsPotentiallyScrollable(body)) {
13841 return body;
13844 return nullptr;
13847 return GetRootElement();
13850 bool Document::IsScrollingElement(Element* aElement) {
13851 // Keep this in sync with GetScrollingElement.
13852 MOZ_ASSERT(aElement);
13854 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
13855 return aElement == GetRootElement();
13858 // In the common case when aElement != body, avoid refcounting.
13859 HTMLBodyElement* body = GetBodyElement();
13860 if (aElement != body) {
13861 return false;
13864 // Now we know body is non-null, since aElement is not null. It's the
13865 // scrolling element for the document if it itself is not potentially
13866 // scrollable.
13867 RefPtr<HTMLBodyElement> strongBody(body);
13868 return !IsPotentiallyScrollable(strongBody);
13871 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
13872 public:
13873 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
13874 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
13876 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
13877 const BlockParsingOptions& aOptions)
13878 : mPromise(aPromise) {
13879 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
13880 if (parser &&
13881 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
13882 parser->BlockParser();
13883 mParser = do_GetWeakReference(parser);
13884 mDocument = aDocument;
13885 mDocument->BlockOnload();
13886 mDocument->BlockDOMContentLoaded();
13890 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13891 ErrorResult& aRv) override {
13892 MaybeUnblockParser();
13894 mPromise->MaybeResolve(aValue);
13897 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13898 ErrorResult& aRv) override {
13899 MaybeUnblockParser();
13901 mPromise->MaybeReject(aValue);
13904 protected:
13905 virtual ~UnblockParsingPromiseHandler() {
13906 // If we're being cleaned up by the cycle collector, our mDocument reference
13907 // may have been unlinked while our mParser weak reference is still alive.
13908 if (mDocument) {
13909 MaybeUnblockParser();
13913 private:
13914 void MaybeUnblockParser() {
13915 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
13916 if (parser) {
13917 MOZ_DIAGNOSTIC_ASSERT(mDocument);
13918 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
13919 if (parser == docParser) {
13920 parser->UnblockParser();
13921 parser->ContinueInterruptedParsingAsync();
13924 if (mDocument) {
13925 // We blocked DOMContentLoaded and load events on this document. Unblock
13926 // them. Note that we want to do that no matter what's going on with the
13927 // parser state for this document. Maybe someone caused it to stop being
13928 // parsed, so CreatorParserOrNull() is returning null, but we still want
13929 // to unblock these.
13930 mDocument->UnblockDOMContentLoaded();
13931 mDocument->UnblockOnload(false);
13933 mParser = nullptr;
13934 mDocument = nullptr;
13937 nsWeakPtr mParser;
13938 RefPtr<Promise> mPromise;
13939 RefPtr<Document> mDocument;
13942 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
13944 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
13945 NS_INTERFACE_MAP_ENTRY(nsISupports)
13946 NS_INTERFACE_MAP_END
13948 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
13949 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
13951 already_AddRefed<Promise> Document::BlockParsing(
13952 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
13953 RefPtr<Promise> resultPromise =
13954 Promise::Create(aPromise.GetParentObject(), aRv);
13955 if (aRv.Failed()) {
13956 return nullptr;
13959 RefPtr<PromiseNativeHandler> promiseHandler =
13960 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
13961 aPromise.AppendNativeHandler(promiseHandler);
13963 return resultPromise.forget();
13966 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
13967 if (mFailedChannel) {
13968 nsCOMPtr<nsIURI> failedURI;
13969 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
13970 return failedURI.forget();
13974 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
13975 if (!uri) {
13976 return nullptr;
13979 return uri.forget();
13982 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
13983 if (mIsGoingAway) {
13984 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13985 return nullptr;
13988 if (!mReadyForIdle) {
13989 nsIGlobalObject* global = GetScopeObject();
13990 if (!global) {
13991 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13992 return nullptr;
13995 mReadyForIdle = Promise::Create(global, aRv);
13996 if (aRv.Failed()) {
13997 return nullptr;
14001 return mReadyForIdle;
14004 void Document::MaybeResolveReadyForIdle() {
14005 IgnoredErrorResult rv;
14006 Promise* readyPromise = GetDocumentReadyForIdle(rv);
14007 if (readyPromise) {
14008 readyPromise->MaybeResolveWithUndefined();
14012 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
14013 // The policy is created when the document is initialized. We _must_ have a
14014 // policy here even if the featurePolicy pref is off. If this assertion fails,
14015 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
14016 MOZ_ASSERT(mFeaturePolicy);
14017 return mFeaturePolicy;
14020 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
14021 // Only chrome documents are allowed to use command dispatcher.
14022 if (!nsContentUtils::IsChromeDoc(this)) {
14023 return nullptr;
14025 if (!mCommandDispatcher) {
14026 // Create our command dispatcher and hook it up.
14027 mCommandDispatcher = new nsXULCommandDispatcher(this);
14029 return mCommandDispatcher;
14032 void Document::InitializeXULBroadcastManager() {
14033 if (mXULBroadcastManager) {
14034 return;
14036 mXULBroadcastManager = new XULBroadcastManager(this);
14039 namespace {
14041 class DevToolsMutationObserver final : public nsStubMutationObserver {
14042 NS_DECL_ISUPPORTS
14043 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
14044 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
14045 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
14047 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
14048 // relies on the event firing _before_ the removal happens.
14049 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
14051 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
14052 // data changes right now (maybe intentionally?).
14053 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
14055 DevToolsMutationObserver() = default;
14057 private:
14058 void FireEvent(nsINode* aTarget, const nsAString& aType);
14060 ~DevToolsMutationObserver() = default;
14063 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
14065 void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
14066 const nsAString& aType) {
14067 AsyncEventDispatcher::RunDOMEventWhenSafe(*aTarget, aType, CanBubble::eNo,
14068 ChromeOnlyDispatch::eYes,
14069 Composed::eYes);
14072 void DevToolsMutationObserver::AttributeChanged(Element* aElement,
14073 int32_t aNamespaceID,
14074 nsAtom* aAttribute,
14075 int32_t aModType,
14076 const nsAttrValue* aOldValue) {
14077 FireEvent(aElement, u"devtoolsattrmodified"_ns);
14080 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
14081 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
14082 ContentInserted(c);
14086 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
14087 FireEvent(aChild, u"devtoolschildinserted"_ns);
14090 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
14092 } // namespace
14094 void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
14095 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
14096 return;
14098 mDevToolsWatchingDOMMutations = aValue;
14099 if (aValue) {
14100 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
14101 sDevToolsMutationObserver = new DevToolsMutationObserver();
14102 ClearOnShutdown(&sDevToolsMutationObserver);
14104 AddMutationObserver(sDevToolsMutationObserver);
14105 } else if (sDevToolsMutationObserver) {
14106 RemoveMutationObserver(sDevToolsMutationObserver);
14110 void EvaluateMediaQueryLists(nsTArray<RefPtr<MediaQueryList>>& aListsToNotify,
14111 Document& aDocument, bool aRecurse) {
14112 if (nsPresContext* pc = aDocument.GetPresContext()) {
14113 pc->FlushPendingMediaFeatureValuesChanged();
14116 for (MediaQueryList* mql : aDocument.MediaQueryLists()) {
14117 if (mql->EvaluateOnRenderingUpdate()) {
14118 aListsToNotify.AppendElement(mql);
14121 if (!aRecurse) {
14122 return;
14124 aDocument.EnumerateSubDocuments([&](Document& aSubDoc) {
14125 EvaluateMediaQueryLists(aListsToNotify, aSubDoc, true);
14126 return CallState::Continue;
14130 void Document::EvaluateMediaQueriesAndReportChanges(bool aRecurse) {
14131 AutoTArray<RefPtr<MediaQueryList>, 32> mqls;
14132 EvaluateMediaQueryLists(mqls, *this, aRecurse);
14133 for (auto& mql : mqls) {
14134 mql->FireChangeEvent();
14138 void Document::MaybeWarnAboutZoom() {
14139 if (mHasWarnedAboutZoom) {
14140 return;
14142 const bool usedZoom = Servo_IsPropertyIdRecordedInUseCounter(
14143 mStyleUseCounters.get(), eCSSProperty_zoom);
14144 if (!usedZoom) {
14145 return;
14148 mHasWarnedAboutZoom = true;
14149 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
14150 this, nsContentUtils::eLAYOUT_PROPERTIES,
14151 "ZoomPropertyWarning");
14154 nsIHTMLCollection* Document::Children() {
14155 if (!mChildrenCollection) {
14156 mChildrenCollection =
14157 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
14158 nsGkAtoms::_asterisk, false);
14161 return mChildrenCollection;
14164 uint32_t Document::ChildElementCount() { return Children()->Length(); }
14166 // Singleton class to manage the list of fullscreen documents which are the
14167 // root of a branch which contains fullscreen documents. We maintain this list
14168 // so that we can easily exit all windows from fullscreen when the user
14169 // presses the escape key.
14170 class FullscreenRoots {
14171 public:
14172 // Adds the root of given document to the manager. Calling this method
14173 // with a document whose root is already contained has no effect.
14174 static void Add(Document* aDoc);
14176 // Iterates over every root in the root list, and calls aFunction, passing
14177 // each root once to aFunction. It is safe to call Add() and Remove() while
14178 // iterating over the list (i.e. in aFunction). Documents that are removed
14179 // from the manager during traversal are not traversed, and documents that
14180 // are added to the manager during traversal are also not traversed.
14181 static void ForEach(void (*aFunction)(Document* aDoc));
14183 // Removes the root of a specific document from the manager.
14184 static void Remove(Document* aDoc);
14186 // Returns true if all roots added to the list have been removed.
14187 static bool IsEmpty();
14189 private:
14190 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
14191 MOZ_COUNTED_DTOR(FullscreenRoots)
14193 using RootsArray = nsTArray<WeakPtr<Document>>;
14195 // Returns true if aRoot is in the list of fullscreen roots.
14196 static bool Contains(Document* aRoot);
14198 // Singleton instance of the FullscreenRoots. This is instantiated when a
14199 // root is added, and it is deleted when the last root is removed.
14200 static FullscreenRoots* sInstance;
14202 // List of weak pointers to roots.
14203 RootsArray mRoots;
14206 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
14208 /* static */
14209 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
14210 if (!sInstance) {
14211 return;
14213 // Create a copy of the roots array, and iterate over the copy. This is so
14214 // that if an element is removed from mRoots we don't mess up our iteration.
14215 RootsArray roots(sInstance->mRoots.Clone());
14216 // Call aFunction on all entries.
14217 for (uint32_t i = 0; i < roots.Length(); i++) {
14218 nsCOMPtr<Document> root(roots[i]);
14219 // Check that the root isn't in the manager. This is so that new additions
14220 // while we were running don't get traversed.
14221 if (root && FullscreenRoots::Contains(root)) {
14222 aFunction(root);
14227 /* static */
14228 bool FullscreenRoots::Contains(Document* aRoot) {
14229 return sInstance && sInstance->mRoots.Contains(aRoot);
14232 /* static */
14233 void FullscreenRoots::Add(Document* aDoc) {
14234 nsCOMPtr<Document> root =
14235 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14236 if (!FullscreenRoots::Contains(root)) {
14237 if (!sInstance) {
14238 sInstance = new FullscreenRoots();
14240 sInstance->mRoots.AppendElement(root);
14244 /* static */
14245 void FullscreenRoots::Remove(Document* aDoc) {
14246 nsCOMPtr<Document> root =
14247 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14248 if (!sInstance || !sInstance->mRoots.RemoveElement(root)) {
14249 NS_ERROR("Should only try to remove roots which are still added!");
14250 return;
14252 if (sInstance->mRoots.IsEmpty()) {
14253 delete sInstance;
14254 sInstance = nullptr;
14258 /* static */
14259 bool FullscreenRoots::IsEmpty() { return !sInstance; }
14261 // Any fullscreen change waiting for the widget to finish transition
14262 // is queued here. This is declared static instead of a member of
14263 // Document because in the majority of time, there would be at most
14264 // one document requesting or exiting fullscreen. We shouldn't waste
14265 // the space to hold for it in every document.
14266 class PendingFullscreenChangeList {
14267 public:
14268 PendingFullscreenChangeList() = delete;
14270 template <typename T>
14271 static void Add(UniquePtr<T> aChange) {
14272 sList.insertBack(aChange.release());
14275 static const FullscreenChange* GetLast() { return sList.getLast(); }
14277 enum IteratorOption {
14278 // When we are committing fullscreen changes or preparing for
14279 // that, we generally want to iterate all requests in the same
14280 // window with eDocumentsWithSameRoot option.
14281 eDocumentsWithSameRoot,
14282 // If we are removing a document from the tree, we would only
14283 // want to remove the requests from the given document and its
14284 // descendants. For that case, use eInclusiveDescendants.
14285 eInclusiveDescendants
14288 template <typename T>
14289 class Iterator {
14290 public:
14291 explicit Iterator(Document* aDoc, IteratorOption aOption)
14292 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
14293 if (mCurrent) {
14294 if (aDoc->GetBrowsingContext()) {
14295 mRootBCForIteration = aDoc->GetBrowsingContext();
14296 if (aOption == eDocumentsWithSameRoot) {
14297 BrowsingContext* bc =
14298 GetParentIgnoreChromeBoundary(mRootBCForIteration);
14299 while (bc) {
14300 mRootBCForIteration = bc;
14301 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
14305 SkipToNextMatch();
14309 UniquePtr<T> TakeAndNext() {
14310 auto thisChange = TakeAndNextInternal();
14311 SkipToNextMatch();
14312 return thisChange;
14314 bool AtEnd() const { return mCurrent == nullptr; }
14316 private:
14317 static BrowsingContext* GetParentIgnoreChromeBoundary(
14318 BrowsingContext* aBC) {
14319 // Chrome BrowsingContexts are only available in the parent process, so if
14320 // we're in a content process, we only worry about the context tree.
14321 if (XRE_IsParentProcess()) {
14322 return aBC->Canonical()->GetParentCrossChromeBoundary();
14324 return aBC->GetParent();
14327 UniquePtr<T> TakeAndNextInternal() {
14328 FullscreenChange* thisChange = mCurrent;
14329 MOZ_ASSERT(thisChange->Type() == T::kType);
14330 mCurrent = mCurrent->removeAndGetNext();
14331 return WrapUnique(static_cast<T*>(thisChange));
14333 void SkipToNextMatch() {
14334 while (mCurrent) {
14335 if (mCurrent->Type() == T::kType) {
14336 BrowsingContext* bc = mCurrent->Document()->GetBrowsingContext();
14337 if (!bc) {
14338 // Always automatically drop fullscreen changes which are
14339 // from a document detached from the doc shell.
14340 UniquePtr<T> change = TakeAndNextInternal();
14341 change->MayRejectPromise("Document is not active");
14342 continue;
14344 while (bc && bc != mRootBCForIteration) {
14345 bc = GetParentIgnoreChromeBoundary(bc);
14347 if (bc) {
14348 break;
14351 // The current one either don't have matched type, or isn't
14352 // inside the given subtree, so skip this item.
14353 mCurrent = mCurrent->getNext();
14357 FullscreenChange* mCurrent;
14358 RefPtr<BrowsingContext> mRootBCForIteration;
14361 private:
14362 static LinkedList<FullscreenChange> sList;
14365 /* static */
14366 LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
14368 size_t Document::CountFullscreenElements() const {
14369 size_t count = 0;
14370 for (const nsWeakPtr& ptr : mTopLayer) {
14371 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14372 if (elem->State().HasState(ElementState::FULLSCREEN)) {
14373 count++;
14377 return count;
14380 // https://github.com/whatwg/html/issues/9143
14381 // We need to consider the precedence between active modal dialog, topmost auto
14382 // popover and fullscreen element once it's specified.
14383 void Document::HandleEscKey() {
14384 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14385 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14386 if (RefPtr popoverHTMLEl = nsGenericHTMLElement::FromNodeOrNull(element)) {
14387 if (element->IsAutoPopover() && element->IsPopoverOpen()) {
14388 popoverHTMLEl->HidePopover(IgnoreErrors());
14389 break;
14392 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14393 dialog->QueueCancelDialog();
14394 break;
14399 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
14400 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
14401 RefPtr<Promise> promise = exit->GetPromise();
14402 RestorePreviousFullscreenState(std::move(exit));
14403 return promise.forget();
14406 static void AskWindowToExitFullscreen(Document* aDoc) {
14407 if (XRE_GetProcessType() == GeckoProcessType_Content) {
14408 nsContentUtils::DispatchEventOnlyToChrome(
14409 aDoc, aDoc, u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
14410 Cancelable::eNo, /* DefaultAction */ nullptr);
14411 } else {
14412 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
14413 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
14418 class nsCallExitFullscreen : public Runnable {
14419 public:
14420 explicit nsCallExitFullscreen(Document* aDoc)
14421 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
14423 NS_IMETHOD Run() final {
14424 if (!mDoc) {
14425 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
14426 } else {
14427 AskWindowToExitFullscreen(mDoc);
14429 return NS_OK;
14432 private:
14433 nsCOMPtr<Document> mDoc;
14436 /* static */
14437 void Document::AsyncExitFullscreen(Document* aDoc) {
14438 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14439 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
14440 NS_DispatchToCurrentThread(exit.forget());
14443 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
14444 uint32_t count = 0;
14445 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
14446 aDoc.EnumerateSubDocuments([&count](Document& aSubDoc) {
14447 if (aSubDoc.Fullscreen()) {
14448 count++;
14450 return CallState::Continue;
14452 return count;
14455 bool Document::IsFullscreenLeaf() {
14456 // A fullscreen leaf document is fullscreen, and has no fullscreen
14457 // subdocuments.
14459 // FIXME(emilio): This doesn't seem to account for fission iframes, is that
14460 // ok?
14461 return Fullscreen() && CountFullscreenSubDocuments(*this) == 0;
14464 static Document* GetFullscreenLeaf(Document& aDoc) {
14465 if (aDoc.IsFullscreenLeaf()) {
14466 return &aDoc;
14468 if (!aDoc.Fullscreen()) {
14469 return nullptr;
14471 Document* leaf = nullptr;
14472 aDoc.EnumerateSubDocuments([&leaf](Document& aSubDoc) {
14473 leaf = GetFullscreenLeaf(aSubDoc);
14474 return leaf ? CallState::Stop : CallState::Continue;
14476 return leaf;
14479 static Document* GetFullscreenLeaf(Document* aDoc) {
14480 if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
14481 return leaf;
14483 // Otherwise we could be either in a non-fullscreen doc tree, or we're
14484 // below the fullscreen doc. Start the search from the root.
14485 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14486 return GetFullscreenLeaf(*root);
14489 static CallState ResetFullscreen(Document& aDocument) {
14490 if (Element* fsElement = aDocument.GetUnretargetedFullscreenElement()) {
14491 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
14492 "Should have at most 1 fullscreen subdocument.");
14493 aDocument.CleanupFullscreenState();
14494 NS_ASSERTION(!aDocument.Fullscreen(), "Should reset fullscreen");
14495 DispatchFullscreenChange(aDocument, fsElement);
14496 aDocument.EnumerateSubDocuments(ResetFullscreen);
14498 return CallState::Continue;
14501 // Since Document::ExitFullscreenInDocTree() could be called from
14502 // Element::UnbindFromTree() where it is not safe to synchronously run
14503 // script. This runnable is the script part of that function.
14504 class ExitFullscreenScriptRunnable : public Runnable {
14505 public:
14506 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
14507 : mozilla::Runnable("ExitFullscreenScriptRunnable"),
14508 mRoot(aRoot),
14509 mLeaf(aLeaf) {}
14511 NS_IMETHOD Run() override {
14512 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
14513 // document since we want this event to follow the same path that
14514 // MozDOMFullscreen:Entered was dispatched.
14515 nsContentUtils::DispatchEventOnlyToChrome(
14516 mLeaf, mLeaf, u"MozDOMFullscreen:Exited"_ns, CanBubble::eYes,
14517 Cancelable::eNo, /* DefaultAction */ nullptr);
14518 // Ensure the window exits fullscreen, as long as we don't have
14519 // pending fullscreen requests.
14520 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
14521 if (!mRoot->HasPendingFullscreenRequests()) {
14522 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
14523 false);
14526 return NS_OK;
14529 private:
14530 nsCOMPtr<Document> mRoot;
14531 nsCOMPtr<Document> mLeaf;
14534 /* static */
14535 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
14536 MOZ_ASSERT(aMaybeNotARootDoc);
14538 // Unlock the pointer
14539 PointerLockManager::Unlock();
14541 // Resolve all promises which waiting for exit fullscreen.
14542 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
14543 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14544 while (!iter.AtEnd()) {
14545 UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
14546 exit->MayResolvePromise();
14549 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
14550 if (!root || !root->Fullscreen()) {
14551 // If a document was detached before exiting from fullscreen, it is
14552 // possible that the root had left fullscreen state. In this case,
14553 // we would not get anything from the ResetFullscreen() call. Root's
14554 // not being a fullscreen doc also means the widget should have
14555 // exited fullscreen state. It means even if we do not return here,
14556 // we would actually do nothing below except crashing ourselves via
14557 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
14558 // document.
14559 return;
14562 // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
14563 // See ExitFullscreenScriptRunnable::Run for details. We have to
14564 // record it here because we don't have such information after we
14565 // reset the fullscreen state below.
14566 Document* fullscreenLeaf = GetFullscreenLeaf(root);
14568 // Walk the tree of fullscreen documents, and reset their fullscreen state.
14569 ResetFullscreen(*root);
14571 NS_ASSERTION(!root->Fullscreen(),
14572 "Fullscreen root should no longer be a fullscreen doc...");
14574 // Move the top-level window out of fullscreen mode.
14575 FullscreenRoots::Remove(root);
14577 nsContentUtils::AddScriptRunner(
14578 new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
14581 static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
14582 RefPtr<AsyncEventDispatcher> asyncDispatcher =
14583 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
14584 CanBubble::eYes, ChromeOnlyDispatch::eYes);
14585 asyncDispatcher->PostDOMEvent();
14588 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
14589 NS_ASSERTION(!Fullscreen() || !FullscreenRoots::IsEmpty(),
14590 "Should have at least 1 fullscreen root when fullscreen!");
14592 if (!GetWindow()) {
14593 aExit->MayRejectPromise("No active window");
14594 return;
14596 if (!Fullscreen() || FullscreenRoots::IsEmpty()) {
14597 aExit->MayRejectPromise("Not in fullscreen mode");
14598 return;
14601 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
14602 AutoTArray<Element*, 8> exitElements;
14604 Document* doc = fullScreenDoc;
14605 // Collect all subdocuments.
14606 for (; doc != this; doc = doc->GetInProcessParentDocument()) {
14607 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14608 MOZ_ASSERT(fsElement,
14609 "Parent document of "
14610 "a fullscreen document without fullscreen element?");
14611 exitElements.AppendElement(fsElement);
14613 MOZ_ASSERT(doc == this, "Must have reached this doc");
14614 // Collect all ancestor documents which we are going to change.
14615 for (; doc; doc = doc->GetInProcessParentDocument()) {
14616 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14617 MOZ_ASSERT(fsElement,
14618 "Ancestor of fullscreen document must also be in fullscreen");
14619 if (doc != this) {
14620 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
14621 if (iframe->FullscreenFlag()) {
14622 // If this is an iframe, and it explicitly requested
14623 // fullscreen, don't rollback it automatically.
14624 break;
14628 exitElements.AppendElement(fsElement);
14629 if (doc->CountFullscreenElements() > 1) {
14630 break;
14634 Document* lastDoc = exitElements.LastElement()->OwnerDoc();
14635 size_t fullscreenCount = lastDoc->CountFullscreenElements();
14636 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
14637 // If we are fully exiting fullscreen, don't touch anything here,
14638 // just wait for the window to get out from fullscreen first.
14639 PendingFullscreenChangeList::Add(std::move(aExit));
14640 AskWindowToExitFullscreen(this);
14641 return;
14644 // If fullscreen mode is updated the pointer should be unlocked
14645 PointerLockManager::Unlock();
14646 // All documents listed in the array except the last one are going to
14647 // completely exit from the fullscreen state.
14648 for (auto i : IntegerRange(exitElements.Length() - 1)) {
14649 exitElements[i]->OwnerDoc()->CleanupFullscreenState();
14651 // The last document will either rollback one fullscreen element, or
14652 // completely exit from the fullscreen state as well.
14653 Document* newFullscreenDoc;
14654 if (fullscreenCount > 1) {
14655 DebugOnly<bool> removedFullscreenElement = lastDoc->PopFullscreenElement();
14656 MOZ_ASSERT(removedFullscreenElement);
14657 newFullscreenDoc = lastDoc;
14658 } else {
14659 lastDoc->CleanupFullscreenState();
14660 newFullscreenDoc = lastDoc->GetInProcessParentDocument();
14662 // Dispatch the fullscreenchange event to all document listed. Note
14663 // that the loop order is reversed so that events are dispatched in
14664 // the tree order as indicated in the spec.
14665 for (Element* e : Reversed(exitElements)) {
14666 DispatchFullscreenChange(*e->OwnerDoc(), e);
14668 aExit->MayResolvePromise();
14670 MOZ_ASSERT(newFullscreenDoc,
14671 "If we were going to exit from fullscreen on "
14672 "all documents in this doctree, we should've asked the window to "
14673 "exit first instead of reaching here.");
14674 if (fullScreenDoc != newFullscreenDoc &&
14675 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
14676 // We've popped so enough off the stack that we've rolled back to
14677 // a fullscreen element in a parent document. If this document is
14678 // cross origin, dispatch an event to chrome so it knows to show
14679 // the warning UI.
14680 DispatchFullscreenNewOriginEvent(newFullscreenDoc);
14684 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
14685 if (nsPresContext* presContext = aDoc->GetPresContext()) {
14686 presContext->UpdateViewportScrollStylesOverride();
14690 static void NotifyFullScreenChangedForMediaElement(Element& aElement) {
14691 // When a media element enters the fullscreen, we would like to notify that
14692 // to the media controller in order to update its status.
14693 if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) {
14694 mediaElem->NotifyFullScreenChanged();
14698 void Document::CleanupFullscreenState() {
14699 while (PopFullscreenElement(UpdateViewport::No)) {
14700 // Remove the next one if appropriate
14703 UpdateViewportScrollbarOverrideForFullscreen(this);
14704 mFullscreenRoot = nullptr;
14706 // Restore the zoom level that was in place prior to entering fullscreen.
14707 if (PresShell* presShell = GetPresShell()) {
14708 if (presShell->GetMobileViewportManager()) {
14709 presShell->SetResolutionAndScaleTo(
14710 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
14715 bool Document::PopFullscreenElement(UpdateViewport aUpdateViewport) {
14716 Element* removedElement = TopLayerPop([](Element* element) -> bool {
14717 return element->State().HasState(ElementState::FULLSCREEN);
14720 if (!removedElement) {
14721 return false;
14724 MOZ_ASSERT(removedElement->State().HasState(ElementState::FULLSCREEN));
14725 removedElement->RemoveStates(ElementState::FULLSCREEN | ElementState::MODAL);
14726 NotifyFullScreenChangedForMediaElement(*removedElement);
14727 // Reset iframe fullscreen flag.
14728 if (auto* iframe = HTMLIFrameElement::FromNode(removedElement)) {
14729 iframe->SetFullscreenFlag(false);
14731 if (aUpdateViewport == UpdateViewport::Yes) {
14732 UpdateViewportScrollbarOverrideForFullscreen(this);
14734 return true;
14737 void Document::SetFullscreenElement(Element& aElement) {
14738 ElementState statesToAdd = ElementState::FULLSCREEN;
14739 if (!IsInChromeDocShell()) {
14740 // Don't make the document modal in chrome documents, since we don't want
14741 // the browser UI like the context menu / etc to be inert.
14742 statesToAdd |= ElementState::MODAL;
14744 aElement.AddStates(statesToAdd);
14745 TopLayerPush(aElement);
14746 NotifyFullScreenChangedForMediaElement(aElement);
14747 UpdateViewportScrollbarOverrideForFullscreen(this);
14750 void Document::TopLayerPush(Element& aElement) {
14751 const bool modal = aElement.State().HasState(ElementState::MODAL);
14753 TopLayerPop(aElement);
14754 if (nsIFrame* f = aElement.GetPrimaryFrame()) {
14755 f->MarkNeedsDisplayItemRebuild();
14758 mTopLayer.AppendElement(do_GetWeakReference(&aElement));
14759 NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
14761 if (modal) {
14762 aElement.AddStates(ElementState::TOPMOST_MODAL);
14764 bool foundExistingModalElement = false;
14765 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14766 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14767 if (element && element != &aElement &&
14768 element->State().HasState(ElementState::TOPMOST_MODAL)) {
14769 element->RemoveStates(ElementState::TOPMOST_MODAL);
14770 foundExistingModalElement = true;
14771 break;
14775 if (!foundExistingModalElement) {
14776 Element* root = GetRootElement();
14777 MOZ_RELEASE_ASSERT(root, "top layer element outside of document?");
14778 if (&aElement != root) {
14779 // Add inert to the root element so that the inertness is applied to the
14780 // entire document.
14781 root->AddStates(ElementState::INERT);
14787 void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
14788 aDialogElement.AddStates(ElementState::MODAL);
14789 TopLayerPush(aDialogElement);
14792 void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
14793 DebugOnly<Element*> removedElement = TopLayerPop(aDialogElement);
14794 MOZ_ASSERT(removedElement == &aDialogElement);
14795 aDialogElement.RemoveStates(ElementState::MODAL);
14798 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
14799 if (mTopLayer.IsEmpty()) {
14800 return nullptr;
14803 // Remove the topmost element that qualifies aPredicate; This
14804 // is required is because the top layer contains not only
14805 // fullscreen elements, but also dialog elements.
14806 Element* removedElement = nullptr;
14807 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
14808 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
14809 if (element && aPredicate(element)) {
14810 removedElement = element;
14811 if (nsIFrame* f = element->GetPrimaryFrame()) {
14812 f->MarkNeedsDisplayItemRebuild();
14814 mTopLayer.RemoveElementAt(i);
14815 break;
14819 // Pop from the stack null elements (references to elements which have
14820 // been GC'd since they were added to the stack) and elements which are
14821 // no longer in this document.
14823 // FIXME(emilio): If this loop does something, it'd violate the assertions
14824 // from GetTopLayerTop()... What gives?
14825 while (!mTopLayer.IsEmpty()) {
14826 Element* element = GetTopLayerTop();
14827 if (!element || element->GetComposedDoc() != this) {
14828 if (element) {
14829 if (nsIFrame* f = element->GetPrimaryFrame()) {
14830 f->MarkNeedsDisplayItemRebuild();
14834 mTopLayer.RemoveLastElement();
14835 } else {
14836 // The top element of the stack is now an in-doc element. Return here.
14837 break;
14841 if (!removedElement) {
14842 return nullptr;
14845 const bool modal = removedElement->State().HasState(ElementState::MODAL);
14847 if (modal) {
14848 removedElement->RemoveStates(ElementState::TOPMOST_MODAL);
14849 bool foundExistingModalElement = false;
14850 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14851 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14852 if (element && element->State().HasState(ElementState::MODAL)) {
14853 element->AddStates(ElementState::TOPMOST_MODAL);
14854 foundExistingModalElement = true;
14855 break;
14858 // No more modal elements, make the document not inert anymore.
14859 if (!foundExistingModalElement) {
14860 Element* root = GetRootElement();
14861 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
14862 root->RemoveStates(ElementState::INERT);
14867 return removedElement;
14870 Element* Document::TopLayerPop(Element& aElement) {
14871 auto predictFunc = [&aElement](Element* element) {
14872 return element == &aElement;
14874 return TopLayerPop(predictFunc);
14877 void Document::GetWireframe(bool aIncludeNodes,
14878 Nullable<Wireframe>& aWireframe) {
14879 FlushPendingNotifications(FlushType::Layout);
14880 GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
14883 void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
14884 Nullable<Wireframe>& aWireframe) {
14885 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
14886 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
14888 PresShell* shell = GetPresShell();
14889 if (!shell) {
14890 return;
14893 nsPresContext* pc = shell->GetPresContext();
14894 if (!pc) {
14895 return;
14898 nsIFrame* rootFrame = shell->GetRootFrame();
14899 if (!rootFrame) {
14900 return;
14903 auto& wireframe = aWireframe.SetValue();
14904 wireframe.mCanvasBackground = shell->ComputeCanvasBackground().mViewportColor;
14906 FrameForPointOptions options;
14907 options.mBits += FrameForPointOption::IgnoreCrossDoc;
14908 options.mBits += FrameForPointOption::IgnorePaintSuppression;
14909 options.mBits += FrameForPointOption::OnlyVisible;
14911 AutoTArray<nsIFrame*, 32> frames;
14912 const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
14913 nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
14914 options);
14916 // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
14917 // something perhaps, but seems hard / like it'd involve at least some extra
14918 // copying around, since they don't outlive GetFramesForArea.
14919 auto& rects = wireframe.mRects.Construct();
14920 if (!rects.SetCapacity(frames.Length(), fallible)) {
14921 return;
14923 for (nsIFrame* frame : Reversed(frames)) {
14924 auto [rectColor,
14925 rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> {
14926 if (frame->IsTextFrame()) {
14927 return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
14928 WireframeRectType::Text};
14930 if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
14931 return {0, WireframeRectType::Image};
14933 if (frame->IsThemed()) {
14934 return {0, WireframeRectType::Background};
14936 bool drawImage = false;
14937 bool drawColor = false;
14938 if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) {
14939 const nscolor color = nsCSSRendering::DetermineBackgroundColor(
14940 pc, bgStyle, frame, drawImage, drawColor);
14941 if (drawImage &&
14942 !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
14943 return {color, WireframeRectType::Image};
14945 if (drawColor && !frame->IsCanvasFrame()) {
14946 // Canvas frame background already accounted for in mCanvasBackground.
14947 return {color, WireframeRectType::Background};
14950 return {0, WireframeRectType::Unknown};
14951 }();
14953 if (rectType == WireframeRectType::Unknown) {
14954 continue;
14957 const auto r =
14958 CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
14959 frame, frame->GetRectRelativeToSelf(), relativeTo));
14960 if ((uint32_t)r.Area() <
14961 StaticPrefs::browser_history_wireframeAreaThreshold()) {
14962 continue;
14965 // Can't really fail because SetCapacity succeeded.
14966 auto& taggedRect = *rects.AppendElement(fallible);
14968 if (aIncludeNodes) {
14969 if (nsIContent* c = frame->GetContent()) {
14970 taggedRect.mNode.Construct(c);
14973 taggedRect.mX = r.x;
14974 taggedRect.mY = r.y;
14975 taggedRect.mWidth = r.width;
14976 taggedRect.mHeight = r.height;
14977 taggedRect.mColor = rectColor;
14978 taggedRect.mType.Construct(rectType);
14982 Element* Document::GetTopLayerTop() {
14983 if (mTopLayer.IsEmpty()) {
14984 return nullptr;
14986 uint32_t last = mTopLayer.Length() - 1;
14987 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
14988 NS_ASSERTION(element, "Should have a top layer element!");
14989 NS_ASSERTION(element->IsInComposedDoc(),
14990 "Top layer element should be in doc");
14991 NS_ASSERTION(element->OwnerDoc() == this,
14992 "Top layer element should be in this doc");
14993 return element;
14996 Element* Document::GetUnretargetedFullscreenElement() const {
14997 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14998 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14999 // Per spec, the fullscreen element is the topmost element in the document’s
15000 // top layer whose fullscreen flag is set, if any, and null otherwise.
15001 if (element && element->State().HasState(ElementState::FULLSCREEN)) {
15002 return element;
15005 return nullptr;
15008 nsTArray<Element*> Document::GetTopLayer() const {
15009 nsTArray<Element*> elements;
15010 for (const nsWeakPtr& ptr : mTopLayer) {
15011 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
15012 elements.AppendElement(elem);
15015 return elements;
15018 bool Document::TopLayerContains(Element& aElement) const {
15019 if (mTopLayer.IsEmpty()) {
15020 return false;
15022 nsWeakPtr weakElement = do_GetWeakReference(&aElement);
15023 return mTopLayer.Contains(weakElement);
15026 void Document::HideAllPopoversUntil(nsINode& aEndpoint,
15027 bool aFocusPreviousElement,
15028 bool aFireEvents) {
15029 auto closeAllOpenPopovers = [&aFocusPreviousElement, &aFireEvents,
15030 this]() MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
15031 while (RefPtr<Element> topmost = GetTopmostAutoPopover()) {
15032 HidePopover(*topmost, aFocusPreviousElement, aFireEvents, IgnoreErrors());
15036 if (aEndpoint.IsElement() && !aEndpoint.AsElement()->IsPopoverOpen()) {
15037 return;
15040 if (&aEndpoint == this) {
15041 closeAllOpenPopovers();
15042 return;
15045 // https://github.com/whatwg/html/pull/9198
15046 auto needRepeatingHide = [&]() {
15047 auto autoList = AutoPopoverList();
15048 return autoList.Contains(&aEndpoint) &&
15049 &aEndpoint != autoList.LastElement();
15052 MOZ_ASSERT((&aEndpoint)->IsElement() &&
15053 (&aEndpoint)->AsElement()->IsAutoPopover());
15054 bool repeatingHide = false;
15055 bool fireEvents = aFireEvents;
15056 do {
15057 RefPtr<const Element> lastToHide = nullptr;
15058 bool foundEndpoint = false;
15059 for (const Element* popover : AutoPopoverList()) {
15060 if (popover == &aEndpoint) {
15061 foundEndpoint = true;
15062 } else if (foundEndpoint) {
15063 lastToHide = popover;
15064 break;
15068 if (!foundEndpoint) {
15069 closeAllOpenPopovers();
15070 return;
15073 while (lastToHide && lastToHide->IsPopoverOpen()) {
15074 RefPtr<Element> topmost = GetTopmostAutoPopover();
15075 if (!topmost) {
15076 break;
15078 HidePopover(*topmost, aFocusPreviousElement, fireEvents, IgnoreErrors());
15081 repeatingHide = needRepeatingHide();
15082 if (repeatingHide) {
15083 fireEvents = false;
15085 } while (repeatingHide);
15088 MOZ_CAN_RUN_SCRIPT_BOUNDARY void
15089 Document::HideAllPopoversWithoutRunningScript() {
15090 return HideAllPopoversUntil(*this, false, false);
15093 void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement,
15094 bool aFireEvents, ErrorResult& aRv) {
15095 RefPtr<nsGenericHTMLElement> popoverHTMLEl =
15096 nsGenericHTMLElement::FromNode(aPopover);
15097 NS_ASSERTION(popoverHTMLEl, "Not a HTML element");
15099 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15100 nullptr, aRv)) {
15101 return;
15104 bool wasShowingOrHiding =
15105 popoverHTMLEl->GetPopoverData()->IsShowingOrHiding();
15106 popoverHTMLEl->GetPopoverData()->SetIsShowingOrHiding(true);
15107 const bool fireEvents = aFireEvents && !wasShowingOrHiding;
15108 auto cleanupHidingFlag = MakeScopeExit([&]() {
15109 if (auto* popoverData = popoverHTMLEl->GetPopoverData()) {
15110 popoverData->SetIsShowingOrHiding(wasShowingOrHiding);
15114 if (popoverHTMLEl->IsAutoPopover()) {
15115 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, fireEvents);
15116 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15117 nullptr, aRv)) {
15118 return;
15120 // TODO: we can't always guarantee:
15121 // The last item in document's auto popover list is popoverHTMLEl.
15122 // See, https://github.com/whatwg/html/issues/9197
15123 // If popoverHTMLEl is not on top, hide popovers again without firing
15124 // events.
15125 if (NS_WARN_IF(GetTopmostAutoPopover() != popoverHTMLEl)) {
15126 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
15127 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15128 nullptr, aRv)) {
15129 return;
15131 MOZ_ASSERT(GetTopmostAutoPopover() == popoverHTMLEl,
15132 "popoverHTMLEl should be on top of auto popover list");
15136 auto* data = popoverHTMLEl->GetPopoverData();
15137 MOZ_ASSERT(data, "Should have popover data");
15138 data->SetInvoker(nullptr);
15140 // Fire beforetoggle event and re-check popover validity.
15141 if (fireEvents) {
15142 // Intentionally ignore the return value here as only on open event for
15143 // beforetoggle the cancelable attribute is initialized to true.
15144 popoverHTMLEl->FireToggleEvent(PopoverVisibilityState::Showing,
15145 PopoverVisibilityState::Hidden,
15146 u"beforetoggle"_ns);
15148 // https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm
15149 // step 10.2.
15150 // Hide all popovers when beforetoggle shows a popover.
15151 if (popoverHTMLEl->IsAutoPopover() &&
15152 GetTopmostAutoPopover() != popoverHTMLEl &&
15153 popoverHTMLEl->PopoverOpen()) {
15154 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
15157 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15158 nullptr, aRv)) {
15159 return;
15163 RemovePopoverFromTopLayer(aPopover);
15165 popoverHTMLEl->PopoverPseudoStateUpdate(false, true);
15166 popoverHTMLEl->GetPopoverData()->SetPopoverVisibilityState(
15167 PopoverVisibilityState::Hidden);
15169 // Queue popover toggle event task.
15170 if (fireEvents) {
15171 popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing);
15174 if (aFocusPreviousElement) {
15175 popoverHTMLEl->FocusPreviousElementAfterHidingPopover();
15176 } else {
15177 popoverHTMLEl->ForgetPreviouslyFocusedElementAfterHidingPopover();
15181 nsTArray<Element*> Document::AutoPopoverList() const {
15182 nsTArray<Element*> elements;
15183 for (const nsWeakPtr& ptr : mTopLayer) {
15184 if (nsCOMPtr<Element> element = do_QueryReferent(ptr)) {
15185 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15186 elements.AppendElement(element);
15190 return elements;
15193 Element* Document::GetTopmostAutoPopover() const {
15194 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15195 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15196 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15197 return element;
15200 return nullptr;
15203 void Document::AddToAutoPopoverList(Element& aElement) {
15204 MOZ_ASSERT(aElement.IsAutoPopover());
15205 TopLayerPush(aElement);
15208 void Document::RemoveFromAutoPopoverList(Element& aElement) {
15209 MOZ_ASSERT(aElement.IsAutoPopover());
15210 TopLayerPop(aElement);
15213 void Document::AddPopoverToTopLayer(Element& aElement) {
15214 MOZ_ASSERT(aElement.GetPopoverData());
15215 TopLayerPush(aElement);
15218 void Document::RemovePopoverFromTopLayer(Element& aElement) {
15219 MOZ_ASSERT(aElement.GetPopoverData());
15220 TopLayerPop(aElement);
15223 // Returns true if aDoc browsing context is focused.
15224 bool IsInFocusedTab(Document* aDoc) {
15225 BrowsingContext* bc = aDoc->GetBrowsingContext();
15226 if (!bc) {
15227 return false;
15230 nsFocusManager* fm = nsFocusManager::GetFocusManager();
15231 if (!fm) {
15232 return false;
15235 if (XRE_IsParentProcess()) {
15236 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
15237 // by retaining the old code path for the parent process.
15238 nsIDocShell* docshell = aDoc->GetDocShell();
15239 if (!docshell) {
15240 return false;
15242 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15243 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15244 if (!rootItem) {
15245 return false;
15247 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
15248 if (!rootWin) {
15249 return false;
15252 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
15253 activeWindow = fm->GetActiveWindow();
15254 if (!activeWindow) {
15255 return false;
15258 return activeWindow == rootWin;
15261 return fm->GetActiveBrowsingContext() == bc->Top();
15264 // Returns true if aDoc browsing context is focused and is also active.
15265 bool IsInActiveTab(Document* aDoc) {
15266 if (!IsInFocusedTab(aDoc)) {
15267 return false;
15270 BrowsingContext* bc = aDoc->GetBrowsingContext();
15271 MOZ_ASSERT(bc, "With no BrowsingContext, we should have failed earlier.");
15272 return bc->IsActive();
15275 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
15276 // Ensure the frame element is the fullscreen element in this document.
15277 // If the frame element is already the fullscreen element in this document,
15278 // this has no effect.
15279 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
15280 RequestFullscreen(std::move(request), XRE_IsContentProcess());
15283 void Document::RemoteFrameFullscreenReverted() {
15284 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
15285 RestorePreviousFullscreenState(std::move(exit));
15288 static bool HasFullscreenSubDocument(Document& aDoc) {
15289 uint32_t count = CountFullscreenSubDocuments(aDoc);
15290 NS_ASSERTION(count <= 1,
15291 "Fullscreen docs should have at most 1 fullscreen child!");
15292 return count >= 1;
15295 // Returns nullptr if a request for Fullscreen API is currently enabled
15296 // in the given document. Returns a static string indicates the reason
15297 // why it is not enabled otherwise.
15298 const char* Document::GetFullscreenError(CallerType aCallerType) {
15299 if (!StaticPrefs::full_screen_api_enabled()) {
15300 return "FullscreenDeniedDisabled";
15303 if (aCallerType == CallerType::System) {
15304 // Chrome code can always use the fullscreen API, provided it's not
15305 // explicitly disabled.
15306 return nullptr;
15309 if (!IsVisible()) {
15310 return "FullscreenDeniedHidden";
15313 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
15314 return "FullscreenDeniedFeaturePolicy";
15317 // Ensure that all containing elements are <iframe> and have allowfullscreen
15318 // attribute set.
15319 BrowsingContext* bc = GetBrowsingContext();
15320 if (!bc || !bc->FullscreenAllowed()) {
15321 return "FullscreenDeniedContainerNotAllowed";
15324 return nullptr;
15327 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
15328 Element* elem = aRequest.Element();
15329 // Strictly speaking, this isn't part of the fullscreen element ready
15330 // check in the spec, but per steps in the spec, when an element which
15331 // is already the fullscreen element requests fullscreen, nothing
15332 // should change and no event should be dispatched, but we still need
15333 // to resolve the returned promise.
15334 Element* fullscreenElement = GetUnretargetedFullscreenElement();
15335 if (elem == fullscreenElement) {
15336 aRequest.MayResolvePromise();
15337 return false;
15339 if (!elem->IsInComposedDoc()) {
15340 aRequest.Reject("FullscreenDeniedNotInDocument");
15341 return false;
15343 if (elem->IsPopoverOpen()) {
15344 aRequest.Reject("FullscreenDeniedPopoverOpen");
15345 return false;
15347 if (elem->OwnerDoc() != this) {
15348 aRequest.Reject("FullscreenDeniedMovedDocument");
15349 return false;
15351 if (!GetWindow()) {
15352 aRequest.Reject("FullscreenDeniedLostWindow");
15353 return false;
15355 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
15356 aRequest.Reject(msg);
15357 return false;
15359 if (HasFullscreenSubDocument(*this)) {
15360 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
15361 return false;
15363 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
15364 aRequest.Reject("FullscreenDeniedHTMLDialog");
15365 return false;
15367 if (!nsContentUtils::IsChromeDoc(this) && !IsInFocusedTab(this)) {
15368 aRequest.Reject("FullscreenDeniedNotFocusedTab");
15369 return false;
15371 return true;
15374 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
15375 MOZ_ASSERT(XRE_IsParentProcess());
15376 nsIDocShell* docShell = aDoc->GetDocShell();
15377 if (!docShell) {
15378 return nullptr;
15380 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15381 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15382 return rootItem ? rootItem->GetWindow() : nullptr;
15385 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
15386 nsPIDOMWindowOuter* aRootWin) {
15387 MOZ_ASSERT(XRE_IsParentProcess());
15388 // If we are in the chrome process, and the window has not been in
15389 // fullscreen, we certainly need to make that fullscreen first.
15390 if (!aRootWin->GetFullScreen()) {
15391 return false;
15393 // The iterator not being at end indicates there is still some
15394 // pending fullscreen request relates to this document. We have to
15395 // push the request to the pending queue so requests are handled
15396 // in the correct order.
15397 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15398 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15399 if (!iter.AtEnd()) {
15400 return false;
15403 // Same thing for exits. If we have any pending, we have to push
15404 // to the pending queue.
15405 PendingFullscreenChangeList::Iterator<FullscreenExit> iterExit(
15406 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15407 if (!iterExit.AtEnd()) {
15408 return false;
15411 // We have to apply the fullscreen state directly in this case,
15412 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
15413 // if it is already in fullscreen. If we do not apply the state but
15414 // instead add it to the queue and wait for the window as normal,
15415 // we would get stuck.
15416 return true;
15419 static bool CheckFullscreenAllowedElementType(const Element* elem) {
15420 // Per spec only HTML, <svg>, and <math> should be allowed, but
15421 // we also need to allow XUL elements right now.
15422 return elem->IsHTMLElement() || elem->IsXULElement() ||
15423 elem->IsSVGElement(nsGkAtoms::svg) ||
15424 elem->IsMathMLElement(nsGkAtoms::math);
15427 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
15428 bool aApplyFullscreenDirectly) {
15429 if (XRE_IsContentProcess()) {
15430 RequestFullscreenInContentProcess(std::move(aRequest),
15431 aApplyFullscreenDirectly);
15432 } else {
15433 RequestFullscreenInParentProcess(std::move(aRequest),
15434 aApplyFullscreenDirectly);
15438 void Document::RequestFullscreenInContentProcess(
15439 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15440 MOZ_ASSERT(XRE_IsContentProcess());
15442 // If we are in the content process, we can apply the fullscreen
15443 // state directly only if we have been in DOM fullscreen, because
15444 // otherwise we always need to notify the chrome.
15445 if (aApplyFullscreenDirectly ||
15446 nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) {
15447 ApplyFullscreen(std::move(aRequest));
15448 return;
15451 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15452 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15453 return;
15456 // We don't need to check element ready before this point, because
15457 // if we called ApplyFullscreen, it would check that for us.
15458 if (!FullscreenElementReadyCheck(*aRequest)) {
15459 return;
15462 PendingFullscreenChangeList::Add(std::move(aRequest));
15463 // If we are not the top level process, dispatch an event to make
15464 // our parent process go fullscreen first.
15465 Dispatch(NS_NewRunnableFunction(
15466 "Document::RequestFullscreenInContentProcess", [self = RefPtr{this}] {
15467 if (!self->HasPendingFullscreenRequests()) {
15468 return;
15470 nsContentUtils::DispatchEventOnlyToChrome(
15471 self, self, u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
15472 Cancelable::eNo, /* DefaultAction */ nullptr);
15473 }));
15476 void Document::RequestFullscreenInParentProcess(
15477 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15478 MOZ_ASSERT(XRE_IsParentProcess());
15479 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
15480 if (!rootWin) {
15481 aRequest->MayRejectPromise("No active window");
15482 return;
15485 if (aApplyFullscreenDirectly ||
15486 ShouldApplyFullscreenDirectly(this, rootWin)) {
15487 ApplyFullscreen(std::move(aRequest));
15488 return;
15491 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15492 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15493 return;
15496 // See if we're waiting on an exit. If so, just make this one pending.
15497 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
15498 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15499 if (!iter.AtEnd()) {
15500 PendingFullscreenChangeList::Add(std::move(aRequest));
15501 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15502 return;
15505 // We don't need to check element ready before this point, because
15506 // if we called ApplyFullscreen, it would check that for us.
15507 if (!FullscreenElementReadyCheck(*aRequest)) {
15508 return;
15511 PendingFullscreenChangeList::Add(std::move(aRequest));
15512 // Make the window fullscreen.
15513 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15516 /* static */
15517 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
15518 bool handled = false;
15519 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15520 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15521 while (!iter.AtEnd()) {
15522 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15523 Document* doc = request->Document();
15524 if (doc->ApplyFullscreen(std::move(request))) {
15525 handled = true;
15528 return handled;
15531 /* static */
15532 void Document::ClearPendingFullscreenRequests(Document* aDoc) {
15533 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15534 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
15535 while (!iter.AtEnd()) {
15536 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15537 request->MayRejectPromise("Fullscreen request aborted");
15541 bool Document::HasPendingFullscreenRequests() {
15542 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15543 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15544 return !iter.AtEnd();
15547 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
15548 if (!FullscreenElementReadyCheck(*aRequest)) {
15549 return false;
15552 RefPtr<Document> doc = aRequest->Document();
15553 doc->HideAllPopoversWithoutRunningScript();
15555 // Stash a reference to any existing fullscreen doc, we'll use this later
15556 // to detect if the origin which is fullscreen has changed.
15557 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
15559 // Stores a list of documents which we must dispatch "fullscreenchange"
15560 // too. We're required by the spec to dispatch the events in root-to-leaf
15561 // order, but we traverse the doctree in a leaf-to-root order, so we save
15562 // references to the documents we must dispatch to so that we get the order
15563 // as specified.
15564 AutoTArray<Document*, 8> changed;
15566 // Remember the root document, so that if a fullscreen document is hidden
15567 // we can reset fullscreen state in the remaining visible fullscreen
15568 // documents.
15569 Document* fullScreenRootDoc =
15570 nsContentUtils::GetInProcessSubtreeRootDocument(this);
15572 // If a document is already in fullscreen, then unlock the mouse pointer
15573 // before setting a new document to fullscreen
15574 PointerLockManager::Unlock();
15576 // Set the fullscreen element. This sets the fullscreen style on the
15577 // element, and the fullscreen-ancestor styles on ancestors of the element
15578 // in this document.
15579 Element* elem = aRequest->Element();
15580 SetFullscreenElement(*elem);
15581 // Set the iframe fullscreen flag.
15582 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
15583 iframe->SetFullscreenFlag(true);
15585 changed.AppendElement(this);
15587 // Propagate up the document hierarchy, setting the fullscreen element as
15588 // the element's container in ancestor documents. This also sets the
15589 // appropriate css styles as well. Note we don't propagate down the
15590 // document hierarchy, the fullscreen element (or its container) is not
15591 // visible there. Stop when we reach the root document.
15592 Document* child = this;
15593 while (true) {
15594 child->SetFullscreenRoot(fullScreenRootDoc);
15596 // When entering fullscreen, reset the RCD's resolution to the intrinsic
15597 // resolution, otherwise the fullscreen content could be sized larger than
15598 // the screen (since fullscreen is implemented using position:fixed and
15599 // fixed elements are sized to the layout viewport).
15600 // This also ensures that things like video controls aren't zoomed in
15601 // when in fullscreen mode.
15602 if (PresShell* presShell = child->GetPresShell()) {
15603 if (RefPtr<MobileViewportManager> manager =
15604 presShell->GetMobileViewportManager()) {
15605 // Save the previous resolution so it can be restored.
15606 child->mSavedResolution = presShell->GetResolution();
15607 presShell->SetResolutionAndScaleTo(
15608 manager->ComputeIntrinsicResolution(),
15609 ResolutionChangeOrigin::MainThreadRestore);
15613 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
15614 "Fullscreen root should be set!");
15615 if (child == fullScreenRootDoc) {
15616 break;
15619 Element* element = child->GetEmbedderElement();
15620 if (!element) {
15621 // We've reached the root.No more changes need to be made
15622 // to the top layer stacks of documents further up the tree.
15623 break;
15626 Document* parent = child->GetInProcessParentDocument();
15627 parent->SetFullscreenElement(*element);
15628 changed.AppendElement(parent);
15629 child = parent;
15632 FullscreenRoots::Add(this);
15634 // If it is the first entry of the fullscreen, trigger an event so
15635 // that the UI can response to this change, e.g. hide chrome, or
15636 // notifying parent process to enter fullscreen. Note that chrome
15637 // code may also want to listen to MozDOMFullscreen:NewOrigin event
15638 // to pop up warning UI.
15639 if (!previousFullscreenDoc) {
15640 nsContentUtils::DispatchEventOnlyToChrome(
15641 this, elem, u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
15642 Cancelable::eNo, /* DefaultAction */ nullptr);
15645 // The origin which is fullscreen gets changed. Trigger an event so
15646 // that the chrome knows to pop up a warning UI. Note that
15647 // previousFullscreenDoc == nullptr upon first entry, we show the warning UI
15648 // directly as soon as chrome document goes into fullscreen state. Also note
15649 // that, in a multi-process browser, the code in content process is
15650 // responsible for sending message with the origin to its parent, and the
15651 // parent shouldn't rely on this event itself.
15652 if (aRequest->mShouldNotifyNewOrigin && previousFullscreenDoc &&
15653 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
15654 DispatchFullscreenNewOriginEvent(this);
15657 // Dispatch "fullscreenchange" events. Note that the loop order is
15658 // reversed so that events are dispatched in the tree order as
15659 // indicated in the spec.
15660 for (Document* d : Reversed(changed)) {
15661 DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement());
15663 aRequest->MayResolvePromise();
15664 return true;
15667 void Document::ClearOrientationPendingPromise() {
15668 mOrientationPendingPromise = nullptr;
15671 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
15672 if (mIsGoingAway) {
15673 return false;
15676 mOrientationPendingPromise = aPromise;
15677 return true;
15680 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
15681 dom::VisibilityState oldState = mVisibilityState;
15682 mVisibilityState = ComputeVisibilityState();
15683 if (oldState != mVisibilityState) {
15684 if (aDispatchEvent == DispatchVisibilityChange::Yes) {
15685 nsContentUtils::DispatchTrustedEvent(this, this, u"visibilitychange"_ns,
15686 CanBubble::eYes, Cancelable::eNo);
15688 NotifyActivityChanged();
15689 if (mVisibilityState == dom::VisibilityState::Visible) {
15690 MaybeActiveMediaComponents();
15693 bool visible = !Hidden();
15694 for (auto* listener : mWorkerListeners) {
15695 listener->OnVisible(visible);
15700 void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
15701 mWorkerListeners.Insert(aListener);
15702 aListener->OnVisible(!Hidden());
15705 void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
15706 mWorkerListeners.Remove(aListener);
15709 VisibilityState Document::ComputeVisibilityState() const {
15710 // We have to check a few pieces of information here:
15711 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
15712 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
15713 // want to use GetWindow here because it does weird groveling for windows
15714 // in some cases.
15715 // 3) Is our outer window background? If so, we're hidden.
15716 // Otherwise, we're visible.
15717 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
15718 mWindow->GetOuterWindow()->IsBackground()) {
15719 return dom::VisibilityState::Hidden;
15722 return dom::VisibilityState::Visible;
15725 void Document::PostVisibilityUpdateEvent() {
15726 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
15727 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
15728 DispatchVisibilityChange::Yes);
15729 Dispatch(event.forget());
15732 void Document::MaybeActiveMediaComponents() {
15733 auto* window = GetWindow();
15734 if (!window || !window->ShouldDelayMediaFromStart()) {
15735 return;
15737 window->ActivateMediaComponents();
15740 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
15741 nsINode::AddSizeOfExcludingThis(aWindowSizes,
15742 &aWindowSizes.mDOMSizes.mDOMOtherSize);
15744 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
15745 AddSizeOfNodeTree(*kid, aWindowSizes);
15748 // IMPORTANT: for our ComputedValues measurements, we want to measure
15749 // ComputedValues accessible from DOM elements before ComputedValues not
15750 // accessible from DOM elements (i.e. accessible only from the frame tree).
15752 // Therefore, the measurement of the Document superclass must happen after
15753 // the measurement of DOM nodes (above), because Document contains the
15754 // PresShell, which contains the frame tree.
15755 if (mPresShell) {
15756 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
15759 if (mStyleSet) {
15760 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
15763 aWindowSizes.mPropertyTablesSize +=
15764 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15766 if (EventListenerManager* elm = GetExistingListenerManager()) {
15767 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15770 if (mNodeInfoManager) {
15771 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
15774 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15775 mDOMMediaQueryLists.sizeOfExcludingThis(
15776 aWindowSizes.mState.mMallocSizeOf);
15778 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
15779 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15780 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15783 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
15785 for (auto& sheetArray : mAdditionalSheets) {
15786 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
15788 // Lumping in the loader with the style-sheets size is not ideal,
15789 // but most of the things in there are in fact stylesheets, so it
15790 // doesn't seem worthwhile to separate it out.
15791 // This can be null if we've already been unlinked.
15792 if (mCSSLoader) {
15793 aWindowSizes.mLayoutStyleSheetsSize +=
15794 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
15797 aWindowSizes.mDOMSizes.mDOMResizeObserverControllerSize +=
15798 mResizeObservers.ShallowSizeOfExcludingThis(
15799 aWindowSizes.mState.mMallocSizeOf);
15801 if (mAttributeStyles) {
15802 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15803 mAttributeStyles->DOMSizeOfIncludingThis(
15804 aWindowSizes.mState.mMallocSizeOf);
15807 if (mRadioGroupContainer) {
15808 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15809 mRadioGroupContainer->SizeOfIncludingThis(
15810 aWindowSizes.mState.mMallocSizeOf);
15813 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15814 mStyledLinks.ShallowSizeOfExcludingThis(
15815 aWindowSizes.mState.mMallocSizeOf);
15817 // Measurement of the following members may be added later if DMD finds it
15818 // is worthwhile:
15819 // - mMidasCommandManager
15820 // - many!
15823 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
15824 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15825 aWindowSizes.mState.mMallocSizeOf(this);
15826 DocAddSizeOfExcludingThis(aWindowSizes);
15829 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
15830 size_t* aNodeSize) const {
15831 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
15832 // nsDocuments can only appear at the top of the DOM tree, and we use the
15833 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
15834 // be called.
15835 MOZ_CRASH();
15838 /* static */
15839 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
15840 size_t nodeSize = 0;
15841 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
15843 // This is where we transfer the nodeSize obtained from
15844 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
15845 switch (aNode.NodeType()) {
15846 case nsINode::ELEMENT_NODE:
15847 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
15848 break;
15849 case nsINode::TEXT_NODE:
15850 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
15851 break;
15852 case nsINode::CDATA_SECTION_NODE:
15853 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
15854 break;
15855 case nsINode::COMMENT_NODE:
15856 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
15857 break;
15858 default:
15859 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
15860 break;
15863 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
15864 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15867 if (aNode.IsContent()) {
15868 nsTArray<nsIContent*> anonKids;
15869 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
15870 nsIContent::eAllChildren);
15871 for (nsIContent* anonKid : anonKids) {
15872 AddSizeOfNodeTree(*anonKid, aWindowSizes);
15875 if (auto* element = Element::FromNode(aNode)) {
15876 if (ShadowRoot* shadow = element->GetShadowRoot()) {
15877 AddSizeOfNodeTree(*shadow, aWindowSizes);
15882 // NOTE(emilio): If you feel smart and want to change this function to use
15883 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
15884 // sane way, and kids of <content> won't point to the parent, so we'd never
15885 // find the root node where we should stop at.
15886 for (nsIContent* kid = aNode.GetFirstChild(); kid;
15887 kid = kid->GetNextSibling()) {
15888 AddSizeOfNodeTree(*kid, aWindowSizes);
15892 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
15893 ErrorResult& rv) {
15894 nsCOMPtr<nsIScriptGlobalObject> global =
15895 do_QueryInterface(aGlobal.GetAsSupports());
15896 if (!global) {
15897 rv.Throw(NS_ERROR_UNEXPECTED);
15898 return nullptr;
15901 nsCOMPtr<nsIScriptObjectPrincipal> prin =
15902 do_QueryInterface(aGlobal.GetAsSupports());
15903 if (!prin) {
15904 rv.Throw(NS_ERROR_UNEXPECTED);
15905 return nullptr;
15908 nsCOMPtr<nsIURI> uri;
15909 NS_NewURI(getter_AddRefs(uri), "about:blank");
15910 if (!uri) {
15911 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
15912 return nullptr;
15915 nsCOMPtr<Document> doc;
15916 nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
15917 nullptr, uri, uri, prin->GetPrincipal(),
15918 true, global, DocumentFlavorPlain);
15919 if (NS_FAILED(res)) {
15920 rv.Throw(res);
15921 return nullptr;
15924 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
15926 return doc.forget();
15929 UniquePtr<XPathExpression> Document::CreateExpression(
15930 const nsAString& aExpression, XPathNSResolver* aResolver, ErrorResult& rv) {
15931 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
15934 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
15935 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
15938 already_AddRefed<XPathResult> Document::Evaluate(
15939 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
15940 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
15941 ErrorResult& rv) {
15942 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
15943 aType, aResult, rv);
15946 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
15947 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
15948 if (!item) {
15949 return nullptr;
15951 nsCOMPtr<nsIDocShellTreeOwner> owner;
15952 item->GetTreeOwner(getter_AddRefs(owner));
15953 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
15954 if (!appWin) {
15955 return nullptr;
15957 nsCOMPtr<nsIDocShell> appWinShell;
15958 appWin->GetDocShell(getter_AddRefs(appWinShell));
15959 if (!SameCOMIdentity(appWinShell, item)) {
15960 return nullptr;
15962 return appWin.forget();
15965 WindowContext* Document::GetTopLevelWindowContext() const {
15966 WindowContext* windowContext = GetWindowContext();
15967 return windowContext ? windowContext->TopWindowContext() : nullptr;
15970 Document* Document::GetTopLevelContentDocumentIfSameProcess() {
15971 Document* parent;
15973 if (!mLoadedAsData) {
15974 parent = this;
15975 } else {
15976 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15977 if (!window) {
15978 return nullptr;
15981 parent = window->GetExtantDoc();
15982 if (!parent) {
15983 return nullptr;
15987 do {
15988 if (parent->IsTopLevelContentDocument()) {
15989 break;
15992 // If we ever have a non-content parent before we hit a toplevel content
15993 // parent, then we're never going to find one. Just bail.
15994 if (!parent->IsContentDocument()) {
15995 return nullptr;
15998 parent = parent->GetInProcessParentDocument();
15999 } while (parent);
16001 return parent;
16004 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
16005 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
16008 void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
16009 MOZ_ASSERT(IsBeingUsedAsImage());
16010 MOZ_ASSERT(aReferencingDocument);
16012 if (!aReferencingDocument->mShouldReportUseCounters) {
16013 // No need to propagate use counters to a document that itself won't report
16014 // use counters.
16015 return;
16018 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16019 ("PropagateImageUseCounters from %s to %s",
16020 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
16021 nsContentUtils::TruncatedURLForDisplay(
16022 aReferencingDocument->mDocumentURI)
16023 .get()));
16025 if (aReferencingDocument->IsBeingUsedAsImage()) {
16026 NS_WARNING(
16027 "Page use counters from nested image documents may not "
16028 "propagate to the top-level document (bug 1657805)");
16031 SetCssUseCounterBits();
16032 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
16033 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
16036 bool Document::HasScriptsBlockedBySandbox() const {
16037 return mSandboxFlags & SANDBOXED_SCRIPTS;
16040 void Document::SetCssUseCounterBits() {
16041 if (StaticPrefs::layout_css_use_counters_enabled()) {
16042 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
16043 auto id = nsCSSPropertyID(i);
16044 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
16045 SetUseCounter(nsCSSProps::UseCounterFor(id));
16050 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
16051 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
16052 auto id = CountedUnknownProperty(i);
16053 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
16054 id)) {
16055 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
16061 void Document::InitUseCounters() {
16062 // We can be called more than once, e.g. when session history navigation shows
16063 // us a second time.
16064 if (mUseCountersInitialized) {
16065 return;
16067 mUseCountersInitialized = true;
16069 if (!ShouldIncludeInTelemetry()) {
16070 return;
16073 // Now we know for sure that we should report use counters from this document.
16074 mShouldReportUseCounters = true;
16076 WindowContext* top = GetWindowContextForPageUseCounters();
16077 if (!top) {
16078 // This is the case for SVG image documents. They are not displayed in a
16079 // window, but we still do want to record document use counters for them.
16081 // Page use counter propagation is handled in PropagateImageUseCounters,
16082 // so there is no need to use the cross-process machinery to send them.
16083 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16084 ("InitUseCounters for a non-displayed document [%s]",
16085 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16086 return;
16089 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16090 if (!wgc) {
16091 return;
16094 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16095 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
16096 " [from %s]",
16097 wgc->InnerWindowId(), top->Id(),
16098 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16100 // Inform the parent process that we will send it page use counters later on.
16101 wgc->SendExpectPageUseCounters(top);
16102 mShouldSendPageUseCounters = true;
16105 // We keep separate counts for individual documents and top-level
16106 // pages to more accurately track how many web pages might break if
16107 // certain features were removed. Consider the case of a single
16108 // HTML document with several SVG images and/or iframes with
16109 // sub-documents of their own. If we maintained a single set of use
16110 // counters and all the sub-documents use a particular feature, then
16111 // telemetry would indicate that we would be breaking N documents if
16112 // that feature were removed. Whereas with a document/top-level
16113 // page split, we can see that N documents would be affected, but
16114 // only a single web page would be affected.
16116 // The difference between the values of these two counts and the
16117 // related use counters below tell us how many pages did *not* use
16118 // the feature in question. For instance, if we see that a given
16119 // session has destroyed 30 content documents, but a particular use
16120 // counter shows only a count of 5, we can infer that the use
16121 // counter was *not* used in 25 of those 30 documents.
16123 // We do things this way, rather than accumulating a boolean flag
16124 // for each use counter, to avoid sending data for features
16125 // that don't get widely used. Doing things in this fashion means
16126 // smaller telemetry payloads and faster processing on the server
16127 // side.
16128 void Document::ReportDocumentUseCounters() {
16129 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
16130 return;
16133 mReportedDocumentUseCounters = true;
16135 // Note that a document is being destroyed. See the comment above for how
16136 // use counter data are interpreted relative to this measurement.
16137 glean::use_counter::content_documents_destroyed.Add();
16139 // Ask all of our resource documents to report their own document use
16140 // counters.
16141 EnumerateExternalResources([](Document& aDoc) {
16142 aDoc.ReportDocumentUseCounters();
16143 return CallState::Continue;
16146 // Copy StyleUseCounters into our document use counters.
16147 SetCssUseCounterBits();
16149 Maybe<nsCString> urlForLogging;
16150 const bool dumpCounters = StaticPrefs::dom_use_counters_dump_document();
16151 if (dumpCounters) {
16152 urlForLogging.emplace(
16153 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()));
16156 // Report our per-document use counters.
16157 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
16158 auto uc = static_cast<UseCounter>(c);
16159 if (!mUseCounters[uc]) {
16160 continue;
16163 const char* metricName = IncrementUseCounter(uc, /* aIsPage = */ false);
16164 if (dumpCounters) {
16165 printf_stderr("USE_COUNTER_DOCUMENT: %s - %s\n", metricName,
16166 urlForLogging->get());
16171 void Document::ReportLCP() {
16172 const nsDOMNavigationTiming* timing = GetNavigationTiming();
16174 if (!timing) {
16175 return;
16178 TimeStamp lcpTime = timing->GetLargestContentfulRenderTimeStamp();
16180 if (!lcpTime) {
16181 return;
16184 mozilla::glean::perf::largest_contentful_paint.AccumulateRawDuration(
16185 lcpTime - timing->GetNavigationStartTimeStamp());
16187 if (!GetChannel()) {
16188 return;
16191 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
16192 if (!timedChannel) {
16193 return;
16196 TimeStamp responseStart;
16197 timedChannel->GetResponseStart(&responseStart);
16199 if (!responseStart) {
16200 return;
16203 mozilla::glean::perf::largest_contentful_paint_from_response_start
16204 .AccumulateRawDuration(lcpTime - responseStart);
16206 if (profiler_thread_is_being_profiled_for_markers()) {
16207 MarkerInnerWindowId innerWindowID =
16208 MarkerInnerWindowIdFromDocShell(GetDocShell());
16209 GetNavigationTiming()->MaybeAddLCPProfilerMarker(innerWindowID);
16213 void Document::SendPageUseCounters() {
16214 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
16215 return;
16218 // Ask all of our resource documents to send their own document use
16219 // counters to the parent process to be counted as page use counters.
16220 EnumerateExternalResources([](Document& aDoc) {
16221 aDoc.SendPageUseCounters();
16222 return CallState::Continue;
16225 // Send our use counters to the parent process to accumulate them towards the
16226 // page use counters for the top-level document.
16228 // We take our own document use counters (those in mUseCounters) and any child
16229 // document use counters (those in mChildDocumentUseCounters) that have been
16230 // explicitly propagated up to us, which includes resource documents, static
16231 // clones, and SVG images.
16232 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16233 if (!wgc) {
16234 MOZ_ASSERT_UNREACHABLE(
16235 "SendPageUseCounters should be called while we still have access "
16236 "to our WindowContext");
16237 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16238 (" > too late to send page use counters"));
16239 return;
16242 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16243 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
16244 wgc->WindowContext()->Id(),
16245 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
16247 // Copy StyleUseCounters into our document use counters.
16248 SetCssUseCounterBits();
16250 UseCounters counters = mUseCounters | mChildDocumentUseCounters;
16251 wgc->SendAccumulatePageUseCounters(counters);
16254 bool Document::RecomputeResistFingerprinting() {
16255 mOverriddenFingerprintingSettings.reset();
16256 const bool previous = mShouldResistFingerprinting;
16258 RefPtr<BrowsingContext> opener =
16259 GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
16260 // If we have a parent or opener document, defer to it only when we have a
16261 // null principal (e.g. a sandboxed iframe or a data: uri) or when the
16262 // document's principal matches. This means we will defer about:blank,
16263 // about:srcdoc, blob and same-origin iframes/popups to the parent/opener,
16264 // but not cross-origin ones. Cross-origin iframes/popups may inherit a
16265 // CookieJarSettings.mShouldRFP = false bit however, which will be respected.
16266 auto shouldInheritFrom = [this](Document* aDoc) {
16267 return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
16268 this->NodePrincipal()->GetIsNullPrincipal());
16271 if (shouldInheritFrom(mParentDocument)) {
16272 MOZ_LOG(
16273 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16274 ("Inside RecomputeResistFingerprinting with URI %s and deferring "
16275 "to parent document %s",
16276 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16277 mParentDocument->GetDocumentURI()->GetSpecOrDefault().get()));
16278 mShouldResistFingerprinting = mParentDocument->ShouldResistFingerprinting(
16279 RFPTarget::IsAlwaysEnabledForPrecompute);
16280 mOverriddenFingerprintingSettings =
16281 mParentDocument->mOverriddenFingerprintingSettings;
16282 } else if (opener && shouldInheritFrom(opener->GetDocument())) {
16283 MOZ_LOG(
16284 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16285 ("Inside RecomputeResistFingerprinting with URI %s and deferring to "
16286 "opener document %s",
16287 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16288 opener->GetDocument()->GetDocumentURI()->GetSpecOrDefault().get()));
16289 mShouldResistFingerprinting =
16290 opener->GetDocument()->ShouldResistFingerprinting(
16291 RFPTarget::IsAlwaysEnabledForPrecompute);
16292 mOverriddenFingerprintingSettings =
16293 opener->GetDocument()->mOverriddenFingerprintingSettings;
16294 } else if (nsContentUtils::IsChromeDoc(this)) {
16295 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16296 ("Inside RecomputeResistFingerprinting with a ChromeDoc"));
16298 mShouldResistFingerprinting = false;
16299 mOverriddenFingerprintingSettings.reset();
16300 } else if (mChannel) {
16301 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16302 ("Inside RecomputeResistFingerprinting with URI %s",
16303 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get()
16304 : "null"));
16305 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
16306 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16308 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16309 mOverriddenFingerprintingSettings =
16310 loadInfo->GetOverriddenFingerprintingSettings();
16311 } else {
16312 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16313 ("Inside RecomputeResistFingerprinting fallback case."));
16314 // We still preserve the behavior in the fallback case. But, it means there
16315 // might be some cases we haven't considered yet and we need to investigate
16316 // them.
16317 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
16318 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16319 mOverriddenFingerprintingSettings.reset();
16322 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16323 ("Finished RecomputeResistFingerprinting with result %x",
16324 mShouldResistFingerprinting));
16326 bool changed = previous != mShouldResistFingerprinting;
16327 if (changed) {
16328 if (auto win = nsGlobalWindowInner::Cast(GetInnerWindow())) {
16329 win->RefreshReduceTimerPrecisionCallerType();
16332 return changed;
16335 bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const {
16336 return mShouldResistFingerprinting &&
16337 nsRFPService::IsRFPEnabledFor(this->IsInPrivateBrowsing(), aTarget,
16338 mOverriddenFingerprintingSettings);
16341 void Document::RecordCanvasUsage(CanvasUsage& aUsage) {
16342 // Limit the number of recent canvas extraction uses that are tracked.
16343 const size_t kTrackedCanvasLimit = 8;
16344 // Timeout between different canvas extractions.
16345 const uint64_t kTimeoutUsec = 3000 * 1000;
16347 uint64_t now = PR_Now();
16348 if ((mCanvasUsage.Length() > kTrackedCanvasLimit) ||
16349 ((now - mLastCanvasUsage) > kTimeoutUsec)) {
16350 mCanvasUsage.ClearAndRetainStorage();
16353 mCanvasUsage.AppendElement(aUsage);
16354 mLastCanvasUsage = now;
16356 nsCString originNoSuffix;
16357 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
16358 return;
16361 nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsage, GetChannel(),
16362 originNoSuffix);
16365 void Document::RecordFontFingerprinting() {
16366 nsCString originNoSuffix;
16367 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
16368 return;
16371 nsRFPService::MaybeReportFontFingerprinter(GetChannel(), originNoSuffix);
16374 bool Document::IsInPrivateBrowsing() const { return mIsInPrivateBrowsing; }
16376 WindowContext* Document::GetWindowContextForPageUseCounters() const {
16377 if (mDisplayDocument) {
16378 // If we are a resource document, then go through it to find the
16379 // top-level document.
16380 return mDisplayDocument->GetWindowContextForPageUseCounters();
16383 if (mOriginalDocument) {
16384 // For static clones (print preview documents), contribute page use counters
16385 // towards the original document.
16386 return mOriginalDocument->GetWindowContextForPageUseCounters();
16389 WindowContext* wc = GetTopLevelWindowContext();
16390 if (!wc || !wc->GetBrowsingContext()->IsContent()) {
16391 return nullptr;
16394 return wc;
16397 void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
16398 if (mIntersectionObservers.IsEmpty()) {
16399 return;
16402 DOMHighResTimeStamp time = 0;
16403 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
16404 if (Performance* perf = win->GetPerformance()) {
16405 time = perf->TimeStampToDOMHighResForRendering(aNowTime);
16409 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16410 mIntersectionObservers);
16411 for (const auto& observer : observers) {
16412 if (observer) {
16413 observer->Update(*this, time);
16418 void Document::ScheduleIntersectionObserverNotification() {
16419 if (mIntersectionObservers.IsEmpty()) {
16420 return;
16422 MOZ_RELEASE_ASSERT(NS_IsMainThread());
16423 nsCOMPtr<nsIRunnable> notification =
16424 NewRunnableMethod("Document::NotifyIntersectionObservers", this,
16425 &Document::NotifyIntersectionObservers);
16426 Dispatch(notification.forget());
16429 void Document::NotifyIntersectionObservers() {
16430 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16431 mIntersectionObservers);
16432 for (const auto& observer : observers) {
16433 if (observer) {
16434 // MOZ_KnownLive because the 'observers' array guarantees to keep it
16435 // alive.
16436 MOZ_KnownLive(observer)->Notify();
16441 DOMIntersectionObserver& Document::EnsureLazyLoadObserver() {
16442 if (!mLazyLoadObserver) {
16443 mLazyLoadObserver = DOMIntersectionObserver::CreateLazyLoadObserver(*this);
16445 return *mLazyLoadObserver;
16448 ResizeObserver& Document::EnsureLastRememberedSizeObserver() {
16449 if (!mLastRememberedSizeObserver) {
16450 mLastRememberedSizeObserver =
16451 ResizeObserver::CreateLastRememberedSizeObserver(*this);
16453 return *mLastRememberedSizeObserver;
16456 void Document::ObserveForLastRememberedSize(Element& aElement) {
16457 if (NS_WARN_IF(!IsActive())) {
16458 return;
16460 // Options are initialized with ResizeObserverBoxOptions::Content_box by
16461 // default, which is what we want.
16462 static ResizeObserverOptions options;
16463 EnsureLastRememberedSizeObserver().Observe(aElement, options);
16466 void Document::UnobserveForLastRememberedSize(Element& aElement) {
16467 if (mLastRememberedSizeObserver) {
16468 mLastRememberedSizeObserver->Unobserve(aElement);
16472 void Document::NotifyLayerManagerRecreated() {
16473 NotifyActivityChanged();
16474 EnumerateSubDocuments([](Document& aSubDoc) {
16475 aSubDoc.NotifyLayerManagerRecreated();
16476 return CallState::Continue;
16480 XPathEvaluator* Document::XPathEvaluator() {
16481 if (!mXPathEvaluator) {
16482 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
16484 return mXPathEvaluator.get();
16487 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
16488 return mCachedEncoder.forget();
16491 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
16492 mCachedEncoder = aEncoder;
16495 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
16497 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
16499 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
16500 mStateObjectContainer = scContainer;
16501 mCachedStateObject = JS::UndefinedValue();
16502 mCachedStateObjectValid = false;
16505 bool Document::ComputeDocumentLWTheme() const {
16506 if (!NodePrincipal()->IsSystemPrincipal()) {
16507 return false;
16510 Element* element = GetRootElement();
16511 return element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
16512 nsGkAtoms::_true, eCaseMatters);
16515 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
16516 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
16517 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
16518 ELEMENT_NODE);
16519 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
16521 nsCOMPtr<Element> element;
16522 DebugOnly<nsresult> rv =
16523 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
16524 mozilla::dom::NOT_FROM_PARSER);
16526 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
16527 return element.forget();
16530 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
16531 BrowsingContext* aContext) {
16532 aContext->PreOrderWalk([&](BrowsingContext* aBC) {
16533 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
16534 if (RefPtr<Document> doc = win->GetExtantDoc()) {
16535 SuppressDocument(doc);
16536 mDocuments.AppendElement(doc);
16542 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
16543 BrowsingContextGroup* aGroup) {
16544 for (const auto& bc : aGroup->Toplevels()) {
16545 SuppressBrowsingContext(bc);
16549 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
16550 SyncOperationBehavior aSyncBehavior)
16551 : mSyncBehavior(aSyncBehavior) {
16552 mMicroTaskLevel = 0;
16553 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16554 mMicroTaskLevel = ccjs->MicroTaskLevel();
16555 ccjs->SetMicroTaskLevel(0);
16557 if (aDoc) {
16558 mBrowsingContext = aDoc->GetBrowsingContext();
16559 if (InputTaskManager::CanSuspendInputEvent()) {
16560 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
16561 SuppressBrowsingContextGroup(bcg);
16563 } else if (mBrowsingContext) {
16564 SuppressBrowsingContext(mBrowsingContext->Top());
16566 if (mBrowsingContext &&
16567 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16568 InputTaskManager::CanSuspendInputEvent()) {
16569 mBrowsingContext->Group()->IncInputEventSuspensionLevel();
16574 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
16575 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16576 win->TimeoutManager().BeginSyncOperation();
16578 aDoc->SetIsInSyncOperation(true);
16581 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
16582 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16583 win->TimeoutManager().EndSyncOperation();
16585 aDoc->SetIsInSyncOperation(false);
16588 nsAutoSyncOperation::~nsAutoSyncOperation() {
16589 UnsuppressDocuments();
16590 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
16591 if (ccjs) {
16592 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
16594 if (mBrowsingContext &&
16595 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16596 InputTaskManager::CanSuspendInputEvent()) {
16597 mBrowsingContext->Group()->DecInputEventSuspensionLevel();
16601 void Document::SetIsInSyncOperation(bool aSync) {
16602 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16603 ccjs->UpdateMicroTaskSuppressionGeneration();
16606 if (aSync) {
16607 ++mInSyncOperationCount;
16608 } else {
16609 --mInSyncOperationCount;
16613 gfxUserFontSet* Document::GetUserFontSet() {
16614 if (!mFontFaceSet) {
16615 return nullptr;
16618 return mFontFaceSet->GetImpl();
16621 void Document::FlushUserFontSet() {
16622 if (!mFontFaceSetDirty) {
16623 return;
16626 mFontFaceSetDirty = false;
16628 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
16629 nsTArray<nsFontFaceRuleContainer> rules;
16630 RefPtr<PresShell> presShell = GetPresShell();
16631 if (presShell) {
16632 MOZ_ASSERT(mStyleSetFilled);
16633 EnsureStyleSet().AppendFontFaceRules(rules);
16636 if (!mFontFaceSet && !rules.IsEmpty()) {
16637 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16640 bool changed = false;
16641 if (mFontFaceSet) {
16642 changed = mFontFaceSet->UpdateRules(rules);
16645 // We need to enqueue a style change reflow (for later) to
16646 // reflect that we're modifying @font-face rules. (However,
16647 // without a reflow, nothing will happen to start any downloads
16648 // that are needed.)
16649 if (changed && presShell) {
16650 if (nsPresContext* presContext = presShell->GetPresContext()) {
16651 presContext->UserFontSetUpdated();
16657 void Document::MarkUserFontSetDirty() {
16658 if (mFontFaceSetDirty) {
16659 return;
16661 mFontFaceSetDirty = true;
16662 if (PresShell* presShell = GetPresShell()) {
16663 presShell->EnsureStyleFlush();
16667 FontFaceSet* Document::Fonts() {
16668 if (!mFontFaceSet) {
16669 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16670 FlushUserFontSet();
16672 return mFontFaceSet;
16675 void Document::ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp) {
16676 MOZ_ASSERT(!aTimeStamp.IsNull());
16678 if (!mLastScrollLinkedEffectDetectionTime.IsNull() &&
16679 mLastScrollLinkedEffectDetectionTime >= aTimeStamp) {
16680 return;
16683 if (mLastScrollLinkedEffectDetectionTime.IsNull()) {
16684 // Report to console just once.
16685 nsContentUtils::ReportToConsole(
16686 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
16687 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
16690 mLastScrollLinkedEffectDetectionTime = aTimeStamp;
16693 bool Document::HasScrollLinkedEffect() const {
16694 if (nsPresContext* pc = GetPresContext()) {
16695 return mLastScrollLinkedEffectDetectionTime ==
16696 pc->RefreshDriver()->MostRecentRefresh();
16699 return false;
16702 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
16703 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16704 // Setting has user interction on a discarded browsing context has
16705 // no effect.
16706 Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
16710 bool Document::GetSHEntryHasUserInteraction() {
16711 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16712 return topWc->GetSHEntryHasUserInteraction();
16714 return false;
16717 void Document::SetUserHasInteracted() {
16718 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
16719 ("Document %p has been interacted by user.", this));
16721 // We maybe need to update the user-interaction permission.
16722 MaybeStoreUserInteractionAsPermission();
16724 // For purposes of reducing irrelevant session history entries on
16725 // the back button, we annotate entries with whether they had user
16726 // interaction. This is gated on its own flag on the WindowContext
16727 // (instead of mUserHasInteracted) to account for the fact that multiple
16728 // top-level SH entries can be associated with the same document.
16729 // Thus, whenever we create a new SH entry for this document,
16730 // this flag is reset.
16731 if (!GetSHEntryHasUserInteraction()) {
16732 nsIDocShell* docShell = GetDocShell();
16733 if (docShell) {
16734 nsCOMPtr<nsISHEntry> currentEntry;
16735 bool oshe;
16736 nsresult rv =
16737 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
16738 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
16739 currentEntry->SetHasUserInteraction(true);
16742 SetSHEntryHasUserInteraction(true);
16745 if (mUserHasInteracted) {
16746 return;
16749 mUserHasInteracted = true;
16751 if (mChannel) {
16752 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16753 loadInfo->SetDocumentHasUserInteracted(true);
16755 // Tell the parent process about user interaction
16756 if (auto* wgc = GetWindowGlobalChild()) {
16757 wgc->SendUpdateDocumentHasUserInteracted(true);
16760 MaybeAllowStorageForOpenerAfterUserInteraction();
16763 BrowsingContext* Document::GetBrowsingContext() const {
16764 return mDocumentContainer ? mDocumentContainer->GetBrowsingContext()
16765 : nullptr;
16768 void Document::NotifyUserGestureActivation(
16769 UserActivation::Modifiers
16770 aModifiers /* = UserActivation::Modifiers::None() */) {
16771 // https://html.spec.whatwg.org/multipage/interaction.html#activation-notification
16772 // 1. "Assert: document is fully active."
16773 RefPtr<BrowsingContext> currentBC = GetBrowsingContext();
16774 if (!currentBC) {
16775 return;
16778 RefPtr<WindowContext> currentWC = GetWindowContext();
16779 if (!currentWC) {
16780 return;
16783 // 2. "Let windows be « document's relevant global object"
16784 // Instead of assembling a list, we just call notify for wanted windows as we
16785 // find them
16786 currentWC->NotifyUserGestureActivation(aModifiers);
16788 // 3. "...windows with the active window of each of document's ancestor
16789 // navigables."
16790 for (WindowContext* wc = currentWC; wc; wc = wc->GetParentWindowContext()) {
16791 wc->NotifyUserGestureActivation(aModifiers);
16794 // 4. "windows with the active window of each of document's descendant
16795 // navigables, filtered to include only those navigables whose active
16796 // document's origin is same origin with document's origin"
16797 currentBC->PreOrderWalk([&](BrowsingContext* bc) {
16798 WindowContext* wc = bc->GetCurrentWindowContext();
16799 if (!wc) {
16800 return;
16803 // Check same-origin as current document
16804 WindowGlobalChild* wgc = wc->GetWindowGlobalChild();
16805 if (!wgc || !wgc->IsSameOriginWith(currentWC)) {
16806 return;
16809 wc->NotifyUserGestureActivation(aModifiers);
16813 bool Document::HasBeenUserGestureActivated() {
16814 RefPtr<WindowContext> wc = GetWindowContext();
16815 return wc && wc->HasBeenUserGestureActivated();
16818 DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
16819 if (RefPtr<WindowContext> wc = GetWindowContext()) {
16820 if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) {
16821 if (Performance* perf = innerWindow->GetPerformance()) {
16822 return perf->GetDOMTiming()->TimeStampToDOMHighRes(
16823 wc->GetUserGestureStart());
16828 NS_WARNING(
16829 "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
16830 return 0;
16833 void Document::ClearUserGestureActivation() {
16834 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16835 bc = bc->Top();
16836 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16837 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
16838 windowContext->NotifyResetUserGestureActivation();
16844 bool Document::HasValidTransientUserGestureActivation() const {
16845 RefPtr<WindowContext> wc = GetWindowContext();
16846 return wc && wc->HasValidTransientUserGestureActivation();
16849 bool Document::ConsumeTransientUserGestureActivation() {
16850 RefPtr<WindowContext> wc = GetWindowContext();
16851 return wc && wc->ConsumeTransientUserGestureActivation();
16854 bool Document::GetTransientUserGestureActivationModifiers(
16855 UserActivation::Modifiers* aModifiers) {
16856 RefPtr<WindowContext> wc = GetWindowContext();
16857 return wc && wc->GetTransientUserGestureActivationModifiers(aModifiers);
16860 void Document::SetDocTreeHadMedia() {
16861 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
16862 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
16863 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
16867 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
16868 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
16869 return;
16872 // This will probably change for project fission, but currently this document
16873 // and the opener are on the same process. In the future, we should make this
16874 // part async.
16875 nsPIDOMWindowInner* inner = GetInnerWindow();
16876 if (NS_WARN_IF(!inner)) {
16877 return;
16880 // We care about first-party tracking resources only.
16881 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
16882 return;
16885 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16886 if (NS_WARN_IF(!outer)) {
16887 return;
16890 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
16891 if (!openerBC) {
16892 // No opener.
16893 return;
16896 // We want to ensure the following check works for both fission mode and
16897 // non-fission mode:
16898 // "If the opener is not a 3rd party and if this window is not a 3rd party
16899 // with respect to the opener, we should not continue."
16901 // In non-fission mode, the opener and the opened window are in the same
16902 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
16903 // In fission mode, if this window is not a 3rd party with respect to the
16904 // opener, they must be in the same process, so we can still use
16905 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
16906 // party.
16907 if (openerBC->IsInProcess()) {
16908 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
16909 if (NS_WARN_IF(!outerOpener)) {
16910 return;
16913 nsCOMPtr<nsPIDOMWindowInner> openerInner =
16914 outerOpener->GetCurrentInnerWindow();
16915 if (NS_WARN_IF(!openerInner)) {
16916 return;
16919 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
16920 if (NS_WARN_IF(!openerDocument)) {
16921 return;
16924 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
16925 if (NS_WARN_IF(!openerURI)) {
16926 return;
16929 // If the opener is not a 3rd party and if this window is not
16930 // a 3rd party with respect to the opener, we should not continue.
16931 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
16932 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
16933 return;
16937 // We don't care when the asynchronous work finishes here.
16938 Unused << StorageAccessAPIHelper::AllowAccessForOnChildProcess(
16939 NodePrincipal(), openerBC,
16940 ContentBlockingNotifier::eOpenerAfterUserInteraction);
16943 namespace {
16945 // Documents can stay alive for days. We don't want to update the permission
16946 // value at any user-interaction, and, using a timer triggered any X seconds
16947 // should be good enough. 'X' is taken from
16948 // privacy.userInteraction.document.interval pref.
16949 // We also want to store the user-interaction before shutting down, and, for
16950 // this reason, this class implements nsIAsyncShutdownBlocker interface.
16951 class UserInteractionTimer final : public Runnable,
16952 public nsITimerCallback,
16953 public nsIAsyncShutdownBlocker {
16954 public:
16955 NS_DECL_ISUPPORTS_INHERITED
16957 explicit UserInteractionTimer(Document* aDocument)
16958 : Runnable("UserInteractionTimer"),
16959 mPrincipal(aDocument->NodePrincipal()),
16960 mDocument(aDocument) {
16961 static int32_t userInteractionTimerId = 0;
16962 // Blocker names must be unique. Let's create it now because when needed,
16963 // the document could be already gone.
16964 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
16965 ++userInteractionTimerId, aDocument);
16968 // Runnable interface
16970 NS_IMETHOD
16971 Run() override {
16972 uint32_t interval =
16973 StaticPrefs::privacy_userInteraction_document_interval();
16974 if (!interval) {
16975 return NS_OK;
16978 RefPtr<UserInteractionTimer> self = this;
16979 auto raii =
16980 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
16982 nsresult rv = NS_NewTimerWithCallback(
16983 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
16984 NS_ENSURE_SUCCESS(rv, NS_OK);
16986 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16987 NS_ENSURE_TRUE(!!phase, NS_OK);
16989 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
16990 __LINE__, u"UserInteractionTimer shutdown"_ns);
16991 NS_ENSURE_SUCCESS(rv, NS_OK);
16993 raii.release();
16994 return NS_OK;
16997 // nsITimerCallback interface
16999 NS_IMETHOD
17000 Notify(nsITimer* aTimer) override {
17001 StoreUserInteraction();
17002 return NS_OK;
17005 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
17006 using nsINamed::GetName;
17007 #endif
17009 // nsIAsyncShutdownBlocker interface
17011 NS_IMETHOD
17012 GetName(nsAString& aName) override {
17013 aName = mBlockerName;
17014 return NS_OK;
17017 NS_IMETHOD
17018 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
17019 CancelTimerAndStoreUserInteraction();
17020 return NS_OK;
17023 NS_IMETHOD
17024 GetState(nsIPropertyBag**) override { return NS_OK; }
17026 private:
17027 ~UserInteractionTimer() = default;
17029 void StoreUserInteraction() {
17030 // Remove the shutting down blocker
17031 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
17032 if (phase) {
17033 phase->RemoveBlocker(this);
17036 // If the document is not gone, let's reset its timer flag.
17037 nsCOMPtr<Document> document(mDocument);
17038 if (document) {
17039 ContentBlockingUserInteraction::Observe(mPrincipal);
17040 document->ResetUserInteractionTimer();
17044 void CancelTimerAndStoreUserInteraction() {
17045 if (mTimer) {
17046 mTimer->Cancel();
17047 mTimer = nullptr;
17050 StoreUserInteraction();
17053 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
17054 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
17055 NS_ENSURE_TRUE(!!svc, nullptr);
17057 nsCOMPtr<nsIAsyncShutdownClient> phase;
17058 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
17059 NS_ENSURE_SUCCESS(rv, nullptr);
17061 return phase.forget();
17064 nsCOMPtr<nsIPrincipal> mPrincipal;
17065 WeakPtr<Document> mDocument;
17067 nsCOMPtr<nsITimer> mTimer;
17069 nsString mBlockerName;
17072 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
17073 nsIAsyncShutdownBlocker)
17075 } // namespace
17077 void Document::MaybeStoreUserInteractionAsPermission() {
17078 // We care about user-interaction stored only for top-level documents
17079 // and documents with access to the Storage Access API
17080 if (!IsTopLevelContentDocument()) {
17081 bool hasSA;
17082 nsresult rv = HasStorageAccessSync(hasSA);
17083 if (NS_FAILED(rv) || !hasSA) {
17084 return;
17088 if (!mUserHasInteracted) {
17089 // First interaction, let's store this info now.
17090 ContentBlockingUserInteraction::Observe(NodePrincipal());
17091 return;
17094 if (mHasUserInteractionTimerScheduled) {
17095 return;
17098 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
17099 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
17100 EventQueuePriority::Idle);
17101 if (NS_WARN_IF(NS_FAILED(rv))) {
17102 return;
17105 // This value will be reset by the timer.
17106 mHasUserInteractionTimerScheduled = true;
17109 void Document::ResetUserInteractionTimer() {
17110 mHasUserInteractionTimerScheduled = false;
17113 bool Document::IsExtensionPage() const {
17114 return BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
17117 void Document::AddResizeObserver(ResizeObserver& aObserver) {
17118 MOZ_ASSERT(!mResizeObservers.Contains(&aObserver));
17119 // Insert internal ResizeObservers before scripted ones, since they may have
17120 // observable side-effects and we don't want to expose the insertion time.
17121 if (aObserver.HasNativeCallback()) {
17122 mResizeObservers.InsertElementAt(0, &aObserver);
17123 } else {
17124 mResizeObservers.AppendElement(&aObserver);
17128 void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
17129 MOZ_ASSERT(mResizeObservers.Contains(&aObserver));
17130 mResizeObservers.RemoveElement(&aObserver);
17133 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
17134 if (!mPermissionDelegateHandler) {
17135 mPermissionDelegateHandler = MakeAndAddRef<PermissionDelegateHandler>(this);
17138 if (!mPermissionDelegateHandler->Initialize()) {
17139 mPermissionDelegateHandler = nullptr;
17142 return mPermissionDelegateHandler;
17145 void Document::ScheduleResizeObserversNotification() const {
17146 if (!mPresShell) {
17147 return;
17149 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
17150 rd->EnsureResizeObserverUpdateHappens();
17154 static void FlushLayoutForWholeBrowsingContextTree(Document& aDoc) {
17155 const ChangesToFlush ctf(FlushType::Layout, /* aFlushAnimations = */ false);
17156 BrowsingContext* bc = aDoc.GetBrowsingContext();
17157 if (bc && bc->GetExtantDocument() == &aDoc) {
17158 RefPtr<BrowsingContext> top = bc->Top();
17159 top->PreOrderWalk([ctf](BrowsingContext* aCur) {
17160 if (Document* doc = aCur->GetExtantDocument()) {
17161 doc->FlushPendingNotifications(ctf);
17164 } else {
17165 // If there is no browsing context, or we're not the current document of the
17166 // browsing context, then we just flush this document itself.
17167 aDoc.FlushPendingNotifications(ctf);
17171 void Document::DetermineProximityToViewportAndNotifyResizeObservers() {
17172 uint32_t shallowestTargetDepth = 0;
17173 bool initialResetOfScrolledIntoViewFlagsDone = false;
17174 while (true) {
17175 // Flush layout, so that any callback functions' style changes / resizes
17176 // get a chance to take effect. The callback functions may do changes in its
17177 // sub-documents or ancestors, so flushing layout for the whole browsing
17178 // context tree makes sure we don't miss anyone.
17179 FlushLayoutForWholeBrowsingContextTree(*this);
17180 if (PresShell* presShell = GetPresShell()) {
17181 auto result = presShell->DetermineProximityToViewport();
17182 if (result.mHadInitialDetermination) {
17183 continue;
17185 if (result.mAnyScrollIntoViewFlag) {
17186 // Not defined in the spec: It's possible that some elements with
17187 // content-visibility: auto were forced to be visible in order to
17188 // perform scrollIntoView() so clear their flags now and restart the
17189 // loop.
17190 // See https://github.com/w3c/csswg-drafts/issues/9337
17191 presShell->ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags();
17192 presShell->ScheduleContentRelevancyUpdate(
17193 ContentRelevancyReason::Visible);
17194 if (!initialResetOfScrolledIntoViewFlagsDone) {
17195 initialResetOfScrolledIntoViewFlagsDone = true;
17196 continue;
17201 // To avoid infinite resize loop, we only gather all active observations
17202 // that have the depth of observed target element more than current
17203 // shallowestTargetDepth.
17204 GatherAllActiveResizeObservations(shallowestTargetDepth);
17206 if (!HasAnyActiveResizeObservations()) {
17207 break;
17210 DebugOnly<uint32_t> oldShallowestTargetDepth = shallowestTargetDepth;
17211 shallowestTargetDepth = BroadcastAllActiveResizeObservations();
17212 NS_ASSERTION(oldShallowestTargetDepth < shallowestTargetDepth,
17213 "shallowestTargetDepth should be getting strictly deeper");
17216 if (HasAnySkippedResizeObservations()) {
17217 if (nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow()) {
17218 // Per spec, we deliver an error if the document has any skipped
17219 // observations. Also, we re-register via ScheduleNotification().
17220 RootedDictionary<ErrorEventInit> init(RootingCx());
17221 init.mMessage.AssignLiteral(
17222 "ResizeObserver loop completed with undelivered notifications.");
17223 init.mBubbles = false;
17224 init.mCancelable = false;
17226 nsEventStatus status = nsEventStatus_eIgnore;
17227 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
17228 MOZ_ASSERT(sgo);
17229 if (NS_WARN_IF(sgo->HandleScriptError(init, &status))) {
17230 status = nsEventStatus_eIgnore;
17232 } else {
17233 // We don't fire error events at any global for non-window JS on the main
17234 // thread.
17237 // We need to deliver pending notifications in next cycle.
17238 ScheduleResizeObserversNotification();
17242 void Document::GatherAllActiveResizeObservations(uint32_t aDepth) {
17243 for (ResizeObserver* observer : mResizeObservers) {
17244 observer->GatherActiveObservations(aDepth);
17248 uint32_t Document::BroadcastAllActiveResizeObservations() {
17249 uint32_t shallowestTargetDepth = std::numeric_limits<uint32_t>::max();
17251 // Copy the observers as this invokes the callbacks and could register and
17252 // unregister observers at will.
17253 const auto observers =
17254 ToTArray<nsTArray<RefPtr<ResizeObserver>>>(mResizeObservers);
17255 for (const auto& observer : observers) {
17256 // MOZ_KnownLive because 'observers' is guaranteed to keep it
17257 // alive.
17259 // This can go away once
17260 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
17261 uint32_t targetDepth =
17262 MOZ_KnownLive(observer)->BroadcastActiveObservations();
17263 if (targetDepth < shallowestTargetDepth) {
17264 shallowestTargetDepth = targetDepth;
17268 return shallowestTargetDepth;
17271 bool Document::HasAnySkippedResizeObservations() const {
17272 for (const auto& observer : mResizeObservers) {
17273 if (observer->HasSkippedObservations()) {
17274 return true;
17277 return false;
17280 bool Document::HasAnyActiveResizeObservations() const {
17281 for (const auto& observer : mResizeObservers) {
17282 if (observer->HasActiveObservations()) {
17283 return true;
17286 return false;
17289 void Document::ClearStaleServoData() {
17290 DocumentStyleRootIterator iter(this);
17291 while (Element* root = iter.GetNextStyleRoot()) {
17292 RestyleManager::ClearServoDataFromSubtree(root);
17296 Selection* Document::GetSelection(ErrorResult& aRv) {
17297 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
17298 if (!window) {
17299 return nullptr;
17302 if (!window->IsCurrentInnerWindow()) {
17303 return nullptr;
17306 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
17309 void Document::MakeBrowsingContextNonSynthetic() {
17310 if (BrowsingContext* bc = GetBrowsingContext()) {
17311 if (bc->GetSyntheticDocumentContainer()) {
17312 Unused << bc->SetSyntheticDocumentContainer(false);
17317 nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
17318 // Step 1: check if cookie permissions are available or denied to this
17319 // document's principal
17320 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17321 if (!inner) {
17322 aHasStorageAccess = false;
17323 return NS_OK;
17325 Maybe<bool> resultBecauseCookiesApproved =
17326 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17327 CookieJarSettings(), NodePrincipal());
17328 if (resultBecauseCookiesApproved.isSome()) {
17329 if (resultBecauseCookiesApproved.value()) {
17330 aHasStorageAccess = true;
17331 return NS_OK;
17332 } else {
17333 aHasStorageAccess = false;
17334 return NS_OK;
17338 // Step 2: Check if the browser settings determine whether or not this
17339 // document has access to its unpartitioned cookies.
17340 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17341 bool isOnThirdPartySkipList = false;
17342 if (mChannel) {
17343 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17344 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17345 nsILoadInfo::StoragePermissionAllowListed;
17347 bool isThirdPartyTracker =
17348 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17349 Maybe<bool> resultBecauseBrowserSettings =
17350 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17351 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
17352 isThirdPartyTracker);
17353 if (resultBecauseBrowserSettings.isSome()) {
17354 if (resultBecauseBrowserSettings.value()) {
17355 aHasStorageAccess = true;
17356 return NS_OK;
17357 } else {
17358 aHasStorageAccess = false;
17359 return NS_OK;
17363 // Step 3: Check if the location of this call (embedded, top level, same-site)
17364 // determines if cookies are permitted or not.
17365 Maybe<bool> resultBecauseCallContext =
17366 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17367 false);
17368 if (resultBecauseCallContext.isSome()) {
17369 if (resultBecauseCallContext.value()) {
17370 aHasStorageAccess = true;
17371 return NS_OK;
17372 } else {
17373 aHasStorageAccess = false;
17374 return NS_OK;
17378 // Step 4: Check if the permissions for this document determine if if has
17379 // access or is denied cookies.
17380 Maybe<bool> resultBecausePreviousPermission =
17381 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17382 this, false);
17383 if (resultBecausePreviousPermission.isSome()) {
17384 if (resultBecausePreviousPermission.value()) {
17385 aHasStorageAccess = true;
17386 return NS_OK;
17387 } else {
17388 aHasStorageAccess = false;
17389 return NS_OK;
17392 // If you get here, we default to not giving you permission.
17393 aHasStorageAccess = false;
17394 return NS_OK;
17397 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
17398 mozilla::ErrorResult& aRv) {
17399 nsIGlobalObject* global = GetScopeObject();
17400 if (!global) {
17401 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17402 return nullptr;
17405 RefPtr<Promise> promise =
17406 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
17407 if (aRv.Failed()) {
17408 return nullptr;
17411 if (!IsCurrentActiveDocument()) {
17412 promise->MaybeRejectWithInvalidStateError(
17413 "hasStorageAccess requires an active document");
17414 return promise.forget();
17417 bool hasStorageAccess;
17418 nsresult rv = HasStorageAccessSync(hasStorageAccess);
17419 if (NS_FAILED(rv)) {
17420 promise->MaybeRejectWithUndefined();
17421 } else {
17422 promise->MaybeResolve(hasStorageAccess);
17425 return promise.forget();
17428 RefPtr<Document::GetContentBlockingEventsPromise>
17429 Document::GetContentBlockingEvents() {
17430 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
17431 if (!wgc) {
17432 return nullptr;
17435 return wgc->SendGetContentBlockingEvents()->Then(
17436 GetCurrentSerialEventTarget(), __func__,
17437 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
17438 ResolveOrRejectValue& aValue) {
17439 if (aValue.IsResolve()) {
17440 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
17441 aValue.ResolveValue(), __func__);
17444 return Document::GetContentBlockingEventsPromise::CreateAndReject(
17445 false, __func__);
17449 StorageAccessAPIHelper::PerformPermissionGrant
17450 Document::CreatePermissionGrantPromise(
17451 nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal,
17452 bool aHasUserInteraction, bool aRequireUserInteraction,
17453 const Maybe<nsCString>& aTopLevelBaseDomain, bool aFrameOnly) {
17454 MOZ_ASSERT(aInnerWindow);
17455 MOZ_ASSERT(aPrincipal);
17456 RefPtr<Document> self(this);
17457 RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
17458 RefPtr<nsIPrincipal> principal(aPrincipal);
17460 return [inner, self, principal, aHasUserInteraction, aRequireUserInteraction,
17461 aTopLevelBaseDomain, aFrameOnly]() {
17462 RefPtr<StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::Private>
17463 p = new StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
17464 Private(__func__);
17466 RefPtr<PWindowGlobalChild::GetStorageAccessPermissionPromise> promise;
17467 // Test the permission
17468 MOZ_ASSERT(XRE_IsContentProcess());
17470 WindowGlobalChild* wgc = inner->GetWindowGlobalChild();
17471 MOZ_ASSERT(wgc);
17473 promise = wgc->SendGetStorageAccessPermission();
17474 MOZ_ASSERT(promise);
17475 promise->Then(
17476 GetCurrentSerialEventTarget(), __func__,
17477 [self, p, inner, principal, aHasUserInteraction,
17478 aRequireUserInteraction, aTopLevelBaseDomain,
17479 aFrameOnly](uint32_t aAction) {
17480 if (aAction == nsIPermissionManager::ALLOW_ACTION) {
17481 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17482 return;
17484 if (aAction == nsIPermissionManager::DENY_ACTION) {
17485 p->Reject(false, __func__);
17486 return;
17489 // We require user activation before conducting a permission request
17490 // See
17491 // https://privacycg.github.io/storage-access/#dom-document-requeststorageaccess
17492 // where we "If has transient activation is false: ..." immediately
17493 // before we "Let permissionState be the result of requesting
17494 // permission to use "storage-access"" from in parallel.
17495 if (!aHasUserInteraction && aRequireUserInteraction) {
17496 // Report an error to the console for this case
17497 nsContentUtils::ReportToConsole(
17498 nsIScriptError::errorFlag,
17499 nsLiteralCString("requestStorageAccess"), self,
17500 nsContentUtils::eDOM_PROPERTIES,
17501 "RequestStorageAccessUserGesture");
17502 p->Reject(false, __func__);
17503 return;
17506 // Create the user prompt
17507 RefPtr<StorageAccessPermissionRequest> sapr =
17508 StorageAccessPermissionRequest::Create(
17509 inner, principal, aTopLevelBaseDomain, aFrameOnly,
17510 // Allow
17511 [p] {
17512 Telemetry::AccumulateCategorical(
17513 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
17514 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17516 // Block
17517 [p] {
17518 Telemetry::AccumulateCategorical(
17519 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
17520 p->Reject(false, __func__);
17523 using PromptResult = ContentPermissionRequestBase::PromptResult;
17524 PromptResult pr = sapr->CheckPromptPrefs();
17526 if (pr == PromptResult::Pending) {
17527 // We're about to show a prompt, record the request attempt
17528 Telemetry::AccumulateCategorical(
17529 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
17532 // Try to auto-grant the storage access so the user doesn't see the
17533 // prompt.
17534 self->AutomaticStorageAccessPermissionCanBeGranted(
17535 aHasUserInteraction)
17536 ->Then(
17537 GetCurrentSerialEventTarget(), __func__,
17538 // If the autogrant check didn't fail, call this function
17539 [p, pr, sapr,
17540 inner](const Document::
17541 AutomaticStorageAccessPermissionGrantPromise::
17542 ResolveOrRejectValue& aValue) -> void {
17543 // Make a copy because we can't modified copy-captured
17544 // lambda variables.
17545 PromptResult pr2 = pr;
17547 // If the user didn't already click "allow" and we can
17548 // autogrant, do that!
17549 bool storageAccessCanBeGrantedAutomatically =
17550 aValue.IsResolve() && aValue.ResolveValue();
17551 bool autoGrant = false;
17552 if (pr2 == PromptResult::Pending &&
17553 storageAccessCanBeGrantedAutomatically) {
17554 pr2 = PromptResult::Granted;
17555 autoGrant = true;
17557 Telemetry::AccumulateCategorical(
17558 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
17559 AllowAutomatically);
17562 // If we can complete the permission request, do so.
17563 if (pr2 != PromptResult::Pending) {
17564 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
17565 pr2 == PromptResult::Denied);
17566 if (pr2 == PromptResult::Granted) {
17567 StorageAccessAPIHelper::StorageAccessPromptChoices
17568 choice = StorageAccessAPIHelper::eAllow;
17569 if (autoGrant) {
17570 choice = StorageAccessAPIHelper::eAllowAutoGrant;
17572 if (!autoGrant) {
17573 p->Resolve(choice, __func__);
17574 } else {
17575 // We capture sapr here to prevent it from destructing
17576 // before the callbacks complete.
17577 sapr->MaybeDelayAutomaticGrants()->Then(
17578 GetCurrentSerialEventTarget(), __func__,
17579 [p, sapr, choice] {
17580 p->Resolve(choice, __func__);
17582 [p, sapr] { p->Reject(false, __func__); });
17584 return;
17586 p->Reject(false, __func__);
17587 return;
17590 // If we get here, the auto-decision failed and we need to
17591 // wait for the user prompt to complete.
17592 sapr->RequestDelayedTask(
17593 GetMainThreadSerialEventTarget(),
17594 ContentPermissionRequestBase::DelayedTaskType::Request);
17597 [p](mozilla::ipc::ResponseRejectReason aError) {
17598 p->Reject(false, __func__);
17599 return p;
17602 return p;
17606 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
17607 mozilla::ErrorResult& aRv) {
17608 nsIGlobalObject* global = GetScopeObject();
17609 if (!global) {
17610 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17611 return nullptr;
17614 RefPtr<Promise> promise = Promise::Create(global, aRv);
17615 if (aRv.Failed()) {
17616 return nullptr;
17619 if (!IsCurrentActiveDocument()) {
17620 promise->MaybeRejectWithInvalidStateError(
17621 "requestStorageAccess requires an active document");
17622 return promise.forget();
17625 // Get a pointer to the inner window- We need this for convenience sake
17626 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17627 if (!inner) {
17628 ConsumeTransientUserGestureActivation();
17629 promise->MaybeRejectWithNotAllowedError(
17630 "requestStorageAccess not allowed"_ns);
17631 return promise.forget();
17634 // Step 1: Check if the principal calling this has a permission that lets
17635 // them use cookies or forbids them from using cookies.
17636 // This is outside of the spec of the StorageAccess API, but makes the return
17637 // values to have proper semantics.
17638 Maybe<bool> resultBecauseCookiesApproved =
17639 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17640 CookieJarSettings(), NodePrincipal());
17641 if (resultBecauseCookiesApproved.isSome()) {
17642 if (resultBecauseCookiesApproved.value()) {
17643 promise->MaybeResolveWithUndefined();
17644 return promise.forget();
17645 } else {
17646 ConsumeTransientUserGestureActivation();
17647 promise->MaybeRejectWithNotAllowedError(
17648 "requestStorageAccess not allowed"_ns);
17649 return promise.forget();
17653 // Step 2: Check if the browser settings always allow or deny cookies.
17654 // We should always return a resolved promise if the cookieBehavior is ACCEPT.
17655 // This is outside of the spec of the StorageAccess API, but makes the return
17656 // values to have proper semantics.
17657 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17658 bool isOnThirdPartySkipList = false;
17659 if (mChannel) {
17660 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17661 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17662 nsILoadInfo::StoragePermissionAllowListed;
17664 bool isThirdPartyTracker =
17665 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17666 Maybe<bool> resultBecauseBrowserSettings =
17667 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17668 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
17669 isThirdPartyTracker);
17670 if (resultBecauseBrowserSettings.isSome()) {
17671 if (resultBecauseBrowserSettings.value()) {
17672 promise->MaybeResolveWithUndefined();
17673 return promise.forget();
17674 } else {
17675 ConsumeTransientUserGestureActivation();
17676 promise->MaybeRejectWithNotAllowedError(
17677 "requestStorageAccess not allowed"_ns);
17678 return promise.forget();
17682 // Step 3: Check if the Document calling requestStorageAccess has anything to
17683 // gain from storage access. It should be embedded, non-null, etc.
17684 Maybe<bool> resultBecauseCallContext =
17685 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17686 true);
17687 if (resultBecauseCallContext.isSome()) {
17688 if (resultBecauseCallContext.value()) {
17689 promise->MaybeResolveWithUndefined();
17690 return promise.forget();
17691 } else {
17692 ConsumeTransientUserGestureActivation();
17693 promise->MaybeRejectWithNotAllowedError(
17694 "requestStorageAccess not allowed"_ns);
17695 return promise.forget();
17699 // Step 4: Check if we already allowed or denied storage access for this
17700 // document's storage key.
17701 Maybe<bool> resultBecausePreviousPermission =
17702 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17703 this, true);
17704 if (resultBecausePreviousPermission.isSome()) {
17705 if (resultBecausePreviousPermission.value()) {
17706 promise->MaybeResolveWithUndefined();
17707 return promise.forget();
17708 } else {
17709 ConsumeTransientUserGestureActivation();
17710 promise->MaybeRejectWithNotAllowedError(
17711 "requestStorageAccess not allowed"_ns);
17712 return promise.forget();
17716 // Get pointers to some objects that will be used in the async portion
17717 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17718 RefPtr<nsGlobalWindowOuter> outer =
17719 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17720 if (!outer) {
17721 ConsumeTransientUserGestureActivation();
17722 promise->MaybeRejectWithNotAllowedError(
17723 "requestStorageAccess not allowed"_ns);
17724 return promise.forget();
17726 RefPtr<Document> self(this);
17728 // Step 5. Start an async call to request storage access. This will either
17729 // perform an automatic decision or notify the user, then perform some follow
17730 // on work changing state to reflect the result of the API. If it resolves,
17731 // the request was granted. If it rejects it was denied.
17732 StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17733 this, inner, bc, NodePrincipal(),
17734 self->HasValidTransientUserGestureActivation(), true, true,
17735 ContentBlockingNotifier::eStorageAccessAPI, true)
17736 ->Then(
17737 GetCurrentSerialEventTarget(), __func__,
17738 [inner] { return inner->SaveStorageAccessPermissionGranted(); },
17739 [] {
17740 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
17742 ->Then(
17743 GetCurrentSerialEventTarget(), __func__,
17744 [promise] { promise->MaybeResolveWithUndefined(); },
17745 [promise, self] {
17746 self->ConsumeTransientUserGestureActivation();
17747 promise->MaybeRejectWithNotAllowedError(
17748 "requestStorageAccess not allowed"_ns);
17751 return promise.forget();
17754 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
17755 const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
17756 mozilla::ErrorResult& aRv) {
17757 nsIGlobalObject* global = GetScopeObject();
17758 if (!global) {
17759 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17760 return nullptr;
17762 RefPtr<Promise> promise = Promise::Create(global, aRv);
17763 if (aRv.Failed()) {
17764 return nullptr;
17767 // Step 0: Check that we have user activation before proceeding to prevent
17768 // rapid calls to the API to leak information.
17769 if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) {
17770 // Report an error to the console for this case
17771 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17772 nsLiteralCString("requestStorageAccess"),
17773 this, nsContentUtils::eDOM_PROPERTIES,
17774 "RequestStorageAccessUserGesture");
17775 ConsumeTransientUserGestureActivation();
17776 promise->MaybeRejectWithNotAllowedError(
17777 "requestStorageAccess not allowed"_ns);
17778 return promise.forget();
17781 // Step 1: Check if the provided URI is different-site to this Document
17782 nsCOMPtr<nsIURI> thirdPartyURI;
17783 nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
17784 if (NS_WARN_IF(NS_FAILED(rv))) {
17785 aRv.Throw(rv);
17786 return nullptr;
17788 bool isThirdPartyDocument;
17789 rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument);
17790 if (NS_WARN_IF(NS_FAILED(rv))) {
17791 aRv.Throw(rv);
17792 return nullptr;
17794 Maybe<bool> resultBecauseBrowserSettings =
17795 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17796 CookieJarSettings(), isThirdPartyDocument, false, true);
17797 if (resultBecauseBrowserSettings.isSome()) {
17798 if (resultBecauseBrowserSettings.value()) {
17799 promise->MaybeResolveWithUndefined();
17800 return promise.forget();
17802 ConsumeTransientUserGestureActivation();
17803 promise->MaybeRejectWithNotAllowedError(
17804 "requestStorageAccess not allowed"_ns);
17805 return promise.forget();
17808 // Step 2: Check that this Document is same-site to the top, and check that
17809 // we have user activation if we require it.
17810 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17811 CheckSameSiteCallingContextDecidesStorageAccessAPI(
17812 this, aRequireUserActivation);
17813 if (resultBecauseCallContext.isSome()) {
17814 if (resultBecauseCallContext.value()) {
17815 promise->MaybeResolveWithUndefined();
17816 return promise.forget();
17818 ConsumeTransientUserGestureActivation();
17819 promise->MaybeRejectWithNotAllowedError(
17820 "requestStorageAccess not allowed"_ns);
17821 return promise.forget();
17824 // Step 3: Get some useful variables that can be captured by the lambda for
17825 // the asynchronous portion
17826 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17827 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17828 if (!inner) {
17829 ConsumeTransientUserGestureActivation();
17830 promise->MaybeRejectWithNotAllowedError(
17831 "requestStorageAccess not allowed"_ns);
17832 return promise.forget();
17834 RefPtr<nsGlobalWindowOuter> outer =
17835 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17836 if (!outer) {
17837 ConsumeTransientUserGestureActivation();
17838 promise->MaybeRejectWithNotAllowedError(
17839 "requestStorageAccess not allowed"_ns);
17840 return promise.forget();
17842 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17843 thirdPartyURI, NodePrincipal()->OriginAttributesRef());
17844 if (!principal) {
17845 ConsumeTransientUserGestureActivation();
17846 promise->MaybeRejectWithNotAllowedError(
17847 "requestStorageAccess not allowed"_ns);
17848 return promise.forget();
17851 RefPtr<Document> self(this);
17852 bool hasUserActivation = HasValidTransientUserGestureActivation();
17854 // Consume user activation before entering the async part of this method.
17855 // This prevents usage of other transient activation-gated APIs.
17856 ConsumeTransientUserGestureActivation();
17858 // Step 4a: Start the async part of this function. Check the cookie
17859 // permission, but this can't be done in this process. We needs the cookie
17860 // permission of the URL as if it were embedded on this page, so we need to
17861 // make this check in the ContentParent.
17862 StorageAccessAPIHelper::
17863 AsyncCheckCookiesPermittedDecidesStorageAccessAPIOnChildProcess(
17864 GetBrowsingContext(), principal)
17865 ->Then(
17866 GetCurrentSerialEventTarget(), __func__,
17867 [inner, thirdPartyURI, bc, principal, hasUserActivation,
17868 aRequireUserActivation, self,
17869 promise](Maybe<bool> cookieResult) {
17870 // Handle the result of the cookie permission check that took
17871 // place in the ContentParent.
17872 if (cookieResult.isSome()) {
17873 if (cookieResult.value()) {
17874 return MozPromise<int, bool, true>::CreateAndResolve(
17875 true, __func__);
17877 return MozPromise<int, bool, true>::CreateAndReject(false,
17878 __func__);
17881 // Step 4b: Check for the existing storage access permission
17882 nsAutoCString type;
17883 bool ok = AntiTrackingUtils::CreateStoragePermissionKey(
17884 principal, type);
17885 if (!ok) {
17886 return MozPromise<int, bool, true>::CreateAndReject(false,
17887 __func__);
17889 if (AntiTrackingUtils::CheckStoragePermission(
17890 self->NodePrincipal(), type,
17891 nsContentUtils::IsInPrivateBrowsing(self), nullptr,
17892 0)) {
17893 return MozPromise<int, bool, true>::CreateAndResolve(
17894 true, __func__);
17897 // Step 4c: Try to request storage access, either automatically
17898 // or with a user-prompt. This is the part that is async in the
17899 // typical requestStorageAccess function.
17900 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17901 self, inner, bc, principal, hasUserActivation,
17902 aRequireUserActivation, false,
17903 ContentBlockingNotifier::
17904 ePrivilegeStorageAccessForOriginAPI,
17905 true);
17907 // If the IPC rejects, we should reject our promise here which
17908 // will cause a rejection of the promise we already returned
17909 [promise]() {
17910 return MozPromise<int, bool, true>::CreateAndReject(false,
17911 __func__);
17913 ->Then(
17914 GetCurrentSerialEventTarget(), __func__,
17915 // If the previous handlers resolved, we should reinstate user
17916 // activation and resolve the promise we returned in Step 5.
17917 [self, inner, promise] {
17918 inner->SaveStorageAccessPermissionGranted();
17919 self->NotifyUserGestureActivation();
17920 promise->MaybeResolveWithUndefined();
17922 // If the previous handler rejected, we should reject the promise
17923 // returned by this function.
17924 [promise] {
17925 promise->MaybeRejectWithNotAllowedError(
17926 "requestStorageAccess not allowed"_ns);
17929 // Step 5: While the async stuff is happening, we should return the promise so
17930 // our caller can continue executing.
17931 return promise.forget();
17934 already_AddRefed<Promise> Document::RequestStorageAccessUnderSite(
17935 const nsAString& aSerializedSite, ErrorResult& aRv) {
17936 nsIGlobalObject* global = GetScopeObject();
17937 if (!global) {
17938 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17939 return nullptr;
17941 RefPtr<Promise> promise = Promise::Create(global, aRv);
17942 if (aRv.Failed()) {
17943 return nullptr;
17946 // Check that we have user activation before proceeding to prevent
17947 // rapid calls to the API to leak information.
17948 if (!ConsumeTransientUserGestureActivation()) {
17949 // Report an error to the console for this case
17950 nsContentUtils::ReportToConsole(
17951 nsIScriptError::errorFlag, "requestStorageAccess"_ns, this,
17952 nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture");
17953 promise->MaybeRejectWithUndefined();
17954 return promise.forget();
17957 // Check if the provided URI is different-site to this Document
17958 nsCOMPtr<nsIURI> siteURI;
17959 nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite);
17960 if (NS_WARN_IF(NS_FAILED(rv))) {
17961 promise->MaybeRejectWithUndefined();
17962 return promise.forget();
17964 bool isCrossSiteArgument;
17965 rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument);
17966 if (NS_WARN_IF(NS_FAILED(rv))) {
17967 aRv.Throw(rv);
17968 return nullptr;
17970 if (!isCrossSiteArgument) {
17971 promise->MaybeRejectWithUndefined();
17972 return promise.forget();
17975 // Check if this party has broad cookie permissions.
17976 Maybe<bool> resultBecauseCookiesApproved =
17977 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17978 CookieJarSettings(), NodePrincipal());
17979 if (resultBecauseCookiesApproved.isSome()) {
17980 if (resultBecauseCookiesApproved.value()) {
17981 promise->MaybeResolveWithUndefined();
17982 return promise.forget();
17984 promise->MaybeRejectWithUndefined();
17985 return promise.forget();
17988 // Check if browser settings preclude this document getting storage
17989 // access under the provided site
17990 Maybe<bool> resultBecauseBrowserSettings =
17991 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17992 CookieJarSettings(), true, false, true);
17993 if (resultBecauseBrowserSettings.isSome()) {
17994 if (resultBecauseBrowserSettings.value()) {
17995 promise->MaybeResolveWithUndefined();
17996 return promise.forget();
17998 promise->MaybeRejectWithUndefined();
17999 return promise.forget();
18002 // Check that this Document is same-site to the top
18003 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
18004 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
18005 if (resultBecauseCallContext.isSome()) {
18006 if (resultBecauseCallContext.value()) {
18007 promise->MaybeResolveWithUndefined();
18008 return promise.forget();
18010 promise->MaybeRejectWithUndefined();
18011 return promise.forget();
18014 nsCOMPtr<nsIPrincipal> principal(NodePrincipal());
18016 // Test if the permission this is requesting is already set
18017 nsCOMPtr<nsIPrincipal> argumentPrincipal =
18018 BasePrincipal::CreateContentPrincipal(
18019 siteURI, NodePrincipal()->OriginAttributesRef());
18020 if (!argumentPrincipal) {
18021 ConsumeTransientUserGestureActivation();
18022 promise->MaybeRejectWithUndefined();
18023 return promise.forget();
18025 nsCString originNoSuffix;
18026 rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix);
18027 if (NS_WARN_IF(NS_FAILED(rv))) {
18028 promise->MaybeRejectWithUndefined();
18029 return promise.forget();
18032 ContentChild* cc = ContentChild::GetSingleton();
18033 MOZ_ASSERT(cc);
18034 RefPtr<Document> self(this);
18035 cc->SendTestStorageAccessPermission(argumentPrincipal, originNoSuffix)
18036 ->Then(
18037 GetCurrentSerialEventTarget(), __func__,
18038 [promise, siteURI,
18039 self](const ContentChild::TestStorageAccessPermissionPromise::
18040 ResolveValueType& aResult) {
18041 if (aResult) {
18042 return StorageAccessAPIHelper::
18043 StorageAccessPermissionGrantPromise::CreateAndResolve(
18044 StorageAccessAPIHelper::eAllow, __func__);
18046 // Get a grant for the storage access permission that will be set
18047 // when this is completed in the embedding context
18048 nsCString serializedSite;
18049 RefPtr<nsEffectiveTLDService> etld =
18050 nsEffectiveTLDService::GetInstance();
18051 if (!etld) {
18052 return StorageAccessAPIHelper::
18053 StorageAccessPermissionGrantPromise::CreateAndReject(
18054 false, __func__);
18056 nsresult rv = etld->GetSite(siteURI, serializedSite);
18057 if (NS_FAILED(rv)) {
18058 return StorageAccessAPIHelper::
18059 StorageAccessPermissionGrantPromise::CreateAndReject(
18060 false, __func__);
18062 return self->CreatePermissionGrantPromise(
18063 self->GetInnerWindow(), self->NodePrincipal(), true, true,
18064 Some(serializedSite), false)();
18066 [](const ContentChild::TestStorageAccessPermissionPromise::
18067 RejectValueType& aResult) {
18068 return StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
18069 CreateAndReject(false, __func__);
18071 ->Then(
18072 GetCurrentSerialEventTarget(), __func__,
18073 [promise, principal, siteURI](int result) {
18074 ContentChild* cc = ContentChild::GetSingleton();
18075 if (!cc) {
18076 // TODO(bug 1778561): Make this work in non-content processes.
18077 promise->MaybeRejectWithUndefined();
18078 return;
18080 // Set a permission in the parent process that this document wants
18081 // storage access under the argument's site, resolving our returned
18082 // promise on success
18083 cc->SendSetAllowStorageAccessRequestFlag(principal, siteURI)
18084 ->Then(
18085 GetCurrentSerialEventTarget(), __func__,
18086 [promise](bool success) {
18087 if (success) {
18088 promise->MaybeResolveWithUndefined();
18089 } else {
18090 promise->MaybeRejectWithUndefined();
18093 [promise](mozilla::ipc::ResponseRejectReason reason) {
18094 promise->MaybeRejectWithUndefined();
18097 [promise](bool result) { promise->MaybeRejectWithUndefined(); });
18099 // Return the promise that is resolved in the async handler above
18100 return promise.forget();
18103 already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite(
18104 const nsAString& aSerializedOrigin, ErrorResult& aRv) {
18105 nsIGlobalObject* global = GetScopeObject();
18106 if (!global) {
18107 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
18108 return nullptr;
18110 RefPtr<Promise> promise = Promise::Create(global, aRv);
18111 if (aRv.Failed()) {
18112 return nullptr;
18115 // Check that the provided URI is different-site to this Document
18116 nsCOMPtr<nsIURI> argumentURI;
18117 nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin);
18118 if (NS_WARN_IF(NS_FAILED(rv))) {
18119 promise->MaybeRejectWithUndefined();
18120 return promise.forget();
18122 bool isCrossSiteArgument;
18123 rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument);
18124 if (NS_WARN_IF(NS_FAILED(rv))) {
18125 aRv.Throw(rv);
18126 return nullptr;
18128 if (!isCrossSiteArgument) {
18129 promise->MaybeRejectWithUndefined();
18130 return promise.forget();
18133 // Check if browser settings preclude this document getting storage
18134 // access under the provided site
18135 Maybe<bool> resultBecauseBrowserSettings =
18136 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
18137 CookieJarSettings(), true, false, true);
18138 if (resultBecauseBrowserSettings.isSome()) {
18139 if (resultBecauseBrowserSettings.value()) {
18140 promise->MaybeResolveWithUndefined();
18141 return promise.forget();
18143 promise->MaybeRejectWithUndefined();
18144 return promise.forget();
18147 // Check that this Document is same-site to the top
18148 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
18149 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
18150 if (resultBecauseCallContext.isSome()) {
18151 if (resultBecauseCallContext.value()) {
18152 promise->MaybeResolveWithUndefined();
18153 return promise.forget();
18155 promise->MaybeRejectWithUndefined();
18156 return promise.forget();
18159 // Create principal of the embedded site requesting storage access
18160 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
18161 argumentURI, NodePrincipal()->OriginAttributesRef());
18162 if (!principal) {
18163 promise->MaybeRejectWithUndefined();
18164 return promise.forget();
18167 // Get versions of these objects that we can use in lambdas for callbacks
18168 RefPtr<Document> self(this);
18169 RefPtr<BrowsingContext> bc = GetBrowsingContext();
18170 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
18172 // Test that the permission was set by a call to RequestStorageAccessUnderSite
18173 // from a top level document that is same-site with the argument
18174 ContentChild* cc = ContentChild::GetSingleton();
18175 if (!cc) {
18176 // TODO(bug 1778561): Make this work in non-content processes.
18177 promise->MaybeRejectWithUndefined();
18178 return promise.forget();
18180 cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI)
18181 ->Then(
18182 GetCurrentSerialEventTarget(), __func__,
18183 [inner, bc, self, principal](bool success) {
18184 if (success) {
18185 // If that resolved with true, check that we don't already have a
18186 // permission that gives cookie access.
18187 return StorageAccessAPIHelper::
18188 AsyncCheckCookiesPermittedDecidesStorageAccessAPIOnChildProcess(
18189 bc, principal);
18191 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18192 NS_ERROR_FAILURE, __func__);
18194 [](mozilla::ipc::ResponseRejectReason reason) {
18195 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18196 NS_ERROR_FAILURE, __func__);
18198 ->Then(
18199 GetCurrentSerialEventTarget(), __func__,
18200 [inner, bc, principal, self, promise](Maybe<bool> cookieResult) {
18201 // Handle the result of the cookie permission check that took place
18202 // in the ContentParent.
18203 if (cookieResult.isSome()) {
18204 if (cookieResult.value()) {
18205 return StorageAccessAPIHelper::
18206 StorageAccessPermissionGrantPromise::CreateAndResolve(
18207 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18209 return StorageAccessAPIHelper::
18210 StorageAccessPermissionGrantPromise::CreateAndReject(
18211 false, __func__);
18214 // Check for the existing storage access permission
18215 nsAutoCString type;
18216 bool ok =
18217 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
18218 if (!ok) {
18219 return StorageAccessAPIHelper::
18220 StorageAccessPermissionGrantPromise::CreateAndReject(
18221 false, __func__);
18223 if (AntiTrackingUtils::CheckStoragePermission(
18224 self->NodePrincipal(), type,
18225 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
18226 return StorageAccessAPIHelper::
18227 StorageAccessPermissionGrantPromise::CreateAndResolve(
18228 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18231 // Try to request storage access, ignoring the final checks.
18232 // We ignore the final checks because this is where the "grant"
18233 // either by prompt doorhanger or autogrant takes place. We already
18234 // gathered an equivalent grant in requestStorageAccessUnderSite.
18235 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
18236 self, inner, bc, principal, true, true, false,
18237 ContentBlockingNotifier::eStorageAccessAPI, false);
18239 // If the IPC rejects, we should reject our promise here which will
18240 // cause a rejection of the promise we already returned
18241 [promise]() {
18242 return MozPromise<int, bool, true>::CreateAndReject(false,
18243 __func__);
18245 ->Then(
18246 GetCurrentSerialEventTarget(), __func__,
18247 // If the previous handlers resolved, we should reinstate user
18248 // activation and resolve the promise we returned in Step 5.
18249 [self, inner, promise] {
18250 inner->SaveStorageAccessPermissionGranted();
18251 promise->MaybeResolveWithUndefined();
18253 // If the previous handler rejected, we should reject the promise
18254 // returned by this function.
18255 [promise] { promise->MaybeRejectWithUndefined(); });
18257 return promise.forget();
18260 nsTHashSet<RefPtr<WakeLockSentinel>>& Document::ActiveWakeLocks(
18261 WakeLockType aType) {
18262 return mActiveLocks.LookupOrInsert(aType);
18265 class UnlockAllWakeLockRunnable final : public Runnable {
18266 public:
18267 UnlockAllWakeLockRunnable(WakeLockType aType, Document* aDoc)
18268 : Runnable("UnlockAllWakeLocks"), mType(aType), mDoc(aDoc) {}
18270 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
18271 // bug 1535398.
18272 MOZ_CAN_RUN_SCRIPT_BOUNDARY
18273 NS_IMETHOD Run() override {
18274 // Move, as ReleaseWakeLock will try to remove from and possibly allow
18275 // scripts via onrelease to add to document.[[ActiveLocks]]["screen"]
18276 nsCOMPtr<Document> doc = mDoc;
18277 nsTHashSet<RefPtr<WakeLockSentinel>> locks =
18278 std::move(doc->ActiveWakeLocks(mType));
18279 for (const auto& lock : locks) {
18280 // ReleaseWakeLock runs script, which could release other locks
18281 if (!lock->Released()) {
18282 ReleaseWakeLock(doc, MOZ_KnownLive(lock), mType);
18285 return NS_OK;
18288 protected:
18289 ~UnlockAllWakeLockRunnable() = default;
18291 private:
18292 WakeLockType mType;
18293 nsCOMPtr<Document> mDoc;
18296 void Document::UnlockAllWakeLocks(WakeLockType aType) {
18297 // Perform unlock in a runnable to prevent UnlockAll being MOZ_CAN_RUN_SCRIPT
18298 RefPtr<UnlockAllWakeLockRunnable> runnable =
18299 MakeRefPtr<UnlockAllWakeLockRunnable>(aType, this);
18300 NS_DispatchToMainThread(runnable);
18303 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
18304 Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
18305 // requestStorageAccessForOrigin may not require user activation. If we don't
18306 // have user activation at this point we should always show the prompt.
18307 if (!hasUserActivation ||
18308 !StaticPrefs::privacy_antitracking_enableWebcompat()) {
18309 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18310 false, __func__);
18312 if (XRE_IsContentProcess()) {
18313 // In the content process, we need to ask the parent process to compute
18314 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
18315 // isn't accessible in the content process.
18316 ContentChild* cc = ContentChild::GetSingleton();
18317 MOZ_ASSERT(cc);
18319 return cc->SendAutomaticStorageAccessPermissionCanBeGranted(NodePrincipal())
18320 ->Then(GetCurrentSerialEventTarget(), __func__,
18321 [](const ContentChild::
18322 AutomaticStorageAccessPermissionCanBeGrantedPromise::
18323 ResolveOrRejectValue& aValue) {
18324 if (aValue.IsResolve()) {
18325 return AutomaticStorageAccessPermissionGrantPromise::
18326 CreateAndResolve(aValue.ResolveValue(), __func__);
18329 return AutomaticStorageAccessPermissionGrantPromise::
18330 CreateAndReject(false, __func__);
18334 if (XRE_IsParentProcess()) {
18335 // In the parent process, we can directly compute this.
18336 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18337 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
18338 __func__);
18341 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
18342 false, __func__);
18345 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
18346 nsIPrincipal* aPrincipal) {
18347 if (!StaticPrefs::dom_storage_access_auto_grants()) {
18348 return false;
18351 if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
18352 return false;
18355 nsCOMPtr<nsIBrowserUsage> bu = do_ImportESModule(
18356 "resource:///modules/BrowserUsageTelemetry.sys.mjs", fallible);
18357 if (NS_WARN_IF(!bu)) {
18358 return false;
18361 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
18362 nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours(
18363 &uniqueDomainsVisitedInPast24Hours);
18364 if (NS_WARN_IF(NS_FAILED(rv))) {
18365 return false;
18368 Maybe<size_t> maybeOriginsThirdPartyHasAccessTo =
18369 AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal);
18370 if (maybeOriginsThirdPartyHasAccessTo.isNothing()) {
18371 return false;
18373 size_t originsThirdPartyHasAccessTo =
18374 maybeOriginsThirdPartyHasAccessTo.value();
18376 // one percent of the number of top-levels origins visited in the current
18377 // session (but not to exceed 24 hours), or the value of the
18378 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
18379 // higher.
18380 size_t maxConcurrentAutomaticGrants = std::max(
18381 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
18382 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
18385 return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
18388 void Document::RecordNavigationTiming(ReadyState aReadyState) {
18389 if (!XRE_IsContentProcess()) {
18390 return;
18392 if (!IsTopLevelContentDocument()) {
18393 return;
18395 // If we dont have the timing yet (mostly because the doc is still loading),
18396 // get it from docshell.
18397 RefPtr<nsDOMNavigationTiming> timing = mTiming;
18398 if (!timing) {
18399 if (!mDocumentContainer) {
18400 return;
18402 timing = mDocumentContainer->GetNavigationTiming();
18403 if (!timing) {
18404 return;
18407 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
18408 switch (aReadyState) {
18409 case READYSTATE_LOADING:
18410 if (!mDOMLoadingSet) {
18411 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
18412 startTime);
18413 mDOMLoadingSet = true;
18415 break;
18416 case READYSTATE_INTERACTIVE:
18417 if (!mDOMInteractiveSet) {
18418 glean::performance_time::dom_interactive.AccumulateRawDuration(
18419 TimeStamp::Now() - startTime);
18420 mDOMInteractiveSet = true;
18422 break;
18423 case READYSTATE_COMPLETE:
18424 if (!mDOMCompleteSet) {
18425 glean::performance_time::dom_complete.AccumulateRawDuration(
18426 TimeStamp::Now() - startTime);
18427 mDOMCompleteSet = true;
18429 break;
18430 default:
18431 NS_WARNING("Unexpected ReadyState value");
18432 break;
18436 void Document::ReportShadowDOMUsage() {
18437 nsPIDOMWindowInner* inner = GetInnerWindow();
18438 if (NS_WARN_IF(!inner)) {
18439 return;
18442 WindowContext* wc = inner->GetWindowContext();
18443 if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
18444 return;
18447 WindowContext* topWc = wc->TopWindowContext();
18448 if (topWc->GetHasReportedShadowDOMUsage()) {
18449 return;
18452 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
18455 // static
18456 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
18457 return StaticPrefs::dom_storage_access_enabled() &&
18458 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
18461 bool Document::StorageAccessSandboxed() const {
18462 return Document::StorageAccessSandboxed(GetSandboxFlags());
18465 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
18466 if (mCachedTabSizeGeneration == 0 ||
18467 GetGeneration() != mCachedTabSizeGeneration) {
18468 return false;
18470 aSizes->mDom += mCachedTabSizes.mDom;
18471 aSizes->mStyle += mCachedTabSizes.mStyle;
18472 aSizes->mOther += mCachedTabSizes.mOther;
18473 return true;
18476 void Document::SetCachedSizes(nsTabSizes* aSizes) {
18477 mCachedTabSizes.mDom = aSizes->mDom;
18478 mCachedTabSizes.mStyle = aSizes->mStyle;
18479 mCachedTabSizes.mOther = aSizes->mOther;
18480 mCachedTabSizeGeneration = GetGeneration();
18483 nsAtom* Document::GetContentLanguageAsAtomForStyle() const {
18484 // Content-Language may be a comma-separated list of language codes,
18485 // in which case the HTML5 spec says to treat it as unknown
18486 if (mContentLanguage &&
18487 !nsDependentAtomString(mContentLanguage).Contains(char16_t(','))) {
18488 return GetContentLanguage();
18491 return nullptr;
18494 nsAtom* Document::GetLanguageForStyle() const {
18495 if (nsAtom* lang = GetContentLanguageAsAtomForStyle()) {
18496 return lang;
18498 return mLanguageFromCharset.get();
18501 void Document::GetContentLanguageForBindings(DOMString& aString) const {
18502 aString.SetKnownLiveAtom(mContentLanguage, DOMString::eTreatNullAsEmpty);
18505 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
18506 nsAtom* aLanguage, bool* aNeedsToCache) const {
18507 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
18508 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
18511 void Document::DoCacheAllKnownLangPrefs() {
18512 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
18513 RefPtr<nsAtom> lang = GetLanguageForStyle();
18514 StaticPresData* data = StaticPresData::Get();
18515 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
18516 data->GetFontPrefsForLang(nsGkAtoms::x_math);
18517 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
18518 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
18519 for (const auto& key : mLanguagesUsed) {
18520 data->GetFontPrefsForLang(key);
18522 mMayNeedFontPrefsUpdate = false;
18525 void Document::RecomputeLanguageFromCharset() {
18526 RefPtr<nsAtom> language;
18527 // Optimize the default character sets.
18528 if (mCharacterSet == WINDOWS_1252_ENCODING) {
18529 language = nsGkAtoms::x_western;
18530 } else {
18531 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
18532 if (mCharacterSet == UTF_8_ENCODING) {
18533 language = nsGkAtoms::Unicode;
18534 } else {
18535 language = service->LookupCharSet(mCharacterSet);
18538 if (language == nsGkAtoms::Unicode) {
18539 language = service->GetLocaleLanguage();
18543 if (language == mLanguageFromCharset) {
18544 return;
18547 mMayNeedFontPrefsUpdate = true;
18548 mLanguageFromCharset = std::move(language);
18551 nsICookieJarSettings* Document::CookieJarSettings() {
18552 // If we are here, this is probably a javascript: URL document. In any case,
18553 // we must have a nsCookieJarSettings. Let's create it.
18554 if (!mCookieJarSettings) {
18555 Document* inProcessParent = GetInProcessParentDocument();
18557 if (inProcessParent) {
18558 mCookieJarSettings = net::CookieJarSettings::Create(
18559 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
18560 mozilla::net::CookieJarSettings::Cast(
18561 inProcessParent->CookieJarSettings())
18562 ->GetPartitionKey(),
18563 inProcessParent->CookieJarSettings()->GetIsFirstPartyIsolated(),
18564 inProcessParent->CookieJarSettings()
18565 ->GetIsOnContentBlockingAllowList(),
18566 inProcessParent->CookieJarSettings()
18567 ->GetShouldResistFingerprinting());
18569 // Inherit the fingerprinting random key from the parent.
18570 nsTArray<uint8_t> randomKey;
18571 nsresult rv = inProcessParent->CookieJarSettings()
18572 ->GetFingerprintingRandomizationKey(randomKey);
18574 if (NS_SUCCEEDED(rv)) {
18575 net::CookieJarSettings::Cast(mCookieJarSettings)
18576 ->SetFingerprintingRandomizationKey(randomKey);
18578 } else {
18579 mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
18582 if (auto* wgc = GetWindowGlobalChild()) {
18583 net::CookieJarSettingsArgs csArgs;
18584 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
18585 // Update cookie settings in the parent process
18586 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
18587 NS_WARNING(
18588 "Failed to update document's cookie jar settings on the "
18589 "WindowGlobalParent");
18594 return mCookieJarSettings;
18597 bool Document::UsingStorageAccess() {
18598 if (WindowContext* wc = GetWindowContext()) {
18599 return wc->GetUsingStorageAccess();
18602 // If we don't yet have a window context, we have to use the decision
18603 // from the Document's Channel's LoadInfo directly.
18604 if (!mChannel) {
18605 return false;
18608 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18609 return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
18612 bool Document::HasStorageAccessPermissionGrantedByAllowList() {
18613 // We only care about if the document gets the storage permission via the
18614 // allow list here. So we don't check the storage access cache in the inner
18615 // window.
18617 if (!mChannel) {
18618 return false;
18621 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18622 return loadInfo->GetStoragePermission() ==
18623 nsILoadInfo::StoragePermissionAllowListed;
18626 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
18627 if (!StaticPrefs::
18628 privacy_partition_always_partition_third_party_non_cookie_storage()) {
18629 return EffectiveCookiePrincipal();
18632 nsPIDOMWindowInner* inner = GetInnerWindow();
18633 if (!inner) {
18634 return NodePrincipal();
18637 // Return our cached storage principal if one exists.
18638 if (mActiveStoragePrincipal) {
18639 return mActiveStoragePrincipal;
18642 // Calling StorageAllowedForDocument will notify the ContentBlockLog. This
18643 // loads TrackingDBService.jsm, which in turn pulls in osfile.jsm, making us
18644 // fail // browser/base/content/test/performance/browser_startup.js. To avoid
18645 // that, we short-circuit the check here by allowing storage access to system
18646 // and addon principles, avoiding the test-failure.
18647 nsIPrincipal* principal = NodePrincipal();
18648 if (principal && (principal->IsSystemPrincipal() ||
18649 principal->GetIsAddonOrExpandedAddonPrincipal())) {
18650 return mActiveStoragePrincipal = NodePrincipal();
18653 auto cookieJarSettings = const_cast<Document*>(this)->CookieJarSettings();
18654 if (cookieJarSettings->GetIsOnContentBlockingAllowList()) {
18655 return mActiveStoragePrincipal = NodePrincipal();
18658 StorageAccess storageAccess = StorageAllowedForDocument(this);
18659 if (!ShouldPartitionStorage(storageAccess) ||
18660 !StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
18661 return mActiveStoragePrincipal = NodePrincipal();
18664 Unused << NS_WARN_IF(NS_FAILED(StoragePrincipalHelper::GetPrincipal(
18665 nsGlobalWindowInner::Cast(inner),
18666 StoragePrincipalHelper::eForeignPartitionedPrincipal,
18667 getter_AddRefs(mActiveStoragePrincipal))));
18668 return mActiveStoragePrincipal;
18671 nsIPrincipal* Document::EffectiveCookiePrincipal() const {
18672 nsPIDOMWindowInner* inner = GetInnerWindow();
18673 if (!inner) {
18674 return NodePrincipal();
18677 // Return our cached storage principal if one exists.
18678 if (mActiveCookiePrincipal) {
18679 return mActiveCookiePrincipal;
18682 // We use the lower-level ContentBlocking API here to ensure this
18683 // check doesn't send notifications.
18684 uint32_t rejectedReason = 0;
18685 if (ShouldAllowAccessFor(inner, GetDocumentURI(), &rejectedReason)) {
18686 return mActiveCookiePrincipal = NodePrincipal();
18689 // Let's use the storage principal only if we need to partition the cookie
18690 // jar. When the permission is granted, access will be different and the
18691 // normal principal will be used.
18692 if (ShouldPartitionStorage(rejectedReason) &&
18693 !StoragePartitioningEnabled(
18694 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
18695 return mActiveCookiePrincipal = NodePrincipal();
18698 return mActiveCookiePrincipal = mPartitionedPrincipal;
18701 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
18702 // If the document is sandboxed document or data: document, we should
18703 // get URI of the parent document.
18704 for (const Document* document = this;
18705 document && document->IsContentDocument();
18706 document = document->GetInProcessParentDocument()) {
18707 // The document URI may be about:blank even if it comes from actual web
18708 // site. Therefore, we need to check the URI of its principal.
18709 nsIPrincipal* principal = document->NodePrincipal();
18710 if (principal->GetIsNullPrincipal()) {
18711 continue;
18713 return principal;
18715 return nullptr;
18718 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
18719 mIsInitialDocumentInWindow = aIsInitialDocument;
18721 if (aIsInitialDocument && !mIsEverInitialDocumentInWindow) {
18722 mIsEverInitialDocumentInWindow = aIsInitialDocument;
18725 // Asynchronously tell the parent process that we are, or are no longer, the
18726 // initial document. This happens async.
18727 if (auto* wgc = GetWindowGlobalChild()) {
18728 wgc->SendSetIsInitialDocument(aIsInitialDocument);
18732 // static
18733 void Document::AddToplevelLoadingDocument(Document* aDoc) {
18734 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18735 // Currently we're interested in foreground documents only, so bail out early.
18736 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
18737 return;
18740 if (!sLoadingForegroundTopLevelContentDocument) {
18741 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
18742 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18743 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18744 if (idleScheduler) {
18745 idleScheduler->SendRunningPrioritizedOperation();
18748 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
18749 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
18753 // static
18754 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
18755 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18756 if (sLoadingForegroundTopLevelContentDocument) {
18757 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
18758 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
18759 delete sLoadingForegroundTopLevelContentDocument;
18760 sLoadingForegroundTopLevelContentDocument = nullptr;
18762 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18763 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18764 if (idleScheduler) {
18765 idleScheduler->SendPrioritizedOperationDone();
18771 ColorScheme Document::DefaultColorScheme() const {
18772 return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
18775 ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
18776 if (ShouldResistFingerprinting(RFPTarget::CSSPrefersColorScheme) &&
18777 aIgnoreRFP == IgnoreRFP::No) {
18778 return ColorScheme::Light;
18781 if (nsPresContext* pc = GetPresContext()) {
18782 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
18783 return *scheme;
18787 return PreferenceSheet::PrefsFor(*this).mColorScheme;
18790 bool Document::HasRecentlyStartedForegroundLoads() {
18791 if (!sLoadingForegroundTopLevelContentDocument) {
18792 return false;
18795 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
18796 ++i) {
18797 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
18798 // A page loaded in foreground could be in background now.
18799 if (!doc->IsInBackgroundWindow()) {
18800 nsPIDOMWindowInner* win = doc->GetInnerWindow();
18801 if (win) {
18802 Performance* perf = win->GetPerformance();
18803 if (perf &&
18804 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
18805 return true;
18811 // Didn't find any loading foreground documents, just clear the array.
18812 delete sLoadingForegroundTopLevelContentDocument;
18813 sLoadingForegroundTopLevelContentDocument = nullptr;
18815 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18816 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18817 if (idleScheduler) {
18818 idleScheduler->SendPrioritizedOperationDone();
18820 return false;
18823 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
18824 nsFrameLoader* aStaticCloneOf) {
18825 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
18826 clone->mElement = aElement;
18827 clone->mStaticCloneOf = aStaticCloneOf;
18830 bool Document::ShouldAvoidNativeTheme() const {
18831 return StaticPrefs::widget_non_native_theme_enabled() &&
18832 (!IsInChromeDocShell() || XRE_IsContentProcess());
18835 bool Document::UseRegularPrincipal() const {
18836 return EffectiveStoragePrincipal() == NodePrincipal();
18839 bool Document::HasThirdPartyChannel() {
18840 nsCOMPtr<nsIChannel> channel = GetChannel();
18841 if (channel) {
18842 // We assume that the channel is a third-party by default.
18843 bool thirdParty = true;
18845 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
18846 components::ThirdPartyUtil::Service();
18847 if (!thirdPartyUtil) {
18848 return thirdParty;
18851 // Check that if the channel is a third-party to its parent.
18852 nsresult rv =
18853 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
18854 if (NS_FAILED(rv)) {
18855 // Assume third-party in case of failure
18856 thirdParty = true;
18859 return thirdParty;
18862 if (mParentDocument) {
18863 return mParentDocument->HasThirdPartyChannel();
18866 return false;
18869 bool Document::IsLikelyContentInaccessibleTopLevelAboutBlank() const {
18870 if (!mDocumentURI || !NS_IsAboutBlank(mDocumentURI)) {
18871 return false;
18873 // FIXME(emilio): This is not quite edge-case free. See bug 1860098.
18875 // For stuff in frames, that makes our per-document telemetry probes not
18876 // really reliable but doesn't affect the correctness of our page probes, so
18877 // it's not too terrible.
18878 BrowsingContext* bc = GetBrowsingContext();
18879 return bc && bc->IsTop() && !bc->HadOriginalOpener();
18882 bool Document::ShouldIncludeInTelemetry() const {
18883 if (!IsContentDocument() && !IsResourceDoc()) {
18884 return false;
18887 if (IsLikelyContentInaccessibleTopLevelAboutBlank()) {
18888 return false;
18891 nsIPrincipal* prin = NodePrincipal();
18892 // TODO(emilio): Should this use GetIsContentPrincipal() +
18893 // GetPrecursorPrincipal() instead (accounting for add-ons separately)?
18894 return !(prin->GetIsAddonOrExpandedAddonPrincipal() ||
18895 prin->IsSystemPrincipal() || prin->SchemeIs("about") ||
18896 prin->SchemeIs("chrome") || prin->SchemeIs("resource"));
18899 void Document::GetConnectedShadowRoots(
18900 nsTArray<RefPtr<ShadowRoot>>& aOut) const {
18901 AppendToArray(aOut, mComposedShadowRoots);
18904 void Document::AddMediaElementWithMSE() {
18905 if (mMediaElementWithMSECount++ == 0) {
18906 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18907 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18912 void Document::RemoveMediaElementWithMSE() {
18913 MOZ_ASSERT(mMediaElementWithMSECount > 0);
18914 if (--mMediaElementWithMSECount == 0) {
18915 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18916 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18921 void Document::UnregisterFromMemoryReportingForDataDocument() {
18922 if (!mAddedToMemoryReportingAsDataDocument) {
18923 return;
18925 mAddedToMemoryReportingAsDataDocument = false;
18926 nsIGlobalObject* global = GetScopeObject();
18927 if (global) {
18928 if (nsPIDOMWindowInner* win = global->GetAsInnerWindow()) {
18929 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
18930 this);
18934 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
18935 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
18936 mOOPChildrenLoading.AppendElement(aChild);
18937 if (mOOPChildrenLoading.Length() == 1) {
18938 // Let's block unload so that we're blocked from going into the BFCache
18939 // until the child has actually notified us that it has done loading.
18940 BlockOnload();
18944 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
18945 // aChild will not be in the list if nsDocLoader::Stop() was called, since
18946 // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
18947 // so we don't need to call DocLoaderIsEmpty in that case.
18948 if (mOOPChildrenLoading.RemoveElement(aChild)) {
18949 if (mOOPChildrenLoading.IsEmpty()) {
18950 UnblockOnload(false);
18952 RefPtr<nsDocLoader> docLoader(mDocumentContainer);
18953 if (docLoader) {
18954 docLoader->OOPChildrenLoadingIsEmpty();
18959 void Document::ClearOOPChildrenLoading() {
18960 nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
18961 mOOPChildrenLoading.SwapElements(oopChildrenLoading);
18962 if (!oopChildrenLoading.IsEmpty()) {
18963 UnblockOnload(false);
18967 bool Document::MayHaveDOMActivateListeners() const {
18968 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
18969 return inner->HasDOMActivateEventListeners();
18972 // If we can't get information from the window object, default to true.
18973 return true;
18976 HighlightRegistry& Document::HighlightRegistry() {
18977 if (!mHighlightRegistry) {
18978 mHighlightRegistry = MakeRefPtr<class HighlightRegistry>(this);
18980 return *mHighlightRegistry;
18983 RadioGroupContainer& Document::OwnedRadioGroupContainer() {
18984 if (!mRadioGroupContainer) {
18985 mRadioGroupContainer = MakeUnique<RadioGroupContainer>();
18987 return *mRadioGroupContainer;
18990 void Document::UpdateHiddenByContentVisibilityForAnimations() {
18991 for (AnimationTimeline* timeline : Timelines()) {
18992 timeline->UpdateHiddenByContentVisibility();
18996 void Document::SetAllowDeclarativeShadowRoots(
18997 bool aAllowDeclarativeShadowRoots) {
18998 mAllowDeclarativeShadowRoots = aAllowDeclarativeShadowRoots;
19001 bool Document::AllowsDeclarativeShadowRoots() const {
19002 return mAllowDeclarativeShadowRoots;
19005 /* static */
19006 already_AddRefed<Document> Document::ParseHTMLUnsafe(GlobalObject& aGlobal,
19007 const nsAString& aHTML) {
19008 nsCOMPtr<nsIURI> uri;
19009 NS_NewURI(getter_AddRefs(uri), "about:blank");
19010 if (!uri) {
19011 return nullptr;
19014 nsCOMPtr<Document> doc;
19015 nsresult rv =
19016 NS_NewHTMLDocument(getter_AddRefs(doc), aGlobal.GetSubjectPrincipal(),
19017 aGlobal.GetSubjectPrincipal());
19018 if (NS_WARN_IF(NS_FAILED(rv))) {
19019 return nullptr;
19022 doc->SetAllowDeclarativeShadowRoots(true);
19023 doc->SetDocumentURI(uri);
19024 rv = nsContentUtils::ParseDocumentHTML(aHTML, doc, false);
19025 if (NS_WARN_IF(NS_FAILED(rv))) {
19026 return nullptr;
19029 return doc.forget();
19032 } // namespace mozilla::dom