Bug 1866566 - If there is change in over-all relevancy, update HiddenByContentVisibil...
[gecko.git] / dom / base / Document.cpp
blobc43f308dc9181027712056522f776cbc70533acd
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/TelemetryHistogramEnums.h"
131 #include "mozilla/TelemetryScalarEnums.h"
132 #include "mozilla/TextControlElement.h"
133 #include "mozilla/TextEditor.h"
134 #include "mozilla/TypedEnumBits.h"
135 #include "mozilla/URLDecorationStripper.h"
136 #include "mozilla/URLExtraData.h"
137 #include "mozilla/Unused.h"
138 #include "mozilla/css/ImageLoader.h"
139 #include "mozilla/css/Loader.h"
140 #include "mozilla/css/Rule.h"
141 #include "mozilla/css/SheetParsingMode.h"
142 #include "mozilla/dom/AnonymousContent.h"
143 #include "mozilla/dom/BlobURLProtocolHandler.h"
144 #include "mozilla/dom/BrowserChild.h"
145 #include "mozilla/dom/BrowsingContext.h"
146 #include "mozilla/dom/BrowsingContextGroup.h"
147 #include "mozilla/dom/CanonicalBrowsingContext.h"
148 #include "mozilla/dom/CanvasRenderingContextHelper.h"
149 #include "mozilla/dom/CDATASection.h"
150 #include "mozilla/dom/CSPDictionariesBinding.h"
151 #include "mozilla/dom/ChromeObserver.h"
152 #include "mozilla/dom/ClientInfo.h"
153 #include "mozilla/dom/ClientState.h"
154 #include "mozilla/dom/Comment.h"
155 #include "mozilla/dom/ContentChild.h"
156 #include "mozilla/dom/DOMImplementation.h"
157 #include "mozilla/dom/DOMIntersectionObserver.h"
158 #include "mozilla/dom/DOMStringList.h"
159 #include "mozilla/dom/DocGroup.h"
160 #include "mozilla/dom/DocumentBinding.h"
161 #include "mozilla/dom/DocumentFragment.h"
162 #include "mozilla/dom/DocumentL10n.h"
163 #include "mozilla/dom/DocumentTimeline.h"
164 #include "mozilla/dom/DocumentType.h"
165 #include "mozilla/dom/ElementBinding.h"
166 #include "mozilla/dom/ErrorEvent.h"
167 #include "mozilla/dom/Event.h"
168 #include "mozilla/dom/EventListenerBinding.h"
169 #include "mozilla/dom/FailedCertSecurityInfoBinding.h"
170 #include "mozilla/dom/FeaturePolicy.h"
171 #include "mozilla/dom/FeaturePolicyUtils.h"
172 #include "mozilla/dom/FontFaceSet.h"
173 #include "mozilla/dom/FromParser.h"
174 #include "mozilla/dom/HighlightRegistry.h"
175 #include "mozilla/dom/HTMLAllCollection.h"
176 #include "mozilla/dom/HTMLBodyElement.h"
177 #include "mozilla/dom/HTMLCollectionBinding.h"
178 #include "mozilla/dom/HTMLDialogElement.h"
179 #include "mozilla/dom/HTMLFormElement.h"
180 #include "mozilla/dom/HTMLIFrameElement.h"
181 #include "mozilla/dom/HTMLImageElement.h"
182 #include "mozilla/dom/HTMLInputElement.h"
183 #include "mozilla/dom/HTMLLinkElement.h"
184 #include "mozilla/dom/HTMLMediaElement.h"
185 #include "mozilla/dom/HTMLMetaElement.h"
186 #include "mozilla/dom/HTMLSharedElement.h"
187 #include "mozilla/dom/HTMLTextAreaElement.h"
188 #include "mozilla/dom/ImageTracker.h"
189 #include "mozilla/dom/Link.h"
190 #include "mozilla/dom/MediaQueryList.h"
191 #include "mozilla/dom/MediaSource.h"
192 #include "mozilla/dom/MutationObservers.h"
193 #include "mozilla/dom/NameSpaceConstants.h"
194 #include "mozilla/dom/Navigator.h"
195 #include "mozilla/dom/NetErrorInfoBinding.h"
196 #include "mozilla/dom/NodeInfo.h"
197 #include "mozilla/dom/NodeIterator.h"
198 #include "mozilla/dom/PContentChild.h"
199 #include "mozilla/dom/PWindowGlobalChild.h"
200 #include "mozilla/dom/PageTransitionEvent.h"
201 #include "mozilla/dom/PageTransitionEventBinding.h"
202 #include "mozilla/dom/Performance.h"
203 #include "mozilla/dom/PermissionMessageUtils.h"
204 #include "mozilla/dom/PostMessageEvent.h"
205 #include "mozilla/dom/ProcessingInstruction.h"
206 #include "mozilla/dom/Promise.h"
207 #include "mozilla/dom/PromiseNativeHandler.h"
208 #include "mozilla/dom/ResizeObserver.h"
209 #include "mozilla/dom/RustTypes.h"
210 #include "mozilla/dom/SVGElement.h"
211 #include "mozilla/dom/SVGDocument.h"
212 #include "mozilla/dom/SVGSVGElement.h"
213 #include "mozilla/dom/SVGUseElement.h"
214 #include "mozilla/dom/ScriptLoader.h"
215 #include "mozilla/dom/ScriptSettings.h"
216 #include "mozilla/dom/Selection.h"
217 #include "mozilla/dom/ServiceWorkerContainer.h"
218 #include "mozilla/dom/ServiceWorkerDescriptor.h"
219 #include "mozilla/dom/ServiceWorkerManager.h"
220 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
221 #include "mozilla/dom/ShadowRoot.h"
222 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
223 #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
224 #include "mozilla/dom/StyleSheetList.h"
225 #include "mozilla/dom/StyleSheetRemovedEvent.h"
226 #include "mozilla/dom/StyleSheetRemovedEventBinding.h"
227 #include "mozilla/dom/TimeoutManager.h"
228 #include "mozilla/dom/ToggleEvent.h"
229 #include "mozilla/dom/Touch.h"
230 #include "mozilla/dom/TouchEvent.h"
231 #include "mozilla/dom/TreeOrderedArrayInlines.h"
232 #include "mozilla/dom/TreeWalker.h"
233 #include "mozilla/dom/URL.h"
234 #include "mozilla/dom/UseCounterMetrics.h"
235 #include "mozilla/dom/UserActivation.h"
236 #include "mozilla/dom/WakeLockJS.h"
237 #include "mozilla/dom/WakeLockSentinel.h"
238 #include "mozilla/dom/WindowBinding.h"
239 #include "mozilla/dom/WindowContext.h"
240 #include "mozilla/dom/WindowGlobalChild.h"
241 #include "mozilla/dom/WindowProxyHolder.h"
242 #include "mozilla/dom/WorkerDocumentListener.h"
243 #include "mozilla/dom/XPathEvaluator.h"
244 #include "mozilla/dom/XPathExpression.h"
245 #include "mozilla/dom/nsCSPContext.h"
246 #include "mozilla/dom/nsCSPUtils.h"
247 #include "mozilla/extensions/WebExtensionPolicy.h"
248 #include "mozilla/fallible.h"
249 #include "mozilla/gfx/BaseCoord.h"
250 #include "mozilla/gfx/BaseSize.h"
251 #include "mozilla/gfx/Coord.h"
252 #include "mozilla/gfx/Point.h"
253 #include "mozilla/gfx/ScaleFactor.h"
254 #include "mozilla/glean/GleanMetrics.h"
255 #include "mozilla/intl/LocaleService.h"
256 #include "mozilla/ipc/IdleSchedulerChild.h"
257 #include "mozilla/ipc/MessageChannel.h"
258 #include "mozilla/net/ChannelEventQueue.h"
259 #include "mozilla/net/CookieJarSettings.h"
260 #include "mozilla/net/NeckoChannelParams.h"
261 #include "mozilla/net/RequestContextService.h"
262 #include "nsAboutProtocolUtils.h"
263 #include "nsAlgorithm.h"
264 #include "nsAttrValue.h"
265 #include "nsAttrValueInlines.h"
266 #include "nsBaseHashtable.h"
267 #include "nsBidiUtils.h"
268 #include "nsCRT.h"
269 #include "nsCSSPropertyID.h"
270 #include "nsCSSProps.h"
271 #include "nsCSSPseudoElements.h"
272 #include "nsCSSRendering.h"
273 #include "nsCanvasFrame.h"
274 #include "nsCaseTreatment.h"
275 #include "nsCharsetSource.h"
276 #include "nsCommandManager.h"
277 #include "nsCommandParams.h"
278 #include "nsComponentManagerUtils.h"
279 #include "nsContentCreatorFunctions.h"
280 #include "nsContentList.h"
281 #include "nsContentPermissionHelper.h"
282 #include "nsContentSecurityUtils.h"
283 #include "nsContentUtils.h"
284 #include "nsCoord.h"
285 #include "nsCycleCollectionNoteChild.h"
286 #include "nsCycleCollectionTraversalCallback.h"
287 #include "nsDOMAttributeMap.h"
288 #include "nsDOMCaretPosition.h"
289 #include "nsDOMNavigationTiming.h"
290 #include "nsDOMString.h"
291 #include "nsDeviceContext.h"
292 #include "nsDocShell.h"
293 #include "nsDocShellLoadTypes.h"
294 #include "nsEffectiveTLDService.h"
295 #include "nsError.h"
296 #include "nsEscape.h"
297 #include "nsFocusManager.h"
298 #include "nsFrameLoader.h"
299 #include "nsFrameLoaderOwner.h"
300 #include "nsGenericHTMLElement.h"
301 #include "nsGlobalWindowInner.h"
302 #include "nsGlobalWindowOuter.h"
303 #include "nsHTMLDocument.h"
304 #include "nsHtml5Module.h"
305 #include "nsHtml5Parser.h"
306 #include "nsHtml5TreeOpExecutor.h"
307 #include "nsIAsyncShutdown.h"
308 #include "nsIAuthPrompt.h"
309 #include "nsIAuthPrompt2.h"
310 #include "nsIBFCacheEntry.h"
311 #include "nsIBaseWindow.h"
312 #include "nsIBrowserChild.h"
313 #include "nsIBrowserUsage.h"
314 #include "nsICSSLoaderObserver.h"
315 #include "nsICategoryManager.h"
316 #include "nsICertOverrideService.h"
317 #include "nsIContent.h"
318 #include "nsIContentInlines.h"
319 #include "nsIContentPolicy.h"
320 #include "nsIContentSecurityPolicy.h"
321 #include "nsIContentSink.h"
322 #include "nsICookieJarSettings.h"
323 #include "nsICookieService.h"
324 #include "nsIDOMXULCommandDispatcher.h"
325 #include "nsIDocShell.h"
326 #include "nsIDocShellTreeItem.h"
327 #include "nsIDocumentActivity.h"
328 #include "nsIDocumentEncoder.h"
329 #include "nsIDocumentLoader.h"
330 #include "nsIDocumentLoaderFactory.h"
331 #include "nsIDocumentObserver.h"
332 #include "nsIDNSService.h"
333 #include "nsIEditingSession.h"
334 #include "nsIEditor.h"
335 #include "nsIEffectiveTLDService.h"
336 #include "nsIFile.h"
337 #include "nsIFileChannel.h"
338 #include "nsIFrame.h"
339 #include "nsIGlobalObject.h"
340 #include "nsIHTMLCollection.h"
341 #include "nsIHttpChannel.h"
342 #include "nsIHttpChannelInternal.h"
343 #include "nsIIOService.h"
344 #include "nsIImageLoadingContent.h"
345 #include "nsIInlineSpellChecker.h"
346 #include "nsIInputStreamChannel.h"
347 #include "nsIInterfaceRequestorUtils.h"
348 #include "nsILayoutHistoryState.h"
349 #include "nsIMultiPartChannel.h"
350 #include "nsIMutationObserver.h"
351 #include "nsINSSErrorsService.h"
352 #include "nsINamed.h"
353 #include "nsINodeList.h"
354 #include "nsIObjectLoadingContent.h"
355 #include "nsIObserverService.h"
356 #include "nsIPermission.h"
357 #include "nsIPrompt.h"
358 #include "nsIPropertyBag2.h"
359 #include "nsIPublicKeyPinningService.h"
360 #include "nsIReferrerInfo.h"
361 #include "nsIRefreshURI.h"
362 #include "nsIRequest.h"
363 #include "nsIRequestContext.h"
364 #include "nsIRunnable.h"
365 #include "nsISHEntry.h"
366 #include "nsIScriptElement.h"
367 #include "nsIScriptError.h"
368 #include "nsIScriptGlobalObject.h"
369 #include "nsIScriptSecurityManager.h"
370 #include "nsISecurityConsoleMessage.h"
371 #include "nsISelectionController.h"
372 #include "nsISerialEventTarget.h"
373 #include "nsISimpleEnumerator.h"
374 #include "nsISiteSecurityService.h"
375 #include "nsISocketProvider.h"
376 #include "nsISpeculativeConnect.h"
377 #include "nsIStructuredCloneContainer.h"
378 #include "nsIThread.h"
379 #include "nsITimedChannel.h"
380 #include "nsITimer.h"
381 #include "nsITransportSecurityInfo.h"
382 #include "nsIURIMutator.h"
383 #include "nsIVariant.h"
384 #include "nsIWeakReference.h"
385 #include "nsIWebNavigation.h"
386 #include "nsIWidget.h"
387 #include "nsIX509Cert.h"
388 #include "nsIX509CertValidity.h"
389 #include "nsIXMLContentSink.h"
390 #include "nsIHTMLContentSink.h"
391 #include "nsIXULRuntime.h"
392 #include "nsImageLoadingContent.h"
393 #include "nsImportModule.h"
394 #include "nsLanguageAtomService.h"
395 #include "nsLayoutUtils.h"
396 #include "nsMimeTypes.h"
397 #include "nsNetCID.h"
398 #include "nsNetUtil.h"
399 #include "nsNodeInfoManager.h"
400 #include "nsObjectLoadingContent.h"
401 #include "nsPIDOMWindowInlines.h"
402 #include "nsPIWindowRoot.h"
403 #include "nsPoint.h"
404 #include "nsPointerHashKeys.h"
405 #include "nsPresContext.h"
406 #include "nsQueryFrame.h"
407 #include "nsQueryObject.h"
408 #include "nsRange.h"
409 #include "nsRect.h"
410 #include "nsRefreshDriver.h"
411 #include "nsSandboxFlags.h"
412 #include "nsSerializationHelper.h"
413 #include "nsServiceManagerUtils.h"
414 #include "nsStringFlags.h"
415 #include "nsStyleUtil.h"
416 #include "nsStringIterator.h"
417 #include "nsStyleSheetService.h"
418 #include "nsStyleStruct.h"
419 #include "nsTextNode.h"
420 #include "nsUnicharUtils.h"
421 #include "nsWrapperCache.h"
422 #include "nsWrapperCacheInlines.h"
423 #include "nsXPCOMCID.h"
424 #include "nsXULAppAPI.h"
425 #include "prthread.h"
426 #include "prtime.h"
427 #include "prtypes.h"
428 #include "xpcpublic.h"
430 // XXX Must be included after mozilla/Encoding.h
431 #include "encoding_rs.h"
433 #include "mozilla/dom/XULBroadcastManager.h"
434 #include "mozilla/dom/XULPersist.h"
435 #include "nsIAppWindow.h"
436 #include "nsXULPrototypeDocument.h"
437 #include "nsXULCommandDispatcher.h"
438 #include "nsXULPopupManager.h"
439 #include "nsIDocShellTreeOwner.h"
441 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
442 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
443 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
444 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
446 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
448 mozilla::LazyLogModule gPageCacheLog("PageCache");
449 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
450 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
451 mozilla::LazyLogModule gUseCountersLog("UseCounters");
453 namespace mozilla {
454 namespace dom {
456 class Document::HeaderData {
457 public:
458 HeaderData(nsAtom* aField, const nsAString& aData)
459 : mField(aField), mData(aData) {}
461 ~HeaderData() {
462 // Delete iteratively to avoid blowing up the stack, though it shouldn't
463 // happen in practice.
464 UniquePtr<HeaderData> next = std::move(mNext);
465 while (next) {
466 next = std::move(next->mNext);
470 RefPtr<nsAtom> mField;
471 nsString mData;
472 UniquePtr<HeaderData> mNext;
475 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
476 nullptr;
478 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
479 static LazyLogModule gCspPRLog("CSP");
480 LazyLogModule gUserInteractionPRLog("UserInteraction");
482 static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
483 nsIHttpChannel** aHttpChannel) {
484 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
485 if (httpChannel) {
486 httpChannel.forget(aHttpChannel);
487 return NS_OK;
490 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
491 if (!multipart) {
492 *aHttpChannel = nullptr;
493 return NS_OK;
496 nsCOMPtr<nsIChannel> baseChannel;
497 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
498 if (NS_WARN_IF(NS_FAILED(rv))) {
499 return rv;
502 httpChannel = do_QueryInterface(baseChannel);
503 httpChannel.forget(aHttpChannel);
505 return NS_OK;
508 } // namespace dom
510 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
512 IdentifierMapEntry::IdentifierMapEntry(
513 const IdentifierMapEntry::DependentAtomOrString* aKey)
514 : mKey(aKey ? *aKey : nullptr) {}
516 void IdentifierMapEntry::Traverse(
517 nsCycleCollectionTraversalCallback* aCallback) {
518 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
519 "mIdentifierMap mNameContentList");
520 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
522 if (mImageElement) {
523 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
524 "mIdentifierMap mImageElement element");
525 nsIContent* imageElement = mImageElement;
526 aCallback->NoteXPCOMChild(imageElement);
530 bool IdentifierMapEntry::IsEmpty() {
531 return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
532 !mImageElement;
535 bool IdentifierMapEntry::HasNameElement() const {
536 return mNameContentList && mNameContentList->Length() != 0;
539 void IdentifierMapEntry::AddContentChangeCallback(
540 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
541 if (!mChangeCallbacks) {
542 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
545 ChangeCallback cc = {aCallback, aData, aForImage};
546 mChangeCallbacks->PutEntry(cc);
549 void IdentifierMapEntry::RemoveContentChangeCallback(
550 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
551 if (!mChangeCallbacks) return;
552 ChangeCallback cc = {aCallback, aData, aForImage};
553 mChangeCallbacks->RemoveEntry(cc);
554 if (mChangeCallbacks->Count() == 0) {
555 mChangeCallbacks = nullptr;
559 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
560 Element* aNewElement,
561 bool aImageOnly) {
562 if (!mChangeCallbacks) return;
564 for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) {
565 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
566 // Don't fire image changes for non-image observers, and don't fire element
567 // changes for image observers when an image override is active.
568 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
569 continue;
572 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
573 iter.Remove();
578 void IdentifierMapEntry::AddIdElement(Element* aElement) {
579 MOZ_ASSERT(aElement, "Must have element");
580 MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
582 size_t index = mIdContentList.Insert(*aElement);
583 if (index == 0) {
584 Element* oldElement = mIdContentList->SafeElementAt(1);
585 FireChangeCallbacks(oldElement, aElement);
589 void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
590 MOZ_ASSERT(aElement, "Missing element");
592 // This should only be called while the document is in an update.
593 // Assertions near the call to this method guarantee this.
595 // This could fire in OOM situations
596 // Only assert this in HTML documents for now as XUL does all sorts of weird
597 // crap.
598 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
599 mIdContentList->Contains(aElement),
600 "Removing id entry that doesn't exist");
602 // XXXbz should this ever Compact() I guess when all the content is gone
603 // we'll just get cleaned up in the natural order of things...
604 Element* currentElement = mIdContentList->SafeElementAt(0);
605 mIdContentList.RemoveElement(*aElement);
606 if (currentElement == aElement) {
607 FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
611 void IdentifierMapEntry::SetImageElement(Element* aElement) {
612 Element* oldElement = GetImageIdElement();
613 mImageElement = aElement;
614 Element* newElement = GetImageIdElement();
615 if (oldElement != newElement) {
616 FireChangeCallbacks(oldElement, newElement, true);
620 void IdentifierMapEntry::ClearAndNotify() {
621 Element* currentElement = mIdContentList->SafeElementAt(0);
622 mIdContentList.Clear();
623 if (currentElement) {
624 FireChangeCallbacks(currentElement, nullptr);
626 mNameContentList = nullptr;
627 if (mImageElement) {
628 SetImageElement(nullptr);
630 mChangeCallbacks = nullptr;
633 namespace dom {
635 class SimpleHTMLCollection final : public nsSimpleContentList,
636 public nsIHTMLCollection {
637 public:
638 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
640 NS_DECL_ISUPPORTS_INHERITED
642 virtual nsINode* GetParentObject() override {
643 return nsSimpleContentList::GetParentObject();
645 virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
646 virtual Element* GetElementAt(uint32_t aIndex) override {
647 return mElements.SafeElementAt(aIndex)->AsElement();
650 virtual Element* GetFirstNamedElement(const nsAString& aName,
651 bool& aFound) override {
652 aFound = false;
653 RefPtr<nsAtom> name = NS_Atomize(aName);
654 for (uint32_t i = 0; i < mElements.Length(); i++) {
655 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
656 Element* element = mElements[i]->AsElement();
657 if (element->GetID() == name ||
658 (element->HasName() &&
659 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
660 aFound = true;
661 return element;
664 return nullptr;
667 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
668 AutoTArray<nsAtom*, 8> atoms;
669 for (uint32_t i = 0; i < mElements.Length(); i++) {
670 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
671 Element* element = mElements[i]->AsElement();
673 nsAtom* id = element->GetID();
674 MOZ_ASSERT(id != nsGkAtoms::_empty);
675 if (id && !atoms.Contains(id)) {
676 atoms.AppendElement(id);
679 if (element->HasName()) {
680 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
681 MOZ_ASSERT(name && name != nsGkAtoms::_empty);
682 if (name && !atoms.Contains(name)) {
683 atoms.AppendElement(name);
688 nsString* names = aNames.AppendElements(atoms.Length());
689 for (uint32_t i = 0; i < atoms.Length(); i++) {
690 atoms[i]->ToString(names[i]);
694 virtual JSObject* GetWrapperPreserveColorInternal() override {
695 return nsWrapperCache::GetWrapperPreserveColor();
697 virtual void PreserveWrapperInternal(
698 nsISupports* aScriptObjectHolder) override {
699 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
701 virtual JSObject* WrapObject(JSContext* aCx,
702 JS::Handle<JSObject*> aGivenProto) override {
703 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
706 using nsBaseContentList::Item;
708 private:
709 virtual ~SimpleHTMLCollection() = default;
712 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
713 nsIHTMLCollection)
715 } // namespace dom
717 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
718 if (!mNameContentList) {
719 mNameContentList = new dom::SimpleHTMLCollection(aNode);
722 mNameContentList->AppendElement(aElement);
725 void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
726 if (mNameContentList) {
727 mNameContentList->RemoveElement(aElement);
731 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const {
732 Element* idElement = GetIdElement();
733 return idElement &&
734 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
737 size_t IdentifierMapEntry::SizeOfExcludingThis(
738 MallocSizeOf aMallocSizeOf) const {
739 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
742 // Helper structs for the content->subdoc map
744 class SubDocMapEntry : public PLDHashEntryHdr {
745 public:
746 // Both of these are strong references
747 dom::Element* mKey; // must be first, to look like PLDHashEntryStub
748 dom::Document* mSubDocument;
751 class OnloadBlocker final : public nsIRequest {
752 public:
753 OnloadBlocker() = default;
755 NS_DECL_ISUPPORTS
756 NS_DECL_NSIREQUEST
758 private:
759 ~OnloadBlocker() = default;
762 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
764 NS_IMETHODIMP
765 OnloadBlocker::GetName(nsACString& aResult) {
766 aResult.AssignLiteral("about:document-onload-blocker");
767 return NS_OK;
770 NS_IMETHODIMP
771 OnloadBlocker::IsPending(bool* _retval) {
772 *_retval = true;
773 return NS_OK;
776 NS_IMETHODIMP
777 OnloadBlocker::GetStatus(nsresult* status) {
778 *status = NS_OK;
779 return NS_OK;
782 NS_IMETHODIMP OnloadBlocker::SetCanceledReason(const nsACString& aReason) {
783 return SetCanceledReasonImpl(aReason);
786 NS_IMETHODIMP OnloadBlocker::GetCanceledReason(nsACString& aReason) {
787 return GetCanceledReasonImpl(aReason);
790 NS_IMETHODIMP OnloadBlocker::CancelWithReason(nsresult aStatus,
791 const nsACString& aReason) {
792 return CancelWithReasonImpl(aStatus, aReason);
794 NS_IMETHODIMP
795 OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
796 NS_IMETHODIMP
797 OnloadBlocker::Suspend(void) { return NS_OK; }
798 NS_IMETHODIMP
799 OnloadBlocker::Resume(void) { return NS_OK; }
801 NS_IMETHODIMP
802 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
803 *aLoadGroup = nullptr;
804 return NS_OK;
807 NS_IMETHODIMP
808 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
810 NS_IMETHODIMP
811 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
812 *aLoadFlags = nsIRequest::LOAD_NORMAL;
813 return NS_OK;
816 NS_IMETHODIMP
817 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
818 return GetTRRModeImpl(aTRRMode);
821 NS_IMETHODIMP
822 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
823 return SetTRRModeImpl(aTRRMode);
826 NS_IMETHODIMP
827 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
829 // ==================================================================
831 namespace dom {
833 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
835 Document* ExternalResourceMap::RequestResource(
836 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
837 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
838 // If we ever start allowing non-same-origin loads here, we might need to do
839 // something interesting with aRequestingPrincipal even for the hashtable
840 // gets.
841 MOZ_ASSERT(aURI, "Must have a URI");
842 MOZ_ASSERT(aRequestingNode, "Must have a node");
843 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
844 *aPendingLoad = nullptr;
845 if (mHaveShutDown) {
846 return nullptr;
849 // First, make sure we strip the ref from aURI.
850 nsCOMPtr<nsIURI> clone;
851 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
852 if (NS_FAILED(rv) || !clone) {
853 return nullptr;
856 ExternalResource* resource;
857 mMap.Get(clone, &resource);
858 if (resource) {
859 return resource->mDocument;
862 bool loadStartSucceeded =
863 mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
864 if (!loadEntry) {
865 loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
867 if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
868 aRequestingNode))) {
869 return false;
873 RefPtr<PendingLoad> load(loadEntry.Data());
874 load.forget(aPendingLoad);
875 return true;
877 if (!loadStartSucceeded) {
878 // Make sure we don't thrash things by trying this load again, since
879 // chances are it failed for good reasons (security check, etc).
880 // This must be done outside the WithEntryHandle functor, as it accesses
881 // mPendingLoads.
882 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
885 return nullptr;
888 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
889 nsTArray<RefPtr<Document>> docs(mMap.Count());
890 for (const auto& entry : mMap.Values()) {
891 if (Document* doc = entry->mDocument) {
892 docs.AppendElement(doc);
896 for (auto& doc : docs) {
897 if (aCallback(*doc) == CallState::Stop) {
898 return;
903 void ExternalResourceMap::Traverse(
904 nsCycleCollectionTraversalCallback* aCallback) const {
905 // mPendingLoads will get cleared out as the requests complete, so
906 // no need to worry about those here.
907 for (const auto& entry : mMap) {
908 ExternalResourceMap::ExternalResource* resource = entry.GetWeak();
910 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
911 "mExternalResourceMap.mMap entry"
912 "->mDocument");
913 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
915 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
916 "mExternalResourceMap.mMap entry"
917 "->mViewer");
918 aCallback->NoteXPCOMChild(resource->mViewer);
920 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
921 "mExternalResourceMap.mMap entry"
922 "->mLoadGroup");
923 aCallback->NoteXPCOMChild(resource->mLoadGroup);
927 void ExternalResourceMap::HideViewers() {
928 for (const auto& entry : mMap) {
929 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
930 if (viewer) {
931 viewer->Hide();
936 void ExternalResourceMap::ShowViewers() {
937 for (const auto& entry : mMap) {
938 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
939 if (viewer) {
940 viewer->Show();
945 void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
946 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
948 if (aFromDoc->IsShowing()) {
949 aToDoc->OnPageShow(true, nullptr);
953 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
954 nsIDocumentViewer* aViewer,
955 nsILoadGroup* aLoadGroup,
956 Document* aDisplayDocument) {
957 MOZ_ASSERT(aURI, "Unexpected call");
958 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
959 "Must have both or neither");
961 RefPtr<PendingLoad> load;
962 mPendingLoads.Remove(aURI, getter_AddRefs(load));
964 nsresult rv = NS_OK;
966 nsCOMPtr<Document> doc;
967 if (aViewer) {
968 doc = aViewer->GetDocument();
969 NS_ASSERTION(doc, "Must have a document");
971 doc->SetDisplayDocument(aDisplayDocument);
973 // Make sure that hiding our viewer will tear down its presentation.
974 aViewer->SetSticky(false);
976 rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
977 if (NS_SUCCEEDED(rv)) {
978 rv = aViewer->Open(nullptr, nullptr);
981 if (NS_FAILED(rv)) {
982 doc = nullptr;
983 aViewer = nullptr;
984 aLoadGroup = nullptr;
988 ExternalResource* newResource =
989 mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get();
991 newResource->mDocument = doc;
992 newResource->mViewer = aViewer;
993 newResource->mLoadGroup = aLoadGroup;
994 if (doc) {
995 if (nsPresContext* pc = doc->GetPresContext()) {
996 pc->RecomputeBrowsingContextDependentData();
998 TransferShowingState(aDisplayDocument, doc);
1001 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
1002 for (uint32_t i = 0; i < obs.Length(); ++i) {
1003 obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
1004 nullptr);
1007 return rv;
1010 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
1011 nsIRequestObserver)
1013 NS_IMETHODIMP
1014 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
1015 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
1016 if (map.HaveShutDown()) {
1017 return NS_BINDING_ABORTED;
1020 nsCOMPtr<nsIDocumentViewer> viewer;
1021 nsCOMPtr<nsILoadGroup> loadGroup;
1022 nsresult rv =
1023 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
1025 // Make sure to do this no matter what
1026 nsresult rv2 =
1027 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
1028 if (NS_FAILED(rv)) {
1029 return rv;
1031 if (NS_FAILED(rv2)) {
1032 mTargetListener = nullptr;
1033 return rv2;
1036 return mTargetListener->OnStartRequest(aRequest);
1039 nsresult ExternalResourceMap::PendingLoad::SetupViewer(
1040 nsIRequest* aRequest, nsIDocumentViewer** aViewer,
1041 nsILoadGroup** aLoadGroup) {
1042 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
1043 *aViewer = nullptr;
1044 *aLoadGroup = nullptr;
1046 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
1047 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
1049 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
1050 if (httpChannel) {
1051 bool requestSucceeded;
1052 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
1053 !requestSucceeded) {
1054 // Bail out on this load, since it looks like we have an HTTP error page
1055 return NS_BINDING_ABORTED;
1059 nsAutoCString type;
1060 chan->GetContentType(type);
1062 nsCOMPtr<nsILoadGroup> loadGroup;
1063 chan->GetLoadGroup(getter_AddRefs(loadGroup));
1065 // Give this document its own loadgroup
1066 nsCOMPtr<nsILoadGroup> newLoadGroup =
1067 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1068 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
1069 newLoadGroup->SetLoadGroup(loadGroup);
1071 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1072 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1074 nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
1075 new LoadgroupCallbacks(callbacks);
1076 newLoadGroup->SetNotificationCallbacks(newCallbacks);
1078 // This is some serious hackery cribbed from docshell
1079 nsCOMPtr<nsICategoryManager> catMan =
1080 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
1081 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
1082 nsCString contractId;
1083 nsresult rv =
1084 catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
1085 NS_ENSURE_SUCCESS(rv, rv);
1086 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
1087 do_GetService(contractId.get());
1088 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
1090 nsCOMPtr<nsIDocumentViewer> viewer;
1091 nsCOMPtr<nsIStreamListener> listener;
1092 rv = docLoaderFactory->CreateInstance(
1093 "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
1094 getter_AddRefs(listener), getter_AddRefs(viewer));
1095 NS_ENSURE_SUCCESS(rv, rv);
1096 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
1098 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
1099 if (!parser) {
1100 /// We don't want to deal with the various fake documents yet
1101 return NS_ERROR_NOT_IMPLEMENTED;
1104 // We can't handle HTML and other weird things here yet.
1105 nsIContentSink* sink = parser->GetContentSink();
1106 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
1107 if (!xmlSink) {
1108 return NS_ERROR_NOT_IMPLEMENTED;
1111 listener.swap(mTargetListener);
1112 viewer.forget(aViewer);
1113 newLoadGroup.forget(aLoadGroup);
1114 return NS_OK;
1117 NS_IMETHODIMP
1118 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
1119 nsIInputStream* aStream,
1120 uint64_t aOffset,
1121 uint32_t aCount) {
1122 // mTargetListener might be null if SetupViewer or AddExternalResource failed.
1123 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
1124 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
1125 return NS_BINDING_ABORTED;
1127 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1130 NS_IMETHODIMP
1131 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
1132 nsresult aStatus) {
1133 // mTargetListener might be null if SetupViewer or AddExternalResource failed
1134 if (mTargetListener) {
1135 nsCOMPtr<nsIStreamListener> listener;
1136 mTargetListener.swap(listener);
1137 return listener->OnStopRequest(aRequest, aStatus);
1140 return NS_OK;
1143 nsresult ExternalResourceMap::PendingLoad::StartLoad(
1144 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
1145 MOZ_ASSERT(aURI, "Must have a URI");
1146 MOZ_ASSERT(aRequestingNode, "Must have a node");
1147 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
1149 nsCOMPtr<nsILoadGroup> loadGroup =
1150 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
1152 nsresult rv = NS_OK;
1153 nsCOMPtr<nsIChannel> channel;
1154 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
1155 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1156 nsIContentPolicy::TYPE_OTHER,
1157 nullptr, // aPerformanceStorage
1158 loadGroup);
1159 NS_ENSURE_SUCCESS(rv, rv);
1161 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1162 if (httpChannel) {
1163 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
1164 Unused << NS_WARN_IF(NS_FAILED(rv));
1167 mURI = aURI;
1169 return channel->AsyncOpen(this);
1172 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
1173 nsIInterfaceRequestor)
1175 #define IMPL_SHIM(_i) \
1176 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
1178 IMPL_SHIM(nsILoadContext)
1179 IMPL_SHIM(nsIProgressEventSink)
1180 IMPL_SHIM(nsIChannelEventSink)
1182 #undef IMPL_SHIM
1184 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
1186 #define TRY_SHIM(_i) \
1187 PR_BEGIN_MACRO \
1188 if (IID_IS(_i)) { \
1189 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
1190 if (!real) { \
1191 return NS_NOINTERFACE; \
1193 nsCOMPtr<_i> shim = new _i##Shim(this, real); \
1194 shim.forget(aSink); \
1195 return NS_OK; \
1197 PR_END_MACRO
1199 NS_IMETHODIMP
1200 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
1201 void** aSink) {
1202 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
1203 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
1204 return mCallbacks->GetInterface(aIID, aSink);
1207 *aSink = nullptr;
1209 TRY_SHIM(nsILoadContext);
1210 TRY_SHIM(nsIProgressEventSink);
1211 TRY_SHIM(nsIChannelEventSink);
1213 return NS_NOINTERFACE;
1216 #undef TRY_SHIM
1217 #undef IID_IS
1219 ExternalResourceMap::ExternalResource::~ExternalResource() {
1220 if (mViewer) {
1221 mViewer->Close(nullptr);
1222 mViewer->Destroy();
1226 // ==================================================================
1227 // =
1228 // ==================================================================
1230 // If we ever have an nsIDocumentObserver notification for stylesheet title
1231 // changes we should update the list from that instead of overriding
1232 // EnsureFresh.
1233 class DOMStyleSheetSetList final : public DOMStringList {
1234 public:
1235 explicit DOMStyleSheetSetList(Document* aDocument);
1237 void Disconnect() { mDocument = nullptr; }
1239 virtual void EnsureFresh() override;
1241 protected:
1242 Document* mDocument; // Our document; weak ref. It'll let us know if it
1243 // dies.
1246 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
1247 : mDocument(aDocument) {
1248 NS_ASSERTION(mDocument, "Must have document!");
1251 void DOMStyleSheetSetList::EnsureFresh() {
1252 MOZ_ASSERT(NS_IsMainThread());
1254 mNames.Clear();
1256 if (!mDocument) {
1257 return; // Spec says "no exceptions", and we have no style sets if we have
1258 // no document, for sure
1261 size_t count = mDocument->SheetCount();
1262 nsAutoString title;
1263 for (size_t index = 0; index < count; index++) {
1264 StyleSheet* sheet = mDocument->SheetAt(index);
1265 NS_ASSERTION(sheet, "Null sheet in sheet list!");
1266 sheet->GetTitle(title);
1267 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
1268 return;
1273 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1275 // ==================================================================
1276 // =
1277 // ==================================================================
1279 Document::InternalCommandDataHashtable*
1280 Document::sInternalCommandDataHashtable = nullptr;
1282 // static
1283 void Document::Shutdown() {
1284 if (sInternalCommandDataHashtable) {
1285 sInternalCommandDataHashtable->Clear();
1286 delete sInternalCommandDataHashtable;
1287 sInternalCommandDataHashtable = nullptr;
1291 Document::Document(const char* aContentType)
1292 : nsINode(nullptr),
1293 DocumentOrShadowRoot(this),
1294 mCharacterSet(WINDOWS_1252_ENCODING),
1295 mCharacterSetSource(0),
1296 mParentDocument(nullptr),
1297 mCachedRootElement(nullptr),
1298 mNodeInfoManager(nullptr),
1299 #ifdef DEBUG
1300 mStyledLinksCleared(false),
1301 #endif
1302 mCachedStateObjectValid(false),
1303 mBlockAllMixedContent(false),
1304 mBlockAllMixedContentPreloads(false),
1305 mUpgradeInsecureRequests(false),
1306 mUpgradeInsecurePreloads(false),
1307 mDevToolsWatchingDOMMutations(false),
1308 mBidiEnabled(false),
1309 mMayNeedFontPrefsUpdate(true),
1310 mMathMLEnabled(false),
1311 mIsInitialDocumentInWindow(false),
1312 mIgnoreDocGroupMismatches(false),
1313 mLoadedAsData(false),
1314 mAddedToMemoryReportingAsDataDocument(false),
1315 mMayStartLayout(true),
1316 mHaveFiredTitleChange(false),
1317 mIsShowing(false),
1318 mVisible(true),
1319 mRemovedFromDocShell(false),
1320 // mAllowDNSPrefetch starts true, so that we can always reliably && it
1321 // with various values that might disable it. Since we never prefetch
1322 // unless we get a window, and in that case the docshell value will get
1323 // &&-ed in, this is safe.
1324 mAllowDNSPrefetch(true),
1325 mIsStaticDocument(false),
1326 mCreatingStaticClone(false),
1327 mHasPrintCallbacks(false),
1328 mInUnlinkOrDeletion(false),
1329 mHasHadScriptHandlingObject(false),
1330 mIsBeingUsedAsImage(false),
1331 mChromeRulesEnabled(false),
1332 mInChromeDocShell(false),
1333 mIsSyntheticDocument(false),
1334 mHasLinksToUpdateRunnable(false),
1335 mFlushingPendingLinkUpdates(false),
1336 mMayHaveDOMMutationObservers(false),
1337 mMayHaveAnimationObservers(false),
1338 mHasCSPDeliveredThroughHeader(false),
1339 mBFCacheDisallowed(false),
1340 mHasHadDefaultView(false),
1341 mStyleSheetChangeEventsEnabled(false),
1342 mDevToolsAnonymousAndShadowEventsEnabled(false),
1343 mIsSrcdocDocument(false),
1344 mHasDisplayDocument(false),
1345 mFontFaceSetDirty(true),
1346 mDidFireDOMContentLoaded(true),
1347 mFrameRequestCallbacksScheduled(false),
1348 mIsTopLevelContentDocument(false),
1349 mIsContentDocument(false),
1350 mDidCallBeginLoad(false),
1351 mEncodingMenuDisabled(false),
1352 mLinksEnabled(true),
1353 mIsSVGGlyphsDocument(false),
1354 mInDestructor(false),
1355 mIsGoingAway(false),
1356 mStyleSetFilled(false),
1357 mQuirkSheetAdded(false),
1358 mContentEditableSheetAdded(false),
1359 mDesignModeSheetAdded(false),
1360 mMayHaveTitleElement(false),
1361 mDOMLoadingSet(false),
1362 mDOMInteractiveSet(false),
1363 mDOMCompleteSet(false),
1364 mAutoFocusFired(false),
1365 mScrolledToRefAlready(false),
1366 mChangeScrollPosWhenScrollingToRef(false),
1367 mDelayFrameLoaderInitialization(false),
1368 mSynchronousDOMContentLoaded(false),
1369 mMaybeServiceWorkerControlled(false),
1370 mAllowZoom(false),
1371 mValidScaleFloat(false),
1372 mValidMinScale(false),
1373 mValidMaxScale(false),
1374 mWidthStrEmpty(false),
1375 mParserAborted(false),
1376 mReportedDocumentUseCounters(false),
1377 mHasReportedShadowDOMUsage(false),
1378 mHasDelayedRefreshEvent(false),
1379 mLoadEventFiring(false),
1380 mSkipLoadEventAfterClose(false),
1381 mDisableCookieAccess(false),
1382 mDisableDocWrite(false),
1383 mTooDeepWriteRecursion(false),
1384 mPendingMaybeEditingStateChanged(false),
1385 mHasBeenEditable(false),
1386 mHasWarnedAboutZoom(false),
1387 mIsRunningExecCommand(false),
1388 mSetCompleteAfterDOMContentLoaded(false),
1389 mDidHitCompleteSheetCache(false),
1390 mUseCountersInitialized(false),
1391 mShouldReportUseCounters(false),
1392 mShouldSendPageUseCounters(false),
1393 mUserHasInteracted(false),
1394 mHasUserInteractionTimerScheduled(false),
1395 mShouldResistFingerprinting(false),
1396 mCloningForSVGUse(false),
1397 mXMLDeclarationBits(0),
1398 mOnloadBlockCount(0),
1399 mWriteLevel(0),
1400 mContentEditableCount(0),
1401 mEditingState(EditingState::eOff),
1402 mCompatMode(eCompatibility_FullStandards),
1403 mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
1404 mAncestorIsLoading(false),
1405 mVisibilityState(dom::VisibilityState::Hidden),
1406 mType(eUnknown),
1407 mDefaultElementType(0),
1408 mAllowXULXBL(eTriUnset),
1409 mSkipDTDSecurityChecks(false),
1410 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
1411 mSandboxFlags(0),
1412 mPartID(0),
1413 mMarkedCCGeneration(0),
1414 mPresShell(nullptr),
1415 mSubtreeModifiedDepth(0),
1416 mPreloadPictureDepth(0),
1417 mEventsSuppressed(0),
1418 mIgnoreDestructiveWritesCounter(0),
1419 mStaticCloneCount(0),
1420 mWindow(nullptr),
1421 mBFCacheEntry(nullptr),
1422 mInSyncOperationCount(0),
1423 mBlockDOMContentLoaded(0),
1424 mUpdateNestLevel(0),
1425 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
1426 mViewportType(Unknown),
1427 mViewportFit(ViewportFitType::Auto),
1428 mSubDocuments(nullptr),
1429 mHeaderData(nullptr),
1430 mServoRestyleRootDirtyBits(0),
1431 mThrowOnDynamicMarkupInsertionCounter(0),
1432 mIgnoreOpensDuringUnloadCounter(0),
1433 mSavedResolution(1.0f),
1434 mSavedResolutionBeforeMVM(1.0f),
1435 mGeneration(0),
1436 mCachedTabSizeGeneration(0),
1437 mNextFormNumber(0),
1438 mNextControlNumber(0),
1439 mPreloadService(this),
1440 mShouldNotifyFetchSuccess(false),
1441 mShouldNotifyFormOrPasswordRemoved(false) {
1442 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
1444 SetIsInDocument();
1445 SetIsConnected(true);
1447 // Create these unconditionally, they will be used to warn about the `zoom`
1448 // property, even if use counters are disabled.
1449 mStyleUseCounters.reset(Servo_UseCounters_Create());
1451 SetContentType(nsDependentCString(aContentType));
1453 // Start out mLastStyleSheetSet as null, per spec
1454 SetDOMStringToNull(mLastStyleSheetSet);
1456 // void state used to differentiate an empty source from an unselected source
1457 mPreloadPictureFoundSource.SetIsVoid(true);
1459 RecomputeLanguageFromCharset();
1461 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
1462 mReferrerInfo = new dom::ReferrerInfo(nullptr);
1465 #ifndef ANDROID
1466 // unused by GeckoView
1467 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
1468 if (NS_WARN_IF(!aWin)) {
1469 return false;
1472 nsIURI* uri = aWin->GetDocumentURI();
1473 if (NS_WARN_IF(!uri)) {
1474 return false;
1476 // getSpec is an expensive operation, hence we first check the scheme
1477 // to see if the caller is actually an about: page.
1478 if (!uri->SchemeIs("about")) {
1479 return false;
1482 nsAutoCString aboutSpec;
1483 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
1484 NS_ENSURE_SUCCESS(rv, false);
1486 return aboutSpec.EqualsASCII(aSpec);
1488 #endif
1490 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
1491 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1492 #ifdef ANDROID
1493 // GeckoView uses data URLs for error pages, so for now just check for any
1494 // error page
1495 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1496 #else
1497 return win && IsAboutErrorPage(win, "neterror");
1498 #endif
1501 bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
1502 JSObject* aObject) {
1503 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1504 #ifdef ANDROID
1505 // GeckoView uses data URLs for error pages, so for now just check for any
1506 // error page
1507 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1508 #else
1509 return win && IsAboutErrorPage(win, "httpsonlyerror");
1510 #endif
1513 already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
1514 bool aIsTemporary, ErrorResult& aError) {
1515 RefPtr<Promise> promise = Promise::Create(GetScopeObject(), aError,
1516 Promise::ePropagateUserInteraction);
1517 if (aError.Failed()) {
1518 return nullptr;
1521 nsresult rv = NS_OK;
1522 if (NS_WARN_IF(!mFailedChannel)) {
1523 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1524 return promise.forget();
1527 nsCOMPtr<nsIURI> failedChannelURI;
1528 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
1529 if (!failedChannelURI) {
1530 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1531 return promise.forget();
1534 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI);
1535 if (!innerURI) {
1536 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1537 return promise.forget();
1540 nsAutoCString host;
1541 innerURI->GetAsciiHost(host);
1542 int32_t port;
1543 innerURI->GetPort(&port);
1545 nsCOMPtr<nsITransportSecurityInfo> tsi;
1546 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1547 if (NS_WARN_IF(NS_FAILED(rv))) {
1548 promise->MaybeReject(rv);
1549 return promise.forget();
1551 if (NS_WARN_IF(!tsi)) {
1552 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1553 return promise.forget();
1556 nsCOMPtr<nsIX509Cert> cert;
1557 rv = tsi->GetServerCert(getter_AddRefs(cert));
1558 if (NS_WARN_IF(NS_FAILED(rv))) {
1559 promise->MaybeReject(rv);
1560 return promise.forget();
1562 if (NS_WARN_IF(!cert)) {
1563 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1564 return promise.forget();
1567 if (XRE_IsContentProcess()) {
1568 ContentChild* cc = ContentChild::GetSingleton();
1569 MOZ_ASSERT(cc);
1570 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1571 cc->SendAddCertException(cert, host, port, attrs, aIsTemporary)
1572 ->Then(GetCurrentSerialEventTarget(), __func__,
1573 [promise](const mozilla::MozPromise<
1574 nsresult, mozilla::ipc::ResponseRejectReason,
1575 true>::ResolveOrRejectValue& aValue) {
1576 if (aValue.IsResolve()) {
1577 promise->MaybeResolve(aValue.ResolveValue());
1578 } else {
1579 promise->MaybeRejectWithUndefined();
1582 return promise.forget();
1585 if (XRE_IsParentProcess()) {
1586 nsCOMPtr<nsICertOverrideService> overrideService =
1587 do_GetService(NS_CERTOVERRIDE_CONTRACTID);
1588 if (!overrideService) {
1589 promise->MaybeReject(NS_ERROR_FAILURE);
1590 return promise.forget();
1593 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1594 rv = overrideService->RememberValidityOverride(host, port, attrs, cert,
1595 aIsTemporary);
1596 if (NS_WARN_IF(NS_FAILED(rv))) {
1597 promise->MaybeReject(rv);
1598 return promise.forget();
1601 promise->MaybeResolveWithUndefined();
1602 return promise.forget();
1605 promise->MaybeReject(NS_ERROR_FAILURE);
1606 return promise.forget();
1609 void Document::ReloadWithHttpsOnlyException() {
1610 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
1611 wgc->SendReloadWithHttpsOnlyException();
1615 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
1616 nsresult rv = NS_OK;
1617 if (NS_WARN_IF(!mFailedChannel)) {
1618 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1619 return;
1622 nsCOMPtr<nsITransportSecurityInfo> tsi;
1623 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1624 if (NS_WARN_IF(NS_FAILED(rv))) {
1625 aRv.Throw(rv);
1626 return;
1628 if (NS_WARN_IF(!tsi)) {
1629 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1630 return;
1633 nsAutoString errorCodeString;
1634 rv = tsi->GetErrorCodeString(errorCodeString);
1635 if (NS_WARN_IF(NS_FAILED(rv))) {
1636 aRv.Throw(rv);
1637 return;
1639 aInfo.mErrorCodeString.Assign(errorCodeString);
1642 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
1643 JSObject* aObject) {
1644 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1645 #ifdef ANDROID
1646 // GeckoView uses data URLs for error pages, so for now just check for any
1647 // error page
1648 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1649 #else
1650 return win && IsAboutErrorPage(win, "certerror");
1651 #endif
1654 bool Document::CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject) {
1655 RefPtr<BasePrincipal> principal =
1656 BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx));
1658 if (!principal) {
1659 return false;
1662 // We allow the privilege SSA to be called from system principal.
1663 if (principal->IsSystemPrincipal()) {
1664 return true;
1667 // We only allow calling the privilege SSA from the content script of the
1668 // webcompat extension.
1669 if (auto* policy = principal->ContentScriptAddonPolicy()) {
1670 nsAutoString addonID;
1671 policy->GetId(addonID);
1673 return addonID.EqualsLiteral("webcompat@mozilla.org");
1676 return false;
1679 bool Document::IsErrorPage() const {
1680 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
1681 return loadInfo && loadInfo->GetLoadErrorPage();
1684 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
1685 ErrorResult& aRv) {
1686 nsresult rv = NS_OK;
1687 if (NS_WARN_IF(!mFailedChannel)) {
1688 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1689 return;
1692 nsCOMPtr<nsITransportSecurityInfo> tsi;
1693 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1694 if (NS_WARN_IF(NS_FAILED(rv))) {
1695 aRv.Throw(rv);
1696 return;
1698 if (NS_WARN_IF(!tsi)) {
1699 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1700 return;
1703 nsAutoString errorCodeString;
1704 rv = tsi->GetErrorCodeString(errorCodeString);
1705 if (NS_WARN_IF(NS_FAILED(rv))) {
1706 aRv.Throw(rv);
1707 return;
1709 aInfo.mErrorCodeString.Assign(errorCodeString);
1711 nsITransportSecurityInfo::OverridableErrorCategory errorCategory;
1712 rv = tsi->GetOverridableErrorCategory(&errorCategory);
1713 if (NS_WARN_IF(NS_FAILED(rv))) {
1714 aRv.Throw(rv);
1715 return;
1717 switch (errorCategory) {
1718 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST:
1719 aInfo.mOverridableErrorCategory =
1720 dom::OverridableErrorCategory::Trust_error;
1721 break;
1722 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN:
1723 aInfo.mOverridableErrorCategory =
1724 dom::OverridableErrorCategory::Domain_mismatch;
1725 break;
1726 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME:
1727 aInfo.mOverridableErrorCategory =
1728 dom::OverridableErrorCategory::Expired_or_not_yet_valid;
1729 break;
1730 default:
1731 aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Unset;
1732 break;
1735 nsCOMPtr<nsIX509Cert> cert;
1736 nsCOMPtr<nsIX509CertValidity> validity;
1737 rv = tsi->GetServerCert(getter_AddRefs(cert));
1738 if (NS_WARN_IF(NS_FAILED(rv))) {
1739 aRv.Throw(rv);
1740 return;
1742 if (NS_WARN_IF(!cert)) {
1743 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1744 return;
1747 rv = cert->GetValidity(getter_AddRefs(validity));
1748 if (NS_WARN_IF(NS_FAILED(rv))) {
1749 aRv.Throw(rv);
1750 return;
1752 if (NS_WARN_IF(!validity)) {
1753 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1754 return;
1757 PRTime validityResult;
1758 rv = validity->GetNotBefore(&validityResult);
1759 if (NS_WARN_IF(NS_FAILED(rv))) {
1760 aRv.Throw(rv);
1761 return;
1763 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1765 rv = validity->GetNotAfter(&validityResult);
1766 if (NS_WARN_IF(NS_FAILED(rv))) {
1767 aRv.Throw(rv);
1768 return;
1770 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1772 nsAutoString issuerCommonName;
1773 nsAutoString certChainPEMString;
1774 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
1775 int64_t maxValidity = std::numeric_limits<int64_t>::max();
1776 int64_t minValidity = 0;
1777 PRTime notBefore, notAfter;
1778 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
1779 rv = tsi->GetFailedCertChain(failedCertArray);
1780 if (NS_WARN_IF(NS_FAILED(rv))) {
1781 aRv.Throw(rv);
1782 return;
1785 if (NS_WARN_IF(failedCertArray.IsEmpty())) {
1786 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1787 return;
1790 for (const auto& certificate : failedCertArray) {
1791 rv = certificate->GetIssuerCommonName(issuerCommonName);
1792 if (NS_WARN_IF(NS_FAILED(rv))) {
1793 aRv.Throw(rv);
1794 return;
1797 rv = certificate->GetValidity(getter_AddRefs(validity));
1798 if (NS_WARN_IF(NS_FAILED(rv))) {
1799 aRv.Throw(rv);
1800 return;
1802 if (NS_WARN_IF(!validity)) {
1803 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1804 return;
1807 rv = validity->GetNotBefore(&notBefore);
1808 if (NS_WARN_IF(NS_FAILED(rv))) {
1809 aRv.Throw(rv);
1810 return;
1813 rv = validity->GetNotAfter(&notAfter);
1814 if (NS_WARN_IF(NS_FAILED(rv))) {
1815 aRv.Throw(rv);
1816 return;
1819 notBefore = std::max(minValidity, notBefore);
1820 notAfter = std::min(maxValidity, notAfter);
1821 nsTArray<uint8_t> certArray;
1822 rv = certificate->GetRawDER(certArray);
1823 if (NS_WARN_IF(NS_FAILED(rv))) {
1824 aRv.Throw(rv);
1825 return;
1828 nsAutoString der64;
1829 rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()),
1830 certArray.Length(), der64);
1831 if (NS_WARN_IF(NS_FAILED(rv))) {
1832 aRv.Throw(rv);
1833 return;
1835 if (!certChainStrings.AppendElement(der64, fallible)) {
1836 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1837 return;
1841 aInfo.mIssuerCommonName.Assign(issuerCommonName);
1842 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
1843 aInfo.mCertValidityRangeNotBefore =
1844 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
1846 int32_t errorCode;
1847 rv = tsi->GetErrorCode(&errorCode);
1848 if (NS_WARN_IF(NS_FAILED(rv))) {
1849 aRv.Throw(rv);
1850 return;
1853 nsCOMPtr<nsINSSErrorsService> nsserr =
1854 do_GetService("@mozilla.org/nss_errors_service;1");
1855 if (NS_WARN_IF(!nsserr)) {
1856 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1857 return;
1859 nsresult res;
1860 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
1861 if (NS_WARN_IF(NS_FAILED(rv))) {
1862 aRv.Throw(rv);
1863 return;
1865 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
1866 if (NS_WARN_IF(NS_FAILED(rv))) {
1867 aRv.Throw(rv);
1868 return;
1871 OriginAttributes attrs;
1872 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1873 nsCOMPtr<nsIURI> aURI;
1874 mFailedChannel->GetURI(getter_AddRefs(aURI));
1875 if (XRE_IsContentProcess()) {
1876 ContentChild* cc = ContentChild::GetSingleton();
1877 MOZ_ASSERT(cc);
1878 cc->SendIsSecureURI(aURI, attrs, &aInfo.mHasHSTS);
1879 } else {
1880 nsCOMPtr<nsISiteSecurityService> sss =
1881 do_GetService(NS_SSSERVICE_CONTRACTID);
1882 if (NS_WARN_IF(!sss)) {
1883 return;
1885 Unused << NS_WARN_IF(
1886 NS_FAILED(sss->IsSecureURI(aURI, attrs, &aInfo.mHasHSTS)));
1888 nsCOMPtr<nsIPublicKeyPinningService> pkps =
1889 do_GetService(NS_PKPSERVICE_CONTRACTID);
1890 if (NS_WARN_IF(!pkps)) {
1891 return;
1893 Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP)));
1896 bool Document::IsAboutPage() const {
1897 return NodePrincipal()->SchemeIs("about");
1900 void Document::ConstructUbiNode(void* storage) {
1901 JS::ubi::Concrete<Document>::construct(storage, this);
1904 void Document::LoadEventFired() {
1905 // Object used to collect some telemetry data so we don't need to query for it
1906 // twice.
1907 glean::perf::PageLoadExtra pageLoadEventData;
1909 // Accumulate timing data located in each document's realm and report to
1910 // telemetry.
1911 AccumulateJSTelemetry(pageLoadEventData);
1913 // Collect page load timings
1914 AccumulatePageLoadTelemetry(pageLoadEventData);
1916 // Record page load event
1917 RecordPageLoadEventTelemetry(pageLoadEventData);
1919 // Release the JS bytecode cache from its wait on the load event, and
1920 // potentially dispatch the encoding of the bytecode.
1921 if (ScriptLoader()) {
1922 ScriptLoader()->LoadEventFired();
1926 static uint32_t ConvertToUnsignedFromDouble(double aNumber) {
1927 return aNumber < 0 ? 0 : static_cast<uint32_t>(aNumber);
1930 void Document::RecordPageLoadEventTelemetry(
1931 glean::perf::PageLoadExtra& aEventTelemetryData) {
1932 // If the page load time is empty, then the content wasn't something we want
1933 // to report (i.e. not a top level document).
1934 if (!aEventTelemetryData.loadTime) {
1935 return;
1937 MOZ_ASSERT(IsTopLevelContentDocument());
1939 nsPIDOMWindowOuter* window = GetWindow();
1940 if (!window) {
1941 return;
1944 nsIDocShell* docshell = window->GetDocShell();
1945 if (!docshell) {
1946 return;
1949 nsAutoCString loadTypeStr;
1950 switch (docshell->GetLoadType()) {
1951 case LOAD_NORMAL:
1952 case LOAD_NORMAL_REPLACE:
1953 case LOAD_NORMAL_BYPASS_CACHE:
1954 case LOAD_NORMAL_BYPASS_PROXY:
1955 case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
1956 loadTypeStr.Append("NORMAL");
1957 break;
1958 case LOAD_HISTORY:
1959 loadTypeStr.Append("HISTORY");
1960 break;
1961 case LOAD_RELOAD_NORMAL:
1962 case LOAD_RELOAD_BYPASS_CACHE:
1963 case LOAD_RELOAD_BYPASS_PROXY:
1964 case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
1965 case LOAD_REFRESH:
1966 case LOAD_REFRESH_REPLACE:
1967 case LOAD_RELOAD_CHARSET_CHANGE:
1968 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
1969 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
1970 loadTypeStr.Append("RELOAD");
1971 break;
1972 case LOAD_LINK:
1973 loadTypeStr.Append("LINK");
1974 break;
1975 case LOAD_STOP_CONTENT:
1976 case LOAD_STOP_CONTENT_AND_REPLACE:
1977 loadTypeStr.Append("STOP");
1978 break;
1979 case LOAD_ERROR_PAGE:
1980 loadTypeStr.Append("ERROR");
1981 break;
1982 default:
1983 loadTypeStr.Append("OTHER");
1984 break;
1987 nsCOMPtr<nsIEffectiveTLDService> tldService =
1988 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
1989 if (tldService && mReferrerInfo &&
1990 (docshell->GetLoadType() & nsIDocShell::LOAD_CMD_NORMAL)) {
1991 nsAutoCString currentBaseDomain, referrerBaseDomain;
1992 nsCOMPtr<nsIURI> referrerURI = mReferrerInfo->GetComputedReferrer();
1993 if (referrerURI) {
1994 auto result = NS_SUCCEEDED(
1995 tldService->GetBaseDomain(referrerURI, 0, referrerBaseDomain));
1996 if (result) {
1997 bool sameOrigin = false;
1998 NodePrincipal()->IsSameOrigin(referrerURI, &sameOrigin);
1999 aEventTelemetryData.sameOriginNav = mozilla::Some(sameOrigin);
2004 aEventTelemetryData.loadType = mozilla::Some(loadTypeStr);
2006 // Sending a glean ping must be done on the parent process.
2007 if (ContentChild* cc = ContentChild::GetSingleton()) {
2008 cc->SendRecordPageLoadEvent(aEventTelemetryData);
2012 void Document::AccumulatePageLoadTelemetry(
2013 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2014 // Interested only in top level documents for real websites that are in the
2015 // foreground.
2016 if (!ShouldIncludeInTelemetry(false) || !IsTopLevelContentDocument() ||
2017 !GetNavigationTiming() ||
2018 !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
2019 return;
2022 if (!GetChannel()) {
2023 return;
2026 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
2027 if (!timedChannel) {
2028 return;
2031 // Default duration is 0, use this to check for bogus negative values.
2032 const TimeDuration zeroDuration;
2034 TimeStamp responseStart;
2035 timedChannel->GetResponseStart(&responseStart);
2037 TimeStamp redirectStart, redirectEnd;
2038 timedChannel->GetRedirectStart(&redirectStart);
2039 timedChannel->GetRedirectEnd(&redirectEnd);
2041 uint8_t redirectCount;
2042 timedChannel->GetRedirectCount(&redirectCount);
2043 if (redirectCount) {
2044 aEventTelemetryDataOut.redirectCount =
2045 mozilla::Some(static_cast<uint32_t>(redirectCount));
2048 if (!redirectStart.IsNull() && !redirectEnd.IsNull()) {
2049 TimeDuration redirectTime = redirectEnd - redirectStart;
2050 if (redirectTime > zeroDuration) {
2051 aEventTelemetryDataOut.redirectTime =
2052 mozilla::Some(static_cast<uint32_t>(redirectTime.ToMilliseconds()));
2056 TimeStamp dnsLookupStart, dnsLookupEnd;
2057 timedChannel->GetDomainLookupStart(&dnsLookupStart);
2058 timedChannel->GetDomainLookupEnd(&dnsLookupEnd);
2060 if (!dnsLookupStart.IsNull() && !dnsLookupEnd.IsNull()) {
2061 TimeDuration dnsLookupTime = dnsLookupEnd - dnsLookupStart;
2062 if (dnsLookupTime > zeroDuration) {
2063 aEventTelemetryDataOut.dnsLookupTime =
2064 mozilla::Some(static_cast<uint32_t>(dnsLookupTime.ToMilliseconds()));
2068 TimeStamp navigationStart =
2069 GetNavigationTiming()->GetNavigationStartTimeStamp();
2071 if (!responseStart || !navigationStart) {
2072 return;
2075 nsAutoCString dnsKey("Native");
2076 nsAutoCString http3Key;
2077 nsAutoCString http3WithPriorityKey;
2078 nsAutoCString earlyHintKey;
2079 nsCOMPtr<nsIHttpChannelInternal> httpChannel =
2080 do_QueryInterface(GetChannel());
2081 if (httpChannel) {
2082 bool resolvedByTRR = false;
2083 Unused << httpChannel->GetIsResolvedByTRR(&resolvedByTRR);
2084 if (resolvedByTRR) {
2085 if (nsCOMPtr<nsIDNSService> dns =
2086 do_GetService(NS_DNSSERVICE_CONTRACTID)) {
2087 dns->GetTRRDomainKey(dnsKey);
2088 } else {
2089 // Failed to get the DNS service.
2090 dnsKey = "(fail)"_ns;
2092 aEventTelemetryDataOut.trrDomain = mozilla::Some(dnsKey);
2095 uint32_t major;
2096 uint32_t minor;
2097 if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
2098 if (major == 3) {
2099 http3Key = "http3"_ns;
2100 nsCOMPtr<nsIHttpChannel> httpChannel2 = do_QueryInterface(GetChannel());
2101 nsCString header;
2102 if (httpChannel2 &&
2103 NS_SUCCEEDED(
2104 httpChannel2->GetResponseHeader("priority"_ns, header)) &&
2105 !header.IsEmpty()) {
2106 http3WithPriorityKey = "with_priority"_ns;
2107 } else {
2108 http3WithPriorityKey = "without_priority"_ns;
2110 } else if (major == 2) {
2111 bool supportHttp3 = false;
2112 if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
2113 supportHttp3 = false;
2115 if (supportHttp3) {
2116 http3Key = "supports_http3"_ns;
2120 aEventTelemetryDataOut.httpVer = mozilla::Some(major);
2123 uint32_t earlyHintType = 0;
2124 Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
2125 if (earlyHintType & LinkStyle::ePRECONNECT) {
2126 earlyHintKey.Append("preconnect_"_ns);
2128 if (earlyHintType & LinkStyle::ePRELOAD) {
2129 earlyHintKey.Append("preload_"_ns);
2130 earlyHintKey.Append(mPreloadService.GetEarlyHintUsed() ? "1"_ns : "0"_ns);
2134 TimeStamp asyncOpen;
2135 timedChannel->GetAsyncOpen(&asyncOpen);
2136 if (asyncOpen) {
2137 Telemetry::AccumulateTimeDelta(Telemetry::DNS_PERF_FIRST_BYTE_MS, dnsKey,
2138 asyncOpen, responseStart);
2141 // First Contentful Composite
2142 if (TimeStamp firstContentfulComposite =
2143 GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) {
2144 Telemetry::AccumulateTimeDelta(Telemetry::PERF_FIRST_CONTENTFUL_PAINT_MS,
2145 navigationStart, firstContentfulComposite);
2147 if (!http3Key.IsEmpty()) {
2148 Telemetry::AccumulateTimeDelta(
2149 Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key,
2150 navigationStart, firstContentfulComposite);
2153 if (!http3WithPriorityKey.IsEmpty()) {
2154 Telemetry::AccumulateTimeDelta(
2155 Telemetry::H3P_PERF_FIRST_CONTENTFUL_PAINT_MS, http3WithPriorityKey,
2156 navigationStart, firstContentfulComposite);
2159 if (!earlyHintKey.IsEmpty()) {
2160 Telemetry::AccumulateTimeDelta(
2161 Telemetry::EH_PERF_FIRST_CONTENTFUL_PAINT_MS, earlyHintKey,
2162 navigationStart, firstContentfulComposite);
2165 Telemetry::AccumulateTimeDelta(
2166 Telemetry::DNS_PERF_FIRST_CONTENTFUL_PAINT_MS, dnsKey, navigationStart,
2167 firstContentfulComposite);
2169 Telemetry::AccumulateTimeDelta(
2170 Telemetry::PERF_FIRST_CONTENTFUL_PAINT_FROM_RESPONSESTART_MS,
2171 responseStart, firstContentfulComposite);
2173 TimeDuration fcpTime = firstContentfulComposite - navigationStart;
2174 if (fcpTime > zeroDuration) {
2175 aEventTelemetryDataOut.fcpTime =
2176 mozilla::Some(static_cast<uint32_t>(fcpTime.ToMilliseconds()));
2180 // 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 // DOM Content Loaded event
2189 if (TimeStamp dclEventStart =
2190 GetNavigationTiming()->GetDOMContentLoadedEventStartTimeStamp()) {
2191 Telemetry::AccumulateTimeDelta(Telemetry::PERF_DOM_CONTENT_LOADED_TIME_MS,
2192 navigationStart, dclEventStart);
2193 Telemetry::AccumulateTimeDelta(
2194 Telemetry::PERF_DOM_CONTENT_LOADED_TIME_FROM_RESPONSESTART_MS,
2195 responseStart, dclEventStart);
2198 // Load event
2199 if (TimeStamp loadEventStart =
2200 GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
2201 Telemetry::AccumulateTimeDelta(Telemetry::PERF_PAGE_LOAD_TIME_MS,
2202 navigationStart, loadEventStart);
2203 if (!http3Key.IsEmpty()) {
2204 Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
2205 http3Key, navigationStart, loadEventStart);
2208 if (!http3WithPriorityKey.IsEmpty()) {
2209 Telemetry::AccumulateTimeDelta(Telemetry::H3P_PERF_PAGE_LOAD_TIME_MS,
2210 http3WithPriorityKey, navigationStart,
2211 loadEventStart);
2214 if (!earlyHintKey.IsEmpty()) {
2215 Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_PAGE_LOAD_TIME_MS,
2216 earlyHintKey, navigationStart,
2217 loadEventStart);
2220 Telemetry::AccumulateTimeDelta(
2221 Telemetry::PERF_PAGE_LOAD_TIME_FROM_RESPONSESTART_MS, responseStart,
2222 loadEventStart);
2224 TimeDuration responseTime = responseStart - navigationStart;
2225 if (responseTime > zeroDuration) {
2226 aEventTelemetryDataOut.responseTime =
2227 mozilla::Some(static_cast<uint32_t>(responseTime.ToMilliseconds()));
2230 TimeDuration loadTime = loadEventStart - navigationStart;
2231 if (loadTime > zeroDuration) {
2232 aEventTelemetryDataOut.loadTime =
2233 mozilla::Some(static_cast<uint32_t>(loadTime.ToMilliseconds()));
2238 void Document::AccumulateJSTelemetry(
2239 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2240 if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry(false)) {
2241 return;
2244 if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
2245 return;
2248 AutoJSContext cx;
2249 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
2250 JSAutoRealm ar(cx, globalObject);
2251 JS::JSTimers timers = JS::GetJSTimers(cx);
2253 if (!timers.executionTime.IsZero()) {
2254 Telemetry::Accumulate(
2255 Telemetry::JS_PAGELOAD_EXECUTION_MS,
2256 ConvertToUnsignedFromDouble(timers.executionTime.ToMilliseconds()));
2257 aEventTelemetryDataOut.jsExecTime = mozilla::Some(
2258 static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
2261 if (!timers.delazificationTime.IsZero()) {
2262 Telemetry::Accumulate(Telemetry::JS_PAGELOAD_DELAZIFICATION_MS,
2263 ConvertToUnsignedFromDouble(
2264 timers.delazificationTime.ToMilliseconds()));
2267 if (!timers.xdrEncodingTime.IsZero()) {
2268 Telemetry::Accumulate(
2269 Telemetry::JS_PAGELOAD_XDR_ENCODING_MS,
2270 ConvertToUnsignedFromDouble(timers.xdrEncodingTime.ToMilliseconds()));
2273 if (!timers.baselineCompileTime.IsZero()) {
2274 Telemetry::Accumulate(Telemetry::JS_PAGELOAD_BASELINE_COMPILE_MS,
2275 ConvertToUnsignedFromDouble(
2276 timers.baselineCompileTime.ToMilliseconds()));
2279 if (!timers.gcTime.IsZero()) {
2280 Telemetry::Accumulate(
2281 Telemetry::JS_PAGELOAD_GC_MS,
2282 ConvertToUnsignedFromDouble(timers.gcTime.ToMilliseconds()));
2285 if (!timers.protectTime.IsZero()) {
2286 Telemetry::Accumulate(
2287 Telemetry::JS_PAGELOAD_PROTECT_MS,
2288 ConvertToUnsignedFromDouble(timers.protectTime.ToMilliseconds()));
2292 Document::~Document() {
2293 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
2294 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
2295 "Can't be top-level and a resource doc at the same time");
2297 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
2299 if (IsTopLevelContentDocument()) {
2300 RemoveToplevelLoadingDocument(this);
2302 // don't report for about: pages
2303 if (!IsAboutPage()) {
2304 if (MOZ_UNLIKELY(mMathMLEnabled)) {
2305 ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
2308 if (IsHTMLDocument()) {
2309 switch (GetCompatibilityMode()) {
2310 case eCompatibility_FullStandards:
2311 Telemetry::AccumulateCategorical(
2312 Telemetry::LABELS_QUIRKS_MODE::FullStandards);
2313 break;
2314 case eCompatibility_AlmostStandards:
2315 Telemetry::AccumulateCategorical(
2316 Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
2317 break;
2318 case eCompatibility_NavQuirks:
2319 Telemetry::AccumulateCategorical(
2320 Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
2321 break;
2322 default:
2323 MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
2324 break;
2330 mInDestructor = true;
2331 mInUnlinkOrDeletion = true;
2333 mozilla::DropJSObjects(this);
2335 // Clear mObservers to keep it in sync with the mutationobserver list
2336 mObservers.Clear();
2338 mIntersectionObservers.Clear();
2340 if (mStyleSheetSetList) {
2341 mStyleSheetSetList->Disconnect();
2344 if (mAnimationController) {
2345 mAnimationController->Disconnect();
2348 MOZ_ASSERT(mTimelines.isEmpty());
2350 mParentDocument = nullptr;
2352 // Kill the subdocument map, doing this will release its strong
2353 // references, if any.
2354 delete mSubDocuments;
2355 mSubDocuments = nullptr;
2357 nsAutoScriptBlocker scriptBlocker;
2359 // Destroy link map now so we don't waste time removing
2360 // links one by one
2361 DestroyElementMaps();
2363 // Invalidate cached array of child nodes
2364 InvalidateChildNodes();
2366 // We should not have child nodes when destructor is called,
2367 // since child nodes keep their owner document alive.
2368 MOZ_ASSERT(!HasChildren());
2370 mCachedRootElement = nullptr;
2372 for (auto& sheets : mAdditionalSheets) {
2373 UnlinkStyleSheets(sheets);
2376 if (mAttributeStyles) {
2377 mAttributeStyles->SetOwningDocument(nullptr);
2380 if (mListenerManager) {
2381 mListenerManager->Disconnect();
2382 UnsetFlags(NODE_HAS_LISTENERMANAGER);
2385 if (mScriptLoader) {
2386 mScriptLoader->DropDocumentReference();
2389 if (mCSSLoader) {
2390 // Could be null here if Init() failed or if we have been unlinked.
2391 mCSSLoader->DropDocumentReference();
2394 if (mStyleImageLoader) {
2395 mStyleImageLoader->DropDocumentReference();
2398 if (mXULBroadcastManager) {
2399 mXULBroadcastManager->DropDocumentReference();
2402 if (mXULPersist) {
2403 mXULPersist->DropDocumentReference();
2406 if (mPermissionDelegateHandler) {
2407 mPermissionDelegateHandler->DropDocumentReference();
2410 mHeaderData = nullptr;
2412 mPendingTitleChangeEvent.Revoke();
2414 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
2415 "must not have media query lists left");
2417 if (mNodeInfoManager) {
2418 mNodeInfoManager->DropDocumentReference();
2421 if (mDocGroup) {
2422 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
2423 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
2426 UnlinkOriginalDocumentIfStatic();
2428 UnregisterFromMemoryReportingForDataDocument();
2431 NS_INTERFACE_TABLE_HEAD(Document)
2432 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
2433 NS_INTERFACE_TABLE_BEGIN
2434 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
2435 NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
2436 NS_INTERFACE_TABLE_ENTRY(Document, Document)
2437 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
2438 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
2439 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
2440 NS_INTERFACE_TABLE_END
2441 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
2442 NS_INTERFACE_MAP_END
2444 NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
2445 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Document, LastRelease())
2447 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2448 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2449 EventListenerManager* elm = tmp->GetExistingListenerManager();
2450 if (elm) {
2451 elm->MarkForCC();
2453 return true;
2455 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2457 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2458 return Element::CanSkipInCC(tmp);
2459 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2461 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2462 return Element::CanSkipThis(tmp);
2463 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2465 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2466 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2467 char name[512];
2468 nsAutoCString loadedAsData;
2469 if (tmp->IsLoadedAsData()) {
2470 loadedAsData.AssignLiteral("data");
2471 } else {
2472 loadedAsData.AssignLiteral("normal");
2474 uint32_t nsid = tmp->GetDefaultNamespaceID();
2475 nsAutoCString uri;
2476 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2477 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2478 "(xhtml)", "(XLink)", "(XSLT)",
2479 "(MathML)", "(RDF)", "(XUL)"};
2480 if (nsid < ArrayLength(kNSURIs)) {
2481 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2482 kNSURIs[nsid], uri.get());
2483 } else {
2484 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2486 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2487 } else {
2488 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2491 if (!nsINode::Traverse(tmp, cb)) {
2492 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2495 tmp->mExternalResourceMap.Traverse(&cb);
2497 // Traverse all Document pointer members.
2498 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2499 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2500 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2501 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2502 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2503 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry)
2505 // Traverse all Document nsCOMPtrs.
2506 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2507 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2508 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2509 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2510 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2512 DocumentOrShadowRoot::Traverse(tmp, cb);
2514 if (tmp->mRadioGroupContainer) {
2515 RadioGroupContainer::Traverse(tmp->mRadioGroupContainer.get(), cb);
2518 for (auto& sheets : tmp->mAdditionalSheets) {
2519 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2522 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2523 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadObserver)
2524 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRememberedSizeObserver)
2525 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2526 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2527 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2528 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2529 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2530 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2531 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker)
2532 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2533 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2534 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2535 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2536 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2537 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2538 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2539 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2540 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2541 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2542 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2543 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2544 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2545 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2546 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2547 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2548 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2549 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
2551 // Traverse all our nsCOMArrays.
2552 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2554 // Traverse animation components
2555 if (tmp->mAnimationController) {
2556 tmp->mAnimationController->Traverse(&cb);
2559 if (tmp->mSubDocuments) {
2560 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2561 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2563 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2564 cb.NoteXPCOMChild(entry->mKey);
2565 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2566 "mSubDocuments entry->mSubDocument");
2567 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2571 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2573 // We own only the items in mDOMMediaQueryLists that have listeners;
2574 // this reference is managed by their AddListener and RemoveListener
2575 // methods.
2576 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2577 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2578 if (mql->HasListeners() &&
2579 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2580 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2581 cb.NoteXPCOMChild(static_cast<EventTarget*>(mql));
2585 // XXX: This should be not needed once bug 1569185 lands.
2586 for (const auto& entry : tmp->mL10nProtoElements) {
2587 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2588 cb.NoteXPCOMChild(entry.GetKey());
2589 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
2592 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2593 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2594 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2595 mPendingFrameStaticClones[i].mStaticCloneOf);
2598 for (auto& tableEntry : tmp->mActiveLocks) {
2599 ImplCycleCollectionTraverse(cb, *tableEntry.GetModifiableData(),
2600 "mActiveLocks entry", 0);
2602 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2604 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2606 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document)
2607 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
2608 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject)
2609 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2611 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2612 tmp->mInUnlinkOrDeletion = true;
2614 tmp->SetStateObject(nullptr);
2616 // Clear out our external resources
2617 tmp->mExternalResourceMap.Shutdown();
2619 nsAutoScriptBlocker scriptBlocker;
2621 nsINode::Unlink(tmp);
2623 while (tmp->HasChildren()) {
2624 // Hold a strong ref to the node when we remove it, because we may be
2625 // the last reference to it.
2626 // If this code changes, change the corresponding code in Document's
2627 // unlink impl and ContentUnbinder::UnbindSubtree.
2628 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2629 tmp->DisconnectChild(child);
2630 child->UnbindFromTree();
2633 tmp->UnlinkOriginalDocumentIfStatic();
2635 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2637 tmp->SetScriptGlobalObject(nullptr);
2639 for (auto& sheets : tmp->mAdditionalSheets) {
2640 tmp->UnlinkStyleSheets(sheets);
2643 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2644 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2645 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadObserver)
2646 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastRememberedSizeObserver)
2647 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2648 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2649 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2650 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry)
2651 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2652 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2653 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2654 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2655 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2656 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2657 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2658 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2659 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker)
2660 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2661 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2662 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2663 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2664 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2665 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2666 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2667 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2668 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2669 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2670 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2671 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2672 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2673 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2674 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2675 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2676 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2677 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2679 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
2680 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
2681 tmp->mDocGroup);
2683 tmp->mDocGroup = nullptr;
2685 if (tmp->IsTopLevelContentDocument()) {
2686 RemoveToplevelLoadingDocument(tmp);
2689 tmp->mParentDocument = nullptr;
2691 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2693 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2695 if (tmp->mListenerManager) {
2696 tmp->mListenerManager->Disconnect();
2697 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2698 tmp->mListenerManager = nullptr;
2701 if (tmp->mStyleSheetSetList) {
2702 tmp->mStyleSheetSetList->Disconnect();
2703 tmp->mStyleSheetSetList = nullptr;
2706 delete tmp->mSubDocuments;
2707 tmp->mSubDocuments = nullptr;
2709 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
2710 MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
2711 "How did we get here without our presshell going away "
2712 "first?");
2714 DocumentOrShadowRoot::Unlink(tmp);
2716 tmp->mRadioGroupContainer = nullptr;
2718 // Document has a pretty complex destructor, so we're going to
2719 // assume that *most* cycles you actually want to break somewhere
2720 // else, and not unlink an awful lot here.
2722 tmp->mExpandoAndGeneration.OwnerUnlinked();
2724 if (tmp->mAnimationController) {
2725 tmp->mAnimationController->Unlink();
2728 tmp->mPendingTitleChangeEvent.Revoke();
2730 if (tmp->mCSSLoader) {
2731 tmp->mCSSLoader->DropDocumentReference();
2732 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2735 // We own only the items in mDOMMediaQueryLists that have listeners;
2736 // this reference is managed by their AddListener and RemoveListener
2737 // methods.
2738 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2739 MediaQueryList* next =
2740 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2741 mql->Disconnect();
2742 mql = next;
2745 tmp->mPendingFrameStaticClones.Clear();
2747 tmp->mActiveLocks.Clear();
2749 tmp->mInUnlinkOrDeletion = false;
2751 tmp->UnregisterFromMemoryReportingForDataDocument();
2753 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2754 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2755 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2756 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2758 nsresult Document::Init(nsIPrincipal* aPrincipal,
2759 nsIPrincipal* aPartitionedPrincipal) {
2760 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2761 return NS_ERROR_ALREADY_INITIALIZED;
2764 // Force initialization.
2765 mOnloadBlocker = new OnloadBlocker();
2766 mStyleImageLoader = new css::ImageLoader(this);
2768 mNodeInfoManager = new nsNodeInfoManager(this, aPrincipal);
2770 // mNodeInfo keeps NodeInfoManager alive!
2771 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2772 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2773 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2774 "Bad NodeType in aNodeInfo");
2776 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2778 mCSSLoader = new css::Loader(this);
2779 // Assume we're not quirky, until we know otherwise
2780 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2782 // If after creation the owner js global is not set for a document
2783 // we use the default compartment for this document, instead of creating
2784 // wrapper in some random compartment when the document is exposed to js
2785 // via some events.
2786 nsCOMPtr<nsIGlobalObject> global =
2787 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2788 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2789 mScopeObject = do_GetWeakReference(global);
2790 MOZ_ASSERT(mScopeObject);
2792 mScriptLoader = new dom::ScriptLoader(this);
2794 // we need to create a policy here so getting the policy within
2795 // ::Policy() can *always* return a non null policy
2796 mFeaturePolicy = new dom::FeaturePolicy(this);
2797 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2799 if (aPrincipal) {
2800 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2801 } else {
2802 RecomputeResistFingerprinting();
2805 return NS_OK;
2808 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2810 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2811 PropertyTable().RemoveAllPropertiesFor(aNode);
2814 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2815 nsCOMPtr<nsIURI> uri;
2816 nsCOMPtr<nsIPrincipal> principal;
2817 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2818 if (aChannel) {
2819 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2820 // nsScriptSecurityManager::GetChannelResultPrincipals.
2821 // Note: this should match the uri used for the OnNewURI call in
2822 // nsDocShell::CreateDocumentViewer.
2823 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2825 nsIScriptSecurityManager* securityManager =
2826 nsContentUtils::GetSecurityManager();
2827 if (securityManager) {
2828 securityManager->GetChannelResultPrincipals(
2829 aChannel, getter_AddRefs(principal),
2830 getter_AddRefs(partitionedPrincipal));
2834 bool equal = principal->Equals(partitionedPrincipal);
2836 principal = MaybeDowngradePrincipal(principal);
2837 if (equal) {
2838 partitionedPrincipal = principal;
2839 } else {
2840 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2843 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2845 // Note that, since mTiming does not change during a reset, the
2846 // navigationStart time remains unchanged and therefore any future new
2847 // timeline will have the same global clock time as the old one.
2848 mDocumentTimeline = nullptr;
2850 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
2851 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
2852 mDocumentBaseURI = baseURI.forget();
2853 mChromeXHRDocBaseURI = nullptr;
2857 mChannel = aChannel;
2858 RecomputeResistFingerprinting();
2861 void Document::DisconnectNodeTree() {
2862 // Delete references to sub-documents and kill the subdocument map,
2863 // if any. This is not strictly needed, but makes the node tree
2864 // teardown a bit faster.
2865 delete mSubDocuments;
2866 mSubDocuments = nullptr;
2868 bool oldVal = mInUnlinkOrDeletion;
2869 mInUnlinkOrDeletion = true;
2870 { // Scope for update
2871 MOZ_AUTO_DOC_UPDATE(this, true);
2873 // Destroy link map now so we don't waste time removing
2874 // links one by one
2875 DestroyElementMaps();
2877 // Invalidate cached array of child nodes
2878 InvalidateChildNodes();
2880 while (HasChildren()) {
2881 nsMutationGuard::DidMutate();
2882 nsCOMPtr<nsIContent> content = GetLastChild();
2883 nsIContent* previousSibling = content->GetPreviousSibling();
2884 DisconnectChild(content);
2885 if (content == mCachedRootElement) {
2886 // Immediately clear mCachedRootElement, now that it's been removed
2887 // from mChildren, so that GetRootElement() will stop returning this
2888 // now-stale value.
2889 mCachedRootElement = nullptr;
2891 MutationObservers::NotifyContentRemoved(this, content, previousSibling);
2892 content->UnbindFromTree();
2894 MOZ_ASSERT(!mCachedRootElement,
2895 "After removing all children, there should be no root elem");
2897 mInUnlinkOrDeletion = oldVal;
2900 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2901 nsIPrincipal* aPrincipal,
2902 nsIPrincipal* aPartitionedPrincipal) {
2903 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2904 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2906 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2907 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2909 mSecurityInfo = nullptr;
2911 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2912 if (!aLoadGroup || group != aLoadGroup) {
2913 mDocumentLoadGroup = nullptr;
2916 DisconnectNodeTree();
2918 // Reset our stylesheets
2919 ResetStylesheetsToURI(aURI);
2921 // Release the listener manager
2922 if (mListenerManager) {
2923 mListenerManager->Disconnect();
2924 mListenerManager = nullptr;
2927 // Release the stylesheets list.
2928 mDOMStyleSheets = nullptr;
2930 // Release our principal after tearing down the document, rather than before.
2931 // This ensures that, during teardown, the document and the dying window
2932 // (which already nulled out its document pointer and cached the principal)
2933 // have matching principals.
2934 SetPrincipals(nullptr, nullptr);
2936 // Clear the original URI so SetDocumentURI sets it.
2937 mOriginalURI = nullptr;
2939 SetDocumentURI(aURI);
2940 mChromeXHRDocURI = nullptr;
2941 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
2942 // mDocumentURI.
2943 mDocumentBaseURI = nullptr;
2944 mChromeXHRDocBaseURI = nullptr;
2946 if (aLoadGroup) {
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 // Check CSP navigate-to
3577 // We need to enforce the CSP of the document that initiated the load,
3578 // which is the CSP to inherit.
3579 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3580 if (cspToInherit) {
3581 bool allowsNavigateTo = false;
3582 rv = cspToInherit->GetAllowsNavigateTo(
3583 mDocumentURI, loadInfo->GetIsFormSubmission(),
3584 !loadInfo->RedirectChain().IsEmpty(), /* aWasRedirected */
3585 true, /* aEnforceWhitelist */
3586 &allowsNavigateTo);
3587 NS_ENSURE_SUCCESS(rv, rv);
3589 if (!allowsNavigateTo) {
3590 aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
3591 return NS_OK;
3595 rv = InitCSP(aChannel);
3596 NS_ENSURE_SUCCESS(rv, rv);
3598 // Initialize FeaturePolicy
3599 rv = InitFeaturePolicy(aChannel);
3600 NS_ENSURE_SUCCESS(rv, rv);
3602 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3603 NS_ENSURE_SUCCESS(rv, rv);
3605 // Generally XFO and CSP frame-ancestors is handled within
3606 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3607 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3608 nsContentPolicyType internalContentType =
3609 loadInfo->InternalContentPolicyType();
3610 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3611 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3612 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3614 nsresult status;
3615 aChannel->GetStatus(&status);
3616 if (status == NS_ERROR_XFO_VIOLATION) {
3617 // stop! ERROR page!
3618 // But before we have to reset the principal of the document
3619 // because the onload() event fires before the error page
3620 // is displayed and we do not want the enclosing document
3621 // to access the contentDocument.
3622 RefPtr<NullPrincipal> nullPrincipal =
3623 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3624 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3625 // and also GetInnerWindow() is still null at this point, before
3626 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3627 // within Document.cpp
3628 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3629 SetPrincipals(nullPrincipal, nullPrincipal);
3633 return NS_OK;
3636 void Document::SetLoadedAsData(bool aLoadedAsData,
3637 bool aConsiderForMemoryReporting) {
3638 mLoadedAsData = aLoadedAsData;
3639 if (aConsiderForMemoryReporting) {
3640 nsIGlobalObject* global = GetScopeObject();
3641 if (global) {
3642 if (nsPIDOMWindowInner* window = global->GetAsInnerWindow()) {
3643 nsGlobalWindowInner::Cast(window)
3644 ->RegisterDataDocumentForMemoryReporting(this);
3650 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3652 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
3654 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3655 return mPreloadCSP;
3658 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3659 mPreloadCSP = aPreloadCSP;
3662 void Document::GetCspJSON(nsString& aJSON) {
3663 aJSON.Truncate();
3665 if (!mCSP) {
3666 dom::CSPPolicies jsonPolicies;
3667 jsonPolicies.ToJSON(aJSON);
3668 return;
3670 mCSP->ToJSON(aJSON);
3673 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3674 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3675 nsAutoString messageTag;
3676 aMessages[i]->GetTag(messageTag);
3678 nsAutoString category;
3679 aMessages[i]->GetCategory(category);
3681 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3682 NS_ConvertUTF16toUTF8(category), this,
3683 nsContentUtils::eSECURITY_PROPERTIES,
3684 NS_ConvertUTF16toUTF8(messageTag).get());
3688 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3689 nsresult rv = NS_OK;
3690 if (!aSpeculative) {
3691 // 1) apply settings from regular CSP
3692 if (mCSP) {
3693 // Set up 'block-all-mixed-content' if not already inherited
3694 // from the parent context or set by any other CSP.
3695 if (!mBlockAllMixedContent) {
3696 bool block = false;
3697 rv = mCSP->GetBlockAllMixedContent(&block);
3698 NS_ENSURE_SUCCESS_VOID(rv);
3699 mBlockAllMixedContent = block;
3701 if (!mBlockAllMixedContentPreloads) {
3702 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3705 // Set up 'upgrade-insecure-requests' if not already inherited
3706 // from the parent context or set by any other CSP.
3707 if (!mUpgradeInsecureRequests) {
3708 bool upgrade = false;
3709 rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
3710 NS_ENSURE_SUCCESS_VOID(rv);
3711 mUpgradeInsecureRequests = upgrade;
3713 if (!mUpgradeInsecurePreloads) {
3714 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3716 // Update csp settings in the parent process
3717 if (auto* wgc = GetWindowGlobalChild()) {
3718 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3719 mUpgradeInsecureRequests);
3722 return;
3725 // 2) apply settings from speculative csp
3726 if (mPreloadCSP) {
3727 if (!mBlockAllMixedContentPreloads) {
3728 bool block = false;
3729 rv = mPreloadCSP->GetBlockAllMixedContent(&block);
3730 NS_ENSURE_SUCCESS_VOID(rv);
3731 mBlockAllMixedContent = block;
3733 if (!mUpgradeInsecurePreloads) {
3734 bool upgrade = false;
3735 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
3736 NS_ENSURE_SUCCESS_VOID(rv);
3737 mUpgradeInsecurePreloads = upgrade;
3742 nsresult Document::InitCSP(nsIChannel* aChannel) {
3743 MOZ_ASSERT(!mScriptGlobalObject,
3744 "CSP must be initialized before mScriptGlobalObject is set!");
3746 // If this is a data document - no need to set CSP.
3747 if (mLoadedAsData) {
3748 return NS_OK;
3751 // If this is an image, no need to set a CSP. Otherwise SVG images
3752 // served with a CSP might block internally applied inline styles.
3753 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3754 if (loadInfo->GetExternalContentPolicyType() ==
3755 ExtContentPolicy::TYPE_IMAGE ||
3756 loadInfo->GetExternalContentPolicyType() ==
3757 ExtContentPolicy::TYPE_IMAGESET) {
3758 return NS_OK;
3761 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3763 // If there is a CSP that needs to be inherited from whatever
3764 // global is considered the client of the document fetch then
3765 // we query it here from the loadinfo in case the newly created
3766 // document needs to inherit the CSP. See:
3767 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3768 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
3769 if (inheritedCSP) {
3770 mCSP = loadInfo->GetCspToInherit();
3773 // If there is no CSP to inherit, then we create a new CSP here so
3774 // that history entries always have the right reference in case a
3775 // Meta CSP gets dynamically added after the history entry has
3776 // already been created.
3777 if (!mCSP) {
3778 mCSP = new nsCSPContext();
3781 // Always overwrite the requesting context of the CSP so that any new
3782 // 'self' keyword added to an inherited CSP translates correctly.
3783 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3784 if (NS_WARN_IF(NS_FAILED(rv))) {
3785 return rv;
3788 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3790 nsCOMPtr<nsIHttpChannel> httpChannel;
3791 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3792 if (NS_WARN_IF(NS_FAILED(rv))) {
3793 return rv;
3796 if (httpChannel) {
3797 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3798 tCspHeaderValue);
3800 Unused << httpChannel->GetResponseHeader(
3801 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3803 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3804 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3806 // Check if this is a document from a WebExtension.
3807 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3808 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3810 // If there's no CSP to apply, go ahead and return early
3811 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
3812 cspROHeaderValue.IsEmpty()) {
3813 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3814 nsCOMPtr<nsIURI> chanURI;
3815 aChannel->GetURI(getter_AddRefs(chanURI));
3816 nsAutoCString aspec;
3817 chanURI->GetAsciiSpec(aspec);
3818 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3819 ("no CSP for document, %s", aspec.get()));
3822 return NS_OK;
3825 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3826 ("Document is an add-on or CSP header specified %p", this));
3828 // ----- if the doc is an addon, apply its CSP.
3829 if (addonPolicy) {
3830 mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
3832 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3833 // Bug 1548468: Move CSP off ExpandedPrincipal
3834 // Currently the LoadInfo holds the source of truth for every resource load
3835 // because LoadInfo::GetCsp() queries the CSP from an ExpandedPrincipal
3836 // (and not from the Client) if the load was triggered by an extension.
3837 auto* basePrin = BasePrincipal::Cast(principal);
3838 if (basePrin->Is<ExpandedPrincipal>()) {
3839 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3843 // ----- if there's a full-strength CSP header, apply it.
3844 if (!cspHeaderValue.IsEmpty()) {
3845 mHasCSPDeliveredThroughHeader = true;
3846 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3847 NS_ENSURE_SUCCESS(rv, rv);
3850 // ----- if there's a report-only CSP header, apply it.
3851 if (!cspROHeaderValue.IsEmpty()) {
3852 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3853 NS_ENSURE_SUCCESS(rv, rv);
3856 // ----- Enforce sandbox policy if supplied in CSP header
3857 // The document may already have some sandbox flags set (e.g. if the document
3858 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3859 // directive, intersect the CSP sandbox flags with the existing flags. This
3860 // corresponds to the _least_ permissive policy.
3861 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3862 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3863 NS_ENSURE_SUCCESS(rv, rv);
3865 // Probably the iframe sandbox attribute already caused the creation of a
3866 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3867 // and no one has been created yet.
3868 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3869 !(mSandboxFlags & SANDBOXED_ORIGIN);
3871 mSandboxFlags |= cspSandboxFlags;
3873 if (needNewNullPrincipal) {
3874 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3875 // Skip setting the content blocking allowlist principal to NullPrincipal.
3876 // The principal is only used to enable/disable trackingprotection via
3877 // permission and can be shared with the top level sandboxed site.
3878 // See Bug 1654546.
3879 SetPrincipals(principal, principal);
3882 ApplySettingsFromCSP(false);
3883 return NS_OK;
3886 static Document* GetInProcessParentDocumentFrom(BrowsingContext* aContext) {
3887 BrowsingContext* parentContext = aContext->GetParent();
3888 if (!parentContext) {
3889 return nullptr;
3892 WindowContext* windowContext = parentContext->GetCurrentWindowContext();
3893 if (!windowContext) {
3894 return nullptr;
3897 return windowContext->GetDocument();
3900 already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
3901 BrowsingContext* browsingContext = GetBrowsingContext();
3902 if (!browsingContext) {
3903 return nullptr;
3905 if (!browsingContext->IsContentSubframe()) {
3906 return nullptr;
3909 HTMLIFrameElement* iframe =
3910 HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement());
3911 if (iframe) {
3912 return do_AddRef(iframe->FeaturePolicy());
3915 if (XRE_IsParentProcess()) {
3916 return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy());
3919 if (Document* parentDocument =
3920 GetInProcessParentDocumentFrom(browsingContext)) {
3921 return do_AddRef(parentDocument->FeaturePolicy());
3924 WindowContext* windowContext = browsingContext->GetCurrentWindowContext();
3925 if (!windowContext) {
3926 return nullptr;
3929 WindowGlobalChild* child = windowContext->GetWindowGlobalChild();
3930 if (!child) {
3931 return nullptr;
3934 return do_AddRef(child->GetContainerFeaturePolicy());
3937 void Document::InitFeaturePolicy() {
3938 MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created");
3940 mFeaturePolicy->ResetDeclaredPolicy();
3942 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
3944 RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
3945 if (parentPolicy) {
3946 // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
3947 mFeaturePolicy->InheritPolicy(parentPolicy);
3948 mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
3952 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
3953 InitFeaturePolicy();
3955 // We don't want to parse the http Feature-Policy header if this pref is off.
3956 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
3957 return NS_OK;
3960 nsCOMPtr<nsIHttpChannel> httpChannel;
3961 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3962 if (NS_WARN_IF(NS_FAILED(rv))) {
3963 return rv;
3966 if (!httpChannel) {
3967 return NS_OK;
3970 // query the policy from the header
3971 nsAutoCString value;
3972 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
3973 if (NS_SUCCEEDED(rv)) {
3974 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
3975 NodePrincipal(), nullptr);
3978 return NS_OK;
3981 void Document::EnsureNotEnteringAndExitFullscreen() {
3982 Document::ClearPendingFullscreenRequests(this);
3983 if (GetFullscreenElement()) {
3984 Document::AsyncExitFullscreen(this);
3988 void Document::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
3989 mReferrerInfo = aReferrerInfo;
3990 mCachedReferrerInfoForInternalCSSAndSVGResources = nullptr;
3991 mCachedURLData = nullptr;
3994 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
3995 MOZ_ASSERT(mReferrerInfo);
3996 MOZ_ASSERT(mPreloadReferrerInfo);
3998 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
3999 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
4000 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
4001 // they have an opaque origin.
4002 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
4003 if (BrowsingContext* bc = GetBrowsingContext()) {
4004 // At this point the document is not fully created and mParentDocument has
4005 // not been set yet,
4006 Document* parentDoc = bc->GetEmbedderElement()
4007 ? bc->GetEmbedderElement()->OwnerDoc()
4008 : nullptr;
4009 if (parentDoc) {
4010 SetReferrerInfo(parentDoc->GetReferrerInfo());
4011 mPreloadReferrerInfo = mReferrerInfo;
4012 return NS_OK;
4015 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
4016 "srcdoc without null principal as toplevel!");
4020 nsCOMPtr<nsIHttpChannel> httpChannel;
4021 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4022 if (NS_WARN_IF(NS_FAILED(rv))) {
4023 return rv;
4026 if (!httpChannel) {
4027 return NS_OK;
4030 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo()) {
4031 SetReferrerInfo(referrerInfo);
4034 // Override policy if we get one from Referrerr-Policy header
4035 mozilla::dom::ReferrerPolicy policy =
4036 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
4037 nsCOMPtr<nsIReferrerInfo> clone =
4038 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
4039 ->CloneWithNewPolicy(policy);
4040 SetReferrerInfo(clone);
4041 mPreloadReferrerInfo = mReferrerInfo;
4042 return NS_OK;
4045 nsresult Document::InitCOEP(nsIChannel* aChannel) {
4046 nsCOMPtr<nsIHttpChannel> httpChannel;
4047 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4048 if (NS_FAILED(rv)) {
4049 return NS_OK;
4052 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
4054 if (!intChannel) {
4055 return NS_OK;
4058 nsILoadInfo::CrossOriginEmbedderPolicy policy =
4059 nsILoadInfo::EMBEDDER_POLICY_NULL;
4060 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(
4061 mTrials.IsEnabled(OriginTrial::CoepCredentialless), &policy))) {
4062 mEmbedderPolicy = Some(policy);
4065 return NS_OK;
4068 void Document::StopDocumentLoad() {
4069 if (mParser) {
4070 mParserAborted = true;
4071 mParser->Terminate();
4075 void Document::SetDocumentURI(nsIURI* aURI) {
4076 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
4077 mDocumentURI = aURI;
4078 nsIURI* newBase = GetDocBaseURI();
4080 mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI);
4082 bool equalBases = false;
4083 // Changing just the ref of a URI does not change how relative URIs would
4084 // resolve wrt to it, so we can treat the bases as equal as long as they're
4085 // equal ignoring the ref.
4086 if (oldBase && newBase) {
4087 oldBase->EqualsExceptRef(newBase, &equalBases);
4088 } else {
4089 equalBases = !oldBase && !newBase;
4092 // If this is the first time we're setting the document's URI, set the
4093 // document's original URI.
4094 if (!mOriginalURI) mOriginalURI = mDocumentURI;
4096 // If changing the document's URI changed the base URI of the document, we
4097 // need to refresh the hrefs of all the links on the page.
4098 if (!equalBases) {
4099 mCachedURLData = nullptr;
4100 RefreshLinkHrefs();
4103 // Recalculate our base domain
4104 mBaseDomain.Truncate();
4105 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
4106 if (thirdPartyUtil) {
4107 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
4110 // Tell our WindowGlobalParent that the document's URI has been changed.
4111 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
4112 wgc->SetDocumentURI(mDocumentURI);
4116 static void GetFormattedTimeString(PRTime aTime,
4117 nsAString& aFormattedTimeString) {
4118 PRExplodedTime prtime;
4119 PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
4120 // "MM/DD/YYYY hh:mm:ss"
4121 char formatedTime[24];
4122 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
4123 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
4124 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
4125 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
4126 } else {
4127 // If we for whatever reason failed to find the last modified time
4128 // (or even the current time), fall back to what NS4.x returned.
4129 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
4133 void Document::GetLastModified(nsAString& aLastModified) const {
4134 if (!mLastModified.IsEmpty()) {
4135 aLastModified.Assign(mLastModified);
4136 } else {
4137 GetFormattedTimeString(PR_Now(), aLastModified);
4141 static void IncrementExpandoGeneration(Document& aDoc) {
4142 ++aDoc.mExpandoAndGeneration.generation;
4145 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
4146 MOZ_ASSERT(
4147 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
4148 "Only put elements that need to be exposed as document['name'] in "
4149 "the named table.");
4151 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
4153 // Null for out-of-memory
4154 if (entry) {
4155 if (!entry->HasNameElement() &&
4156 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4157 IncrementExpandoGeneration(*this);
4159 entry->AddNameElement(this, aElement);
4163 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
4164 // Speed up document teardown
4165 if (mIdentifierMap.Count() == 0) return;
4167 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
4168 if (!entry) // Could be false if the element was anonymous, hence never added
4169 return;
4171 entry->RemoveNameElement(aElement);
4172 if (!entry->HasNameElement() &&
4173 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4174 IncrementExpandoGeneration(*this);
4178 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
4179 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
4181 if (entry) { /* True except on OOM */
4182 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4183 !entry->HasNameElement() &&
4184 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4185 IncrementExpandoGeneration(*this);
4187 entry->AddIdElement(aElement);
4191 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
4192 NS_ASSERTION(aId, "huhwhatnow?");
4194 // Speed up document teardown
4195 if (mIdentifierMap.Count() == 0) {
4196 return;
4199 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
4200 if (!entry) // Can be null for XML elements with changing ids.
4201 return;
4203 entry->RemoveIdElement(aElement);
4204 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4205 !entry->HasNameElement() &&
4206 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4207 IncrementExpandoGeneration(*this);
4209 if (entry->IsEmpty()) {
4210 mIdentifierMap.RemoveEntry(entry);
4214 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
4215 bool aPreload) {
4216 ReferrerPolicyEnum policy =
4217 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
4218 // The empty string "" corresponds to no referrer policy, causing a fallback
4219 // to a referrer policy defined elsewhere.
4220 if (policy == ReferrerPolicy::_empty) {
4221 return;
4224 MOZ_ASSERT(mReferrerInfo);
4225 MOZ_ASSERT(mPreloadReferrerInfo);
4227 if (aPreload) {
4228 mPreloadReferrerInfo =
4229 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
4230 ->CloneWithNewPolicy(policy);
4231 } else {
4232 nsCOMPtr<nsIReferrerInfo> clone =
4233 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
4234 ->CloneWithNewPolicy(policy);
4235 SetReferrerInfo(clone);
4239 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
4240 nsIPrincipal* aNewPartitionedPrincipal) {
4241 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
4242 if (aNewPrincipal && mAllowDNSPrefetch &&
4243 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
4244 if (aNewPrincipal->SchemeIs("https")) {
4245 mAllowDNSPrefetch = false;
4249 mCSSLoader->DeregisterFromSheetCache();
4251 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
4252 mPartitionedPrincipal = aNewPartitionedPrincipal;
4254 mCachedURLData = nullptr;
4256 mCSSLoader->RegisterInSheetCache();
4258 RecomputeResistFingerprinting();
4260 #ifdef DEBUG
4261 // Validate that the docgroup is set correctly by calling its getter and
4262 // triggering its sanity check.
4264 // If we're setting the principal to null, we don't want to perform the check,
4265 // as the document is entering an intermediate state where it does not have a
4266 // principal. It will be given another real principal shortly which we will
4267 // check. It's not unsafe to have a document which has a null principal in the
4268 // same docgroup as another document, so this should not be a problem.
4269 if (aNewPrincipal) {
4270 GetDocGroup();
4272 #endif
4275 #ifdef DEBUG
4276 void Document::AssertDocGroupMatchesKey() const {
4277 // Sanity check that we have an up-to-date and accurate docgroup
4278 // We only check if the principal when we can get the browsing context.
4280 // Note that we can be invoked during cycle collection, so we need to handle
4281 // the browsingcontext being partially unlinked - normally you shouldn't
4282 // null-check `Group()` as it shouldn't return nullptr.
4283 if (!GetBrowsingContext() || !GetBrowsingContext()->Group()) {
4284 return;
4287 if (mDocGroup && mDocGroup->GetBrowsingContextGroup()) {
4288 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
4289 GetBrowsingContext()->Group());
4291 // GetKey() can fail, e.g. after the TLD service has shut down.
4292 nsAutoCString docGroupKey;
4293 nsresult rv = mozilla::dom::DocGroup::GetKey(
4294 NodePrincipal(),
4295 GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated(),
4296 docGroupKey);
4297 if (NS_SUCCEEDED(rv)) {
4298 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
4302 #endif
4304 nsresult Document::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) const {
4305 return SchedulerGroup::Dispatch(std::move(aRunnable));
4308 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
4309 bool aIsTracking) {
4310 if (aIsTracking) {
4311 mTrackingScripts.Insert(aURL);
4312 } else {
4313 MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
4317 bool Document::IsScriptTracking(JSContext* aCx) const {
4318 JS::AutoFilename filename;
4319 if (!JS::DescribeScriptedCaller(aCx, &filename)) {
4320 return false;
4322 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
4325 void Document::GetContentType(nsAString& aContentType) {
4326 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
4329 void Document::SetContentType(const nsACString& aContentType) {
4330 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
4331 aContentType.EqualsLiteral("application/xhtml+xml")) {
4332 mDefaultElementType = kNameSpaceID_XHTML;
4335 mCachedEncoder = nullptr;
4336 mContentType = aContentType;
4339 bool Document::GetAllowPlugins() {
4340 // First, we ask our docshell if it allows plugins.
4341 auto* browsingContext = GetBrowsingContext();
4343 if (browsingContext) {
4344 if (!browsingContext->GetAllowPlugins()) {
4345 return false;
4348 // If the docshell allows plugins, we check whether
4349 // we are sandboxed and plugins should not be allowed.
4350 if (mSandboxFlags & SANDBOXED_PLUGINS) {
4351 return false;
4355 return true;
4358 bool Document::HasPendingInitialTranslation() {
4359 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
4362 bool Document::HasPendingL10nMutations() const {
4363 return mDocumentL10n && mDocumentL10n->HasPendingMutations();
4366 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
4367 JS::Rooted<JSObject*> object(aCx, aObject);
4368 nsCOMPtr<nsIPrincipal> callerPrincipal =
4369 nsContentUtils::SubjectPrincipal(aCx);
4370 nsGlobalWindowInner* win = xpc::WindowOrNull(object);
4371 bool allowed = false;
4372 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
4373 &allowed);
4374 return allowed;
4377 void Document::LocalizationLinkAdded(Element* aLinkElement) {
4378 if (!AllowsL10n()) {
4379 return;
4382 nsAutoString href;
4383 aLinkElement->GetAttr(nsGkAtoms::href, href);
4385 if (!mDocumentL10n) {
4386 Element* elem = GetDocumentElement();
4387 MOZ_DIAGNOSTIC_ASSERT(elem);
4389 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
4390 mDocumentL10n = DocumentL10n::Create(this, isSync);
4391 if (NS_WARN_IF(!mDocumentL10n)) {
4392 return;
4396 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
4398 if (mReadyState >= READYSTATE_INTERACTIVE) {
4399 nsContentUtils::AddScriptRunner(NewRunnableMethod(
4400 "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
4401 &DocumentL10n::TriggerInitialTranslation));
4402 } else {
4403 if (!mDocumentL10n->mBlockingLayout) {
4404 // Our initial translation is going to block layout start. Make sure
4405 // we don't fire the load event until after that stops happening and
4406 // layout has a chance to start.
4407 BlockOnload();
4408 mDocumentL10n->mBlockingLayout = true;
4413 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
4414 if (!AllowsL10n()) {
4415 return;
4418 if (mDocumentL10n) {
4419 nsAutoString href;
4420 aLinkElement->GetAttr(nsGkAtoms::href, href);
4421 uint32_t remaining =
4422 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
4423 if (remaining == 0) {
4424 if (mDocumentL10n->mBlockingLayout) {
4425 mDocumentL10n->mBlockingLayout = false;
4426 UnblockOnload(/* aFireSync = */ false);
4428 mDocumentL10n = nullptr;
4434 * This method should be called once the end of the l10n
4435 * resource container has been parsed.
4437 * In XUL this is the end of the first </linkset>,
4438 * In XHTML/HTML this is the end of </head>.
4440 * This milestone is used to allow for batch
4441 * localization context I/O and building done
4442 * once when all resources in the document have been
4443 * collected.
4445 void Document::OnL10nResourceContainerParsed() {
4446 // XXX: This is a scaffolding for where we might inject prefetch
4447 // in bug 1717241.
4450 void Document::OnParsingCompleted() {
4451 // Let's call it again, in case the resource
4452 // container has not been closed, and only
4453 // now we're closing the document.
4454 OnL10nResourceContainerParsed();
4456 if (mDocumentL10n) {
4457 RefPtr<DocumentL10n> l10n = mDocumentL10n;
4458 l10n->TriggerInitialTranslation();
4462 void Document::InitialTranslationCompleted(bool aL10nCached) {
4463 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4464 // This means we blocked the load event in LocalizationLinkAdded. It's
4465 // important that the load blocker removal here be async, because our caller
4466 // will notify the content sink after us, and we want the content sync's
4467 // work to happen before the load event fires.
4468 mDocumentL10n->mBlockingLayout = false;
4469 UnblockOnload(/* aFireSync = */ false);
4472 mL10nProtoElements.Clear();
4474 nsXULPrototypeDocument* proto = GetPrototype();
4475 if (proto) {
4476 proto->SetIsL10nCached(aL10nCached);
4480 bool Document::AllowsL10n() const {
4481 if (IsStaticDocument()) {
4482 // We don't allow l10n on static documents, because the nodes are already
4483 // cloned translated, and static docs don't get parsed so we never
4484 // TriggerInitialTranslation, etc, so a load blocker would keep hanging
4485 // forever.
4486 return false;
4488 bool allowed = false;
4489 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4490 return allowed;
4493 bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
4494 JSObject* /*unused*/
4496 MOZ_ASSERT(NS_IsMainThread());
4498 return nsContentUtils::IsSystemCaller(aCx) ||
4499 StaticPrefs::dom_animations_api_getAnimations_enabled();
4502 bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
4503 JSObject* /*unused*/
4505 MOZ_ASSERT(NS_IsMainThread());
4507 return nsContentUtils::IsSystemCaller(aCx) ||
4508 StaticPrefs::dom_animations_api_timelines_enabled();
4511 DocumentTimeline* Document::Timeline() {
4512 if (!mDocumentTimeline) {
4513 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4516 return mDocumentTimeline;
4519 SVGSVGElement* Document::GetSVGRootElement() const {
4520 Element* root = GetRootElement();
4521 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4522 return nullptr;
4524 return static_cast<SVGSVGElement*>(root);
4527 /* Return true if the document is in the focused top-level window, and is an
4528 * ancestor of the focused DOMWindow. */
4529 bool Document::HasFocus(ErrorResult& rv) const {
4530 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4531 if (!fm) {
4532 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4533 return false;
4536 BrowsingContext* bc = GetBrowsingContext();
4537 if (!bc) {
4538 return false;
4541 if (!fm->IsInActiveWindow(bc)) {
4542 return false;
4545 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
4548 bool Document::ThisDocumentHasFocus() const {
4549 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4550 return fm && fm->GetFocusedWindow() &&
4551 fm->GetFocusedWindow()->GetExtantDoc() == this;
4554 void Document::GetDesignMode(nsAString& aDesignMode) {
4555 if (IsInDesignMode()) {
4556 aDesignMode.AssignLiteral("on");
4557 } else {
4558 aDesignMode.AssignLiteral("off");
4562 void Document::SetDesignMode(const nsAString& aDesignMode,
4563 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4564 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4567 static void NotifyEditableStateChange(Document& aDoc) {
4568 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4569 nsMutationGuard g;
4570 #endif
4571 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4572 node = node->GetNextNode(&aDoc)) {
4573 if (auto* element = Element::FromNode(node)) {
4574 element->UpdateEditableState(true);
4577 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4580 void Document::SetDesignMode(const nsAString& aDesignMode,
4581 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4582 ErrorResult& rv) {
4583 if (aSubjectPrincipal.isSome() &&
4584 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4585 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4586 return;
4588 const bool editableMode = IsInDesignMode();
4589 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4590 SetEditableFlag(!editableMode);
4591 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4592 // state of all descendant elements of it. Update that now.
4593 NotifyEditableStateChange(*this);
4594 rv = EditingStateChanged();
4598 nsCommandManager* Document::GetMidasCommandManager() {
4599 // check if we have it cached
4600 if (mMidasCommandManager) {
4601 return mMidasCommandManager;
4604 nsPIDOMWindowOuter* window = GetWindow();
4605 if (!window) {
4606 return nullptr;
4609 nsIDocShell* docshell = window->GetDocShell();
4610 if (!docshell) {
4611 return nullptr;
4614 mMidasCommandManager = docshell->GetCommandManager();
4615 return mMidasCommandManager;
4618 // static
4619 void Document::EnsureInitializeInternalCommandDataHashtable() {
4620 if (sInternalCommandDataHashtable) {
4621 return;
4623 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
4624 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4625 // clang-format off
4626 sInternalCommandDataHashtable->InsertOrUpdate(
4627 u"bold"_ns,
4628 InternalCommandData(
4629 "cmd_bold",
4630 Command::FormatBold,
4631 ExecCommandParam::Ignore,
4632 StyleUpdatingCommand::GetInstance,
4633 CommandOnTextEditor::Disabled));
4634 sInternalCommandDataHashtable->InsertOrUpdate(
4635 u"italic"_ns,
4636 InternalCommandData(
4637 "cmd_italic",
4638 Command::FormatItalic,
4639 ExecCommandParam::Ignore,
4640 StyleUpdatingCommand::GetInstance,
4641 CommandOnTextEditor::Disabled));
4642 sInternalCommandDataHashtable->InsertOrUpdate(
4643 u"underline"_ns,
4644 InternalCommandData(
4645 "cmd_underline",
4646 Command::FormatUnderline,
4647 ExecCommandParam::Ignore,
4648 StyleUpdatingCommand::GetInstance,
4649 CommandOnTextEditor::Disabled));
4650 sInternalCommandDataHashtable->InsertOrUpdate(
4651 u"strikethrough"_ns,
4652 InternalCommandData(
4653 "cmd_strikethrough",
4654 Command::FormatStrikeThrough,
4655 ExecCommandParam::Ignore,
4656 StyleUpdatingCommand::GetInstance,
4657 CommandOnTextEditor::Disabled));
4658 sInternalCommandDataHashtable->InsertOrUpdate(
4659 u"subscript"_ns,
4660 InternalCommandData(
4661 "cmd_subscript",
4662 Command::FormatSubscript,
4663 ExecCommandParam::Ignore,
4664 StyleUpdatingCommand::GetInstance,
4665 CommandOnTextEditor::Disabled));
4666 sInternalCommandDataHashtable->InsertOrUpdate(
4667 u"superscript"_ns,
4668 InternalCommandData(
4669 "cmd_superscript",
4670 Command::FormatSuperscript,
4671 ExecCommandParam::Ignore,
4672 StyleUpdatingCommand::GetInstance,
4673 CommandOnTextEditor::Disabled));
4674 sInternalCommandDataHashtable->InsertOrUpdate(
4675 u"cut"_ns,
4676 InternalCommandData(
4677 "cmd_cut",
4678 Command::Cut,
4679 ExecCommandParam::Ignore,
4680 CutCommand::GetInstance,
4681 CommandOnTextEditor::Enabled));
4682 sInternalCommandDataHashtable->InsertOrUpdate(
4683 u"copy"_ns,
4684 InternalCommandData(
4685 "cmd_copy",
4686 Command::Copy,
4687 ExecCommandParam::Ignore,
4688 CopyCommand::GetInstance,
4689 CommandOnTextEditor::Enabled));
4690 sInternalCommandDataHashtable->InsertOrUpdate(
4691 u"paste"_ns,
4692 InternalCommandData(
4693 "cmd_paste",
4694 Command::Paste,
4695 ExecCommandParam::Ignore,
4696 PasteCommand::GetInstance,
4697 CommandOnTextEditor::Enabled));
4698 sInternalCommandDataHashtable->InsertOrUpdate(
4699 u"delete"_ns,
4700 InternalCommandData(
4701 "cmd_deleteCharBackward",
4702 Command::DeleteCharBackward,
4703 ExecCommandParam::Ignore,
4704 DeleteCommand::GetInstance,
4705 CommandOnTextEditor::Enabled));
4706 sInternalCommandDataHashtable->InsertOrUpdate(
4707 u"forwarddelete"_ns,
4708 InternalCommandData(
4709 "cmd_deleteCharForward",
4710 Command::DeleteCharForward,
4711 ExecCommandParam::Ignore,
4712 DeleteCommand::GetInstance,
4713 CommandOnTextEditor::Enabled));
4714 sInternalCommandDataHashtable->InsertOrUpdate(
4715 u"selectall"_ns,
4716 InternalCommandData(
4717 "cmd_selectAll",
4718 Command::SelectAll,
4719 ExecCommandParam::Ignore,
4720 SelectAllCommand::GetInstance,
4721 CommandOnTextEditor::Enabled));
4722 sInternalCommandDataHashtable->InsertOrUpdate(
4723 u"undo"_ns,
4724 InternalCommandData(
4725 "cmd_undo",
4726 Command::HistoryUndo,
4727 ExecCommandParam::Ignore,
4728 UndoCommand::GetInstance,
4729 CommandOnTextEditor::Enabled));
4730 sInternalCommandDataHashtable->InsertOrUpdate(
4731 u"redo"_ns,
4732 InternalCommandData(
4733 "cmd_redo",
4734 Command::HistoryRedo,
4735 ExecCommandParam::Ignore,
4736 RedoCommand::GetInstance,
4737 CommandOnTextEditor::Enabled));
4738 sInternalCommandDataHashtable->InsertOrUpdate(
4739 u"indent"_ns,
4740 InternalCommandData("cmd_indent",
4741 Command::FormatIndent,
4742 ExecCommandParam::Ignore,
4743 IndentCommand::GetInstance,
4744 CommandOnTextEditor::Disabled));
4745 sInternalCommandDataHashtable->InsertOrUpdate(
4746 u"outdent"_ns,
4747 InternalCommandData(
4748 "cmd_outdent",
4749 Command::FormatOutdent,
4750 ExecCommandParam::Ignore,
4751 OutdentCommand::GetInstance,
4752 CommandOnTextEditor::Disabled));
4753 sInternalCommandDataHashtable->InsertOrUpdate(
4754 u"backcolor"_ns,
4755 InternalCommandData(
4756 "cmd_highlight",
4757 Command::FormatBackColor,
4758 ExecCommandParam::String,
4759 HighlightColorStateCommand::GetInstance,
4760 CommandOnTextEditor::Disabled));
4761 sInternalCommandDataHashtable->InsertOrUpdate(
4762 u"hilitecolor"_ns,
4763 InternalCommandData(
4764 "cmd_highlight",
4765 Command::FormatBackColor,
4766 ExecCommandParam::String,
4767 HighlightColorStateCommand::GetInstance,
4768 CommandOnTextEditor::Disabled));
4769 sInternalCommandDataHashtable->InsertOrUpdate(
4770 u"forecolor"_ns,
4771 InternalCommandData(
4772 "cmd_fontColor",
4773 Command::FormatFontColor,
4774 ExecCommandParam::String,
4775 FontColorStateCommand::GetInstance,
4776 CommandOnTextEditor::Disabled));
4777 sInternalCommandDataHashtable->InsertOrUpdate(
4778 u"fontname"_ns,
4779 InternalCommandData(
4780 "cmd_fontFace",
4781 Command::FormatFontName,
4782 ExecCommandParam::String,
4783 FontFaceStateCommand::GetInstance,
4784 CommandOnTextEditor::Disabled));
4785 sInternalCommandDataHashtable->InsertOrUpdate(
4786 u"fontsize"_ns,
4787 InternalCommandData(
4788 "cmd_fontSize",
4789 Command::FormatFontSize,
4790 ExecCommandParam::String,
4791 FontSizeStateCommand::GetInstance,
4792 CommandOnTextEditor::Disabled));
4793 sInternalCommandDataHashtable->InsertOrUpdate(
4794 u"inserthorizontalrule"_ns,
4795 InternalCommandData(
4796 "cmd_insertHR",
4797 Command::InsertHorizontalRule,
4798 ExecCommandParam::Ignore,
4799 InsertTagCommand::GetInstance,
4800 CommandOnTextEditor::Disabled));
4801 sInternalCommandDataHashtable->InsertOrUpdate(
4802 u"createlink"_ns,
4803 InternalCommandData(
4804 "cmd_insertLinkNoUI",
4805 Command::InsertLink,
4806 ExecCommandParam::String,
4807 InsertTagCommand::GetInstance,
4808 CommandOnTextEditor::Disabled));
4809 sInternalCommandDataHashtable->InsertOrUpdate(
4810 u"insertimage"_ns,
4811 InternalCommandData(
4812 "cmd_insertImageNoUI",
4813 Command::InsertImage,
4814 ExecCommandParam::String,
4815 InsertTagCommand::GetInstance,
4816 CommandOnTextEditor::Disabled));
4817 sInternalCommandDataHashtable->InsertOrUpdate(
4818 u"inserthtml"_ns,
4819 InternalCommandData(
4820 "cmd_insertHTML",
4821 Command::InsertHTML,
4822 ExecCommandParam::String,
4823 InsertHTMLCommand::GetInstance,
4824 // TODO: Chromium inserts text content of the document fragment
4825 // created from the param.
4826 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
4827 CommandOnTextEditor::Disabled));
4828 sInternalCommandDataHashtable->InsertOrUpdate(
4829 u"inserttext"_ns,
4830 InternalCommandData(
4831 "cmd_insertText",
4832 Command::InsertText,
4833 ExecCommandParam::String,
4834 InsertPlaintextCommand::GetInstance,
4835 CommandOnTextEditor::Enabled));
4836 sInternalCommandDataHashtable->InsertOrUpdate(
4837 u"justifyleft"_ns,
4838 InternalCommandData(
4839 "cmd_align",
4840 Command::FormatJustifyLeft,
4841 ExecCommandParam::Ignore, // Will be set to "left"
4842 AlignCommand::GetInstance,
4843 CommandOnTextEditor::Disabled));
4844 sInternalCommandDataHashtable->InsertOrUpdate(
4845 u"justifyright"_ns,
4846 InternalCommandData(
4847 "cmd_align",
4848 Command::FormatJustifyRight,
4849 ExecCommandParam::Ignore, // Will be set to "right"
4850 AlignCommand::GetInstance,
4851 CommandOnTextEditor::Disabled));
4852 sInternalCommandDataHashtable->InsertOrUpdate(
4853 u"justifycenter"_ns,
4854 InternalCommandData(
4855 "cmd_align",
4856 Command::FormatJustifyCenter,
4857 ExecCommandParam::Ignore, // Will be set to "center"
4858 AlignCommand::GetInstance,
4859 CommandOnTextEditor::Disabled));
4860 sInternalCommandDataHashtable->InsertOrUpdate(
4861 u"justifyfull"_ns,
4862 InternalCommandData(
4863 "cmd_align",
4864 Command::FormatJustifyFull,
4865 ExecCommandParam::Ignore, // Will be set to "justify"
4866 AlignCommand::GetInstance,
4867 CommandOnTextEditor::Disabled));
4868 sInternalCommandDataHashtable->InsertOrUpdate(
4869 u"removeformat"_ns,
4870 InternalCommandData(
4871 "cmd_removeStyles",
4872 Command::FormatRemove,
4873 ExecCommandParam::Ignore,
4874 RemoveStylesCommand::GetInstance,
4875 CommandOnTextEditor::Disabled));
4876 sInternalCommandDataHashtable->InsertOrUpdate(
4877 u"unlink"_ns,
4878 InternalCommandData(
4879 "cmd_removeLinks",
4880 Command::FormatRemoveLink,
4881 ExecCommandParam::Ignore,
4882 StyleUpdatingCommand::GetInstance,
4883 CommandOnTextEditor::Disabled));
4884 sInternalCommandDataHashtable->InsertOrUpdate(
4885 u"insertorderedlist"_ns,
4886 InternalCommandData(
4887 "cmd_ol",
4888 Command::InsertOrderedList,
4889 ExecCommandParam::Ignore,
4890 ListCommand::GetInstance,
4891 CommandOnTextEditor::Disabled));
4892 sInternalCommandDataHashtable->InsertOrUpdate(
4893 u"insertunorderedlist"_ns,
4894 InternalCommandData(
4895 "cmd_ul",
4896 Command::InsertUnorderedList,
4897 ExecCommandParam::Ignore,
4898 ListCommand::GetInstance,
4899 CommandOnTextEditor::Disabled));
4900 sInternalCommandDataHashtable->InsertOrUpdate(
4901 u"insertparagraph"_ns,
4902 InternalCommandData(
4903 "cmd_insertParagraph",
4904 Command::InsertParagraph,
4905 ExecCommandParam::Ignore,
4906 InsertParagraphCommand::GetInstance,
4907 CommandOnTextEditor::Enabled));
4908 sInternalCommandDataHashtable->InsertOrUpdate(
4909 u"insertlinebreak"_ns,
4910 InternalCommandData(
4911 "cmd_insertLineBreak",
4912 Command::InsertLineBreak,
4913 ExecCommandParam::Ignore,
4914 InsertLineBreakCommand::GetInstance,
4915 CommandOnTextEditor::Enabled));
4916 sInternalCommandDataHashtable->InsertOrUpdate(
4917 u"formatblock"_ns,
4918 InternalCommandData(
4919 "cmd_formatBlock",
4920 Command::FormatBlock,
4921 ExecCommandParam::String,
4922 FormatBlockStateCommand::GetInstance,
4923 CommandOnTextEditor::Disabled));
4924 sInternalCommandDataHashtable->InsertOrUpdate(
4925 u"styleWithCSS"_ns,
4926 InternalCommandData(
4927 "cmd_setDocumentUseCSS",
4928 Command::SetDocumentUseCSS,
4929 ExecCommandParam::Boolean,
4930 SetDocumentStateCommand::GetInstance,
4931 CommandOnTextEditor::FallThrough));
4932 sInternalCommandDataHashtable->InsertOrUpdate(
4933 u"usecss"_ns, // Legacy command
4934 InternalCommandData(
4935 "cmd_setDocumentUseCSS",
4936 Command::SetDocumentUseCSS,
4937 ExecCommandParam::InvertedBoolean,
4938 SetDocumentStateCommand::GetInstance,
4939 CommandOnTextEditor::FallThrough));
4940 sInternalCommandDataHashtable->InsertOrUpdate(
4941 u"contentReadOnly"_ns,
4942 InternalCommandData(
4943 "cmd_setDocumentReadOnly",
4944 Command::SetDocumentReadOnly,
4945 ExecCommandParam::Boolean,
4946 SetDocumentStateCommand::GetInstance,
4947 CommandOnTextEditor::Enabled));
4948 sInternalCommandDataHashtable->InsertOrUpdate(
4949 u"insertBrOnReturn"_ns,
4950 InternalCommandData(
4951 "cmd_insertBrOnReturn",
4952 Command::SetDocumentInsertBROnEnterKeyPress,
4953 ExecCommandParam::Boolean,
4954 SetDocumentStateCommand::GetInstance,
4955 CommandOnTextEditor::FallThrough));
4956 sInternalCommandDataHashtable->InsertOrUpdate(
4957 u"defaultParagraphSeparator"_ns,
4958 InternalCommandData(
4959 "cmd_defaultParagraphSeparator",
4960 Command::SetDocumentDefaultParagraphSeparator,
4961 ExecCommandParam::String,
4962 SetDocumentStateCommand::GetInstance,
4963 CommandOnTextEditor::FallThrough));
4964 sInternalCommandDataHashtable->InsertOrUpdate(
4965 u"enableObjectResizing"_ns,
4966 InternalCommandData(
4967 "cmd_enableObjectResizing",
4968 Command::ToggleObjectResizers,
4969 ExecCommandParam::Boolean,
4970 SetDocumentStateCommand::GetInstance,
4971 CommandOnTextEditor::FallThrough));
4972 sInternalCommandDataHashtable->InsertOrUpdate(
4973 u"enableInlineTableEditing"_ns,
4974 InternalCommandData(
4975 "cmd_enableInlineTableEditing",
4976 Command::ToggleInlineTableEditor,
4977 ExecCommandParam::Boolean,
4978 SetDocumentStateCommand::GetInstance,
4979 CommandOnTextEditor::FallThrough));
4980 sInternalCommandDataHashtable->InsertOrUpdate(
4981 u"enableAbsolutePositionEditing"_ns,
4982 InternalCommandData(
4983 "cmd_enableAbsolutePositionEditing",
4984 Command::ToggleAbsolutePositionEditor,
4985 ExecCommandParam::Boolean,
4986 SetDocumentStateCommand::GetInstance,
4987 CommandOnTextEditor::FallThrough));
4988 sInternalCommandDataHashtable->InsertOrUpdate(
4989 u"enableCompatibleJoinSplitDirection"_ns,
4990 InternalCommandData("cmd_enableCompatibleJoinSplitNodeDirection",
4991 Command::EnableCompatibleJoinSplitNodeDirection,
4992 ExecCommandParam::Boolean,
4993 SetDocumentStateCommand::GetInstance,
4994 CommandOnTextEditor::FallThrough));
4995 #if 0
4996 // with empty string
4997 sInternalCommandDataHashtable->InsertOrUpdate(
4998 u"justifynone"_ns,
4999 InternalCommandData(
5000 "cmd_align",
5001 Command::Undefined,
5002 ExecCommandParam::Ignore,
5003 nullptr,
5004 CommandOnTextEditor::Disabled)); // Not implemented yet.
5005 // REQUIRED SPECIAL REVIEW special review
5006 sInternalCommandDataHashtable->InsertOrUpdate(
5007 u"saveas"_ns,
5008 InternalCommandData(
5009 "cmd_saveAs",
5010 Command::Undefined,
5011 ExecCommandParam::Boolean,
5012 nullptr,
5013 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5014 // REQUIRED SPECIAL REVIEW special review
5015 sInternalCommandDataHashtable->InsertOrUpdate(
5016 u"print"_ns,
5017 InternalCommandData(
5018 "cmd_print",
5019 Command::Undefined,
5020 ExecCommandParam::Boolean,
5021 nullptr,
5022 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5023 #endif // #if 0
5024 // clang-format on
5027 Document::InternalCommandData Document::ConvertToInternalCommand(
5028 const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */,
5029 nsAString* aAdjustedValue /* = nullptr */) {
5030 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
5031 EnsureInitializeInternalCommandDataHashtable();
5032 InternalCommandData commandData;
5033 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
5034 return InternalCommandData();
5036 // Ignore if the command is disabled by a corresponding pref due to Gecko
5037 // specific.
5038 switch (commandData.mCommand) {
5039 case Command::SetDocumentReadOnly:
5040 if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() &&
5041 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) {
5042 return InternalCommandData();
5044 break;
5045 case Command::SetDocumentInsertBROnEnterKeyPress:
5046 MOZ_DIAGNOSTIC_ASSERT(
5047 aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn"));
5048 if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) {
5049 return InternalCommandData();
5051 break;
5052 default:
5053 break;
5055 if (!aAdjustedValue) {
5056 // No further work to do
5057 return commandData;
5059 switch (commandData.mExecCommandParam) {
5060 case ExecCommandParam::Ignore:
5061 // Just have to copy it, no checking
5062 switch (commandData.mCommand) {
5063 case Command::FormatJustifyLeft:
5064 aAdjustedValue->AssignLiteral("left");
5065 break;
5066 case Command::FormatJustifyRight:
5067 aAdjustedValue->AssignLiteral("right");
5068 break;
5069 case Command::FormatJustifyCenter:
5070 aAdjustedValue->AssignLiteral("center");
5071 break;
5072 case Command::FormatJustifyFull:
5073 aAdjustedValue->AssignLiteral("justify");
5074 break;
5075 default:
5076 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
5077 EditorCommandParamType::None);
5078 break;
5080 return commandData;
5082 case ExecCommandParam::Boolean:
5083 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5084 EditorCommandParamType::Bool));
5085 // If this is a boolean value and it's not explicitly false (e.g. no
5086 // value). We default to "true" (see bug 301490).
5087 if (!aValue.LowerCaseEqualsLiteral("false")) {
5088 aAdjustedValue->AssignLiteral("true");
5089 } else {
5090 aAdjustedValue->AssignLiteral("false");
5092 return commandData;
5094 case ExecCommandParam::InvertedBoolean:
5095 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5096 EditorCommandParamType::Bool));
5097 // For old backwards commands we invert the check.
5098 if (aValue.LowerCaseEqualsLiteral("false")) {
5099 aAdjustedValue->AssignLiteral("true");
5100 } else {
5101 aAdjustedValue->AssignLiteral("false");
5103 return commandData;
5105 case ExecCommandParam::String:
5106 MOZ_ASSERT(!!(
5107 EditorCommand::GetParamType(commandData.mCommand) &
5108 (EditorCommandParamType::String | EditorCommandParamType::CString)));
5109 switch (commandData.mCommand) {
5110 case Command::FormatBlock: {
5111 const char16_t* start = aValue.BeginReading();
5112 const char16_t* end = aValue.EndReading();
5113 if (start != end && *start == '<' && *(end - 1) == '>') {
5114 ++start;
5115 --end;
5117 // XXX Should we reorder this array with actual usage?
5118 static const nsStaticAtom* kFormattableBlockTags[] = {
5119 // clang-format off
5120 nsGkAtoms::address,
5121 nsGkAtoms::article,
5122 nsGkAtoms::aside,
5123 nsGkAtoms::blockquote,
5124 nsGkAtoms::dd,
5125 nsGkAtoms::div,
5126 nsGkAtoms::dl,
5127 nsGkAtoms::dt,
5128 nsGkAtoms::footer,
5129 nsGkAtoms::h1,
5130 nsGkAtoms::h2,
5131 nsGkAtoms::h3,
5132 nsGkAtoms::h4,
5133 nsGkAtoms::h5,
5134 nsGkAtoms::h6,
5135 nsGkAtoms::header,
5136 nsGkAtoms::hgroup,
5137 nsGkAtoms::main,
5138 nsGkAtoms::nav,
5139 nsGkAtoms::p,
5140 nsGkAtoms::pre,
5141 nsGkAtoms::section,
5142 // clang-format on
5144 nsAutoString value(nsDependentSubstring(start, end));
5145 ToLowerCase(value);
5146 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
5147 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
5148 if (valueAtom == kTag) {
5149 kTag->ToString(*aAdjustedValue);
5150 return commandData;
5153 return InternalCommandData();
5155 case Command::FormatFontSize: {
5156 // Per editing spec as of April 23, 2012, we need to reject the value
5157 // if it's not a valid floating-point number surrounded by optional
5158 // whitespace. Otherwise, we parse it as a legacy font size. For
5159 // now, we just parse as a legacy font size regardless (matching
5160 // WebKit) -- bug 747879.
5161 int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
5162 if (!size) {
5163 return InternalCommandData();
5165 MOZ_ASSERT(aAdjustedValue->IsEmpty());
5166 aAdjustedValue->AppendInt(size);
5167 return commandData;
5169 case Command::InsertImage:
5170 case Command::InsertLink:
5171 if (aValue.IsEmpty()) {
5172 // Invalid value, return false
5173 return InternalCommandData();
5175 aAdjustedValue->Assign(aValue);
5176 return commandData;
5177 case Command::SetDocumentDefaultParagraphSeparator:
5178 if (!aValue.LowerCaseEqualsLiteral("div") &&
5179 !aValue.LowerCaseEqualsLiteral("p") &&
5180 !aValue.LowerCaseEqualsLiteral("br")) {
5181 // Invalid value
5182 return InternalCommandData();
5184 aAdjustedValue->Assign(aValue);
5185 return commandData;
5186 default:
5187 aAdjustedValue->Assign(aValue);
5188 return commandData;
5191 default:
5192 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
5193 return InternalCommandData();
5197 Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
5198 Document& aDocument, const InternalCommandData& aCommandData)
5199 : mCommandData(aCommandData) {
5200 // We'll retrieve an editor with current DOM tree and layout information.
5201 // However, JS may have already hidden or remove exposed root content of
5202 // the editor. Therefore, we need the latest layout information here.
5203 aDocument.FlushPendingNotifications(FlushType::Layout);
5204 if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) {
5205 mDoNothing = true;
5206 return;
5209 if (nsPresContext* presContext = aDocument.GetPresContext()) {
5210 // Consider context of command handling which is automatically resolved
5211 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5212 // The order is:
5213 // 1. TextEditor if there is an active element and it has TextEditor like
5214 // <input type="text"> or <textarea>.
5215 // 2. HTMLEditor for the document, if there is.
5216 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
5217 if (aCommandData.IsCutOrCopyCommand()) {
5218 // Note that we used to use DocShell to handle `cut` and `copy` command
5219 // for dispatching corresponding events for making possible web apps to
5220 // implement their own editor without editable elements but supports
5221 // standard shortcut keys, etc. In this case, we prefer to use active
5222 // element's editor to keep same behavior.
5223 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5224 } else {
5225 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5226 mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
5227 if (!mActiveEditor) {
5228 mActiveEditor = mHTMLEditor;
5233 // Then, retrieve editor command class instance which should handle it
5234 // and can handle it now.
5235 if (!mActiveEditor) {
5236 // If the command is available without editor, we should redirect the
5237 // command to focused descendant with DocShell.
5238 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5239 mDoNothing = true;
5240 return;
5242 return;
5245 // Otherwise, we should use EditorCommand instance (which is singleton
5246 // instance) when it's enabled.
5247 mEditorCommand = aCommandData.mGetEditorCommandFunc
5248 ? aCommandData.mGetEditorCommandFunc()
5249 : nullptr;
5250 if (!mEditorCommand) {
5251 mDoNothing = true;
5252 mActiveEditor = nullptr;
5253 mHTMLEditor = nullptr;
5254 return;
5257 if (IsCommandEnabled()) {
5258 return;
5261 // If the EditorCommand instance is disabled, we should do nothing if
5262 // the command requires an editor.
5263 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5264 // Do nothing if editor specific commands is disabled (bug 760052).
5265 mDoNothing = true;
5266 return;
5269 // Otherwise, we should redirect it to focused descendant with DocShell.
5270 mEditorCommand = nullptr;
5271 mActiveEditor = nullptr;
5272 mHTMLEditor = nullptr;
5275 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
5276 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
5277 switch (mCommandData.mCommandOnTextEditor) {
5278 case CommandOnTextEditor::Enabled:
5279 return mActiveEditor;
5280 case CommandOnTextEditor::Disabled:
5281 return mActiveEditor && mActiveEditor->IsTextEditor()
5282 ? nullptr
5283 : mActiveEditor.get();
5284 case CommandOnTextEditor::FallThrough:
5285 return mHTMLEditor;
5287 return nullptr;
5290 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
5291 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
5292 // Make sure frames are up to date, since that can affect whether
5293 // we're editable.
5294 doc->FlushPendingNotifications(FlushType::Frames);
5296 EditorBase* targetEditor = GetTargetEditor();
5297 if (targetEditor && targetEditor->IsTextEditor()) {
5298 // FYI: When `disabled` attribute is set, `TextEditor` treats it as
5299 // "readonly" too.
5300 return !targetEditor->IsReadonly();
5302 return aDocument->IsEditingOn();
5305 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
5306 EditorBase* targetEditor = GetTargetEditor();
5307 if (!targetEditor) {
5308 return false;
5310 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5311 return MOZ_KnownLive(mEditorCommand)
5312 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
5315 nsresult Document::AutoEditorCommandTarget::DoCommand(
5316 nsIPrincipal* aPrincipal) const {
5317 MOZ_ASSERT(!DoNothing());
5318 MOZ_ASSERT(mEditorCommand);
5319 EditorBase* targetEditor = GetTargetEditor();
5320 if (!targetEditor) {
5321 return NS_SUCCESS_DOM_NO_OPERATION;
5323 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5324 return MOZ_KnownLive(mEditorCommand)
5325 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
5326 aPrincipal);
5329 template <typename ParamType>
5330 nsresult Document::AutoEditorCommandTarget::DoCommandParam(
5331 const ParamType& aParam, nsIPrincipal* aPrincipal) const {
5332 MOZ_ASSERT(!DoNothing());
5333 MOZ_ASSERT(mEditorCommand);
5334 EditorBase* targetEditor = GetTargetEditor();
5335 if (!targetEditor) {
5336 return NS_SUCCESS_DOM_NO_OPERATION;
5338 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5339 return MOZ_KnownLive(mEditorCommand)
5340 ->DoCommandParam(mCommandData.mCommand, aParam,
5341 MOZ_KnownLive(*targetEditor), aPrincipal);
5344 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
5345 nsCommandParams& aParams) const {
5346 MOZ_ASSERT(mEditorCommand);
5347 EditorBase* targetEditor = GetTargetEditor();
5348 if (!targetEditor) {
5349 return NS_OK;
5351 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5352 return MOZ_KnownLive(mEditorCommand)
5353 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
5354 MOZ_KnownLive(targetEditor), nullptr);
5357 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
5358 const nsAString& aValue,
5359 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
5360 // Only allow on HTML documents.
5361 if (!IsHTMLOrXHTML()) {
5362 aRv.ThrowInvalidStateError(
5363 "execCommand is only supported on HTML documents");
5364 return false;
5366 // Otherwise, don't throw exception for compatibility with Chrome.
5368 // if they are requesting UI from us, let's fail since we have no UI
5369 if (aShowUI) {
5370 return false;
5373 // If we're running an execCommand, we should just return false.
5374 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
5375 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
5376 mIsRunningExecCommand) {
5377 return false;
5380 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
5381 // this might add some ugly JS dependencies?
5383 nsAutoString adjustedValue;
5384 InternalCommandData commandData =
5385 ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
5386 switch (commandData.mCommand) {
5387 case Command::DoNothing:
5388 return false;
5389 case Command::SetDocumentReadOnly:
5390 SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly);
5391 break;
5392 case Command::EnableCompatibleJoinSplitNodeDirection:
5393 // We didn't allow to enable the legacy behavior once we've enabled the
5394 // new behavior by default. For keeping the behavior at supporting both
5395 // mode, we should keep returning `false` if the web app to enable the
5396 // legacy mode. Additionally, we don't support the legacy direction
5397 // anymore. Therefore, we can return `false` here even if the caller is
5398 // an addon or chrome script.
5399 if (!adjustedValue.EqualsLiteral("true")) {
5400 return false;
5402 break;
5403 default:
5404 break;
5407 // Do security check first.
5408 if (commandData.IsCutOrCopyCommand()) {
5409 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
5410 // We have rejected the event due to it not being performed in an
5411 // input-driven context therefore, we report the error to the console.
5412 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
5413 this, nsContentUtils::eDOM_PROPERTIES,
5414 "ExecCommandCutCopyDeniedNotInputDriven");
5415 return false;
5417 } else if (commandData.IsPasteCommand()) {
5418 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
5419 nsGkAtoms::clipboardRead)) {
5420 return false;
5424 AutoRunningExecCommandMarker markRunningExecCommand(*this);
5426 // Next, consider context of command handling which is automatically resolved
5427 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5428 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5429 if (commandData.IsAvailableOnlyWhenEditable() &&
5430 !editCommandTarget.IsEditable(this)) {
5431 return false;
5434 if (editCommandTarget.DoNothing()) {
5435 return false;
5438 // If we cannot use EditorCommand instance directly, we need to handle the
5439 // command with traditional path (i.e., with DocShell or nsCommandManager).
5440 if (!editCommandTarget.IsEditor()) {
5441 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
5443 // Special case clipboard write commands like Command::Cut and
5444 // Command::Copy. For such commands, we need the behaviour from
5445 // nsWindowRoot::GetControllers() which is to look at the focused element,
5446 // and defer to a focused textbox's controller. The code past taken by
5447 // other commands in ExecCommand() always uses the window directly, rather
5448 // than deferring to the textbox, which is desireable for most editor
5449 // commands, but not these commands (as those should allow copying out of
5450 // embedded editors). This behaviour is invoked if we call DoCommand()
5451 // directly on the docShell.
5452 // XXX This means that we allow web app to pick up selected content in
5453 // descendant document and write it into the clipboard when a
5454 // descendant document has focus. However, Chromium does not allow
5455 // this and this seems that it's not good behavior from point of view
5456 // of security. We should treat this issue in another bug.
5457 if (commandData.IsCutOrCopyCommand()) {
5458 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
5459 if (!docShell) {
5460 return false;
5462 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
5463 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
5464 return false;
5466 return NS_SUCCEEDED(rv);
5469 // Otherwise (currently, only clipboard read commands like Command::Paste),
5470 // we don't need to redirect the command to focused subdocument.
5471 // Therefore, we should handle it with nsCommandManager as used to be.
5472 // It may dispatch only preceding event of editing on non-editable element
5473 // to make web apps possible to handle standard shortcut key, etc in
5474 // their own editor.
5475 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5476 if (!commandManager) {
5477 return false;
5480 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5481 if (!window) {
5482 return false;
5485 // Return false for disabled commands (bug 760052)
5486 if (!commandManager->IsCommandEnabled(
5487 nsDependentCString(commandData.mXULCommandName), window)) {
5488 return false;
5491 MOZ_ASSERT(commandData.IsPasteCommand() ||
5492 commandData.mCommand == Command::SelectAll);
5493 nsresult rv =
5494 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
5495 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5498 // Now, our target is fixed to the editor. So, we can use EditorCommand
5499 // in EditorCommandTarget directly.
5501 EditorCommandParamType paramType =
5502 EditorCommand::GetParamType(commandData.mCommand);
5504 // If we don't have meaningful parameter or the EditorCommand does not
5505 // require additional parameter, we can use `DoCommand()`.
5506 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
5507 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
5508 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
5509 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5512 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
5513 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
5514 // a `bool` value.
5515 if (!!(paramType & EditorCommandParamType::Bool)) {
5516 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
5517 adjustedValue.EqualsLiteral("false"));
5518 nsresult rv = editCommandTarget.DoCommandParam(
5519 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
5520 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5523 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
5524 // in this case. However, `paramType` may contain both `String` and
5525 // `CString` but in such case, we should use `DoCommandParam()` which
5526 // takes `nsAString`. So, we should check whether `paramType` contains
5527 // `String` or not first.
5528 if (!!(paramType & EditorCommandParamType::String)) {
5529 MOZ_ASSERT(!adjustedValue.IsVoid());
5530 nsresult rv =
5531 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
5532 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5535 // Finally, `paramType` should have `CString`. We should use
5536 // `DoCommandParam()` which takes `nsACString`.
5537 if (!!(paramType & EditorCommandParamType::CString)) {
5538 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
5539 MOZ_ASSERT(!utf8Value.IsVoid());
5540 nsresult rv =
5541 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
5542 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5545 MOZ_ASSERT_UNREACHABLE(
5546 "Not yet implemented to handle new EditorCommandParamType");
5547 return false;
5550 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
5551 nsIPrincipal& aSubjectPrincipal,
5552 ErrorResult& aRv) {
5553 // Only allow on HTML documents.
5554 if (!IsHTMLOrXHTML()) {
5555 aRv.ThrowInvalidStateError(
5556 "queryCommandEnabled is only supported on HTML documents");
5557 return false;
5559 // Otherwise, don't throw exception for compatibility with Chrome.
5561 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5562 switch (commandData.mCommand) {
5563 case Command::DoNothing:
5564 return false;
5565 case Command::SetDocumentReadOnly:
5566 SetUseCounter(
5567 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5568 break;
5569 case Command::SetDocumentInsertBROnEnterKeyPress:
5570 SetUseCounter(
5571 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5572 break;
5573 default:
5574 break;
5577 // cut & copy are always allowed
5578 if (commandData.IsCutOrCopyCommand()) {
5579 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
5582 // Report false for restricted commands
5583 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
5584 return false;
5587 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5588 if (commandData.IsAvailableOnlyWhenEditable() &&
5589 !editCommandTarget.IsEditable(this)) {
5590 return false;
5593 if (editCommandTarget.IsEditor()) {
5594 return editCommandTarget.IsCommandEnabled();
5597 // get command manager and dispatch command to our window if it's acceptable
5598 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5599 if (!commandManager) {
5600 return false;
5603 nsPIDOMWindowOuter* window = GetWindow();
5604 if (!window) {
5605 return false;
5608 return commandManager->IsCommandEnabled(
5609 nsDependentCString(commandData.mXULCommandName), window);
5612 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5613 ErrorResult& aRv) {
5614 // Only allow on HTML documents.
5615 if (!IsHTMLOrXHTML()) {
5616 aRv.ThrowInvalidStateError(
5617 "queryCommandIndeterm is only supported on HTML documents");
5618 return false;
5620 // Otherwise, don't throw exception for compatibility with Chrome.
5622 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5623 if (commandData.mCommand == Command::DoNothing) {
5624 return false;
5627 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5628 if (commandData.IsAvailableOnlyWhenEditable() &&
5629 !editCommandTarget.IsEditable(this)) {
5630 return false;
5632 RefPtr<nsCommandParams> params = new nsCommandParams();
5633 if (editCommandTarget.IsEditor()) {
5634 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5635 return false;
5637 } else {
5638 // get command manager and dispatch command to our window if it's acceptable
5639 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5640 if (!commandManager) {
5641 return false;
5644 nsPIDOMWindowOuter* window = GetWindow();
5645 if (!window) {
5646 return false;
5649 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5650 window, params))) {
5651 return false;
5655 // If command does not have a state_mixed value, this call fails and sets
5656 // retval to false. This is fine -- we want to return false in that case
5657 // anyway (bug 738385), so we just don't throw regardless.
5658 return params->GetBool("state_mixed");
5661 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5662 ErrorResult& aRv) {
5663 // Only allow on HTML documents.
5664 if (!IsHTMLOrXHTML()) {
5665 aRv.ThrowInvalidStateError(
5666 "queryCommandState is only supported on HTML documents");
5667 return false;
5669 // Otherwise, don't throw exception for compatibility with Chrome.
5671 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5672 switch (commandData.mCommand) {
5673 case Command::DoNothing:
5674 return false;
5675 case Command::SetDocumentReadOnly:
5676 SetUseCounter(
5677 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5678 break;
5679 case Command::SetDocumentInsertBROnEnterKeyPress:
5680 SetUseCounter(
5681 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5682 break;
5683 default:
5684 break;
5687 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5688 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5689 // return false always.
5690 return false;
5693 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5694 if (commandData.IsAvailableOnlyWhenEditable() &&
5695 !editCommandTarget.IsEditable(this)) {
5696 return false;
5698 RefPtr<nsCommandParams> params = new nsCommandParams();
5699 if (editCommandTarget.IsEditor()) {
5700 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5701 return false;
5703 } else {
5704 // get command manager and dispatch command to our window if it's acceptable
5705 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5706 if (!commandManager) {
5707 return false;
5710 nsPIDOMWindowOuter* window = GetWindow();
5711 if (!window) {
5712 return false;
5715 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5716 window, params))) {
5717 return false;
5721 // handle alignment as a special case (possibly other commands too?)
5722 // Alignment is special because the external api is individual
5723 // commands but internally we use cmd_align with different
5724 // parameters. When getting the state of this command, we need to
5725 // return the boolean for this particular alignment rather than the
5726 // string of 'which alignment is this?'
5727 switch (commandData.mCommand) {
5728 case Command::FormatJustifyLeft: {
5729 nsAutoCString currentValue;
5730 nsresult rv = params->GetCString("state_attribute", currentValue);
5731 if (NS_FAILED(rv)) {
5732 return false;
5734 return currentValue.EqualsLiteral("left");
5736 case Command::FormatJustifyRight: {
5737 nsAutoCString currentValue;
5738 nsresult rv = params->GetCString("state_attribute", currentValue);
5739 if (NS_FAILED(rv)) {
5740 return false;
5742 return currentValue.EqualsLiteral("right");
5744 case Command::FormatJustifyCenter: {
5745 nsAutoCString currentValue;
5746 nsresult rv = params->GetCString("state_attribute", currentValue);
5747 if (NS_FAILED(rv)) {
5748 return false;
5750 return currentValue.EqualsLiteral("center");
5752 case Command::FormatJustifyFull: {
5753 nsAutoCString currentValue;
5754 nsresult rv = params->GetCString("state_attribute", currentValue);
5755 if (NS_FAILED(rv)) {
5756 return false;
5758 return currentValue.EqualsLiteral("justify");
5760 default:
5761 break;
5764 // If command does not have a state_all value, this call fails and sets
5765 // retval to false. This is fine -- we want to return false in that case
5766 // anyway (bug 738385), so we just succeed and return false regardless.
5767 return params->GetBool("state_all");
5770 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5771 CallerType aCallerType, ErrorResult& aRv) {
5772 // Only allow on HTML documents.
5773 if (!IsHTMLOrXHTML()) {
5774 aRv.ThrowInvalidStateError(
5775 "queryCommandSupported is only supported on HTML documents");
5776 return false;
5778 // Otherwise, don't throw exception for compatibility with Chrome.
5780 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5781 switch (commandData.mCommand) {
5782 case Command::DoNothing:
5783 return false;
5784 case Command::SetDocumentReadOnly:
5785 SetUseCounter(
5786 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5787 break;
5788 case Command::SetDocumentInsertBROnEnterKeyPress:
5789 SetUseCounter(
5790 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5791 break;
5792 default:
5793 break;
5796 // Gecko technically supports all the clipboard commands including
5797 // cut/copy/paste, but non-privileged content will be unable to call
5798 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5799 // may also be disallowed to be called from non-privileged content.
5800 // For that reason, we report the support status of corresponding
5801 // command accordingly.
5802 if (aCallerType != CallerType::System) {
5803 if (commandData.IsPasteCommand()) {
5804 return false;
5806 if (commandData.IsCutOrCopyCommand() &&
5807 !StaticPrefs::dom_allow_cut_copy()) {
5808 // XXXbz should we worry about correctly reporting "true" in the
5809 // "restricted, but we're an addon with clipboardWrite permissions" case?
5810 // See also nsContentUtils::IsCutCopyAllowed.
5811 return false;
5815 // aHTMLCommandName is supported if it can be converted to a Midas command
5816 return true;
5819 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5820 nsAString& aValue, ErrorResult& aRv) {
5821 aValue.Truncate();
5823 // Only allow on HTML documents.
5824 if (!IsHTMLOrXHTML()) {
5825 aRv.ThrowInvalidStateError(
5826 "queryCommandValue is only supported on HTML documents");
5827 return;
5829 // Otherwise, don't throw exception for compatibility with Chrome.
5831 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5832 switch (commandData.mCommand) {
5833 case Command::DoNothing:
5834 // Return empty string
5835 return;
5836 case Command::SetDocumentReadOnly:
5837 SetUseCounter(
5838 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5839 break;
5840 case Command::SetDocumentInsertBROnEnterKeyPress:
5841 SetUseCounter(
5842 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5843 break;
5844 default:
5845 break;
5848 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5849 if (commandData.IsAvailableOnlyWhenEditable() &&
5850 !editCommandTarget.IsEditable(this)) {
5851 return;
5853 RefPtr<nsCommandParams> params = new nsCommandParams();
5854 if (editCommandTarget.IsEditor()) {
5855 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5856 return;
5859 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5860 return;
5862 } else {
5863 // get command manager and dispatch command to our window if it's acceptable
5864 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5865 if (!commandManager) {
5866 return;
5869 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5870 if (!window) {
5871 return;
5874 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5875 return;
5878 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5879 window, params))) {
5880 return;
5884 // If command does not have a state_attribute value, this call fails, and
5885 // aValue will wind up being the empty string. This is fine -- we want to
5886 // return "" in that case anyway (bug 738385), so we just return NS_OK
5887 // regardless.
5888 nsAutoCString result;
5889 params->GetCString("state_attribute", result);
5890 CopyUTF8toUTF16(result, aValue);
5893 void Document::MaybeEditingStateChanged() {
5894 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
5895 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
5896 if (nsContentUtils::IsSafeToRunScript()) {
5897 EditingStateChanged();
5898 } else if (!mInDestructor) {
5899 nsContentUtils::AddScriptRunner(
5900 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
5901 &Document::MaybeEditingStateChanged));
5906 void Document::NotifyFetchOrXHRSuccess() {
5907 if (mShouldNotifyFetchSuccess) {
5908 nsContentUtils::DispatchEventOnlyToChrome(
5909 this, this, u"DOMDocFetchSuccess"_ns, CanBubble::eNo, Cancelable::eNo,
5910 /* DefaultAction */ nullptr);
5914 void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
5915 mShouldNotifyFetchSuccess = aShouldNotify;
5918 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
5919 mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
5922 void Document::TearingDownEditor() {
5923 if (IsEditingOn()) {
5924 mEditingState = EditingState::eTearingDown;
5925 if (IsHTMLOrXHTML()) {
5926 RemoveContentEditableStyleSheets();
5931 nsresult Document::TurnEditingOff() {
5932 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
5934 nsPIDOMWindowOuter* window = GetWindow();
5935 if (!window) {
5936 return NS_ERROR_FAILURE;
5939 nsIDocShell* docshell = window->GetDocShell();
5940 if (!docshell) {
5941 return NS_ERROR_FAILURE;
5944 bool isBeingDestroyed = false;
5945 docshell->IsBeingDestroyed(&isBeingDestroyed);
5946 if (isBeingDestroyed) {
5947 return NS_ERROR_FAILURE;
5950 nsCOMPtr<nsIEditingSession> editSession;
5951 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5952 NS_ENSURE_SUCCESS(rv, rv);
5954 // turn editing off
5955 rv = editSession->TearDownEditorOnWindow(window);
5956 NS_ENSURE_SUCCESS(rv, rv);
5958 mEditingState = EditingState::eOff;
5960 // Editor resets selection since it is being destroyed. But if focus is
5961 // still into editable control, we have to initialize selection again.
5962 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
5963 if (RefPtr<TextControlElement> textControlElement =
5964 TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
5965 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
5966 textEditor->ReinitializeSelection(*textControlElement);
5971 return NS_OK;
5974 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
5975 nsIDocShell* docShell = aWindow->GetDocShell();
5976 if (!docShell) {
5977 return false;
5979 return docShell->GetPresShell() != nullptr;
5982 HTMLEditor* Document::GetHTMLEditor() const {
5983 nsPIDOMWindowOuter* window = GetWindow();
5984 if (!window) {
5985 return nullptr;
5988 nsIDocShell* docshell = window->GetDocShell();
5989 if (!docshell) {
5990 return nullptr;
5993 return docshell->GetHTMLEditor();
5996 nsresult Document::EditingStateChanged() {
5997 if (mRemovedFromDocShell) {
5998 return NS_OK;
6001 if (mEditingState == EditingState::eSettingUp ||
6002 mEditingState == EditingState::eTearingDown) {
6003 // XXX We shouldn't recurse
6004 return NS_OK;
6007 const bool designMode = IsInDesignMode();
6008 EditingState newState =
6009 designMode ? EditingState::eDesignMode
6010 : (mContentEditableCount > 0 ? EditingState::eContentEditable
6011 : EditingState::eOff);
6012 if (mEditingState == newState) {
6013 // No changes in editing mode.
6014 return NS_OK;
6017 const bool thisDocumentHasFocus = ThisDocumentHasFocus();
6018 if (newState == EditingState::eOff) {
6019 // Editing is being turned off.
6020 nsAutoScriptBlocker scriptBlocker;
6021 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6022 NotifyEditableStateChange(*this);
6023 nsresult rv = TurnEditingOff();
6024 // If this document has focus and the editing state of this document
6025 // becomes "off", it means that HTMLEditor won't handle any inputs nor
6026 // modify the DOM tree. However, HTMLEditor may not receive `blur`
6027 // event for this state change since this may occur without focus change.
6028 // Therefore, let's notify HTMLEditor of this editing state change.
6029 // Note that even if focusedElement is an editable text control element,
6030 // it becomes not editable from HTMLEditor point of view since text
6031 // control elements are manged by TextEditor.
6032 RefPtr<Element> focusedElement =
6033 nsFocusManager::GetFocusManager()
6034 ? nsFocusManager::GetFocusManager()->GetFocusedElement()
6035 : nullptr;
6036 DebugOnly<nsresult> rvIgnored =
6037 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6038 htmlEditor, *this, focusedElement);
6039 NS_WARNING_ASSERTION(
6040 NS_SUCCEEDED(rvIgnored),
6041 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but "
6042 "ignored");
6043 return rv;
6046 // Flush out style changes on our _parent_ document, if any, so that
6047 // our check for a presshell won't get stale information.
6048 if (mParentDocument) {
6049 mParentDocument->FlushPendingNotifications(FlushType::Style);
6052 // get editing session, make sure this is a strong reference so the
6053 // window can't get deleted during the rest of this call.
6054 const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6055 if (!window) {
6056 return NS_ERROR_FAILURE;
6059 nsIDocShell* docshell = window->GetDocShell();
6060 if (!docshell) {
6061 return NS_ERROR_FAILURE;
6064 // FlushPendingNotifications might destroy our docshell.
6065 bool isBeingDestroyed = false;
6066 docshell->IsBeingDestroyed(&isBeingDestroyed);
6067 if (isBeingDestroyed) {
6068 return NS_ERROR_FAILURE;
6071 nsCOMPtr<nsIEditingSession> editSession;
6072 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6073 NS_ENSURE_SUCCESS(rv, rv);
6075 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
6076 if (htmlEditor) {
6077 // We might already have an editor if it was set up for mail, let's see
6078 // if this is actually the case.
6079 uint32_t flags = 0;
6080 htmlEditor->GetFlags(&flags);
6081 if (flags & nsIEditor::eEditorMailMask) {
6082 // We already have a mail editor, then we should not attempt to create
6083 // another one.
6084 return NS_OK;
6088 if (!HasPresShell(window)) {
6089 // We should not make the window editable or setup its editor.
6090 // It's probably style=display:none.
6091 return NS_OK;
6094 bool makeWindowEditable = mEditingState == EditingState::eOff;
6095 bool spellRecheckAll = false;
6096 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
6097 htmlEditor = nullptr;
6100 EditingState oldState = mEditingState;
6101 nsAutoEditingState push(this, EditingState::eSettingUp);
6103 RefPtr<PresShell> presShell = GetPresShell();
6104 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
6106 // If we're entering the design mode from non-editable state, put the
6107 // selection at the beginning of the document for compatibility reasons.
6108 bool collapseSelectionAtBeginningOfDocument =
6109 designMode && oldState == EditingState::eOff;
6110 // However, mEditingState may be eOff even if there is some
6111 // `contenteditable` area and selection has been initialized for it because
6112 // mEditingState for `contenteditable` may have been scheduled to modify
6113 // when safe. In such case, we should not reinitialize selection.
6114 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
6115 Selection* selection =
6116 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
6117 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
6118 if (selection && selection->RangeCount()) {
6119 // Perhaps, we don't need to check whether the selection is in
6120 // an editing host or not because all contents will be editable
6121 // in designMode. (And we don't want to make this code so complicated
6122 // because of legacy API.)
6123 collapseSelectionAtBeginningOfDocument = false;
6127 MOZ_ASSERT(mStyleSetFilled);
6129 // Before making this window editable, we need to modify UA style sheet
6130 // because new style may change whether focused element will be focusable
6131 // or not.
6132 if (IsHTMLOrXHTML()) {
6133 AddContentEditableStyleSheetsToStyleSet(designMode);
6136 if (designMode) {
6137 // designMode is being turned on (overrides contentEditable).
6138 spellRecheckAll = oldState == EditingState::eContentEditable;
6141 // Adjust focused element with new style but blur event shouldn't be fired
6142 // until mEditingState is modified with newState.
6143 nsAutoScriptBlocker scriptBlocker;
6144 if (designMode) {
6145 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6146 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6147 window, nsFocusManager::eOnlyCurrentWindow,
6148 getter_AddRefs(focusedWindow));
6149 if (focusedContent) {
6150 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
6151 bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable()
6152 : !focusedContent->IsFocusable();
6153 if (clearFocus) {
6154 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
6155 fm->ClearFocus(window);
6156 // If we need to dispatch blur event, we should put off after
6157 // modifying mEditingState since blur event handler may change
6158 // designMode state again.
6159 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
6165 if (makeWindowEditable) {
6166 // Editing is being turned on (through designMode or contentEditable)
6167 // Turn on editor.
6168 // XXX This can cause flushing which can change the editing state, so make
6169 // sure to avoid recursing.
6170 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
6171 NS_ENSURE_SUCCESS(rv, rv);
6174 // XXX Need to call TearDownEditorOnWindow for all failures.
6175 htmlEditor = docshell->GetHTMLEditor();
6176 if (!htmlEditor) {
6177 // Return NS_OK even though we've failed to create an editor here. This
6178 // is so that the setter of designMode on non-HTML documents does not
6179 // fail.
6180 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
6181 // would detect that we can't support the mimetype if appropriate and
6182 // would fall onto the eEditorErrorCantEditMimeType path.
6183 return NS_OK;
6186 if (collapseSelectionAtBeginningOfDocument) {
6187 htmlEditor->BeginningOfDocument();
6190 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6191 nsContentUtils::AddScriptBlocker();
6195 mEditingState = newState;
6196 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6197 nsContentUtils::RemoveScriptBlocker();
6198 // If mEditingState is overwritten by another call and already disabled
6199 // the editing, we shouldn't keep making window editable.
6200 if (mEditingState == EditingState::eOff) {
6201 return NS_OK;
6205 if (makeWindowEditable) {
6206 // TODO: We should do this earlier in this method.
6207 // Previously, we called `ExecCommand` with `insertBrOnReturn` command
6208 // whose argument is false here. Then, if it returns error, we
6209 // stopped making it editable. However, after bug 1697078 fixed,
6210 // `ExecCommand` returns error only when the document is not XHTML's
6211 // nor HTML's. Therefore, we use same error handling for now.
6212 if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
6213 // Editor setup failed. Editing is not on after all.
6214 // XXX Should we reset the editable flag on nodes?
6215 editSession->TearDownEditorOnWindow(window);
6216 mEditingState = EditingState::eOff;
6217 return NS_ERROR_DOM_INVALID_STATE_ERR;
6219 // Set the editor to not insert <br> elements on return when in <p> elements
6220 // by default.
6221 htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
6224 // Resync the editor's spellcheck state.
6225 if (spellRecheckAll) {
6226 nsCOMPtr<nsISelectionController> selectionController =
6227 htmlEditor->GetSelectionController();
6228 if (NS_WARN_IF(!selectionController)) {
6229 return NS_ERROR_FAILURE;
6232 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
6233 nsISelectionController::SELECTION_SPELLCHECK);
6234 if (spellCheckSelection) {
6235 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
6238 htmlEditor->SyncRealTimeSpell();
6240 MaybeDispatchCheckKeyPressEventModelEvent();
6242 // If this document keeps having focus and the HTMLEditor is in the design
6243 // mode, it may not receive `focus` event for this editing state change since
6244 // this may occur without a focus change. Therefore, let's notify HTMLEditor
6245 // of this editing state change.
6246 if (thisDocumentHasFocus && htmlEditor->IsInDesignMode() &&
6247 ThisDocumentHasFocus()) {
6248 DebugOnly<nsresult> rvIgnored =
6249 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, nullptr);
6250 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6251 "HTMLEditor::FocusedElementOrDocumentBecomesEditable()"
6252 " failed, but ignored");
6255 return NS_OK;
6258 // Helper class, used below in ChangeContentEditableCount().
6259 class DeferredContentEditableCountChangeEvent : public Runnable {
6260 public:
6261 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
6262 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
6263 mDoc(aDoc),
6264 mElement(aElement) {}
6266 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
6267 if (mElement && mElement->OwnerDoc() == mDoc) {
6268 RefPtr<Document> doc = std::move(mDoc);
6269 RefPtr<Element> element = std::move(mElement);
6270 doc->DeferredContentEditableCountChange(element);
6272 return NS_OK;
6275 private:
6276 RefPtr<Document> mDoc;
6277 RefPtr<Element> mElement;
6280 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
6281 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
6282 "Trying to decrement too much.");
6284 mContentEditableCount += aChange;
6286 if (aElement) {
6287 nsContentUtils::AddScriptRunner(
6288 new DeferredContentEditableCountChangeEvent(this, aElement));
6292 void Document::DeferredContentEditableCountChange(Element* aElement) {
6293 const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
6294 const bool elementHasFocus =
6295 aElement && fm && fm->GetFocusedElement() == aElement;
6296 if (elementHasFocus) {
6297 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
6298 // When contenteditable of aElement is changed and HTMLEditor works with it
6299 // or needs to start working with it, HTMLEditor may not receive `focus`
6300 // event nor `blur` event because this may occur without a focus change.
6301 // Therefore, we need to notify HTMLEditor of this contenteditable attribute
6302 // change.
6303 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6304 if (aElement->HasFlag(NODE_IS_EDITABLE)) {
6305 if (htmlEditor) {
6306 DebugOnly<nsresult> rvIgnored =
6307 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this,
6308 aElement);
6309 NS_WARNING_ASSERTION(
6310 NS_SUCCEEDED(rvIgnored),
6311 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6312 "ignored");
6314 } else {
6315 DebugOnly<nsresult> rvIgnored =
6316 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6317 htmlEditor, *this, aElement);
6318 NS_WARNING_ASSERTION(
6319 NS_SUCCEEDED(rvIgnored),
6320 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, "
6321 "but ignored");
6325 if (mParser ||
6326 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
6327 return;
6330 EditingState oldState = mEditingState;
6332 nsresult rv = EditingStateChanged();
6333 NS_ENSURE_SUCCESS_VOID(rv);
6335 if (oldState == mEditingState &&
6336 mEditingState == EditingState::eContentEditable) {
6337 // We just changed the contentEditable state of a node, we need to reset
6338 // the spellchecking state of that node.
6339 if (aElement) {
6340 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6341 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
6342 rv = htmlEditor->GetInlineSpellChecker(false,
6343 getter_AddRefs(spellChecker));
6344 NS_ENSURE_SUCCESS_VOID(rv);
6346 if (spellChecker &&
6347 aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) {
6348 RefPtr<nsRange> range = nsRange::Create(aElement);
6349 IgnoredErrorResult res;
6350 range->SelectNode(*aElement, res);
6351 if (res.Failed()) {
6352 // The node might be detached from the document at this point,
6353 // which would cause this call to fail. In this case, we can
6354 // safely ignore the contenteditable count change.
6355 return;
6358 rv = spellChecker->SpellCheckRange(range);
6359 NS_ENSURE_SUCCESS_VOID(rv);
6365 // aElement causes creating new HTMLEditor and the element had and keep
6366 // having focus, the HTMLEditor won't receive `focus` event. Therefore, we
6367 // need to notify HTMLEditor of it becomes editable.
6368 if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) &&
6369 fm->GetFocusedElement() == aElement) {
6370 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6371 DebugOnly<nsresult> rvIgnored =
6372 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement);
6373 NS_WARNING_ASSERTION(
6374 NS_SUCCEEDED(rvIgnored),
6375 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6376 "ignored");
6381 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
6382 // Currently, we need to check only when we're becoming editable for
6383 // contenteditable.
6384 if (mEditingState != EditingState::eContentEditable) {
6385 return;
6388 if (mHasBeenEditable) {
6389 return;
6391 mHasBeenEditable = true;
6393 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
6394 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
6395 // with proper keypress event for the active web app.
6396 WidgetEvent checkEvent(true, eUnidentifiedEvent);
6397 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
6398 checkEvent.mFlags.mCancelable = false;
6399 checkEvent.mFlags.mBubbles = false;
6400 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
6401 // Post the event rather than dispatching it synchronously because we need
6402 // a call of SetKeyPressEventModel() before first key input. Therefore, we
6403 // can avoid paying unnecessary runtime cost for most web apps.
6404 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
6407 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
6408 PresShell* presShell = GetPresShell();
6409 if (!presShell) {
6410 return;
6412 presShell->SetKeyPressEventModel(aKeyPressEventModel);
6415 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
6417 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
6418 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
6419 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
6420 aFocusTime >= mLastFocusTime);
6421 mLastFocusTime = aFocusTime;
6424 void Document::GetReferrer(nsAString& aReferrer) const {
6425 aReferrer.Truncate();
6426 if (!mReferrerInfo) {
6427 return;
6430 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
6431 if (!referrer) {
6432 return;
6435 nsAutoCString uri;
6436 nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
6437 if (NS_WARN_IF(NS_FAILED(rv))) {
6438 return;
6441 CopyUTF8toUTF16(uri, aReferrer);
6444 void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
6445 aCookie.Truncate(); // clear current cookie in case service fails;
6446 // no cookie isn't an error condition.
6448 if (mDisableCookieAccess) {
6449 return;
6452 // If the document's sandboxed origin flag is set, then reading cookies
6453 // is prohibited.
6454 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6455 aRv.ThrowSecurityError(
6456 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6457 "flag.");
6458 return;
6461 StorageAccess storageAccess = CookieAllowedForDocument(this);
6462 if (storageAccess == StorageAccess::eDeny) {
6463 return;
6466 if (ShouldPartitionStorage(storageAccess) &&
6467 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6468 return;
6471 // If the document is a cookie-averse Document... return the empty string.
6472 if (IsCookieAverse()) {
6473 return;
6476 // not having a cookie service isn't an error
6477 nsCOMPtr<nsICookieService> service =
6478 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6479 if (service) {
6480 nsAutoCString cookie;
6481 service->GetCookieStringFromDocument(this, cookie);
6482 // CopyUTF8toUTF16 doesn't handle error
6483 // because it assumes that the input is valid.
6484 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
6488 void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
6489 if (mDisableCookieAccess) {
6490 return;
6493 // If the document's sandboxed origin flag is set, then setting cookies
6494 // is prohibited.
6495 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6496 aRv.ThrowSecurityError(
6497 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6498 "flag.");
6499 return;
6502 StorageAccess storageAccess = CookieAllowedForDocument(this);
6503 if (storageAccess == StorageAccess::eDeny) {
6504 return;
6507 if (ShouldPartitionStorage(storageAccess) &&
6508 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6509 return;
6512 // If the document is a cookie-averse Document... do nothing.
6513 if (IsCookieAverse()) {
6514 return;
6517 if (!mDocumentURI) {
6518 return;
6521 // not having a cookie service isn't an error
6522 nsCOMPtr<nsICookieService> service =
6523 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6524 if (!service) {
6525 return;
6528 NS_ConvertUTF16toUTF8 cookie(aCookie);
6529 nsresult rv = service->SetCookieStringFromDocument(this, cookie);
6531 // No warning messages here.
6532 if (NS_FAILED(rv)) {
6533 return;
6536 nsCOMPtr<nsIObserverService> observerService =
6537 mozilla::services::GetObserverService();
6538 if (observerService) {
6539 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
6540 nsString(aCookie).get());
6544 ReferrerPolicy Document::GetReferrerPolicy() const {
6545 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
6546 : ReferrerPolicy::_empty;
6549 void Document::GetAlinkColor(nsAString& aAlinkColor) {
6550 aAlinkColor.Truncate();
6552 HTMLBodyElement* body = GetBodyElement();
6553 if (body) {
6554 body->GetALink(aAlinkColor);
6558 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
6559 HTMLBodyElement* body = GetBodyElement();
6560 if (body) {
6561 body->SetALink(aAlinkColor);
6565 void Document::GetLinkColor(nsAString& aLinkColor) {
6566 aLinkColor.Truncate();
6568 HTMLBodyElement* body = GetBodyElement();
6569 if (body) {
6570 body->GetLink(aLinkColor);
6574 void Document::SetLinkColor(const nsAString& aLinkColor) {
6575 HTMLBodyElement* body = GetBodyElement();
6576 if (body) {
6577 body->SetLink(aLinkColor);
6581 void Document::GetVlinkColor(nsAString& aVlinkColor) {
6582 aVlinkColor.Truncate();
6584 HTMLBodyElement* body = GetBodyElement();
6585 if (body) {
6586 body->GetVLink(aVlinkColor);
6590 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
6591 HTMLBodyElement* body = GetBodyElement();
6592 if (body) {
6593 body->SetVLink(aVlinkColor);
6597 void Document::GetBgColor(nsAString& aBgColor) {
6598 aBgColor.Truncate();
6600 HTMLBodyElement* body = GetBodyElement();
6601 if (body) {
6602 body->GetBgColor(aBgColor);
6606 void Document::SetBgColor(const nsAString& aBgColor) {
6607 HTMLBodyElement* body = GetBodyElement();
6608 if (body) {
6609 body->SetBgColor(aBgColor);
6613 void Document::GetFgColor(nsAString& aFgColor) {
6614 aFgColor.Truncate();
6616 HTMLBodyElement* body = GetBodyElement();
6617 if (body) {
6618 body->GetText(aFgColor);
6622 void Document::SetFgColor(const nsAString& aFgColor) {
6623 HTMLBodyElement* body = GetBodyElement();
6624 if (body) {
6625 body->SetText(aFgColor);
6629 void Document::CaptureEvents() {
6630 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
6633 void Document::ReleaseEvents() {
6634 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
6637 HTMLAllCollection* Document::All() {
6638 if (!mAll) {
6639 mAll = new HTMLAllCollection(this);
6641 return mAll;
6644 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
6645 if (mIsSrcdocDocument) {
6646 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
6647 if (inStrmChan) {
6648 return inStrmChan->GetSrcdocData(aSrcdocData);
6651 aSrcdocData = VoidString();
6652 return NS_OK;
6655 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
6656 nsPIDOMWindowOuter* win = GetWindow();
6657 if (!win) {
6658 return nullptr;
6660 return WindowProxyHolder(win->GetBrowsingContext());
6663 nsIContent* Document::GetUnretargetedFocusedContent(
6664 IncludeChromeOnly aIncludeChromeOnly) const {
6665 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6666 if (!window) {
6667 return nullptr;
6669 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6670 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6671 window, nsFocusManager::eOnlyCurrentWindow,
6672 getter_AddRefs(focusedWindow));
6673 if (!focusedContent) {
6674 return nullptr;
6676 // be safe and make sure the element is from this document
6677 if (focusedContent->OwnerDoc() != this) {
6678 return nullptr;
6680 if (focusedContent->ChromeOnlyAccess() &&
6681 aIncludeChromeOnly == IncludeChromeOnly::No) {
6682 return focusedContent->FindFirstNonChromeOnlyAccessContent();
6684 return focusedContent;
6687 Element* Document::GetActiveElement() {
6688 // Get the focused element.
6689 Element* focusedElement = GetRetargetedFocusedElement();
6690 if (focusedElement) {
6691 return focusedElement;
6694 // No focused element anywhere in this document. Try to get the BODY.
6695 if (IsHTMLOrXHTML()) {
6696 Element* bodyElement = AsHTMLDocument()->GetBody();
6697 if (bodyElement) {
6698 return bodyElement;
6700 // Special case to handle the transition to XHTML from XUL documents
6701 // where there currently isn't a body element, but we need to match the
6702 // XUL behavior. This should be removed when bug 1540278 is resolved.
6703 if (nsContentUtils::IsChromeDoc(this)) {
6704 Element* docElement = GetDocumentElement();
6705 if (docElement && docElement->IsXULElement()) {
6706 return docElement;
6709 // Because of IE compatibility, return null when html document doesn't have
6710 // a body.
6711 return nullptr;
6714 // If we couldn't get a BODY, return the root element.
6715 return GetDocumentElement();
6718 Element* Document::GetCurrentScript() {
6719 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
6720 return el;
6723 void Document::ReleaseCapture() const {
6724 // only release the capture if the caller can access it. This prevents a
6725 // page from stopping a scrollbar grab for example.
6726 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
6727 if (node && nsContentUtils::CanCallerAccess(node)) {
6728 PresShell::ReleaseCapturingContent();
6732 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
6733 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
6734 return mChromeXHRDocBaseURI;
6737 return GetDocBaseURI();
6740 void Document::SetBaseURI(nsIURI* aURI) {
6741 if (!aURI && !mDocumentBaseURI) {
6742 return;
6745 // Don't do anything if the URI wasn't actually changed.
6746 if (aURI && mDocumentBaseURI) {
6747 bool equalBases = false;
6748 mDocumentBaseURI->Equals(aURI, &equalBases);
6749 if (equalBases) {
6750 return;
6754 mDocumentBaseURI = aURI;
6755 mCachedURLData = nullptr;
6756 RefreshLinkHrefs();
6759 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
6760 const nsAString& aURI) {
6761 RefPtr<nsIURI> resolvedURI;
6762 MOZ_TRY(
6763 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
6764 return OwningNonNull<nsIURI>(std::move(resolvedURI));
6767 nsIReferrerInfo* Document::ReferrerInfoForInternalCSSAndSVGResources() {
6768 if (!mCachedReferrerInfoForInternalCSSAndSVGResources) {
6769 mCachedReferrerInfoForInternalCSSAndSVGResources =
6770 ReferrerInfo::CreateForInternalCSSAndSVGResources(this);
6772 return mCachedReferrerInfoForInternalCSSAndSVGResources;
6775 URLExtraData* Document::DefaultStyleAttrURLData() {
6776 MOZ_ASSERT(NS_IsMainThread());
6777 if (!mCachedURLData) {
6778 mCachedURLData = new URLExtraData(
6779 GetDocBaseURI(), ReferrerInfoForInternalCSSAndSVGResources(),
6780 NodePrincipal());
6782 return mCachedURLData;
6785 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
6786 if (mCharacterSet != aEncoding) {
6787 mCharacterSet = aEncoding;
6788 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
6789 RecomputeLanguageFromCharset();
6791 if (nsPresContext* context = GetPresContext()) {
6792 context->DocumentCharSetChanged(aEncoding);
6797 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
6798 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
6801 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
6802 aData.Truncate();
6803 const HeaderData* data = mHeaderData.get();
6804 while (data) {
6805 if (data->mField == aHeaderField) {
6806 aData = data->mData;
6807 break;
6809 data = data->mNext.get();
6813 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
6814 if (!aHeaderField) {
6815 NS_ERROR("null headerField");
6816 return;
6819 if (!mHeaderData) {
6820 if (!aData.IsEmpty()) { // don't bother storing empty string
6821 mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
6823 } else {
6824 HeaderData* data = mHeaderData.get();
6825 UniquePtr<HeaderData>* lastPtr = &mHeaderData;
6826 bool found = false;
6827 do { // look for existing and replace
6828 if (data->mField == aHeaderField) {
6829 if (!aData.IsEmpty()) {
6830 data->mData.Assign(aData);
6831 } else { // don't store empty string
6832 // Note that data->mNext is moved to a temporary before the old value
6833 // of *lastPtr is deleted.
6834 *lastPtr = std::move(data->mNext);
6836 found = true;
6838 break;
6840 lastPtr = &data->mNext;
6841 data = lastPtr->get();
6842 } while (data);
6844 if (!aData.IsEmpty() && !found) {
6845 // didn't find, append
6846 *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
6850 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
6851 if (aData.IsEmpty()) {
6852 mContentLanguage = nullptr;
6853 } else {
6854 mContentLanguage = NS_AtomizeMainThread(aData);
6856 mMayNeedFontPrefsUpdate = true;
6857 if (auto* presContext = GetPresContext()) {
6858 presContext->ContentLanguageChanged();
6862 if (aHeaderField == nsGkAtoms::origin_trial) {
6863 mTrials.UpdateFromToken(aData, NodePrincipal());
6864 if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) {
6865 InitCOEP(mChannel);
6867 // If we still don't have a WindowContext, WindowContext::OnNewDocument
6868 // will take care of this.
6869 if (WindowContext* ctx = GetWindowContext()) {
6870 if (mEmbedderPolicy) {
6871 Unused << ctx->SetEmbedderPolicy(mEmbedderPolicy.value());
6877 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
6878 SetPreferredStyleSheetSet(aData);
6881 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
6882 // We get into this code before we have a script global yet, so get to our
6883 // container via mDocumentContainer.
6884 if (mDocumentContainer) {
6885 // Note: using mDocumentURI instead of mBaseURI here, for consistency
6886 // (used to just use the current URI of our webnavigation, but that
6887 // should really be the same thing). Note that this code can run
6888 // before the current URI of the webnavigation has been updated, so we
6889 // can't assert equality here.
6890 mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
6894 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
6895 mAllowDNSPrefetch) {
6896 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
6897 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
6900 if (aHeaderField == nsGkAtoms::handheldFriendly) {
6901 mViewportType = Unknown;
6905 void Document::SetEarlyHints(
6906 nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) {
6907 mEarlyHints = std::move(aEarlyHints);
6910 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
6911 NotNull<const Encoding*>& aEncoding,
6912 nsHtml5TreeOpExecutor* aExecutor) {
6913 if (aChannel) {
6914 nsAutoCString charsetVal;
6915 nsresult rv = aChannel->GetContentCharset(charsetVal);
6916 if (NS_SUCCEEDED(rv)) {
6917 const Encoding* preferred = Encoding::ForLabel(charsetVal);
6918 if (preferred) {
6919 if (aExecutor && preferred == REPLACEMENT_ENCODING) {
6920 aExecutor->ComplainAboutBogusProtocolCharset(this, false);
6922 aEncoding = WrapNotNull(preferred);
6923 aCharsetSource = kCharsetFromChannel;
6924 return;
6925 } else if (aExecutor && !charsetVal.IsEmpty()) {
6926 aExecutor->ComplainAboutBogusProtocolCharset(this, true);
6932 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
6933 #ifdef DEBUG
6934 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
6935 const Element* element = Element::FromNode(node);
6936 if (!element) {
6937 continue;
6939 MOZ_ASSERT(!element->HasServoData());
6941 #endif
6944 already_AddRefed<PresShell> Document::CreatePresShell(
6945 nsPresContext* aContext, nsViewManager* aViewManager) {
6946 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
6948 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
6950 AssertNoStaleServoDataIn(*this);
6952 RefPtr<PresShell> presShell = new PresShell(this);
6953 // Note: we don't hold a ref to the shell (it holds a ref to us)
6954 mPresShell = presShell;
6956 if (!mStyleSetFilled) {
6957 FillStyleSet();
6960 presShell->Init(aContext, aViewManager);
6961 if (RefPtr<class HighlightRegistry> highlightRegistry = mHighlightRegistry) {
6962 highlightRegistry->AddHighlightSelectionsToFrameSelection();
6964 // Gaining a shell causes changes in how media queries are evaluated, so
6965 // invalidate that.
6966 aContext->MediaFeatureValuesChanged(
6967 {MediaFeatureChange::kAllChanges},
6968 MediaFeatureChangePropagation::JustThisDocument);
6970 // Make sure to never paint if we belong to an invisible DocShell.
6971 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
6972 if (docShell && docShell->IsInvisible()) {
6973 presShell->SetNeverPainting(true);
6976 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
6977 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
6978 presShell.get(), docShell.get()));
6980 mExternalResourceMap.ShowViewers();
6982 UpdateFrameRequestCallbackSchedulingState();
6984 if (mDocumentL10n) {
6985 // In case we already accumulated mutations,
6986 // we'll trigger the refresh driver now.
6987 mDocumentL10n->OnCreatePresShell();
6990 if (HasAutoFocusCandidates()) {
6991 ScheduleFlushAutoFocusCandidates();
6993 // Now that we have a shell, we might have @font-face rules (the presence of a
6994 // shell may change which rules apply to us). We don't need to do anything
6995 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
6996 // is ready to update we'll flush the font set.
6997 MarkUserFontSetDirty();
6999 // Take the author style disabled state from the top browsing cvontext.
7000 // (PageStyleChild.sys.mjs ensures this is up to date.)
7001 if (BrowsingContext* bc = GetBrowsingContext()) {
7002 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
7005 return presShell.forget();
7008 void Document::UpdateFrameRequestCallbackSchedulingState(
7009 PresShell* aOldPresShell) {
7010 // If this condition changes to depend on some other variable, make sure to
7011 // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
7012 // that variable can change. Also consider if you should change
7013 // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
7014 // condition.
7015 bool shouldBeScheduled =
7016 WouldScheduleFrameRequestCallbacks() && !mFrameRequestManager.IsEmpty();
7017 if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
7018 // nothing to do
7019 return;
7022 PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
7023 MOZ_RELEASE_ASSERT(presShell);
7025 nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
7026 if (shouldBeScheduled) {
7027 rd->ScheduleFrameRequestCallbacks(this);
7028 } else {
7029 rd->RevokeFrameRequestCallbacks(this);
7032 mFrameRequestCallbacksScheduled = shouldBeScheduled;
7035 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
7036 MOZ_ASSERT(aCallbacks.IsEmpty());
7037 mFrameRequestManager.Take(aCallbacks);
7038 // No need to manually remove ourselves from the refresh driver; it will
7039 // handle that part. But we do have to update our state.
7040 mFrameRequestCallbacksScheduled = false;
7043 bool Document::ShouldThrottleFrameRequests() const {
7044 if (mStaticCloneCount > 0) {
7045 // Even if we're not visible, a static clone may be, so run at full speed.
7046 return false;
7049 if (Hidden()) {
7050 // We're not visible (probably in a background tab or the bf cache).
7051 return true;
7054 if (!mPresShell) {
7055 // Can't do anything smarter. We don't run frame requests in documents
7056 // without a pres shell anyways.
7057 return false;
7060 if (!mPresShell->IsActive()) {
7061 // The pres shell is not active (we're an invisible OOP iframe or such), so
7062 // throttle.
7063 return true;
7066 if (mPresShell->IsPaintingSuppressed()) {
7067 // Historically we have throttled frame requests until we've painted at
7068 // least once, so keep doing that.
7069 return true;
7072 if (mPresShell->IsUnderHiddenEmbedderElement()) {
7073 // For display: none and visibility: hidden we always throttle, for
7074 // consistency with OOP iframes.
7075 return true;
7078 Element* el = GetEmbedderElement();
7079 if (!el) {
7080 // If we're not in-process, our refresh driver is throttled separately (via
7081 // PresShell::SetIsActive, so not much more we can do here.
7082 return false;
7085 if (!StaticPrefs::layout_throttle_in_process_iframes()) {
7086 return false;
7089 // Note that because we have to scroll this document into view at least once
7090 // to unthrottle it, we will drop one requestAnimationFrame frame when a
7091 // document that previously wasn't visible scrolls into view. This is
7092 // acceptable / unlikely to be human-perceivable, though we could improve on
7093 // it if needed by adding an intersection margin or something of that sort.
7094 const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
7095 *el->OwnerDoc(), /* aRoot = */ nullptr, /* aRootMargin = */ nullptr);
7096 const IntersectionOutput output =
7097 DOMIntersectionObserver::Intersect(input, *el);
7098 return !output.Intersects();
7101 void Document::DeletePresShell() {
7102 mExternalResourceMap.HideViewers();
7103 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
7104 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
7105 presContext->RefreshDriver()->CancelFlushAutoFocus(this);
7108 // When our shell goes away, request that all our images be immediately
7109 // discarded, so we don't carry around decoded image data for a document we
7110 // no longer intend to paint.
7111 ImageTracker()->RequestDiscardAll();
7113 // Now that we no longer have a shell, we need to forget about any FontFace
7114 // objects for @font-face rules that came from the style set. There's no need
7115 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
7116 // no point on it.
7117 MarkUserFontSetDirty();
7119 if (IsEditingOn()) {
7120 TurnEditingOff();
7123 PresShell* oldPresShell = mPresShell;
7124 mPresShell = nullptr;
7125 UpdateFrameRequestCallbackSchedulingState(oldPresShell);
7127 ClearStaleServoData();
7128 AssertNoStaleServoDataIn(*this);
7130 mStyleSet->ShellDetachedFromDocument();
7131 mStyleSetFilled = false;
7132 mQuirkSheetAdded = false;
7133 mContentEditableSheetAdded = false;
7134 mDesignModeSheetAdded = false;
7137 void Document::DisallowBFCaching(uint32_t aStatus) {
7138 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
7139 if (!mBFCacheDisallowed) {
7140 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
7141 wgc->SendUpdateBFCacheStatus(aStatus, 0);
7144 mBFCacheDisallowed = true;
7147 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
7148 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
7150 if (mPresShell) {
7151 if (aEntry) {
7152 mPresShell->StopObservingRefreshDriver();
7153 } else if (mBFCacheEntry) {
7154 mPresShell->StartObservingRefreshDriver();
7157 mBFCacheEntry = aEntry;
7160 bool Document::RemoveFromBFCacheSync() {
7161 bool removed = false;
7162 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
7163 entry->RemoveFromBFCacheSync();
7164 removed = true;
7165 } else if (!IsCurrentActiveDocument()) {
7166 // In the old bfcache implementation while the new page is loading, but
7167 // before nsIDocumentViewer.show() has been called, the previous page
7168 // doesn't yet have nsIBFCacheEntry. However, the previous page isn't the
7169 // current active document anymore.
7170 DisallowBFCaching();
7171 removed = true;
7174 if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
7175 if (BrowsingContext* bc = GetBrowsingContext()) {
7176 if (bc->IsInBFCache()) {
7177 ContentChild* cc = ContentChild::GetSingleton();
7178 // IPC is asynchronous but the caller is supposed to check the return
7179 // value. The reason for 'Sync' in the method name is that the old
7180 // implementation may run scripts. There is Async variant in
7181 // the old session history implementation for the cases where
7182 // synchronous operation isn't safe.
7183 cc->SendRemoveFromBFCache(bc->Top());
7184 removed = true;
7188 return removed;
7191 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
7192 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
7194 NS_RELEASE(e->mKey);
7195 if (e->mSubDocument) {
7196 e->mSubDocument->SetParentDocument(nullptr);
7197 NS_RELEASE(e->mSubDocument);
7201 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
7202 SubDocMapEntry* e =
7203 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
7205 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
7206 NS_ADDREF(e->mKey);
7208 e->mSubDocument = nullptr;
7211 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
7212 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
7214 if (!aSubDoc) {
7215 // aSubDoc is nullptr, remove the mapping
7217 if (mSubDocuments) {
7218 mSubDocuments->Remove(aElement);
7220 } else {
7221 if (!mSubDocuments) {
7222 // Create a new hashtable
7224 static const PLDHashTableOps hash_table_ops = {
7225 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
7226 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
7228 mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
7231 // Add a mapping to the hash table
7232 auto entry =
7233 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
7235 if (!entry) {
7236 return NS_ERROR_OUT_OF_MEMORY;
7239 if (entry->mSubDocument) {
7240 entry->mSubDocument->SetParentDocument(nullptr);
7242 // Release the old sub document
7243 NS_RELEASE(entry->mSubDocument);
7246 entry->mSubDocument = aSubDoc;
7247 NS_ADDREF(entry->mSubDocument);
7249 aSubDoc->SetParentDocument(this);
7252 return NS_OK;
7255 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
7256 if (mSubDocuments && aContent->IsElement()) {
7257 auto entry = static_cast<SubDocMapEntry*>(
7258 mSubDocuments->Search(aContent->AsElement()));
7260 if (entry) {
7261 return entry->mSubDocument;
7265 return nullptr;
7268 Element* Document::GetEmbedderElement() const {
7269 // We check if we're the active document in our BrowsingContext
7270 // by comparing against its document, rather than checking if the
7271 // WindowContext is cached, since mWindow may be null when we're
7272 // called (such as in nsPresContext::Init).
7273 if (BrowsingContext* bc = GetBrowsingContext()) {
7274 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
7277 return nullptr;
7280 Element* Document::GetRootElement() const {
7281 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
7282 ? mCachedRootElement
7283 : GetRootElementInternal();
7286 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
7288 Element* Document::GetRootElementInternal() const {
7289 // We invoke GetRootElement() immediately before the servo traversal, so we
7290 // should always have a cache hit from Servo.
7291 MOZ_ASSERT(NS_IsMainThread());
7293 // Loop backwards because any non-elements, such as doctypes and PIs
7294 // are likely to appear before the root element.
7295 for (nsIContent* child = GetLastChild(); child;
7296 child = child->GetPreviousSibling()) {
7297 if (Element* element = Element::FromNode(child)) {
7298 const_cast<Document*>(this)->mCachedRootElement = element;
7299 return element;
7303 const_cast<Document*>(this)->mCachedRootElement = nullptr;
7304 return nullptr;
7307 void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
7308 bool aNotify, ErrorResult& aRv) {
7309 if (aKid->IsElement() && GetRootElement()) {
7310 NS_WARNING("Inserting root element when we already have one");
7311 aRv.ThrowHierarchyRequestError("There is already a root element.");
7312 return;
7315 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
7318 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
7319 Maybe<mozAutoDocUpdate> updateBatch;
7320 if (aKid->IsElement()) {
7321 updateBatch.emplace(this, aNotify);
7322 // Destroy the link map up front before we mess with the child list.
7323 DestroyElementMaps();
7326 // Preemptively clear mCachedRootElement, since we may be about to remove it
7327 // from our child list, and we don't want to return this maybe-obsolete value
7328 // from any GetRootElement() calls that happen inside of RemoveChildNode().
7329 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
7330 // GetRootElement() calls until after it's removed the child from mChildren.
7331 // Any call before that point would restore this soon-to-be-obsolete cached
7332 // answer, and our clearing here would be fruitless.)
7333 mCachedRootElement = nullptr;
7334 nsINode::RemoveChildNode(aKid, aNotify);
7335 MOZ_ASSERT(mCachedRootElement != aKid,
7336 "Stale pointer in mCachedRootElement, after we tried to clear it "
7337 "(maybe somebody called GetRootElement() too early?)");
7340 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
7341 if (mStyleSetFilled) {
7342 EnsureStyleSet().AddDocStyleSheet(aSheet);
7343 ApplicableStylesChanged();
7347 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
7348 EnsureStyleSet().RecordShadowStyleChange(aShadowRoot);
7349 ApplicableStylesChanged(/* aKnownInShadowTree= */ true);
7352 void Document::ApplicableStylesChanged(bool aKnownInShadowTree) {
7353 // TODO(emilio): if we decide to resolve style in display: none iframes, then
7354 // we need to always track style changes and remove the mStyleSetFilled.
7355 if (!mStyleSetFilled) {
7356 return;
7358 if (!aKnownInShadowTree) {
7359 MarkUserFontSetDirty();
7361 PresShell* ps = GetPresShell();
7362 if (!ps) {
7363 return;
7366 ps->EnsureStyleFlush();
7367 nsPresContext* pc = ps->GetPresContext();
7368 if (!pc) {
7369 return;
7372 if (!aKnownInShadowTree) {
7373 pc->MarkCounterStylesDirty();
7374 pc->MarkFontFeatureValuesDirty();
7375 pc->MarkFontPaletteValuesDirty();
7377 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
7380 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
7381 if (mStyleSetFilled) {
7382 mStyleSet->RemoveStyleSheet(aSheet);
7383 ApplicableStylesChanged();
7387 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
7388 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
7390 if (aSheet.IsApplicable()) {
7391 AddStyleSheetToStyleSets(aSheet);
7395 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
7396 const bool applicable = aSheet.IsApplicable();
7397 // If we're actually in the document style sheet list
7398 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
7399 if (applicable) {
7400 AddStyleSheetToStyleSets(aSheet);
7401 } else {
7402 RemoveStyleSheetFromStyleSets(aSheet);
7407 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
7408 if (!StyleSheetChangeEventsEnabled()) {
7409 return;
7412 StyleSheetApplicableStateChangeEventInit init;
7413 init.mBubbles = true;
7414 init.mCancelable = true;
7415 init.mStylesheet = &aSheet;
7416 init.mApplicable = aSheet.IsApplicable();
7418 RefPtr<StyleSheetApplicableStateChangeEvent> event =
7419 StyleSheetApplicableStateChangeEvent::Constructor(
7420 this, u"StyleSheetApplicableStateChanged"_ns, init);
7421 event->SetTrusted(true);
7422 event->SetTarget(this);
7423 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7424 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7425 asyncDispatcher->PostDOMEvent();
7428 void Document::PostStyleSheetRemovedEvent(StyleSheet& aSheet) {
7429 if (!StyleSheetChangeEventsEnabled()) {
7430 return;
7433 StyleSheetRemovedEventInit init;
7434 init.mBubbles = true;
7435 init.mCancelable = false;
7436 init.mStylesheet = &aSheet;
7438 RefPtr<StyleSheetRemovedEvent> event =
7439 StyleSheetRemovedEvent::Constructor(this, u"StyleSheetRemoved"_ns, init);
7440 event->SetTrusted(true);
7441 event->SetTarget(this);
7442 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7443 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7444 asyncDispatcher->PostDOMEvent();
7447 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
7448 nsIURI* aSheetURI) {
7449 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
7450 bool bEqual;
7451 nsIURI* uri = aSheets[i]->GetSheetURI();
7453 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
7454 return i;
7457 return -1;
7460 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
7461 nsIURI* aSheetURI) {
7462 MOZ_ASSERT(aSheetURI, "null arg");
7464 // Checking if we have loaded this one already.
7465 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
7466 return NS_ERROR_INVALID_ARG;
7468 // Loading the sheet sync.
7469 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
7471 css::SheetParsingMode parsingMode;
7472 switch (aType) {
7473 case Document::eAgentSheet:
7474 parsingMode = css::eAgentSheetFeatures;
7475 break;
7477 case Document::eUserSheet:
7478 parsingMode = css::eUserSheetFeatures;
7479 break;
7481 case Document::eAuthorSheet:
7482 parsingMode = css::eAuthorSheetFeatures;
7483 break;
7485 default:
7486 MOZ_CRASH("impossible value for aType");
7489 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
7490 css::Loader::UseSystemPrincipal::Yes);
7491 if (result.isErr()) {
7492 return result.unwrapErr();
7495 RefPtr<StyleSheet> sheet = result.unwrap();
7497 sheet->SetAssociatedDocumentOrShadowRoot(this);
7498 MOZ_ASSERT(sheet->IsApplicable());
7500 return AddAdditionalStyleSheet(aType, sheet);
7503 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
7504 StyleSheet* aSheet) {
7505 if (mAdditionalSheets[aType].Contains(aSheet)) {
7506 return NS_ERROR_INVALID_ARG;
7509 if (!aSheet->IsApplicable()) {
7510 return NS_ERROR_INVALID_ARG;
7513 mAdditionalSheets[aType].AppendElement(aSheet);
7515 if (mStyleSetFilled) {
7516 EnsureStyleSet().AppendStyleSheet(*aSheet);
7517 ApplicableStylesChanged();
7519 return NS_OK;
7522 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
7523 nsIURI* aSheetURI) {
7524 MOZ_ASSERT(aSheetURI);
7526 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
7528 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
7529 if (i >= 0) {
7530 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
7531 sheets.RemoveElementAt(i);
7533 if (!mIsGoingAway) {
7534 MOZ_ASSERT(sheetRef->IsApplicable());
7535 if (mStyleSetFilled) {
7536 EnsureStyleSet().RemoveStyleSheet(*sheetRef);
7537 ApplicableStylesChanged();
7540 sheetRef->ClearAssociatedDocumentOrShadowRoot();
7544 nsIGlobalObject* Document::GetScopeObject() const {
7545 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
7546 return scope;
7549 DocGroup* Document::GetDocGroupOrCreate() {
7550 if (!mDocGroup && GetBrowsingContext()) {
7551 BrowsingContextGroup* group = GetBrowsingContext()->Group();
7552 MOZ_ASSERT(group);
7554 nsAutoCString docGroupKey;
7555 nsresult rv = mozilla::dom::DocGroup::GetKey(
7556 NodePrincipal(), group->IsPotentiallyCrossOriginIsolated(),
7557 docGroupKey);
7558 if (NS_SUCCEEDED(rv)) {
7559 mDocGroup = group->AddDocument(docGroupKey, this);
7562 return mDocGroup;
7565 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
7566 mScopeObject = do_GetWeakReference(aGlobal);
7567 if (aGlobal) {
7568 mHasHadScriptHandlingObject = true;
7570 nsPIDOMWindowInner* window = aGlobal->GetAsInnerWindow();
7571 if (!window) {
7572 return;
7575 // Same origin data documents should have the same docGroup as their scope
7576 // window.
7577 if (mLoadedAsData && window->GetExtantDoc() &&
7578 window->GetExtantDoc() != this &&
7579 window->GetExtantDoc()->NodePrincipal() == NodePrincipal()) {
7580 DocGroup* docGroup = window->GetExtantDoc()->GetDocGroup();
7582 if (docGroup) {
7583 if (!mDocGroup) {
7584 mDocGroup = docGroup;
7585 mDocGroup->AddDocument(this);
7586 } else {
7587 MOZ_ASSERT(mDocGroup == docGroup,
7588 "Data document has a mismatched doc group?");
7590 #ifdef DEBUG
7591 AssertDocGroupMatchesKey();
7592 #endif
7593 return;
7596 MOZ_ASSERT_UNREACHABLE(
7597 "Scope window doesn't have DocGroup when creating data document?");
7598 // ... but fall through to be safe.
7601 BrowsingContextGroup* browsingContextGroup =
7602 window->GetBrowsingContextGroup();
7604 // We should already have the principal, and now that we have been added
7605 // to a window, we should be able to join a DocGroup!
7606 nsAutoCString docGroupKey;
7607 nsresult rv = mozilla::dom::DocGroup::GetKey(
7608 NodePrincipal(),
7609 browsingContextGroup->IsPotentiallyCrossOriginIsolated(), docGroupKey);
7610 if (mDocGroup) {
7611 if (NS_SUCCEEDED(rv)) {
7612 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
7614 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
7615 browsingContextGroup);
7616 } else {
7617 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
7619 MOZ_ASSERT(mDocGroup);
7622 MOZ_ASSERT_IF(
7623 mNodeInfoManager->GetArenaAllocator(),
7624 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
7628 bool Document::ContainsEMEContent() {
7629 nsPIDOMWindowInner* win = GetInnerWindow();
7630 // Note this case is different from checking just media elements in that
7631 // it covers when we've created MediaKeys but not associated them with a
7632 // media element.
7633 return win && win->HasActiveMediaKeysInstance();
7636 bool Document::ContainsMSEContent() {
7637 bool containsMSE = false;
7639 auto check = [&containsMSE](nsISupports* aSupports) {
7640 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7641 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7642 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
7643 if (ms) {
7644 containsMSE = true;
7649 EnumerateActivityObservers(check);
7650 return containsMSE;
7653 static void NotifyActivityChangedCallback(nsISupports* aSupports) {
7654 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7655 if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7656 mediaElem->NotifyOwnerDocumentActivityChanged();
7658 nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
7659 do_QueryInterface(aSupports));
7660 if (objectLoadingContent) {
7661 nsObjectLoadingContent* olc =
7662 static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
7663 olc->NotifyOwnerDocumentActivityChanged();
7665 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
7666 do_QueryInterface(aSupports));
7667 if (objectDocumentActivity) {
7668 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
7669 } else {
7670 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
7671 do_QueryInterface(aSupports));
7672 if (imageLoadingContent) {
7673 auto ilc = static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
7674 ilc->NotifyOwnerDocumentActivityChanged();
7679 void Document::NotifyActivityChanged() {
7680 EnumerateActivityObservers(NotifyActivityChangedCallback);
7683 void Document::SetContainer(nsDocShell* aContainer) {
7684 if (aContainer) {
7685 mDocumentContainer = aContainer;
7686 } else {
7687 mDocumentContainer = WeakPtr<nsDocShell>();
7690 mInChromeDocShell =
7691 aContainer && aContainer->GetBrowsingContext()->IsChrome();
7693 NotifyActivityChanged();
7695 // IsTopLevelWindowInactive depends on the docshell, so
7696 // update the cached value now that it's available.
7697 UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false);
7698 if (!aContainer) {
7699 return;
7702 BrowsingContext* context = aContainer->GetBrowsingContext();
7703 MOZ_ASSERT_IF(context && mDocGroup,
7704 context->Group() == mDocGroup->GetBrowsingContextGroup());
7705 if (context && context->IsContent()) {
7706 SetIsTopLevelContentDocument(context->IsTopContent());
7707 SetIsContentDocument(true);
7708 } else {
7709 SetIsTopLevelContentDocument(false);
7710 SetIsContentDocument(false);
7714 nsISupports* Document::GetContainer() const {
7715 return static_cast<nsIDocShell*>(mDocumentContainer);
7718 void Document::SetScriptGlobalObject(
7719 nsIScriptGlobalObject* aScriptGlobalObject) {
7720 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
7721 mAnimationController->IsPausedByType(
7722 SMILTimeContainer::PAUSE_PAGEHIDE |
7723 SMILTimeContainer::PAUSE_BEGIN),
7724 "Clearing window pointer while animations are unpaused");
7726 if (mScriptGlobalObject && !aScriptGlobalObject) {
7727 // We're detaching from the window. We need to grab a pointer to
7728 // our layout history state now.
7729 mLayoutHistoryState = GetLayoutHistoryState();
7731 // Also make sure to remove our onload blocker now if we haven't done it yet
7732 if (mOnloadBlockCount != 0) {
7733 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
7734 if (loadGroup) {
7735 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
7739 if (GetController().isSome()) {
7740 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
7741 loader->ClearCacheForControlledDocument(this);
7744 // We may become controlled again if this document comes back out
7745 // of bfcache. Clear our state to allow that to happen. Only
7746 // clear this flag if we are actually controlled, though, so pages
7747 // that were force reloaded don't become controlled when they
7748 // come out of bfcache.
7749 mMaybeServiceWorkerControlled = false;
7752 if (GetWindowContext()) {
7753 // The document is about to lose its window, so this is a good time to
7754 // send our page use counters, while we still have access to our
7755 // WindowContext.
7757 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
7758 // catches some cases of documents losing their window that don't
7759 // get in here.)
7760 SendPageUseCounters();
7764 // BlockOnload() might be called before mScriptGlobalObject is set.
7765 // We may need to add the blocker once mScriptGlobalObject is set.
7766 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
7768 mScriptGlobalObject = aScriptGlobalObject;
7770 if (needOnloadBlocker) {
7771 EnsureOnloadBlocker();
7774 UpdateFrameRequestCallbackSchedulingState();
7776 if (aScriptGlobalObject) {
7777 // Go back to using the docshell for the layout history state
7778 mLayoutHistoryState = nullptr;
7779 SetScopeObject(aScriptGlobalObject);
7780 mHasHadDefaultView = true;
7782 if (mAllowDNSPrefetch) {
7783 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7784 if (docShell) {
7785 #ifdef DEBUG
7786 nsCOMPtr<nsIWebNavigation> webNav =
7787 do_GetInterface(aScriptGlobalObject);
7788 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
7789 "Unexpected container or script global?");
7790 #endif
7791 bool allowDNSPrefetch;
7792 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
7793 mAllowDNSPrefetch = allowDNSPrefetch;
7797 // If we are set in a window that is already focused we should remember this
7798 // as the time the document gained focus.
7799 if (HasFocus(IgnoreErrors())) {
7800 SetLastFocusTime(TimeStamp::Now());
7804 // Remember the pointer to our window (or lack there of), to avoid
7805 // having to QI every time it's asked for.
7806 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
7807 mWindow = window;
7809 // Now that we know what our window is, we can flush the CSP errors to the
7810 // Web Console. We are flushing all messages that occurred and were stored in
7811 // the queue prior to this point.
7812 if (mCSP) {
7813 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
7816 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
7817 do_QueryInterface(GetChannel());
7818 if (internalChannel) {
7819 nsCOMArray<nsISecurityConsoleMessage> messages;
7820 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
7821 MOZ_ASSERT(NS_SUCCEEDED(rv));
7822 SendToConsole(messages);
7825 // Set our visibility state, but do not fire the event. This is correct
7826 // because either we're coming out of bfcache (in which case IsVisible() will
7827 // still test false at this point and no state change will happen) or we're
7828 // doing the initial document load and don't want to fire the event for this
7829 // change.
7831 // When the visibility is changed, notify it to observers.
7832 // Some observers need the notification, for example HTMLMediaElement uses
7833 // it to update internal media resource allocation.
7834 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
7835 // creation are already done before Document::SetScriptGlobalObject() call.
7836 // MediaDecoder decides whether starting decoding is decided based on
7837 // document's visibility. When the MediaDecoder is created,
7838 // Document::SetScriptGlobalObject() is not yet called and document is
7839 // hidden state. Therefore the MediaDecoder decides that decoding is
7840 // not yet necessary. But soon after Document::SetScriptGlobalObject()
7841 // call, the document becomes not hidden. At the time, MediaDecoder needs
7842 // to know it and needs to start updating decoding.
7843 UpdateVisibilityState(DispatchVisibilityChange::No);
7845 // The global in the template contents owner document should be the same.
7846 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
7847 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
7850 // Tell the script loader about the new global object.
7851 if (mScriptLoader && !IsTemplateContentsOwner()) {
7852 mScriptLoader->SetGlobalObject(mScriptGlobalObject);
7855 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
7856 mScriptGlobalObject && GetChannel()) {
7857 // If we are shift-reloaded, don't associate with a ServiceWorker.
7858 if (mDocumentContainer->IsForceReloading()) {
7859 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
7860 return;
7863 mMaybeServiceWorkerControlled = true;
7867 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
7868 MOZ_ASSERT(!mScriptGlobalObject,
7869 "Do not call this when mScriptGlobalObject is set!");
7870 if (mHasHadDefaultView) {
7871 return nullptr;
7874 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
7875 do_QueryReferent(mScopeObject);
7876 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
7877 if (win) {
7878 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
7879 if (!outer || outer->GetCurrentInnerWindow() != win) {
7880 NS_WARNING("Wrong inner/outer window combination!");
7881 return nullptr;
7884 return scriptHandlingObject;
7886 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
7887 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
7888 "Wrong script object!");
7889 if (aScriptObject) {
7890 SetScopeObject(aScriptObject);
7891 mHasHadDefaultView = false;
7895 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
7896 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
7897 // Let's use mScriptGlobalObject. Even if the document is already removed from
7898 // the docshell, the outer window might be still obtainable from the it.
7899 nsCOMPtr<nsPIDOMWindowOuter> win;
7900 if (mRemovedFromDocShell) {
7901 // The docshell returns the outer window we are done.
7902 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
7903 if (kungFuDeathGrip) {
7904 win = kungFuDeathGrip->GetWindow();
7906 } else {
7907 if (nsCOMPtr<nsPIDOMWindowInner> inner =
7908 do_QueryInterface(mScriptGlobalObject)) {
7909 // mScriptGlobalObject is always the inner window, let's get the outer.
7910 win = inner->GetOuterWindow();
7914 return win;
7917 bool Document::InternalAllowXULXBL() {
7918 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
7919 mAllowXULXBL = eTriTrue;
7920 return true;
7923 mAllowXULXBL = eTriFalse;
7924 return false;
7927 // Note: We don't hold a reference to the document observer; we assume
7928 // that it has a live reference to the document.
7929 void Document::AddObserver(nsIDocumentObserver* aObserver) {
7930 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
7931 "Observer already in the list");
7932 mObservers.AppendElement(aObserver);
7933 AddMutationObserver(aObserver);
7936 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
7937 // If we're in the process of destroying the document (and we're
7938 // informing the observers of the destruction), don't remove the
7939 // observers from the list. This is not a big deal, since we
7940 // don't hold a live reference to the observers.
7941 if (!mInDestructor) {
7942 RemoveMutationObserver(aObserver);
7943 return mObservers.RemoveElement(aObserver);
7946 return mObservers.Contains(aObserver);
7949 void Document::BeginUpdate() {
7950 ++mUpdateNestLevel;
7951 nsContentUtils::AddScriptBlocker();
7952 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
7955 void Document::EndUpdate() {
7956 const bool reset = !mPendingMaybeEditingStateChanged;
7957 mPendingMaybeEditingStateChanged = true;
7959 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
7961 --mUpdateNestLevel;
7963 nsContentUtils::RemoveScriptBlocker();
7965 if (mXULBroadcastManager) {
7966 mXULBroadcastManager->MaybeBroadcast();
7969 if (reset) {
7970 mPendingMaybeEditingStateChanged = false;
7972 MaybeEditingStateChanged();
7975 void Document::BeginLoad() {
7976 if (IsEditingOn()) {
7977 // Reset() blows away all event listeners in the document, and our
7978 // editor relies heavily on those. Midas is turned on, to make it
7979 // work, re-initialize it to give it a chance to add its event
7980 // listeners again.
7982 TurnEditingOff();
7983 EditingStateChanged();
7986 MOZ_ASSERT(!mDidCallBeginLoad);
7987 mDidCallBeginLoad = true;
7989 // Block onload here to prevent having to deal with blocking and
7990 // unblocking it while we know the document is loading.
7991 BlockOnload();
7992 mDidFireDOMContentLoaded = false;
7993 BlockDOMContentLoaded();
7995 if (mScriptLoader) {
7996 mScriptLoader->BeginDeferringScripts();
7999 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
8002 void Document::MozSetImageElement(const nsAString& aImageElementId,
8003 Element* aElement) {
8004 if (aImageElementId.IsEmpty()) return;
8006 // Hold a script blocker while calling SetImageElement since that can call
8007 // out to id-observers
8008 nsAutoScriptBlocker scriptBlocker;
8010 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
8011 if (entry) {
8012 entry->SetImageElement(aElement);
8013 if (entry->IsEmpty()) {
8014 mIdentifierMap.RemoveEntry(entry);
8019 void Document::DispatchContentLoadedEvents() {
8020 // If you add early returns from this method, make sure you're
8021 // calling UnblockOnload properly.
8023 // Unpin references to preloaded images
8024 mPreloadingImages.Clear();
8026 // DOM manipulation after content loaded should not care if the element
8027 // came from the preloader.
8028 mPreloadedPreconnects.Clear();
8030 if (mTiming) {
8031 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
8034 // Dispatch observer notification to notify observers document is interactive.
8035 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
8036 if (os) {
8037 nsIPrincipal* principal = NodePrincipal();
8038 os->NotifyObservers(ToSupports(this),
8039 principal->IsSystemPrincipal()
8040 ? "chrome-document-interactive"
8041 : "content-document-interactive",
8042 nullptr);
8045 // Fire a DOM event notifying listeners that this document has been
8046 // loaded (excluding images and other loads initiated by this
8047 // document).
8048 nsContentUtils::DispatchTrustedEvent(this, this, u"DOMContentLoaded"_ns,
8049 CanBubble::eYes, Cancelable::eNo);
8051 if (auto* const window = GetInnerWindow()) {
8052 const RefPtr<ServiceWorkerContainer> serviceWorker =
8053 window->Navigator()->ServiceWorker();
8055 // This could cause queued messages from a service worker to get
8056 // dispatched on serviceWorker.
8057 serviceWorker->StartMessages();
8060 if (MayStartLayout()) {
8061 MaybeResolveReadyForIdle();
8064 if (mTiming) {
8065 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
8068 // If this document is a [i]frame, fire a DOMFrameContentLoaded
8069 // event on all parent documents notifying that the HTML (excluding
8070 // other external files such as images and stylesheets) in a frame
8071 // has finished loading.
8073 // target_frame is the [i]frame element that will be used as the
8074 // target for the event. It's the [i]frame whose content is done
8075 // loading.
8076 nsCOMPtr<Element> target_frame = GetEmbedderElement();
8078 if (target_frame && target_frame->IsInComposedDoc()) {
8079 nsCOMPtr<Document> parent = target_frame->OwnerDoc();
8080 while (parent) {
8081 RefPtr<Event> event;
8082 if (parent) {
8083 IgnoredErrorResult ignored;
8084 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
8087 if (event) {
8088 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
8090 event->SetTarget(target_frame);
8091 event->SetTrusted(true);
8093 // To dispatch this event we must manually call
8094 // EventDispatcher::Dispatch() on the ancestor document since the
8095 // target is not in the same document, so the event would never reach
8096 // the ancestor document if we used the normal event
8097 // dispatching code.
8099 WidgetEvent* innerEvent = event->WidgetEventPtr();
8100 if (innerEvent) {
8101 nsEventStatus status = nsEventStatus_eIgnore;
8103 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
8104 EventDispatcher::Dispatch(parent, context, innerEvent, event,
8105 &status);
8110 parent = parent->GetInProcessParentDocument();
8114 nsPIDOMWindowInner* inner = GetInnerWindow();
8115 if (inner) {
8116 inner->NoteDOMContentLoaded();
8119 // TODO
8120 if (mMaybeServiceWorkerControlled) {
8121 using mozilla::dom::ServiceWorkerManager;
8122 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
8123 if (swm) {
8124 Maybe<ClientInfo> clientInfo = GetClientInfo();
8125 if (clientInfo.isSome()) {
8126 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
8131 if (mSetCompleteAfterDOMContentLoaded) {
8132 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
8133 mSetCompleteAfterDOMContentLoaded = false;
8136 UnblockOnload(true);
8139 void Document::EndLoad() {
8140 bool turnOnEditing =
8141 mParser && (IsInDesignMode() || mContentEditableCount > 0);
8143 #if defined(DEBUG)
8144 // only assert if nothing stopped the load on purpose
8145 if (!mParserAborted) {
8146 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
8148 #endif
8150 // EndLoad may have been called without a matching call to BeginLoad, in the
8151 // case of a failed parse (for example, due to timeout). In such a case, we
8152 // still want to execute part of this code to do appropriate cleanup, but we
8153 // gate part of it because it is intended to match 1-for-1 with calls to
8154 // BeginLoad. We have an explicit flag bit for this purpose, since it's
8155 // complicated and error prone to derive this condition from other related
8156 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
8158 // Part 1: Code that always executes to cleanup end of parsing, whether
8159 // that parsing was successful or not.
8161 // Drop the ref to our parser, if any, but keep hold of the sink so that we
8162 // can flush it from FlushPendingNotifications as needed. We might have to
8163 // do that to get a StartLayout() to happen.
8164 if (mParser) {
8165 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
8166 mParser = nullptr;
8169 // Update the attributes on the PerformanceNavigationTiming before notifying
8170 // the onload observers.
8171 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
8172 if (RefPtr<Performance> performance = window->GetPerformance()) {
8173 performance->UpdateNavigationTimingEntry();
8177 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
8179 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
8181 if (!mDidCallBeginLoad) {
8182 return;
8184 mDidCallBeginLoad = false;
8186 UnblockDOMContentLoaded();
8188 if (turnOnEditing) {
8189 EditingStateChanged();
8192 if (!GetWindow()) {
8193 // This is a document that's not in a window. For example, this could be an
8194 // XMLHttpRequest responseXML document, or a document created via DOMParser
8195 // or DOMImplementation. We don't reach this code normally for such
8196 // documents (which is not obviously correct), but can reach it via
8197 // document.open()/document.close().
8199 // Such documents don't fire load events, but per spec should set their
8200 // readyState to "complete" when parsing and all loading of subresources is
8201 // done. Parsing is done now, and documents not in a window don't load
8202 // subresources, so just go ahead and mark ourselves as complete.
8203 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
8204 /* updateTimingInformation = */ false);
8206 // Reset mSkipLoadEventAfterClose just in case.
8207 mSkipLoadEventAfterClose = false;
8211 void Document::UnblockDOMContentLoaded() {
8212 MOZ_ASSERT(mBlockDOMContentLoaded);
8213 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
8214 return;
8217 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
8218 ("DOCUMENT %p UnblockDOMContentLoaded", this));
8220 mDidFireDOMContentLoaded = true;
8221 if (PresShell* presShell = GetPresShell()) {
8222 presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
8225 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
8226 if (!mSynchronousDOMContentLoaded) {
8227 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8228 nsCOMPtr<nsIRunnable> ev =
8229 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
8230 &Document::DispatchContentLoadedEvents);
8231 Dispatch(ev.forget());
8232 } else {
8233 DispatchContentLoadedEvents();
8237 void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) {
8238 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
8239 "Someone forgot a scriptblocker");
8240 NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged,
8241 (this, aElement, aStateMask));
8244 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
8245 StyleRuleChangeKind) {
8246 if (aSheet.IsApplicable()) {
8247 ApplicableStylesChanged();
8251 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
8252 if (aRule.IsIncompleteImportRule()) {
8253 return;
8256 if (aSheet.IsApplicable()) {
8257 ApplicableStylesChanged();
8261 void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
8262 if (aSheet.IsApplicable()) {
8263 ApplicableStylesChanged();
8267 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
8268 if (aSheet.IsApplicable()) {
8269 ApplicableStylesChanged();
8273 static Element* GetCustomContentContainer(PresShell* aPresShell) {
8274 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
8275 return nullptr;
8278 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
8281 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
8282 bool aForce, ErrorResult& aRv) {
8283 RefPtr<PresShell> shell = GetPresShell();
8284 if (aForce && !GetCustomContentContainer(shell)) {
8285 FlushPendingNotifications(FlushType::Layout);
8286 shell = GetPresShell();
8289 nsAutoScriptBlocker scriptBlocker;
8291 RefPtr<AnonymousContent> anonContent = AnonymousContent::Create(*this);
8292 if (!anonContent) {
8293 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
8294 return nullptr;
8297 mAnonymousContents.AppendElement(anonContent);
8299 if (RefPtr<Element> container = GetCustomContentContainer(shell)) {
8300 // If the container is empty and we have other anon content we should be
8301 // about to show all the other anonymous content nodes.
8302 if (container->HasChildren() || mAnonymousContents.Length() == 1) {
8303 container->AppendChildTo(anonContent->Host(), true, IgnoreErrors());
8304 if (auto* canvasFrame = shell->GetCanvasFrame()) {
8305 canvasFrame->ShowCustomContentContainer();
8310 return anonContent.forget();
8313 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
8314 PresShell* aPresShell) {
8315 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
8316 if (!container) {
8317 return;
8319 container->RemoveChild(*aAnonContent.Host(), IgnoreErrors());
8322 void Document::RemoveAnonymousContent(AnonymousContent& aContent) {
8323 nsAutoScriptBlocker scriptBlocker;
8325 auto index = mAnonymousContents.IndexOf(&aContent);
8326 if (index == mAnonymousContents.NoIndex) {
8327 return;
8330 mAnonymousContents.RemoveElementAt(index);
8331 RemoveAnonContentFromCanvas(aContent, GetPresShell());
8333 if (mAnonymousContents.IsEmpty() &&
8334 GetCustomContentContainer(GetPresShell())) {
8335 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
8339 Element* Document::GetAnonRootIfInAnonymousContentContainer(
8340 nsINode* aNode) const {
8341 if (!aNode->IsInNativeAnonymousSubtree()) {
8342 return nullptr;
8345 PresShell* presShell = GetPresShell();
8346 if (!presShell || !presShell->GetCanvasFrame()) {
8347 return nullptr;
8350 nsAutoScriptBlocker scriptBlocker;
8351 nsCOMPtr<Element> customContainer =
8352 presShell->GetCanvasFrame()->GetCustomContentContainer();
8353 if (!customContainer) {
8354 return nullptr;
8357 // An arbitrary number of elements can be inserted as children of the custom
8358 // container frame. We want the one that was added that contains aNode, so
8359 // we need to keep track of the last child separately using |child| here.
8360 nsINode* child = aNode;
8361 nsINode* parent = aNode->GetParentNode();
8362 while (parent && parent->IsInNativeAnonymousSubtree()) {
8363 if (parent == customContainer) {
8364 return Element::FromNode(child);
8366 child = parent;
8367 parent = child->GetParentNode();
8369 return nullptr;
8372 Maybe<ClientInfo> Document::GetClientInfo() const {
8373 if (const Document* orig = GetOriginalDocument()) {
8374 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
8375 return info;
8379 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8380 return inner->GetClientInfo();
8383 return Maybe<ClientInfo>();
8386 Maybe<ClientState> Document::GetClientState() const {
8387 if (const Document* orig = GetOriginalDocument()) {
8388 if (Maybe<ClientState> state = orig->GetClientState()) {
8389 return state;
8393 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8394 return inner->GetClientState();
8397 return Maybe<ClientState>();
8400 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
8401 if (const Document* orig = GetOriginalDocument()) {
8402 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
8403 return controller;
8407 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8408 return inner->GetController();
8411 return Maybe<ServiceWorkerDescriptor>();
8415 // Document interface
8417 DocumentType* Document::GetDoctype() const {
8418 for (nsIContent* child = GetFirstChild(); child;
8419 child = child->GetNextSibling()) {
8420 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
8421 return static_cast<DocumentType*>(child);
8424 return nullptr;
8427 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
8428 if (!mDOMImplementation) {
8429 nsCOMPtr<nsIURI> uri;
8430 NS_NewURI(getter_AddRefs(uri), "about:blank");
8431 if (!uri) {
8432 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
8433 return nullptr;
8435 bool hasHadScriptObject = true;
8436 nsIScriptGlobalObject* scriptObject =
8437 GetScriptHandlingObject(hasHadScriptObject);
8438 if (!scriptObject && hasHadScriptObject) {
8439 rv.Throw(NS_ERROR_UNEXPECTED);
8440 return nullptr;
8442 mDOMImplementation = new DOMImplementation(
8443 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
8446 return mDOMImplementation;
8449 bool IsLowercaseASCII(const nsAString& aValue) {
8450 int32_t len = aValue.Length();
8451 for (int32_t i = 0; i < len; ++i) {
8452 char16_t c = aValue[i];
8453 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
8454 return false;
8457 return true;
8460 already_AddRefed<Element> Document::CreateElement(
8461 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8462 ErrorResult& rv) {
8463 rv = nsContentUtils::CheckQName(aTagName, false);
8464 if (rv.Failed()) {
8465 return nullptr;
8468 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
8469 nsAutoString lcTagName;
8470 if (needsLowercase) {
8471 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
8474 const nsString* is = nullptr;
8475 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
8476 if (aOptions.IsElementCreationOptions()) {
8477 const ElementCreationOptions& options =
8478 aOptions.GetAsElementCreationOptions();
8480 if (options.mIs.WasPassed()) {
8481 is = &options.mIs.Value();
8484 // Check 'pseudo' and throw an exception if it's not one allowed
8485 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
8486 if (options.mPseudo.WasPassed()) {
8487 Maybe<PseudoStyleType> type =
8488 nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
8489 if (!type || *type == PseudoStyleType::NotPseudo ||
8490 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
8491 rv.ThrowNotSupportedError("Invalid pseudo-element");
8492 return nullptr;
8494 pseudoType = *type;
8498 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
8499 nullptr, mDefaultElementType, is);
8501 if (pseudoType != PseudoStyleType::NotPseudo) {
8502 elem->SetPseudoElementType(pseudoType);
8505 return elem.forget();
8508 already_AddRefed<Element> Document::CreateElementNS(
8509 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8510 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
8511 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8512 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8513 mNodeInfoManager, ELEMENT_NODE,
8514 getter_AddRefs(nodeInfo));
8515 if (rv.Failed()) {
8516 return nullptr;
8519 const nsString* is = nullptr;
8520 if (aOptions.IsElementCreationOptions()) {
8521 const ElementCreationOptions& options =
8522 aOptions.GetAsElementCreationOptions();
8523 if (options.mIs.WasPassed()) {
8524 is = &options.mIs.Value();
8528 nsCOMPtr<Element> element;
8529 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
8530 NOT_FROM_PARSER, is);
8531 if (rv.Failed()) {
8532 return nullptr;
8535 return element.forget();
8538 already_AddRefed<Element> Document::CreateXULElement(
8539 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8540 ErrorResult& aRv) {
8541 aRv = nsContentUtils::CheckQName(aTagName, false);
8542 if (aRv.Failed()) {
8543 return nullptr;
8546 const nsString* is = nullptr;
8547 if (aOptions.IsElementCreationOptions()) {
8548 const ElementCreationOptions& options =
8549 aOptions.GetAsElementCreationOptions();
8550 if (options.mIs.WasPassed()) {
8551 is = &options.mIs.Value();
8555 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
8556 if (!elem) {
8557 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
8558 return nullptr;
8560 return elem.forget();
8563 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
8564 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8565 return text.forget();
8568 already_AddRefed<nsTextNode> Document::CreateTextNode(
8569 const nsAString& aData) const {
8570 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8571 // Don't notify; this node is still being created.
8572 text->SetText(aData, false);
8573 return text.forget();
8576 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
8577 RefPtr<DocumentFragment> frag =
8578 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
8579 return frag.forget();
8582 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
8583 already_AddRefed<dom::Comment> Document::CreateComment(
8584 const nsAString& aData) const {
8585 RefPtr<dom::Comment> comment =
8586 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
8588 // Don't notify; this node is still being created.
8589 comment->SetText(aData, false);
8590 return comment.forget();
8593 already_AddRefed<CDATASection> Document::CreateCDATASection(
8594 const nsAString& aData, ErrorResult& rv) {
8595 if (IsHTMLDocument()) {
8596 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8597 return nullptr;
8600 if (FindInReadable(u"]]>"_ns, aData)) {
8601 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8602 return nullptr;
8605 RefPtr<CDATASection> cdata =
8606 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
8608 // Don't notify; this node is still being created.
8609 cdata->SetText(aData, false);
8611 return cdata.forget();
8614 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
8615 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
8616 nsresult res = nsContentUtils::CheckQName(aTarget, false);
8617 if (NS_FAILED(res)) {
8618 rv.Throw(res);
8619 return nullptr;
8622 if (FindInReadable(u"?>"_ns, aData)) {
8623 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8624 return nullptr;
8627 RefPtr<ProcessingInstruction> pi =
8628 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
8630 return pi.forget();
8633 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
8634 ErrorResult& rv) {
8635 if (!mNodeInfoManager) {
8636 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8637 return nullptr;
8640 nsresult res = nsContentUtils::CheckQName(aName, false);
8641 if (NS_FAILED(res)) {
8642 rv.Throw(res);
8643 return nullptr;
8646 nsAutoString name;
8647 if (IsHTMLDocument()) {
8648 nsContentUtils::ASCIIToLower(aName, name);
8649 } else {
8650 name = aName;
8653 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8654 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
8655 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
8656 if (NS_FAILED(res)) {
8657 rv.Throw(res);
8658 return nullptr;
8661 RefPtr<Attr> attribute =
8662 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8663 return attribute.forget();
8666 already_AddRefed<Attr> Document::CreateAttributeNS(
8667 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8668 ErrorResult& rv) {
8669 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8670 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8671 mNodeInfoManager, ATTRIBUTE_NODE,
8672 getter_AddRefs(nodeInfo));
8673 if (rv.Failed()) {
8674 return nullptr;
8677 RefPtr<Attr> attribute =
8678 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8679 return attribute.forget();
8682 void Document::ScheduleForPresAttrEvaluation(Element* aElement) {
8683 MOZ_ASSERT(aElement->IsInComposedDoc());
8684 DebugOnly<bool> inserted = mLazyPresElements.EnsureInserted(aElement);
8685 MOZ_ASSERT(inserted);
8686 if (aElement->HasServoData()) {
8687 // TODO(emilio): RESTYLE_SELF is too strong, there should be no need to
8688 // re-selector-match, but right now this is needed to pick up the new mapped
8689 // attributes. We need something like RESTYLE_STYLE_ATTRIBUTE but for mapped
8690 // attributes.
8691 nsLayoutUtils::PostRestyleEvent(aElement, RestyleHint::RESTYLE_SELF,
8692 nsChangeHint(0));
8696 void Document::UnscheduleForPresAttrEvaluation(Element* aElement) {
8697 mLazyPresElements.Remove(aElement);
8700 void Document::DoResolveScheduledPresAttrs() {
8701 MOZ_ASSERT(!mLazyPresElements.IsEmpty());
8702 for (Element* el : mLazyPresElements) {
8703 MOZ_ASSERT(el->IsInComposedDoc(),
8704 "Un-schedule when removing from the document");
8705 MOZ_ASSERT(el->IsPendingMappedAttributeEvaluation());
8706 if (auto* svg = SVGElement::FromNode(el)) {
8707 // SVG does its own (very similar) thing, for now at least.
8708 svg->UpdateMappedDeclarationBlock();
8709 } else {
8710 MappedDeclarationsBuilder builder(*el, *this,
8711 el->GetMappedAttributeStyle());
8712 auto function = el->GetAttributeMappingFunction();
8713 function(builder);
8714 el->SetMappedDeclarationBlock(builder.TakeDeclarationBlock());
8716 MOZ_ASSERT(!el->IsPendingMappedAttributeEvaluation());
8718 mLazyPresElements.Clear();
8721 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
8722 const {
8723 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
8725 for (const nsWeakPtr& weakNode : mBlockedNodesByClassifier) {
8726 if (nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode)) {
8727 // Consider only nodes to which we have managed to get strong references.
8728 // Coping with nullptrs since it's expected for nodes to disappear when
8729 // nobody else is referring to them.
8730 list->AppendElement(node);
8734 return list.forget();
8737 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
8738 aSheetSet.Truncate();
8740 // Look through our sheets, find the selected set title
8741 size_t count = SheetCount();
8742 nsAutoString title;
8743 for (size_t index = 0; index < count; index++) {
8744 StyleSheet* sheet = SheetAt(index);
8745 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8747 if (sheet->Disabled()) {
8748 // Disabled sheets don't affect the currently selected set
8749 continue;
8752 sheet->GetTitle(title);
8754 if (aSheetSet.IsEmpty()) {
8755 aSheetSet = title;
8756 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
8757 // Sheets from multiple sets enabled; return null string, per spec.
8758 SetDOMStringToNull(aSheetSet);
8759 return;
8764 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
8765 if (DOMStringIsNull(aSheetSet)) {
8766 return;
8769 // Must update mLastStyleSheetSet before doing anything else with stylesheets
8770 // or CSSLoaders.
8771 mLastStyleSheetSet = aSheetSet;
8772 EnableStyleSheetsForSetInternal(aSheetSet, true);
8775 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
8776 mPreferredStyleSheetSet = aSheetSet;
8777 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
8778 // spec.
8779 if (DOMStringIsNull(mLastStyleSheetSet)) {
8780 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
8781 // per spec. The idea here is that we're changing our preferred set and
8782 // that shouldn't change the value of lastStyleSheetSet. Also, we're
8783 // using the Internal version so we can update the CSSLoader and not have
8784 // to worry about null strings.
8785 EnableStyleSheetsForSetInternal(aSheetSet, true);
8789 DOMStringList* Document::StyleSheetSets() {
8790 if (!mStyleSheetSetList) {
8791 mStyleSheetSetList = new DOMStyleSheetSetList(this);
8793 return mStyleSheetSetList;
8796 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
8797 // Per spec, passing in null is a no-op.
8798 if (!DOMStringIsNull(aSheetSet)) {
8799 // Note: must make sure to not change the CSSLoader's preferred sheet --
8800 // that value should be equal to either our lastStyleSheetSet (if that's
8801 // non-null) or to our preferredStyleSheetSet. And this method doesn't
8802 // change either of those.
8803 EnableStyleSheetsForSetInternal(aSheetSet, false);
8807 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
8808 bool aUpdateCSSLoader) {
8809 size_t count = SheetCount();
8810 nsAutoString title;
8811 for (size_t index = 0; index < count; index++) {
8812 StyleSheet* sheet = SheetAt(index);
8813 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8815 sheet->GetTitle(title);
8816 if (!title.IsEmpty()) {
8817 sheet->SetEnabled(title.Equals(aSheetSet));
8820 if (aUpdateCSSLoader) {
8821 CSSLoader()->DocumentStyleSheetSetChanged();
8823 if (EnsureStyleSet().StyleSheetsHaveChanged()) {
8824 ApplicableStylesChanged();
8828 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
8829 nsAutoCString charset;
8830 GetDocumentCharacterSet()->Name(charset);
8831 CopyASCIItoUTF16(charset, aCharacterSet);
8834 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
8835 ErrorResult& rv) const {
8836 nsINode* imported = &aNode;
8838 switch (imported->NodeType()) {
8839 case DOCUMENT_NODE: {
8840 break;
8842 case DOCUMENT_FRAGMENT_NODE:
8843 case ATTRIBUTE_NODE:
8844 case ELEMENT_NODE:
8845 case PROCESSING_INSTRUCTION_NODE:
8846 case TEXT_NODE:
8847 case CDATA_SECTION_NODE:
8848 case COMMENT_NODE:
8849 case DOCUMENT_TYPE_NODE: {
8850 return imported->Clone(aDeep, mNodeInfoManager, rv);
8852 default: {
8853 NS_WARNING("Don't know how to clone this nodetype for importNode.");
8857 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8858 return nullptr;
8861 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
8862 return nsRange::Create(this, 0, this, 0, rv);
8865 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
8866 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
8867 ErrorResult& rv) const {
8868 RefPtr<NodeIterator> iterator =
8869 new NodeIterator(&aRoot, aWhatToShow, aFilter);
8870 return iterator.forget();
8873 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
8874 uint32_t aWhatToShow,
8875 NodeFilter* aFilter,
8876 ErrorResult& rv) const {
8877 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
8878 return walker.forget();
8881 already_AddRefed<Location> Document::GetLocation() const {
8882 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
8884 if (!w) {
8885 return nullptr;
8888 return do_AddRef(w->Location());
8891 already_AddRefed<nsIURI> Document::GetDomainURI() {
8892 nsIPrincipal* principal = NodePrincipal();
8894 nsCOMPtr<nsIURI> uri;
8895 principal->GetDomain(getter_AddRefs(uri));
8896 if (uri) {
8897 return uri.forget();
8899 auto* basePrin = BasePrincipal::Cast(principal);
8900 basePrin->GetURI(getter_AddRefs(uri));
8901 return uri.forget();
8904 void Document::GetDomain(nsAString& aDomain) {
8905 nsCOMPtr<nsIURI> uri = GetDomainURI();
8907 if (!uri) {
8908 aDomain.Truncate();
8909 return;
8912 nsAutoCString hostName;
8913 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
8914 if (NS_SUCCEEDED(rv)) {
8915 CopyUTF8toUTF16(hostName, aDomain);
8916 } else {
8917 // If we can't get the host from the URI (e.g. about:, javascript:,
8918 // etc), just return an empty string.
8919 aDomain.Truncate();
8923 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
8924 if (!GetBrowsingContext()) {
8925 // If our browsing context is null; disallow setting domain
8926 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8927 return;
8930 if (mSandboxFlags & SANDBOXED_DOMAIN) {
8931 // We're sandboxed; disallow setting domain
8932 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8933 return;
8936 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
8937 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8938 return;
8941 if (aDomain.IsEmpty()) {
8942 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8943 return;
8946 nsCOMPtr<nsIURI> uri = GetDomainURI();
8947 if (!uri) {
8948 rv.Throw(NS_ERROR_FAILURE);
8949 return;
8952 // Check new domain - must be a superdomain of the current host
8953 // For example, a page from foo.bar.com may set domain to bar.com,
8954 // but not to ar.com, baz.com, or fi.foo.bar.com.
8956 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
8957 if (!newURI) {
8958 // Error: illegal domain
8959 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8960 return;
8963 if (GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated()) {
8964 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
8965 return;
8968 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
8969 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
8970 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
8971 wgc->SendSetDocumentDomain(newURI);
8975 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
8976 const nsACString& aHostString) {
8977 if (aHostString.IsEmpty()) {
8978 return nullptr;
8981 // Create new URI
8982 nsCOMPtr<nsIURI> uri = GetDomainURI();
8983 if (!uri) {
8984 return nullptr;
8987 nsresult rv;
8988 rv = NS_MutateURI(uri)
8989 .SetUserPass(""_ns)
8990 .SetPort(-1) // we want to reset the port number if needed.
8991 .SetHostPort(aHostString)
8992 .Finalize(uri);
8993 if (NS_FAILED(rv)) {
8994 return nullptr;
8997 return uri.forget();
9000 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
9001 const nsAString& aNewDomain, nsIURI* aOrigHost) {
9002 if (NS_WARN_IF(!aOrigHost)) {
9003 return nullptr;
9006 nsCOMPtr<nsIURI> newURI =
9007 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
9008 if (!newURI) {
9009 // Error: failed to parse input domain
9010 return nullptr;
9013 if (!IsValidDomain(aOrigHost, newURI)) {
9014 // Error: illegal domain
9015 return nullptr;
9018 nsAutoCString domain;
9019 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
9020 return nullptr;
9023 return CreateInheritingURIForHost(domain);
9026 /* static */
9027 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
9028 // Check new domain - must be a superdomain of the current host
9029 // For example, a page from foo.bar.com may set domain to bar.com,
9030 // but not to ar.com, baz.com, or fi.foo.bar.com.
9031 nsAutoCString current;
9032 nsAutoCString domain;
9033 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
9034 current.Truncate();
9036 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
9037 domain.Truncate();
9040 bool ok = current.Equals(domain);
9041 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
9042 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
9043 // We're golden if the new domain is the current page's base domain or a
9044 // subdomain of it.
9045 nsCOMPtr<nsIEffectiveTLDService> tldService =
9046 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
9047 if (!tldService) {
9048 return false;
9051 nsAutoCString currentBaseDomain;
9052 ok = NS_SUCCEEDED(
9053 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
9054 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
9055 (domain.Length() >= currentBaseDomain.Length()),
9056 "uh-oh! slight optimization wasn't valid somehow!");
9057 ok = ok && domain.Length() >= currentBaseDomain.Length();
9060 return ok;
9063 Element* Document::GetHtmlElement() const {
9064 Element* rootElement = GetRootElement();
9065 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
9066 return rootElement;
9067 return nullptr;
9070 Element* Document::GetHtmlChildElement(nsAtom* aTag) {
9071 Element* html = GetHtmlElement();
9072 if (!html) return nullptr;
9074 // Look for the element with aTag inside html. This needs to run
9075 // forwards to find the first such element.
9076 for (nsIContent* child = html->GetFirstChild(); child;
9077 child = child->GetNextSibling()) {
9078 if (child->IsHTMLElement(aTag)) return child->AsElement();
9080 return nullptr;
9083 nsGenericHTMLElement* Document::GetBody() {
9084 Element* html = GetHtmlElement();
9085 if (!html) {
9086 return nullptr;
9089 for (nsIContent* child = html->GetFirstChild(); child;
9090 child = child->GetNextSibling()) {
9091 if (child->IsHTMLElement(nsGkAtoms::body) ||
9092 child->IsHTMLElement(nsGkAtoms::frameset)) {
9093 return static_cast<nsGenericHTMLElement*>(child);
9097 return nullptr;
9100 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
9101 nsCOMPtr<Element> root = GetRootElement();
9103 // The body element must be either a body tag or a frameset tag. And we must
9104 // have a root element to be able to add kids to it.
9105 if (!newBody ||
9106 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
9107 rv.ThrowHierarchyRequestError(
9108 "The new body must be either a body tag or frameset tag.");
9109 return;
9112 if (!root) {
9113 rv.ThrowHierarchyRequestError("No root element.");
9114 return;
9117 // Use DOM methods so that we pass through the appropriate security checks.
9118 nsCOMPtr<Element> currentBody = GetBody();
9119 if (currentBody) {
9120 root->ReplaceChild(*newBody, *currentBody, rv);
9121 } else {
9122 root->AppendChild(*newBody, rv);
9126 HTMLSharedElement* Document::GetHead() {
9127 return static_cast<HTMLSharedElement*>(GetHeadElement());
9130 Element* Document::GetTitleElement() {
9131 // mMayHaveTitleElement will have been set to true if any HTML or SVG
9132 // <title> element has been bound to this document. So if it's false,
9133 // we know there is nothing to do here. This avoids us having to search
9134 // the whole DOM if someone calls document.title on a large document
9135 // without a title.
9136 if (!mMayHaveTitleElement) {
9137 return nullptr;
9140 Element* root = GetRootElement();
9141 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
9142 // In SVG, the document's title must be a child
9143 for (nsIContent* child = root->GetFirstChild(); child;
9144 child = child->GetNextSibling()) {
9145 if (child->IsSVGElement(nsGkAtoms::title)) {
9146 return child->AsElement();
9149 return nullptr;
9152 // We check the HTML namespace even for non-HTML documents, except SVG. This
9153 // matches the spec and the behavior of all tested browsers.
9154 for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
9155 if (node->IsHTMLElement(nsGkAtoms::title)) {
9156 return node->AsElement();
9159 return nullptr;
9162 void Document::GetTitle(nsAString& aTitle) {
9163 aTitle.Truncate();
9165 Element* rootElement = GetRootElement();
9166 if (!rootElement) {
9167 return;
9170 if (rootElement->IsXULElement()) {
9171 rootElement->GetAttr(nsGkAtoms::title, aTitle);
9172 } else if (Element* title = GetTitleElement()) {
9173 nsContentUtils::GetNodeTextContent(title, false, aTitle);
9174 } else {
9175 return;
9178 aTitle.CompressWhitespace();
9181 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
9182 Element* rootElement = GetRootElement();
9183 if (!rootElement) {
9184 return;
9187 if (rootElement->IsXULElement()) {
9188 aRv =
9189 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
9190 return;
9193 Maybe<mozAutoDocUpdate> updateBatch;
9194 nsCOMPtr<Element> title = GetTitleElement();
9195 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
9196 if (!title) {
9197 // Batch updates so that mutation events don't change "the title
9198 // element" under us
9199 updateBatch.emplace(this, true);
9200 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
9201 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
9202 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
9203 NOT_FROM_PARSER);
9204 if (!title) {
9205 return;
9207 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
9208 IgnoreErrors());
9210 } else if (rootElement->IsHTMLElement()) {
9211 if (!title) {
9212 // Batch updates so that mutation events don't change "the title
9213 // element" under us
9214 updateBatch.emplace(this, true);
9215 Element* head = GetHeadElement();
9216 if (!head) {
9217 return;
9220 RefPtr<mozilla::dom::NodeInfo> titleInfo;
9221 titleInfo = mNodeInfoManager->GetNodeInfo(
9222 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
9223 title = NS_NewHTMLTitleElement(titleInfo.forget());
9224 if (!title) {
9225 return;
9228 head->AppendChildTo(title, true, IgnoreErrors());
9230 } else {
9231 return;
9234 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
9237 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
9238 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
9239 "Setting a title while unlinking or destroying the element?");
9240 if (mInUnlinkOrDeletion) {
9241 return;
9244 if (aBoundTitleElement) {
9245 mMayHaveTitleElement = true;
9247 if (mPendingTitleChangeEvent.IsPending()) {
9248 return;
9251 MOZ_RELEASE_ASSERT(NS_IsMainThread());
9252 RefPtr<nsRunnableMethod<Document, void, false>> event =
9253 NewNonOwningRunnableMethod("Document::DoNotifyPossibleTitleChange", this,
9254 &Document::DoNotifyPossibleTitleChange);
9255 if (NS_WARN_IF(NS_FAILED(Dispatch(do_AddRef(event))))) {
9256 return;
9258 mPendingTitleChangeEvent = std::move(event);
9261 void Document::DoNotifyPossibleTitleChange() {
9262 if (!mPendingTitleChangeEvent.IsPending()) {
9263 return;
9265 // Make sure the pending runnable method is cleared.
9266 mPendingTitleChangeEvent.Revoke();
9267 mHaveFiredTitleChange = true;
9269 nsAutoString title;
9270 GetTitle(title);
9272 if (RefPtr<PresShell> presShell = GetPresShell()) {
9273 nsCOMPtr<nsISupports> container =
9274 presShell->GetPresContext()->GetContainerWeak();
9275 if (container) {
9276 if (nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container)) {
9277 docShellWin->SetTitle(title);
9282 if (WindowGlobalChild* child = GetWindowGlobalChild()) {
9283 child->SendUpdateDocumentTitle(title);
9286 // Fire a DOM event for the title change.
9287 nsContentUtils::DispatchChromeEvent(this, this, u"DOMTitleChanged"_ns,
9288 CanBubble::eYes, Cancelable::eYes);
9290 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
9291 if (obs) {
9292 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
9296 already_AddRefed<MediaQueryList> Document::MatchMedia(
9297 const nsACString& aMediaQueryList, CallerType aCallerType) {
9298 RefPtr<MediaQueryList> result =
9299 new MediaQueryList(this, aMediaQueryList, aCallerType);
9301 mDOMMediaQueryLists.insertBack(result);
9303 return result.forget();
9306 void Document::SetMayStartLayout(bool aMayStartLayout) {
9307 mMayStartLayout = aMayStartLayout;
9308 if (MayStartLayout()) {
9309 // Before starting layout, check whether we're a toplevel chrome
9310 // window. If we are, setup some state so that we don't have to restyle
9311 // the whole tree after StartLayout.
9312 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
9313 // We're the chrome document!
9314 win->BeforeStartLayout();
9316 ReadyState state = GetReadyStateEnum();
9317 if (state >= READYSTATE_INTERACTIVE) {
9318 // DOMContentLoaded has fired already.
9319 MaybeResolveReadyForIdle();
9323 MaybeEditingStateChanged();
9326 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
9327 mInitializableFrameLoaders.RemoveElement(aLoader);
9328 // Don't even try to initialize.
9329 if (mInDestructor) {
9330 NS_WARNING(
9331 "Trying to initialize a frame loader while"
9332 "document is being deleted");
9333 return NS_ERROR_FAILURE;
9336 mInitializableFrameLoaders.AppendElement(aLoader);
9337 if (!mFrameLoaderRunner) {
9338 mFrameLoaderRunner =
9339 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9340 &Document::MaybeInitializeFinalizeFrameLoaders);
9341 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9342 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9344 return NS_OK;
9347 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
9348 nsIRunnable* aFinalizer) {
9349 mInitializableFrameLoaders.RemoveElement(aLoader);
9350 if (mInDestructor) {
9351 return NS_ERROR_FAILURE;
9354 LogRunnable::LogDispatch(aFinalizer);
9355 mFrameLoaderFinalizers.AppendElement(aFinalizer);
9356 if (!mFrameLoaderRunner) {
9357 mFrameLoaderRunner =
9358 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9359 &Document::MaybeInitializeFinalizeFrameLoaders);
9360 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9361 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9363 return NS_OK;
9366 void Document::MaybeInitializeFinalizeFrameLoaders() {
9367 if (mDelayFrameLoaderInitialization) {
9368 // This method will be recalled when !mDelayFrameLoaderInitialization.
9369 mFrameLoaderRunner = nullptr;
9370 return;
9373 // We're not in an update, but it is not safe to run scripts, so
9374 // postpone frameloader initialization and finalization.
9375 if (!nsContentUtils::IsSafeToRunScript()) {
9376 if (!mInDestructor && !mFrameLoaderRunner &&
9377 (mInitializableFrameLoaders.Length() ||
9378 mFrameLoaderFinalizers.Length())) {
9379 mFrameLoaderRunner = NewRunnableMethod(
9380 "Document::MaybeInitializeFinalizeFrameLoaders", this,
9381 &Document::MaybeInitializeFinalizeFrameLoaders);
9382 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9384 return;
9386 mFrameLoaderRunner = nullptr;
9388 // Don't use a temporary array for mInitializableFrameLoaders, because
9389 // loading a frame may cause some other frameloader to be removed from the
9390 // array. But be careful to keep the loader alive when starting the load!
9391 while (mInitializableFrameLoaders.Length()) {
9392 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
9393 mInitializableFrameLoaders.RemoveElementAt(0);
9394 NS_ASSERTION(loader, "null frameloader in the array?");
9395 loader->ReallyStartLoading();
9398 uint32_t length = mFrameLoaderFinalizers.Length();
9399 if (length > 0) {
9400 nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
9401 std::move(mFrameLoaderFinalizers);
9402 for (uint32_t i = 0; i < length; ++i) {
9403 LogRunnable::Run run(finalizers[i]);
9404 finalizers[i]->Run();
9409 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
9410 uint32_t length = mInitializableFrameLoaders.Length();
9411 for (uint32_t i = 0; i < length; ++i) {
9412 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
9413 mInitializableFrameLoaders.RemoveElementAt(i);
9414 return;
9419 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
9420 mPrototypeDocument = aPrototype;
9421 mSynchronousDOMContentLoaded = true;
9424 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
9425 return GetPermissionDelegateHandler();
9428 Document* Document::RequestExternalResource(
9429 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
9430 ExternalResourceLoad** aPendingLoad) {
9431 MOZ_ASSERT(aURI, "Must have a URI");
9432 MOZ_ASSERT(aRequestingNode, "Must have a node");
9433 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
9434 if (mDisplayDocument) {
9435 return mDisplayDocument->RequestExternalResource(
9436 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
9439 return mExternalResourceMap.RequestResource(
9440 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
9443 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
9444 mExternalResourceMap.EnumerateResources(aCallback);
9447 SMILAnimationController* Document::GetAnimationController() {
9448 // We create the animation controller lazily because most documents won't want
9449 // one and only SVG documents and the like will call this
9450 if (mAnimationController) return mAnimationController;
9451 // Refuse to create an Animation Controller for data documents.
9452 if (mLoadedAsData) return nullptr;
9454 mAnimationController = new SMILAnimationController(this);
9456 // If there's a presContext then check the animation mode and pause if
9457 // necessary.
9458 nsPresContext* context = GetPresContext();
9459 if (mAnimationController && context &&
9460 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
9461 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
9464 // If we're hidden (or being hidden), notify the newly-created animation
9465 // controller. (Skip this check for SVG-as-an-image documents, though,
9466 // because they don't get OnPageShow / OnPageHide calls).
9467 if (!mIsShowing && !mIsBeingUsedAsImage) {
9468 mAnimationController->OnPageHide();
9471 return mAnimationController;
9474 ScrollTimelineAnimationTracker*
9475 Document::GetOrCreateScrollTimelineAnimationTracker() {
9476 if (!mScrollTimelineAnimationTracker) {
9477 mScrollTimelineAnimationTracker = new ScrollTimelineAnimationTracker(this);
9480 return mScrollTimelineAnimationTracker;
9484 * Retrieve the "direction" property of the document.
9486 * @lina 01/09/2001
9488 void Document::GetDir(nsAString& aDirection) const {
9489 aDirection.Truncate();
9490 Element* rootElement = GetHtmlElement();
9491 if (rootElement) {
9492 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
9497 * Set the "direction" property of the document.
9499 * @lina 01/09/2001
9501 void Document::SetDir(const nsAString& aDirection) {
9502 Element* rootElement = GetHtmlElement();
9503 if (rootElement) {
9504 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
9508 nsIHTMLCollection* Document::Images() {
9509 if (!mImages) {
9510 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
9511 nsGkAtoms::img);
9513 return mImages;
9516 nsIHTMLCollection* Document::Embeds() {
9517 if (!mEmbeds) {
9518 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
9519 nsGkAtoms::embed);
9521 return mEmbeds;
9524 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9525 void* aData) {
9526 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
9527 aElement->HasAttr(nsGkAtoms::href);
9530 nsIHTMLCollection* Document::Links() {
9531 if (!mLinks) {
9532 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
9534 return mLinks;
9537 nsIHTMLCollection* Document::Forms() {
9538 if (!mForms) {
9539 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
9540 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
9541 nsGkAtoms::form);
9544 return mForms;
9547 nsIHTMLCollection* Document::Scripts() {
9548 if (!mScripts) {
9549 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
9550 nsGkAtoms::script);
9552 return mScripts;
9555 nsIHTMLCollection* Document::Applets() {
9556 if (!mApplets) {
9557 mApplets = new nsEmptyContentList(this);
9559 return mApplets;
9562 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9563 void* aData) {
9564 return aElement->IsHTMLElement(nsGkAtoms::a) &&
9565 aElement->HasAttr(nsGkAtoms::name);
9568 nsIHTMLCollection* Document::Anchors() {
9569 if (!mAnchors) {
9570 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
9572 return mAnchors;
9575 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
9576 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
9577 ErrorResult& rv) {
9578 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9579 "XOW should have caught this!");
9581 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
9582 if (!window) {
9583 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
9584 return nullptr;
9586 nsCOMPtr<nsPIDOMWindowOuter> outer =
9587 nsPIDOMWindowOuter::GetFromCurrentInner(window);
9588 if (!outer) {
9589 rv.Throw(NS_ERROR_NOT_INITIALIZED);
9590 return nullptr;
9592 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
9593 RefPtr<BrowsingContext> newBC;
9594 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
9595 if (!newBC) {
9596 return nullptr;
9598 return WindowProxyHolder(std::move(newBC));
9601 Document* Document::Open(const Optional<nsAString>& /* unused */,
9602 const Optional<nsAString>& /* unused */,
9603 ErrorResult& aError) {
9604 // Implements
9605 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
9607 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9608 "XOW should have caught this!");
9610 // Step 1 -- throw if we're an XML document.
9611 if (!IsHTMLDocument() || mDisableDocWrite) {
9612 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9613 return nullptr;
9616 // Step 2 -- throw if dynamic markup insertion should throw.
9617 if (ShouldThrowOnDynamicMarkupInsertion()) {
9618 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9619 return nullptr;
9622 // Step 3 -- get the entry document, so we can use it for security checks.
9623 nsCOMPtr<Document> callerDoc = GetEntryDocument();
9624 if (!callerDoc) {
9625 // If we're called from C++ or in some other way without an originating
9626 // document we can't do a document.open w/o changing the principal of the
9627 // document to something like about:blank (as that's the only sane thing to
9628 // do when we don't know the origin of this call), and since we can't
9629 // change the principals of a document for security reasons we'll have to
9630 // refuse to go ahead with this call.
9632 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9633 return nullptr;
9636 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
9637 // the entry document.
9638 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
9639 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9640 return nullptr;
9643 // Step 5 -- if we have an active parser with a nonzero script nesting level,
9644 // just no-op.
9645 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
9646 return this;
9649 // Step 6 -- check for open() during unload. Per spec, this is just a check
9650 // of the ignore-opens-during-unload counter, but our unload event code
9651 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
9652 // do), so we check for unload directly.
9653 if (ShouldIgnoreOpens()) {
9654 return this;
9657 RefPtr<nsDocShell> shell(mDocumentContainer);
9658 if (shell) {
9659 bool inUnload;
9660 shell->GetIsInUnload(&inUnload);
9661 if (inUnload) {
9662 return this;
9666 // document.open() inherits the CSP from the opening document.
9667 // Please create an actual copy of the CSP (do not share the same
9668 // reference) otherwise appending a new policy within the opened
9669 // document will be incorrectly propagated to the opening doc.
9670 nsCOMPtr<nsIContentSecurityPolicy> csp = callerDoc->GetCsp();
9671 if (csp) {
9672 RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
9673 cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
9674 mCSP = cspToInherit;
9677 // At this point we know this is a valid-enough document.open() call
9678 // and not a no-op. Increment our use counter.
9679 SetUseCounter(eUseCounter_custom_DocumentOpen);
9681 // Step 7 -- stop existing navigation of our browsing context (and all other
9682 // loads it's doing) if we're the active document of our browsing context.
9683 // Note that we do not want to stop anything if there is no existing
9684 // navigation.
9685 if (shell && IsCurrentActiveDocument() &&
9686 shell->GetIsAttemptingToNavigate()) {
9687 shell->Stop(nsIWebNavigation::STOP_NETWORK);
9689 // The Stop call may have cancelled the onload blocker request or
9690 // prevented it from getting added, so we need to make sure it gets added
9691 // to the document again otherwise the document could have a non-zero
9692 // onload block count without the onload blocker request being in the
9693 // loadgroup.
9694 EnsureOnloadBlocker();
9697 // Step 8 -- clear event listeners out of our DOM tree
9698 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
9699 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
9700 elm->RemoveAllListeners();
9704 // Step 9 -- clear event listeners from our window, if we have one.
9706 // Note that we explicitly want the inner window, and only if we're its
9707 // document. We want to do this (per spec) even when we're not the "active
9708 // document", so we can't go through GetWindow(), because it might forward to
9709 // the wrong inner.
9710 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
9711 if (win->GetExtantDoc() == this) {
9712 if (EventListenerManager* elm =
9713 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
9714 elm->RemoveAllListeners();
9719 // If we have a parser that has a zero script nesting level, we need to
9720 // properly terminate it. We do that after we've removed all the event
9721 // listeners (so termination won't trigger event listeners if it does
9722 // something to the DOM), but before we remove all elements from the document
9723 // (so if termination does modify the DOM in some way we will just blow it
9724 // away immediately. See the similar code in WriteCommon that handles the
9725 // !IsInsertionPointDefined() case and should stay in sync with this code.
9726 if (mParser) {
9727 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
9728 "Why didn't we take the early return?");
9729 // Make sure we don't re-enter.
9730 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9731 mParser->Terminate();
9732 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9735 // Step 10 -- remove all our DOM kids without firing any mutation events.
9737 // We want to ignore any recursive calls to Open() that happen while
9738 // disconnecting the node tree. The spec doesn't say to do this, but the
9739 // spec also doesn't envision unload events on subframes firing while we do
9740 // this, while all browsers fire them in practice. See
9741 // <https://github.com/whatwg/html/issues/4611>.
9742 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9743 DisconnectNodeTree();
9746 // Step 11 -- if we're the current document in our docshell, do the
9747 // equivalent of pushState() with the new URL we should have.
9748 if (shell && IsCurrentActiveDocument()) {
9749 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
9750 if (callerDoc != this) {
9751 nsCOMPtr<nsIURI> noFragmentURI;
9752 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
9753 if (NS_WARN_IF(NS_FAILED(rv))) {
9754 aError.Throw(rv);
9755 return nullptr;
9757 newURI = std::move(noFragmentURI);
9760 // UpdateURLAndHistory might do various member-setting, so make sure we're
9761 // holding strong refs to all the refcounted args on the stack. We can
9762 // assume that our caller is holding on to "this" already.
9763 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
9764 bool equalURIs;
9765 nsresult rv = currentURI->Equals(newURI, &equalURIs);
9766 if (NS_WARN_IF(NS_FAILED(rv))) {
9767 aError.Throw(rv);
9768 return nullptr;
9770 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
9771 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
9772 /* aReplace = */ true, currentURI,
9773 equalURIs);
9774 if (NS_WARN_IF(NS_FAILED(rv))) {
9775 aError.Throw(rv);
9776 return nullptr;
9779 // And use the security info of the caller document as well, since
9780 // it's the thing providing our data.
9781 mSecurityInfo = callerDoc->GetSecurityInfo();
9783 // This is not mentioned in the spec, but I think that's a spec bug. See
9784 // <https://github.com/whatwg/html/issues/4299>. In any case, since our
9785 // URL may be changing away from about:blank here, we really want to unset
9786 // this flag no matter what, since only about:blank can be an initial
9787 // document.
9788 SetIsInitialDocument(false);
9790 // And let our docloader know that it will need to track our load event.
9791 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
9794 // Per spec nothing happens with our URI in other cases, though note
9795 // <https://github.com/whatwg/html/issues/4286>.
9797 // Note that we don't need to do anything here with base URIs per spec.
9798 // That said, this might be assuming that we implement
9799 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
9800 // correctly, which we don't right now for the about:blank case.
9802 // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
9803 mSkipLoadEventAfterClose = mLoadEventFiring;
9805 // Preliminary to steps 13-16. Set our ready state to uninitialized before
9806 // we do anything else, so we can then proceed to later ready state levels.
9807 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
9808 /* updateTimingInformation = */ false);
9809 // Reset a flag that affects readyState behavior.
9810 mSetCompleteAfterDOMContentLoaded = false;
9812 // Step 13 -- set our compat mode to standards.
9813 SetCompatibilityMode(eCompatibility_FullStandards);
9815 // Step 14 -- create a new parser associated with document. This also does
9816 // step 16 implicitly.
9817 mParserAborted = false;
9818 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
9819 mParser = parser;
9820 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
9821 nsresult rv = parser->StartExecutor();
9822 if (NS_WARN_IF(NS_FAILED(rv))) {
9823 aError.Throw(rv);
9824 return nullptr;
9827 // Clear out our form control state, because the state of controls
9828 // in the pre-open() document should not affect the state of
9829 // controls that are now going to be written.
9830 mLayoutHistoryState = nullptr;
9832 if (shell) {
9833 // Prepare the docshell and the document viewer for the impending
9834 // out-of-band document.write()
9835 shell->PrepareForNewContentModel();
9837 nsCOMPtr<nsIDocumentViewer> viewer;
9838 shell->GetDocViewer(getter_AddRefs(viewer));
9839 if (viewer) {
9840 viewer->LoadStart(this);
9844 // Step 15.
9845 SetReadyStateInternal(Document::READYSTATE_LOADING,
9846 /* updateTimingInformation = */ false);
9848 // Step 16 happened with step 14 above.
9850 // Step 17.
9851 return this;
9854 void Document::Close(ErrorResult& rv) {
9855 if (!IsHTMLDocument()) {
9856 // No calling document.close() on XHTML!
9858 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9859 return;
9862 if (ShouldThrowOnDynamicMarkupInsertion()) {
9863 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9864 return;
9867 if (!mParser || !mParser->IsScriptCreated()) {
9868 return;
9871 ++mWriteLevel;
9872 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
9873 ->Parse(u""_ns, nullptr, true);
9874 --mWriteLevel;
9877 void Document::WriteCommon(const Sequence<nsString>& aText,
9878 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
9879 // Fast path the common case
9880 if (aText.Length() == 1) {
9881 WriteCommon(aText[0], aNewlineTerminate, rv);
9882 } else {
9883 // XXXbz it would be nice if we could pass all the strings to the parser
9884 // without having to do all this copying and then ask it to start
9885 // parsing....
9886 nsString text;
9887 for (size_t i = 0; i < aText.Length(); ++i) {
9888 text.Append(aText[i]);
9890 WriteCommon(text, aNewlineTerminate, rv);
9894 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
9895 ErrorResult& aRv) {
9896 #ifdef DEBUG
9898 // Assert that we do not use or accidentally introduce doc.write()
9899 // in system privileged context or in any of our about: pages.
9900 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
9901 bool isAboutOrPrivContext = principal->IsSystemPrincipal();
9902 if (!isAboutOrPrivContext) {
9903 if (principal->SchemeIs("about")) {
9904 // about:blank inherits the security contetext and this assertion
9905 // is only meant for actual about: pages.
9906 nsAutoCString host;
9907 principal->GetHost(host);
9908 isAboutOrPrivContext = !host.EqualsLiteral("blank");
9911 // Some automated tests use an empty string to kick off some parsing
9912 // mechansims, but they do not do any harm since they use an empty string.
9913 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
9914 "do not use doc.write in privileged context!");
9916 #endif
9918 mTooDeepWriteRecursion =
9919 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
9920 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
9921 aRv.Throw(NS_ERROR_UNEXPECTED);
9922 return;
9925 if (!IsHTMLDocument() || mDisableDocWrite) {
9926 // No calling document.write*() on XHTML!
9928 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9929 return;
9932 if (ShouldThrowOnDynamicMarkupInsertion()) {
9933 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9934 return;
9937 if (mParserAborted) {
9938 // Hixie says aborting the parser doesn't undefine the insertion point.
9939 // However, since we null out mParser in that case, we track the
9940 // theoretically defined insertion point using mParserAborted.
9941 return;
9944 // Implement Step 4.1 of:
9945 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
9946 if (ShouldIgnoreOpens()) {
9947 return;
9950 void* key = GenerateParserKey();
9951 if (mParser && !mParser->IsInsertionPointDefined()) {
9952 if (mIgnoreDestructiveWritesCounter) {
9953 // Instead of implying a call to document.open(), ignore the call.
9954 nsContentUtils::ReportToConsole(
9955 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9956 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9957 return;
9959 // The spec doesn't tell us to ignore opens from here, but we need to
9960 // ensure opens are ignored here. See similar code in Open() that handles
9961 // the case of an existing parser which is not currently running script and
9962 // should stay in sync with this code.
9963 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9964 mParser->Terminate();
9965 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9968 if (!mParser) {
9969 if (mIgnoreDestructiveWritesCounter) {
9970 // Instead of implying a call to document.open(), ignore the call.
9971 nsContentUtils::ReportToConsole(
9972 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9973 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9974 return;
9977 Open({}, {}, aRv);
9979 // If Open() fails, or if it didn't create a parser (as it won't
9980 // if the user chose to not discard the current document through
9981 // onbeforeunload), don't write anything.
9982 if (aRv.Failed() || !mParser) {
9983 return;
9987 static constexpr auto new_line = u"\n"_ns;
9989 ++mWriteLevel;
9991 // This could be done with less code, but for performance reasons it
9992 // makes sense to have the code for two separate Parse() calls here
9993 // since the concatenation of strings costs more than we like. And
9994 // why pay that price when we don't need to?
9995 if (aNewlineTerminate) {
9996 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
9997 ->Parse(aText + new_line, key, false);
9998 } else {
9999 aRv =
10000 (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
10003 --mWriteLevel;
10005 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
10008 void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
10009 WriteCommon(aText, false, rv);
10012 void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
10013 WriteCommon(aText, true, rv);
10016 void* Document::GenerateParserKey(void) {
10017 if (!mScriptLoader) {
10018 // If we don't have a script loader, then the parser probably isn't parsing
10019 // anything anyway, so just return null.
10020 return nullptr;
10023 // The script loader provides us with the currently executing script element,
10024 // which is guaranteed to be unique per script.
10025 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
10026 if (script && mParser && mParser->IsScriptCreated()) {
10027 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
10028 if (creatorParser != mParser) {
10029 // Make scripts that aren't inserted by the active parser of this document
10030 // participate in the context of the script that document.open()ed
10031 // this document.
10032 return nullptr;
10035 return script;
10038 /* static */
10039 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
10040 nsAtom* aAtom, void* aData) {
10041 MOZ_ASSERT(aElement, "Must have element to work with!");
10043 if (!aElement->HasName()) {
10044 return false;
10047 nsString* elementName = static_cast<nsString*>(aData);
10048 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
10049 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
10050 eCaseMatters);
10053 /* static */
10054 void* Document::UseExistingNameString(nsINode* aRootNode,
10055 const nsString* aName) {
10056 return const_cast<nsString*>(aName);
10059 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
10060 if (mDocumentURI) {
10061 nsAutoCString uri;
10062 nsresult rv = mDocumentURI->GetSpec(uri);
10063 NS_ENSURE_SUCCESS(rv, rv);
10065 CopyUTF8toUTF16(uri, aDocumentURI);
10066 } else {
10067 aDocumentURI.Truncate();
10070 return NS_OK;
10073 // Alias of above
10074 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
10076 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
10077 CallerType aCallerType,
10078 ErrorResult& aRv) const {
10079 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
10080 aRv = GetDocumentURI(aDocumentURI);
10081 return;
10084 nsAutoCString uri;
10085 nsresult res = mChromeXHRDocURI->GetSpec(uri);
10086 if (NS_FAILED(res)) {
10087 aRv.Throw(res);
10088 return;
10090 CopyUTF8toUTF16(uri, aDocumentURI);
10093 nsIURI* Document::GetDocumentURIObject() const {
10094 if (!mChromeXHRDocURI) {
10095 return GetDocumentURI();
10098 return mChromeXHRDocURI;
10101 void Document::GetCompatMode(nsString& aCompatMode) const {
10102 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
10103 mCompatMode == eCompatibility_AlmostStandards ||
10104 mCompatMode == eCompatibility_FullStandards,
10105 "mCompatMode is neither quirks nor strict for this document");
10107 if (mCompatMode == eCompatibility_NavQuirks) {
10108 aCompatMode.AssignLiteral("BackCompat");
10109 } else {
10110 aCompatMode.AssignLiteral("CSS1Compat");
10114 } // namespace dom
10115 } // namespace mozilla
10117 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
10118 if (Element* element = Element::FromNode(aNode)) {
10119 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
10120 while (true) {
10121 RefPtr<Attr> attr;
10123 // Use an iterator to get an arbitrary attribute from the
10124 // cache. The iterator must be destroyed before any other
10125 // operations on mAttributeCache, to avoid hash table
10126 // assertions.
10127 auto iter = map->mAttributeCache.ConstIter();
10128 if (iter.Done()) {
10129 break;
10131 attr = iter.UserData();
10134 BlastSubtreeToPieces(attr);
10136 mozilla::DebugOnly<nsresult> rv =
10137 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
10138 attr->NodeInfo()->NameAtom(), false);
10140 // XXX Should we abort here?
10141 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
10145 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
10146 BlastSubtreeToPieces(shadow);
10147 element->UnattachShadow();
10151 while (aNode->HasChildren()) {
10152 nsIContent* node = aNode->GetFirstChild();
10153 BlastSubtreeToPieces(node);
10154 aNode->RemoveChildNode(node, false);
10158 namespace mozilla::dom {
10160 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv,
10161 bool aAcceptShadowRoot) {
10162 OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
10163 if (adoptedNode->IsShadowRoot() && !aAcceptShadowRoot) {
10164 rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
10165 return nullptr;
10168 // Scope firing mutation events so that we don't carry any state that
10169 // might be stale
10171 if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
10172 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
10176 nsAutoScriptBlocker scriptBlocker;
10178 switch (adoptedNode->NodeType()) {
10179 case ATTRIBUTE_NODE: {
10180 // Remove from ownerElement.
10181 OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
10183 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement();
10184 if (rv.Failed()) {
10185 return nullptr;
10188 if (ownerElement) {
10189 OwningNonNull<Attr> newAttr =
10190 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
10191 if (rv.Failed()) {
10192 return nullptr;
10196 break;
10198 case DOCUMENT_FRAGMENT_NODE:
10199 case ELEMENT_NODE:
10200 case PROCESSING_INSTRUCTION_NODE:
10201 case TEXT_NODE:
10202 case CDATA_SECTION_NODE:
10203 case COMMENT_NODE:
10204 case DOCUMENT_TYPE_NODE: {
10205 // Don't allow adopting a node's anonymous subtree out from under it.
10206 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
10207 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10208 return nullptr;
10211 // We don't want to adopt an element into its own contentDocument or into
10212 // a descendant contentDocument, so we check if the frameElement of this
10213 // document or any of its parents is the adopted node or one of its
10214 // descendants.
10215 RefPtr<BrowsingContext> bc = GetBrowsingContext();
10216 while (bc) {
10217 nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
10218 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
10219 rv.ThrowHierarchyRequestError(
10220 "Trying to adopt a node into its own contentDocument or a "
10221 "descendant contentDocument.");
10222 return nullptr;
10225 if (XRE_IsParentProcess()) {
10226 bc = bc->Canonical()->GetParentCrossChromeBoundary();
10227 } else {
10228 bc = bc->GetParent();
10232 // Remove from parent.
10233 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
10234 if (parent) {
10235 parent->RemoveChildNode(adoptedNode->AsContent(), true);
10236 } else {
10237 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
10240 break;
10242 case DOCUMENT_NODE: {
10243 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10244 return nullptr;
10246 default: {
10247 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
10249 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10250 return nullptr;
10254 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
10255 bool sameDocument = oldDocument == this;
10257 AutoJSContext cx;
10258 JS::Rooted<JSObject*> newScope(cx, nullptr);
10259 if (!sameDocument) {
10260 newScope = GetWrapper();
10261 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
10262 // Make sure cx is in a semi-sane compartment before we call WrapNative.
10263 // It's kind of irrelevant, given that we're passing aAllowWrapping =
10264 // false, and documents should always insist on being wrapped in an
10265 // canonical scope. But we try to pass something sane anyway.
10266 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
10267 JSAutoRealm ar(cx, globalObject);
10268 JS::Rooted<JS::Value> v(cx);
10269 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
10270 /* aAllowWrapping = */ false);
10271 if (rv.Failed()) return nullptr;
10272 newScope = &v.toObject();
10276 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
10277 if (rv.Failed()) {
10278 // Disconnect all nodes from their parents, since some have the old document
10279 // as their ownerDocument and some have this as their ownerDocument.
10280 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
10281 return nullptr;
10284 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
10285 "Should still be in the document we just got adopted into");
10287 return adoptedNode;
10290 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
10292 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
10293 const nsString& aScaleString) {
10294 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
10295 if (aScaleString.EqualsLiteral("device-width") ||
10296 aScaleString.EqualsLiteral("device-height")) {
10297 return Some(LayoutDeviceToScreenScale(10.0f));
10298 } else if (aScaleString.EqualsLiteral("yes")) {
10299 return Some(LayoutDeviceToScreenScale(1.0f));
10300 } else if (aScaleString.EqualsLiteral("no")) {
10301 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10302 } else if (aScaleString.IsEmpty()) {
10303 return Nothing();
10306 nsresult scaleErrorCode;
10307 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
10308 if (NS_FAILED(scaleErrorCode)) {
10309 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10312 if (scale < 0) {
10313 return Nothing();
10315 return Some(clamped(LayoutDeviceToScreenScale(scale), ViewportMinScale(),
10316 ViewportMaxScale()));
10319 void Document::ParseScalesInViewportMetaData(
10320 const ViewportMetaData& aViewportMetaData) {
10321 Maybe<LayoutDeviceToScreenScale> scale;
10323 scale = ParseScaleString(aViewportMetaData.mInitialScale);
10324 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
10325 mValidScaleFloat = scale.isSome();
10327 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
10328 // Chrome uses '5' for the fallback value of maximum-scale, we might
10329 // consider matching it in future.
10330 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
10331 mScaleMaxFloat = scale.valueOr(ViewportMaxScale());
10332 mValidMaxScale = scale.isSome();
10334 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
10335 mScaleMinFloat = scale.valueOr(ViewportMinScale());
10336 mValidMinScale = scale.isSome();
10338 // Resolve min-zoom and max-zoom values.
10339 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
10340 if (mValidMaxScale && mValidMinScale) {
10341 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
10345 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
10346 const nsAString& aHeightString,
10347 bool aHasValidScale) {
10348 // The width and height properties
10349 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
10351 // The width and height viewport <META> properties are translated into width
10352 // and height descriptors, setting the min-width/min-height value to
10353 // extend-to-zoom and the max-width/max-height value to the length from the
10354 // viewport <META> property as follows:
10356 // 1. Non-negative number values are translated to pixel lengths, clamped to
10357 // the range: [1px, 10000px]
10358 // 2. Negative number values are dropped
10359 // 3. device-width and device-height translate to 100vw and 100vh respectively
10360 // 4. Other keywords and unknown values are also dropped
10361 mMinWidth = nsViewportInfo::kAuto;
10362 mMaxWidth = nsViewportInfo::kAuto;
10363 if (!aWidthString.IsEmpty()) {
10364 mMinWidth = nsViewportInfo::kExtendToZoom;
10365 if (aWidthString.EqualsLiteral("device-width")) {
10366 mMaxWidth = nsViewportInfo::kDeviceSize;
10367 } else {
10368 nsresult widthErrorCode;
10369 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
10370 if (NS_FAILED(widthErrorCode)) {
10371 mMaxWidth = nsViewportInfo::kAuto;
10372 } else if (mMaxWidth >= 0.0f) {
10373 mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
10374 } else {
10375 mMaxWidth = nsViewportInfo::kAuto;
10378 } else if (aHasValidScale) {
10379 if (aHeightString.IsEmpty()) {
10380 mMinWidth = nsViewportInfo::kExtendToZoom;
10381 mMaxWidth = nsViewportInfo::kExtendToZoom;
10383 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
10384 mMinWidth = nsViewportInfo::kExtendToZoom;
10385 mMaxWidth = nsViewportInfo::kDeviceSize;
10388 mMinHeight = nsViewportInfo::kAuto;
10389 mMaxHeight = nsViewportInfo::kAuto;
10390 if (!aHeightString.IsEmpty()) {
10391 mMinHeight = nsViewportInfo::kExtendToZoom;
10392 if (aHeightString.EqualsLiteral("device-height")) {
10393 mMaxHeight = nsViewportInfo::kDeviceSize;
10394 } else {
10395 nsresult heightErrorCode;
10396 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
10397 if (NS_FAILED(heightErrorCode)) {
10398 mMaxHeight = nsViewportInfo::kAuto;
10399 } else if (mMaxHeight >= 0.0f) {
10400 mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
10401 } else {
10402 mMaxHeight = nsViewportInfo::kAuto;
10408 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
10409 MOZ_ASSERT(mPresShell);
10411 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
10412 // widget scale and the full zoom.
10413 nsPresContext* context = mPresShell->GetPresContext();
10414 // When querying the full zoom, get it from the device context rather than
10415 // directly from the pres context, because the device context's value can
10416 // include an adjustment necessary to keep the number of app units per device
10417 // pixel an integer, and we want the adjusted value.
10418 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
10419 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
10420 CSSToLayoutDeviceScale layoutDeviceScale =
10421 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
10423 CSSToScreenScale defaultScale =
10424 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
10426 // Special behaviour for desktop mode, provided we are not on an about: page,
10427 // or fullscreen.
10428 const bool fullscreen = Fullscreen();
10429 auto* bc = GetBrowsingContext();
10430 if (bc && bc->ForceDesktopViewport() && !IsAboutPage() && !fullscreen) {
10431 CSSCoord viewportWidth =
10432 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
10433 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
10434 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
10435 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
10436 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
10437 return nsViewportInfo(fakeDesktopSize, scaleToFit,
10438 nsViewportInfo::ZoomFlag::AllowZoom,
10439 nsViewportInfo::ZoomBehaviour::Mobile,
10440 nsViewportInfo::AutoScaleFlag::AutoScale);
10443 // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
10444 if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
10445 return nsViewportInfo(aDisplaySize, defaultScale,
10446 nsLayoutUtils::AllowZoomingForDocument(this)
10447 ? nsViewportInfo::ZoomFlag::AllowZoom
10448 : nsViewportInfo::ZoomFlag::DisallowZoom,
10449 StaticPrefs::apz_allow_zooming_out()
10450 ? nsViewportInfo::ZoomBehaviour::Mobile
10451 : nsViewportInfo::ZoomBehaviour::Desktop);
10454 // In cases where the width of the CSS viewport is less than or equal to the
10455 // width of the display (i.e. width <= device-width) then we disable
10456 // double-tap-to-zoom behaviour. See bug 941995 for details.
10458 switch (mViewportType) {
10459 case DisplayWidthHeight:
10460 return nsViewportInfo(aDisplaySize, defaultScale,
10461 nsViewportInfo::ZoomFlag::AllowZoom,
10462 nsViewportInfo::ZoomBehaviour::Mobile);
10463 case Unknown: {
10464 // We might early exit if the viewport is empty. Even if we don't,
10465 // at the end of this case we'll note that it was empty. Later, when
10466 // we're using the cached values, this will trigger alternate code paths.
10467 if (!mLastModifiedViewportMetaData) {
10468 // If the docType specifies that we are on a site optimized for mobile,
10469 // then we want to return specially crafted defaults for the viewport
10470 // info.
10471 if (RefPtr<DocumentType> docType = GetDoctype()) {
10472 nsAutoString docId;
10473 docType->GetPublicId(docId);
10474 if ((docId.Find(u"WAP") != -1) || (docId.Find(u"Mobile") != -1) ||
10475 (docId.Find(u"WML") != -1)) {
10476 // We're making an assumption that the docType can't change here
10477 mViewportType = DisplayWidthHeight;
10478 return nsViewportInfo(aDisplaySize, defaultScale,
10479 nsViewportInfo::ZoomFlag::AllowZoom,
10480 nsViewportInfo::ZoomBehaviour::Mobile);
10484 nsAutoString handheldFriendly;
10485 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
10486 if (handheldFriendly.EqualsLiteral("true")) {
10487 mViewportType = DisplayWidthHeight;
10488 return nsViewportInfo(aDisplaySize, defaultScale,
10489 nsViewportInfo::ZoomFlag::AllowZoom,
10490 nsViewportInfo::ZoomBehaviour::Mobile);
10494 ViewportMetaData metaData = GetViewportMetaData();
10496 // Parse initial-scale, minimum-scale and maximum-scale.
10497 ParseScalesInViewportMetaData(metaData);
10499 // Parse width and height properties
10500 // This function sets m{Min,Max}{Width,Height}.
10501 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
10502 mValidScaleFloat);
10504 mAllowZoom = true;
10505 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
10506 (metaData.mUserScalable.EqualsLiteral("no")) ||
10507 (metaData.mUserScalable.EqualsLiteral("false"))) {
10508 mAllowZoom = false;
10511 // Resolve viewport-fit value.
10512 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
10513 mViewportFit = ViewportFitType::Auto;
10514 if (!metaData.mViewportFit.IsEmpty()) {
10515 if (metaData.mViewportFit.EqualsLiteral("contain")) {
10516 mViewportFit = ViewportFitType::Contain;
10517 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
10518 mViewportFit = ViewportFitType::Cover;
10522 mWidthStrEmpty = metaData.mWidth.IsEmpty();
10524 mViewportType = Specified;
10525 [[fallthrough]];
10527 case Specified:
10528 default:
10529 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
10530 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
10531 bool effectiveValidMaxScale = mValidMaxScale;
10533 nsViewportInfo::ZoomFlag effectiveZoomFlag =
10534 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
10535 : nsViewportInfo::ZoomFlag::DisallowZoom;
10536 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
10537 // If the pref to force user-scalable is enabled, we ignore the values
10538 // from the meta-viewport tag for these properties and just assume they
10539 // allow the page to be scalable. Note in particular that this code is
10540 // in the "Specified" branch of the enclosing switch statement, so that
10541 // calls to GetViewportInfo always use the latest value of the
10542 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
10543 // return nsViewportInfo instances are all consistent with
10544 // browser_ui_zoom_force_user_scalable() already.
10545 effectiveMinScale = ViewportMinScale();
10546 effectiveMaxScale = ViewportMaxScale();
10547 effectiveValidMaxScale = true;
10548 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
10551 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
10552 auto ComputeExtendZoom = [&]() -> float {
10553 if (mValidScaleFloat && effectiveValidMaxScale) {
10554 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
10556 if (mValidScaleFloat) {
10557 return mScaleFloat.scale;
10559 if (effectiveValidMaxScale) {
10560 return effectiveMaxScale.scale;
10562 return nsViewportInfo::kAuto;
10565 // Resolving 'extend-to-zoom'
10566 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
10567 float extendZoom = ComputeExtendZoom();
10569 CSSCoord minWidth = mMinWidth;
10570 CSSCoord maxWidth = mMaxWidth;
10571 CSSCoord minHeight = mMinHeight;
10572 CSSCoord maxHeight = mMaxHeight;
10574 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
10575 // viewport size. We need to use this scaled size for any clamping of
10576 // width or height.
10577 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
10579 // Our min and max width and height values are mostly as specified by
10580 // the viewport declaration, but we make an exception for max width.
10581 // Max width, if auto, and if there's no initial-scale, will be set
10582 // to a default size. This is to support legacy site design with no
10583 // viewport declaration, and to do that using the same scheme as
10584 // Chrome does, in order to maintain web compatibility. Since the
10585 // default size has a complicated calculation, we fixup the maxWidth
10586 // value after setting it, above.
10587 if (maxWidth == nsViewportInfo::kAuto && !mValidScaleFloat) {
10588 if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
10589 bc->InRDMPane()) {
10590 // If RDM and touch simulation are active, then use the simulated
10591 // screen width to accommodate for cases where the screen width is
10592 // larger than the desktop viewport default.
10593 maxWidth = nsViewportInfo::Max(
10594 displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
10595 } else {
10596 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
10598 // Divide by fullZoom to stretch CSS pixel size of viewport in order
10599 // to keep device pixel size unchanged after full zoom applied.
10600 // See bug 974242.
10601 maxWidth /= fullZoom;
10603 // We set minWidth to ExtendToZoom, which will cause our later width
10604 // calculation to expand to maxWidth, if scale restrictions allow it.
10605 minWidth = nsViewportInfo::kExtendToZoom;
10608 // Resolve device-width and device-height first.
10609 if (maxWidth == nsViewportInfo::kDeviceSize) {
10610 maxWidth = displaySize.width;
10612 if (maxHeight == nsViewportInfo::kDeviceSize) {
10613 maxHeight = displaySize.height;
10615 if (extendZoom == nsViewportInfo::kAuto) {
10616 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10617 maxWidth = nsViewportInfo::kAuto;
10619 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10620 maxHeight = nsViewportInfo::kAuto;
10622 if (minWidth == nsViewportInfo::kExtendToZoom) {
10623 minWidth = maxWidth;
10625 if (minHeight == nsViewportInfo::kExtendToZoom) {
10626 minHeight = maxHeight;
10628 } else {
10629 CSSSize extendSize = displaySize / extendZoom;
10630 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10631 maxWidth = extendSize.width;
10633 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10634 maxHeight = extendSize.height;
10636 if (minWidth == nsViewportInfo::kExtendToZoom) {
10637 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
10639 if (minHeight == nsViewportInfo::kExtendToZoom) {
10640 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
10644 // Resolve initial width and height from min/max descriptors
10645 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
10646 CSSCoord width = nsViewportInfo::kAuto;
10647 if (minWidth != nsViewportInfo::kAuto ||
10648 maxWidth != nsViewportInfo::kAuto) {
10649 width = nsViewportInfo::Max(
10650 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
10652 CSSCoord height = nsViewportInfo::kAuto;
10653 if (minHeight != nsViewportInfo::kAuto ||
10654 maxHeight != nsViewportInfo::kAuto) {
10655 height = nsViewportInfo::Max(
10656 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
10659 // Resolve width value
10660 // https://drafts.csswg.org/css-device-adapt/#resolve-width
10661 if (width == nsViewportInfo::kAuto) {
10662 if (height == nsViewportInfo::kAuto || aDisplaySize.height == 0) {
10663 width = displaySize.width;
10664 } else {
10665 width = height * aDisplaySize.width / aDisplaySize.height;
10669 // Resolve height value
10670 // https://drafts.csswg.org/css-device-adapt/#resolve-height
10671 if (height == nsViewportInfo::kAuto) {
10672 if (aDisplaySize.width == 0) {
10673 height = displaySize.height;
10674 } else {
10675 height = width * aDisplaySize.height / aDisplaySize.width;
10678 MOZ_ASSERT(width != nsViewportInfo::kAuto &&
10679 height != nsViewportInfo::kAuto);
10681 CSSSize size(width, height);
10683 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
10684 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
10685 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
10687 nsViewportInfo::AutoSizeFlag sizeFlag =
10688 nsViewportInfo::AutoSizeFlag::FixedSize;
10689 if (mMaxWidth == nsViewportInfo::kDeviceSize ||
10690 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::kDeviceSize ||
10691 mScaleFloat.scale == 1.0f)) ||
10692 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::kAuto &&
10693 mMaxHeight < 0)) {
10694 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
10697 // FIXME: Resolving width and height should be done above 'Resolve width
10698 // value' and 'Resolve height value'.
10699 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
10700 size = displaySize;
10703 // The purpose of clamping the viewport width to a minimum size is to
10704 // prevent page authors from setting it to a ridiculously small value.
10705 // If the page is actually being rendered in a very small area (as might
10706 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
10707 // prevent the viewport from taking on that size.
10708 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
10710 size.width = clamped(size.width, effectiveMinSize.width,
10711 float(kViewportMaxSize.width));
10713 // Also recalculate the default zoom, if it wasn't specified in the
10714 // metadata, and the width is specified.
10715 if (!mValidScaleFloat && !mWidthStrEmpty) {
10716 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
10717 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
10720 size.height = clamped(size.height, effectiveMinSize.height,
10721 float(kViewportMaxSize.height));
10723 // In cases of user-scalable=no, if we have a positive scale, clamp it to
10724 // min and max, and then use the clamped value for the scale, the min, and
10725 // the max. If we don't have a positive scale, assert that we are setting
10726 // the auto scale flag.
10727 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
10728 scaleFloat > CSSToScreenScale(0.0f)) {
10729 scaleFloat = scaleMinFloat = scaleMaxFloat =
10730 clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
10732 MOZ_ASSERT(
10733 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
10734 "If we don't have a positive scale, we should be using auto scale.");
10736 // We need to perform a conversion, but only if the initial or maximum
10737 // scale were set explicitly by the user.
10738 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
10739 scaleFloat <= scaleMaxFloat) {
10740 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
10741 size.width = std::max(size.width, displaySize.width);
10742 size.height = std::max(size.height, displaySize.height);
10743 } else if (effectiveValidMaxScale) {
10744 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
10745 size.width = std::max(size.width, displaySize.width);
10746 size.height = std::max(size.height, displaySize.height);
10749 return nsViewportInfo(
10750 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
10751 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
10752 : nsViewportInfo::AutoScaleFlag::AutoScale,
10753 effectiveZoomFlag, mViewportFit);
10757 ViewportMetaData Document::GetViewportMetaData() const {
10758 return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
10759 : ViewportMetaData();
10762 void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
10763 mLastModifiedViewportMetaData = std::move(aData);
10764 // Trigger recomputation of the nsViewportInfo the next time it's queried.
10765 mViewportType = Unknown;
10767 AsyncEventDispatcher::RunDOMEventWhenSafe(
10768 *this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes,
10769 ChromeOnlyDispatch::eYes);
10772 EventListenerManager* Document::GetOrCreateListenerManager() {
10773 if (!mListenerManager) {
10774 mListenerManager =
10775 new EventListenerManager(static_cast<EventTarget*>(this));
10776 SetFlags(NODE_HAS_LISTENERMANAGER);
10779 return mListenerManager;
10782 EventListenerManager* Document::GetExistingListenerManager() const {
10783 return mListenerManager;
10786 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
10787 aVisitor.mCanHandle = true;
10788 // FIXME! This is a hack to make middle mouse paste working also in Editor.
10789 // Bug 329119
10790 aVisitor.mForceContentDispatch = true;
10792 // Load events must not propagate to |window| object, see bug 335251.
10793 if (aVisitor.mEvent->mMessage != eLoad) {
10794 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
10795 aVisitor.SetParentTarget(
10796 window ? window->GetTargetForEventTargetChain() : nullptr, false);
10800 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
10801 CallerType aCallerType,
10802 ErrorResult& rv) const {
10803 nsPresContext* presContext = GetPresContext();
10805 // Create event even without presContext.
10806 RefPtr<Event> ev =
10807 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
10808 nullptr, aEventType, aCallerType);
10809 if (!ev) {
10810 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10811 return nullptr;
10813 WidgetEvent* e = ev->WidgetEventPtr();
10814 e->mFlags.mBubbles = false;
10815 e->mFlags.mCancelable = false;
10816 return ev.forget();
10819 void Document::FlushPendingNotifications(FlushType aType) {
10820 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
10821 FlushPendingNotifications(flush);
10824 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
10825 FlushType flushType = aFlush.mFlushType;
10827 RefPtr<Document> documentOnStack = this;
10829 // We need to flush the sink for non-HTML documents (because the XML
10830 // parser still does insertion with deferred notifications). We
10831 // also need to flush the sink if this is a layout-related flush, to
10832 // make sure that layout is started as needed. But we can skip that
10833 // part if we have no presshell or if it's already done an initial
10834 // reflow.
10835 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
10836 mPresShell && !mPresShell->DidInitialize())) &&
10837 (mParser || mWeakSink)) {
10838 nsCOMPtr<nsIContentSink> sink;
10839 if (mParser) {
10840 sink = mParser->GetContentSink();
10841 } else {
10842 sink = do_QueryReferent(mWeakSink);
10843 if (!sink) {
10844 mWeakSink = nullptr;
10847 // Determine if it is safe to flush the sink notifications
10848 // by determining if it safe to flush all the presshells.
10849 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
10850 sink->FlushPendingNotifications(flushType);
10854 // Should we be flushing pending binding constructors in here?
10856 if (flushType <= FlushType::ContentAndNotify) {
10857 // Nothing to do here
10858 return;
10861 // If we have a parent we must flush the parent too to ensure that our
10862 // container is reflowed if its size was changed.
10864 // We do it only if the subdocument and the parent can observe each other
10865 // synchronously (that is, if we're not cross-origin), to avoid work that is
10866 // not observable, and if the parent document has finished loading all its
10867 // render-blocking stylesheets and may start laying out the document, to avoid
10868 // unnecessary flashes of unstyled content on the parent document. Note that
10869 // this last bit means that size-dependent media queries in this document may
10870 // produce incorrect results temporarily.
10872 // But if it's not safe to flush ourselves, then don't flush the parent, since
10873 // that can cause things like resizes of our frame's widget, which we can't
10874 // handle while flushing is unsafe.
10875 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
10876 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
10877 ChangesToFlush parentFlush = aFlush;
10878 if (flushType >= FlushType::Style) {
10879 // Since media queries mean that a size change of our container can affect
10880 // style, we need to promote a style flush on ourself to a layout flush on
10881 // our parent, since we need our container to be the correct size to
10882 // determine the correct style.
10883 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
10885 mParentDocument->FlushPendingNotifications(parentFlush);
10888 if (RefPtr<PresShell> presShell = GetPresShell()) {
10889 presShell->FlushPendingNotifications(aFlush);
10893 void Document::FlushExternalResources(FlushType aType) {
10894 NS_ASSERTION(
10895 aType >= FlushType::Style,
10896 "should only need to flush for style or higher in external resources");
10897 if (GetDisplayDocument()) {
10898 return;
10901 auto flush = [aType](Document& aDoc) {
10902 aDoc.FlushPendingNotifications(aType);
10903 return CallState::Continue;
10906 EnumerateExternalResources(flush);
10909 void Document::SetXMLDeclaration(const char16_t* aVersion,
10910 const char16_t* aEncoding,
10911 const int32_t aStandalone) {
10912 if (!aVersion || *aVersion == '\0') {
10913 mXMLDeclarationBits = 0;
10914 return;
10917 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
10919 if (aEncoding && *aEncoding != '\0') {
10920 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
10923 if (aStandalone == 1) {
10924 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
10925 XML_DECLARATION_BITS_STANDALONE_YES;
10926 } else if (aStandalone == 0) {
10927 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
10931 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
10932 nsAString& aStandalone) {
10933 aVersion.Truncate();
10934 aEncoding.Truncate();
10935 aStandalone.Truncate();
10937 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
10938 return;
10941 // always until we start supporting 1.1 etc.
10942 aVersion.AssignLiteral("1.0");
10944 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
10945 // This is what we have stored, not necessarily what was written
10946 // in the original
10947 GetCharacterSet(aEncoding);
10950 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
10951 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
10952 aStandalone.AssignLiteral("yes");
10953 } else {
10954 aStandalone.AssignLiteral("no");
10959 void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
10960 mColorSchemeMetaTags.Insert(aMeta);
10961 RecomputeColorScheme();
10964 void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
10965 mColorSchemeMetaTags.RemoveElement(aMeta);
10966 RecomputeColorScheme();
10969 void Document::RecomputeColorScheme() {
10970 auto oldColorScheme = mColorSchemeBits;
10971 mColorSchemeBits = 0;
10972 const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
10973 for (const HTMLMetaElement* el : elements) {
10974 nsAutoString content;
10975 if (!el->GetAttr(nsGkAtoms::content, content)) {
10976 continue;
10979 NS_ConvertUTF16toUTF8 contentU8(content);
10980 if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
10981 break;
10985 if (mColorSchemeBits == oldColorScheme) {
10986 return;
10989 if (nsPresContext* pc = GetPresContext()) {
10990 // This affects system colors, which are inherited, so we need to recascade.
10991 pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
10995 bool Document::IsScriptEnabled() const {
10996 // If this document is sandboxed without 'allow-scripts'
10997 // script is not enabled
10998 if (HasScriptsBlockedBySandbox()) {
10999 return false;
11002 nsCOMPtr<nsIScriptGlobalObject> globalObject =
11003 do_QueryInterface(GetInnerWindow());
11004 if (!globalObject || !globalObject->HasJSGlobal()) {
11005 return false;
11008 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
11009 .Allowed();
11012 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
11013 PRTime modDate = 0;
11014 nsresult rv;
11016 nsCOMPtr<nsIHttpChannel> httpChannel;
11017 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
11018 if (NS_WARN_IF(NS_FAILED(rv))) {
11019 return;
11022 if (httpChannel) {
11023 nsAutoCString tmp;
11024 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
11026 if (NS_SUCCEEDED(rv)) {
11027 PRTime time;
11028 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
11029 if (st == PR_SUCCESS) {
11030 modDate = time;
11034 static const char* const headers[] = {
11035 "default-style", "content-style-type", "content-language",
11036 "content-disposition", "refresh", "x-dns-prefetch-control",
11037 "x-frame-options", "origin-trial",
11038 // add more http headers if you need
11039 // XXXbz don't add content-location support without reading bug
11040 // 238654 and its dependencies/dups first.
11043 nsAutoCString headerVal;
11044 const char* const* name = headers;
11045 while (*name) {
11046 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
11047 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
11048 RefPtr<nsAtom> key = NS_Atomize(*name);
11049 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
11051 ++name;
11053 } else {
11054 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
11055 if (fileChannel) {
11056 nsCOMPtr<nsIFile> file;
11057 fileChannel->GetFile(getter_AddRefs(file));
11058 if (file) {
11059 PRTime msecs;
11060 rv = file->GetLastModifiedTime(&msecs);
11062 if (NS_SUCCEEDED(rv)) {
11063 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
11066 } else {
11067 nsAutoCString contentDisp;
11068 rv = aChannel->GetContentDispositionHeader(contentDisp);
11069 if (NS_SUCCEEDED(rv)) {
11070 SetHeaderData(nsGkAtoms::headerContentDisposition,
11071 NS_ConvertASCIItoUTF16(contentDisp));
11076 mLastModified.Truncate();
11077 if (modDate != 0) {
11078 GetFormattedTimeString(modDate, mLastModified);
11082 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
11083 // set any HTTP-EQUIV data into document's header data as well as url
11084 nsAutoString header;
11085 aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
11086 if (!header.IsEmpty()) {
11087 // Ignore META REFRESH when document is sandboxed from automatic features.
11088 nsContentUtils::ASCIIToLower(header);
11089 if (nsGkAtoms::refresh->Equals(header) &&
11090 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
11091 return;
11094 nsAutoString result;
11095 aMetaElement->GetAttr(nsGkAtoms::content, result);
11096 if (!result.IsEmpty()) {
11097 RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
11098 SetHeaderData(fieldAtom, result);
11102 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
11103 nsGkAtoms::handheldFriendly, eIgnoreCase)) {
11104 nsAutoString result;
11105 aMetaElement->GetAttr(nsGkAtoms::content, result);
11106 if (!result.IsEmpty()) {
11107 nsContentUtils::ASCIIToLower(result);
11108 SetHeaderData(nsGkAtoms::handheldFriendly, result);
11113 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
11114 nsAtom* aPrefix,
11115 int32_t aNamespaceID,
11116 const nsAString* aIs) {
11117 #ifdef DEBUG
11118 nsAutoString qName;
11119 if (aPrefix) {
11120 aPrefix->ToString(qName);
11121 qName.Append(':');
11123 qName.Append(aName);
11125 // Note: "a:b:c" is a valid name in non-namespaces XML, and
11126 // Document::CreateElement can call us with such a name and no prefix,
11127 // which would cause an error if we just used true here.
11128 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
11129 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
11130 "Don't pass invalid prefixes to Document::CreateElem, "
11131 "check caller.");
11132 #endif
11134 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
11135 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
11136 getter_AddRefs(nodeInfo));
11137 NS_ENSURE_TRUE(nodeInfo, nullptr);
11139 nsCOMPtr<Element> element;
11140 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
11141 NOT_FROM_PARSER, aIs);
11142 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
11145 bool Document::IsSafeToFlush() const {
11146 PresShell* presShell = GetPresShell();
11147 if (!presShell) {
11148 return true;
11150 return presShell->IsSafeToFlush();
11153 void Document::Sanitize() {
11154 // Sanitize the document by resetting all (current and former) password fields
11155 // and any form fields with autocomplete=off to their default values. We do
11156 // this now, instead of when the presentation is restored, to offer some
11157 // protection in case there is ever an exploit that allows a cached document
11158 // to be accessed from a different document.
11160 // First locate all input elements, regardless of whether they are
11161 // in a form, and reset the password and autocomplete=off elements.
11163 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
11165 nsAutoString value;
11167 uint32_t length = nodes->Length(true);
11168 for (uint32_t i = 0; i < length; ++i) {
11169 NS_ASSERTION(nodes->Item(i), "null item in node list!");
11171 RefPtr<HTMLInputElement> input =
11172 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
11173 if (!input) continue;
11175 input->GetAttr(nsGkAtoms::autocomplete, value);
11176 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
11177 input->Reset();
11181 // Now locate all _form_ elements that have autocomplete=off and reset them
11182 nodes = GetElementsByTagName(u"form"_ns);
11184 length = nodes->Length(true);
11185 for (uint32_t i = 0; i < length; ++i) {
11186 // Reset() may change the list dynamically.
11187 RefPtr<HTMLFormElement> form =
11188 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
11189 if (!form) continue;
11191 form->GetAttr(nsGkAtoms::autocomplete, value);
11192 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
11196 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
11197 if (!mSubDocuments) {
11198 return;
11201 // PLDHashTable::Iterator can't handle modifications while iterating so we
11202 // copy all entries to an array first before calling any callbacks.
11203 AutoTArray<RefPtr<Document>, 8> subdocs;
11204 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11205 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11206 if (Document* subdoc = entry->mSubDocument) {
11207 subdocs.AppendElement(subdoc);
11210 for (auto& subdoc : subdocs) {
11211 if (aCallback(*subdoc) == CallState::Stop) {
11212 break;
11217 void Document::CollectDescendantDocuments(
11218 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
11219 if (!mSubDocuments) {
11220 return;
11223 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11224 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11225 const Document* subdoc = entry->mSubDocument;
11226 if (subdoc) {
11227 if (aCallback(subdoc)) {
11228 aDescendants.AppendElement(entry->mSubDocument);
11230 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
11235 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
11236 uint32_t& aBFCacheCombo,
11237 bool aIncludeSubdocuments,
11238 bool aAllowUnloadListeners) {
11239 bool ret = true;
11241 if (!IsBFCachingAllowed()) {
11242 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
11243 ret = false;
11246 nsAutoCString uri;
11247 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11248 if (mDocumentURI) {
11249 mDocumentURI->GetSpec(uri);
11253 if (EventHandlingSuppressed()) {
11254 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11255 ("Save of %s blocked on event handling suppression", uri.get()));
11256 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
11257 ret = false;
11260 // Do not allow suspended windows to be placed in the
11261 // bfcache. This method is also used to verify a document
11262 // coming out of the bfcache is ok to restore, though. So
11263 // we only want to block suspend windows that aren't also
11264 // frozen.
11265 nsPIDOMWindowInner* win = GetInnerWindow();
11266 if (win && win->IsSuspended() && !win->IsFrozen()) {
11267 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11268 ("Save of %s blocked on suspended Window", uri.get()));
11269 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
11270 ret = false;
11273 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
11274 bool thirdParty = false;
11275 // Currently some other mobile browsers seem to bfcache only cross-domain
11276 // pages, but bfcache those also when there are unload event listeners, so
11277 // this is trying to match that behavior as much as possible.
11278 bool allowUnloadListeners =
11279 aAllowUnloadListeners &&
11280 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
11281 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
11282 channel, &thirdParty)) &&
11283 thirdParty));
11285 // Check our event listener manager for unload/beforeunload listeners.
11286 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
11287 if (!allowUnloadListeners && piTarget) {
11288 EventListenerManager* manager = piTarget->GetExistingListenerManager();
11289 if (manager) {
11290 if (manager->HasUnloadListeners()) {
11291 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11292 ("Save of %s blocked due to unload handlers", uri.get()));
11293 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
11294 ret = false;
11296 if (manager->HasBeforeUnloadListeners()) {
11297 if (!mozilla::SessionHistoryInParent() ||
11298 !StaticPrefs::
11299 docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
11300 MOZ_LOG(
11301 gPageCacheLog, mozilla::LogLevel::Verbose,
11302 ("Save of %s blocked due to beforeUnload handlers", uri.get()));
11303 aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
11304 ret = false;
11310 // Check if we have pending network requests
11311 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11312 if (loadGroup) {
11313 nsCOMPtr<nsISimpleEnumerator> requests;
11314 loadGroup->GetRequests(getter_AddRefs(requests));
11316 bool hasMore = false;
11318 // We want to bail out if we have any requests other than aNewRequest (or
11319 // in the case when aNewRequest is a part of a multipart response the base
11320 // channel the multipart response is coming in on).
11321 nsCOMPtr<nsIChannel> baseChannel;
11322 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
11323 if (part) {
11324 part->GetBaseChannel(getter_AddRefs(baseChannel));
11327 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11328 nsCOMPtr<nsISupports> elem;
11329 requests->GetNext(getter_AddRefs(elem));
11331 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11332 if (request && request != aNewRequest && request != baseChannel) {
11333 // Favicon loads don't need to block caching.
11334 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
11335 if (channel) {
11336 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
11337 if (li->InternalContentPolicyType() ==
11338 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
11339 continue;
11343 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11344 nsAutoCString requestName;
11345 request->GetName(requestName);
11346 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
11347 ("Save of %s blocked because document has request %s",
11348 uri.get(), requestName.get()));
11350 aBFCacheCombo |= BFCacheStatus::REQUEST;
11351 ret = false;
11356 // Check if we have active GetUserMedia use
11357 if (MediaManager::Exists() && win &&
11358 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
11359 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11360 ("Save of %s blocked due to GetUserMedia", uri.get()));
11361 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
11362 ret = false;
11365 #ifdef MOZ_WEBRTC
11366 // Check if we have active PeerConnections
11367 if (win && win->HasActivePeerConnections()) {
11368 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11369 ("Save of %s blocked due to PeerConnection", uri.get()));
11370 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
11371 ret = false;
11373 #endif // MOZ_WEBRTC
11375 // Don't save presentations for documents containing EME content, so that
11376 // CDMs reliably shutdown upon user navigation.
11377 if (ContainsEMEContent()) {
11378 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
11379 ret = false;
11382 // Don't save presentations for documents containing MSE content, to
11383 // reduce memory usage.
11384 if (ContainsMSEContent()) {
11385 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11386 ("Save of %s blocked due to MSE use", uri.get()));
11387 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
11388 ret = false;
11391 if (aIncludeSubdocuments && mSubDocuments) {
11392 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11393 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11394 Document* subdoc = entry->mSubDocument;
11396 uint32_t subDocBFCacheCombo = 0;
11397 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
11398 bool canCache =
11399 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
11400 true, allowUnloadListeners)
11401 : false;
11402 if (!canCache) {
11403 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11404 ("Save of %s blocked due to subdocument blocked", uri.get()));
11405 aBFCacheCombo |= subDocBFCacheCombo;
11406 ret = false;
11411 if (!mozilla::BFCacheInParent()) {
11412 // BFCache is currently not compatible with remote subframes (bug 1609324)
11413 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
11414 for (auto& child : browsingContext->Children()) {
11415 if (!child->IsInProcess()) {
11416 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
11417 ret = false;
11418 break;
11424 if (win) {
11425 auto* globalWindow = nsGlobalWindowInner::Cast(win);
11426 #ifdef MOZ_WEBSPEECH
11427 if (globalWindow->HasActiveSpeechSynthesis()) {
11428 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11429 ("Save of %s blocked due to Speech use", uri.get()));
11430 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
11431 ret = false;
11433 #endif
11434 if (globalWindow->HasUsedVR()) {
11435 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11436 ("Save of %s blocked due to having used VR", uri.get()));
11437 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
11438 ret = false;
11441 if (win->HasActiveLocks()) {
11442 MOZ_LOG(
11443 gPageCacheLog, mozilla::LogLevel::Verbose,
11444 ("Save of %s blocked due to having active lock requests", uri.get()));
11445 aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
11446 ret = false;
11449 if (win->HasActiveWebTransports()) {
11450 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11451 ("Save of %s blocked due to WebTransport", uri.get()));
11452 aBFCacheCombo |= BFCacheStatus::ACTIVE_WEBTRANSPORT;
11453 ret = false;
11457 return ret;
11460 void Document::Destroy() {
11461 // The ContentViewer wants to release the document now. So, tell our content
11462 // to drop any references to the document so that it can be destroyed.
11463 if (mIsGoingAway) {
11464 return;
11467 ReportDocumentUseCounters();
11468 ReportLCP();
11469 SetDevToolsWatchingDOMMutations(false);
11471 mIsGoingAway = true;
11473 ScriptLoader()->Destroy();
11474 SetScriptGlobalObject(nullptr);
11475 RemovedFromDocShell();
11477 bool oldVal = mInUnlinkOrDeletion;
11478 mInUnlinkOrDeletion = true;
11480 #ifdef DEBUG
11481 uint32_t oldChildCount = GetChildCount();
11482 #endif
11484 for (nsIContent* child = GetFirstChild(); child;
11485 child = child->GetNextSibling()) {
11486 child->DestroyContent();
11487 MOZ_ASSERT(child->GetParentNode() == this);
11489 MOZ_ASSERT(oldChildCount == GetChildCount());
11490 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
11492 mInUnlinkOrDeletion = oldVal;
11494 mLayoutHistoryState = nullptr;
11496 if (mOriginalDocument) {
11497 mOriginalDocument->mLatestStaticClone = nullptr;
11500 if (IsStaticDocument()) {
11501 RemoveProperty(nsGkAtoms::printisfocuseddoc);
11502 RemoveProperty(nsGkAtoms::printselectionranges);
11505 // Shut down our external resource map. We might not need this for
11506 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
11507 // tearing down all those frame trees right now is the right thing to do.
11508 mExternalResourceMap.Shutdown();
11510 // Manually break cycles via promise's global object pointer.
11511 mReadyForIdle = nullptr;
11512 mOrientationPendingPromise = nullptr;
11514 // To break cycles.
11515 mPreloadService.ClearAllPreloads();
11517 if (mDocumentL10n) {
11518 mDocumentL10n->Destroy();
11522 void Document::RemovedFromDocShell() {
11523 mEditingState = EditingState::eOff;
11525 if (mRemovedFromDocShell) return;
11527 mRemovedFromDocShell = true;
11528 NotifyActivityChanged();
11530 for (nsIContent* child = GetFirstChild(); child;
11531 child = child->GetNextSibling()) {
11532 child->SaveSubtreeState();
11535 nsIDocShell* docShell = GetDocShell();
11536 if (docShell) {
11537 docShell->SynchronizeLayoutHistoryState();
11541 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
11542 const {
11543 nsCOMPtr<nsILayoutHistoryState> state;
11544 if (!mScriptGlobalObject) {
11545 state = mLayoutHistoryState;
11546 } else {
11547 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
11548 if (docShell) {
11549 docShell->GetLayoutHistoryState(getter_AddRefs(state));
11553 return state.forget();
11556 void Document::EnsureOnloadBlocker() {
11557 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11558 // -- it's not ours.
11559 if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
11560 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11561 if (loadGroup) {
11562 // Check first to see if mOnloadBlocker is in the loadgroup.
11563 nsCOMPtr<nsISimpleEnumerator> requests;
11564 loadGroup->GetRequests(getter_AddRefs(requests));
11566 bool hasMore = false;
11567 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11568 nsCOMPtr<nsISupports> elem;
11569 requests->GetNext(getter_AddRefs(elem));
11570 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11571 if (request && request == mOnloadBlocker) {
11572 return;
11576 // Not in the loadgroup, so add it.
11577 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11582 void Document::BlockOnload() {
11583 if (mDisplayDocument) {
11584 mDisplayDocument->BlockOnload();
11585 return;
11588 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11589 // -- it's not ours.
11590 if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
11591 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11592 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11595 ++mOnloadBlockCount;
11598 void Document::UnblockOnload(bool aFireSync) {
11599 if (mDisplayDocument) {
11600 mDisplayDocument->UnblockOnload(aFireSync);
11601 return;
11604 --mOnloadBlockCount;
11606 if (mOnloadBlockCount == 0) {
11607 if (mScriptGlobalObject) {
11608 // Only manipulate the loadgroup in this case, because if
11609 // mScriptGlobalObject is null, it's not ours.
11610 if (aFireSync) {
11611 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
11612 ++mOnloadBlockCount;
11613 DoUnblockOnload();
11614 } else {
11615 PostUnblockOnloadEvent();
11617 } else if (mIsBeingUsedAsImage) {
11618 // To correctly unblock onload for a document that contains an SVG
11619 // image, we need to know when all of the SVG document's resources are
11620 // done loading, in a way comparable to |window.onload|. We fire this
11621 // event to indicate that the SVG should be considered fully loaded.
11622 // Because scripting is disabled on SVG-as-image documents, this event
11623 // is not accessible to content authors. (See bug 837315.)
11624 RefPtr<AsyncEventDispatcher> asyncDispatcher =
11625 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
11626 CanBubble::eNo, ChromeOnlyDispatch::eNo);
11627 asyncDispatcher->PostDOMEvent();
11632 class nsUnblockOnloadEvent : public Runnable {
11633 public:
11634 explicit nsUnblockOnloadEvent(Document* aDoc)
11635 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
11636 NS_IMETHOD Run() override {
11637 mDoc->DoUnblockOnload();
11638 return NS_OK;
11641 private:
11642 RefPtr<Document> mDoc;
11645 void Document::PostUnblockOnloadEvent() {
11646 MOZ_RELEASE_ASSERT(NS_IsMainThread());
11647 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
11648 nsresult rv = Dispatch(evt.forget());
11649 if (NS_SUCCEEDED(rv)) {
11650 // Stabilize block count so we don't post more events while this one is up
11651 ++mOnloadBlockCount;
11652 } else {
11653 NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
11657 void Document::DoUnblockOnload() {
11658 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
11659 MOZ_ASSERT(mOnloadBlockCount != 0,
11660 "Shouldn't have a count of zero here, since we stabilized in "
11661 "PostUnblockOnloadEvent");
11663 --mOnloadBlockCount;
11665 if (mOnloadBlockCount != 0) {
11666 // We blocked again after the last unblock. Nothing to do here. We'll
11667 // post a new event when we unblock again.
11668 return;
11671 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11672 // -- it's not ours.
11673 if (mScriptGlobalObject) {
11674 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11675 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
11680 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
11681 for (nsIFrame* f = aFrame; f;
11682 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
11683 nsIContent* content = f->GetContent();
11684 if (!content) {
11685 continue;
11688 if (content->OwnerDoc() == this) {
11689 return content;
11691 // We must be in a subdocument so jump directly to the root frame.
11692 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
11693 // the containing document.
11694 f = f->PresContext()->GetPresShell()->GetRootFrame();
11697 return nullptr;
11700 void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
11701 const nsAString& aType, bool aInFrameSwap,
11702 bool aPersisted, bool aOnlySystemGroup) {
11703 if (!aDispatchTarget) {
11704 return;
11707 PageTransitionEventInit init;
11708 init.mBubbles = true;
11709 init.mCancelable = true;
11710 init.mPersisted = aPersisted;
11711 init.mInFrameSwap = aInFrameSwap;
11713 RefPtr<PageTransitionEvent> event =
11714 PageTransitionEvent::Constructor(this, aType, init);
11716 event->SetTrusted(true);
11717 event->SetTarget(this);
11718 if (aOnlySystemGroup) {
11719 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
11721 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
11722 nullptr);
11725 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
11726 bool aOnlySystemGroup) {
11727 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11728 nsCString uri;
11729 if (GetDocumentURI()) {
11730 uri = GetDocumentURI()->GetSpecOrDefault();
11732 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11733 ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
11736 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11737 MOZ_DIAGNOSTIC_ASSERT(
11738 inFrameLoaderSwap ==
11739 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11741 Element* root = GetRootElement();
11742 if (aPersisted && root) {
11743 // Send out notifications that our <link> elements are attached.
11744 RefPtr<nsContentList> links =
11745 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11747 uint32_t linkCount = links->Length(true);
11748 for (uint32_t i = 0; i < linkCount; ++i) {
11749 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
11753 // See Document
11754 if (!inFrameLoaderSwap) {
11755 if (aPersisted) {
11756 ImageTracker()->SetAnimatingState(true);
11759 // Set mIsShowing before firing events, in case those event handlers
11760 // move us around.
11761 mIsShowing = true;
11762 mVisible = true;
11764 UpdateVisibilityState();
11767 NotifyActivityChanged();
11769 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11770 aExternalResource.OnPageShow(aPersisted, nullptr);
11771 return CallState::Continue;
11773 EnumerateExternalResources(notifyExternal);
11775 if (mAnimationController) {
11776 mAnimationController->OnPageShow();
11779 if (!mIsBeingUsedAsImage) {
11780 // Dispatch observer notification to notify observers page is shown.
11781 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11782 if (os) {
11783 nsIPrincipal* principal = NodePrincipal();
11784 os->NotifyObservers(ToSupports(this),
11785 principal->IsSystemPrincipal() ? "chrome-page-shown"
11786 : "content-page-shown",
11787 nullptr);
11790 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11791 if (!target) {
11792 target = do_QueryInterface(GetWindow());
11794 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
11795 aPersisted, aOnlySystemGroup);
11799 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
11800 if (nsPresContext* presContext = aDocument.GetPresContext()) {
11801 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
11802 FullscreenEventType::Change, &aDocument, aTarget);
11803 presContext->RefreshDriver()->ScheduleFullscreenEvent(
11804 std::move(pendingEvent));
11808 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
11809 bool aOnlySystemGroup) {
11810 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11811 nsCString uri;
11812 if (GetDocumentURI()) {
11813 uri = GetDocumentURI()->GetSpecOrDefault();
11815 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11816 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
11819 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11820 MOZ_DIAGNOSTIC_ASSERT(
11821 inFrameLoaderSwap ==
11822 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11824 if (mAnimationController) {
11825 mAnimationController->OnPageHide();
11828 if (!inFrameLoaderSwap) {
11829 if (aPersisted) {
11830 // We do not stop the animations (bug 1024343) when the page is refreshing
11831 // while being dragged out.
11832 ImageTracker()->SetAnimatingState(false);
11835 // Set mIsShowing before firing events, in case those event handlers
11836 // move us around.
11837 mIsShowing = false;
11838 mVisible = false;
11841 ExitPointerLock();
11843 if (!mIsBeingUsedAsImage) {
11844 // Dispatch observer notification to notify observers page is hidden.
11845 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11846 if (os) {
11847 nsIPrincipal* principal = NodePrincipal();
11848 os->NotifyObservers(ToSupports(this),
11849 principal->IsSystemPrincipal()
11850 ? "chrome-page-hidden"
11851 : "content-page-hidden",
11852 nullptr);
11855 // Now send out a PageHide event.
11856 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11857 if (!target) {
11858 target = do_QueryInterface(GetWindow());
11861 PageUnloadingEventTimeStamp timeStamp(this);
11862 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
11863 aPersisted, aOnlySystemGroup);
11867 if (!inFrameLoaderSwap) {
11868 UpdateVisibilityState();
11871 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11872 aExternalResource.OnPageHide(aPersisted, nullptr);
11873 return CallState::Continue;
11875 EnumerateExternalResources(notifyExternal);
11876 NotifyActivityChanged();
11878 ClearPendingFullscreenRequests(this);
11879 if (Fullscreen()) {
11880 // If this document was fullscreen, we should exit fullscreen in this
11881 // doctree branch. This ensures that if the user navigates while in
11882 // fullscreen mode we don't leave its still visible ancestor documents
11883 // in fullscreen mode. So exit fullscreen in the document's fullscreen
11884 // root document, as this will exit fullscreen in all the root's
11885 // descendant documents. Note that documents are removed from the
11886 // doctree by the time OnPageHide() is called, so we must store a
11887 // reference to the root (in Document::mFullscreenRoot) since we can't
11888 // just traverse the doctree to get the root.
11889 Document::ExitFullscreenInDocTree(this);
11891 // Since the document is removed from the doctree before OnPageHide() is
11892 // called, ExitFullscreen() can't traverse from the root down to *this*
11893 // document, so we must manually call CleanupFullscreenState() below too.
11894 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
11895 // so we *must* call it after ExitFullscreen(), not before.
11896 // OnPageHide() is called in every hidden (i.e. descendant) document,
11897 // so calling CleanupFullscreenState() here will ensure all hidden
11898 // documents have their fullscreen state reset.
11899 CleanupFullscreenState();
11901 // The fullscreenchange event is to be queued in the refresh driver,
11902 // however a hidden page wouldn't trigger that again, so it makes no
11903 // sense to dispatch such event here.
11907 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
11908 NS_ASSERTION(
11909 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
11910 "mSubtreeModifiedTargets not cleared after dispatching?");
11911 ++mSubtreeModifiedDepth;
11912 if (aTarget) {
11913 // MayDispatchMutationEvent is often called just before this method,
11914 // so it has already appended the node to mSubtreeModifiedTargets.
11915 int32_t count = mSubtreeModifiedTargets.Count();
11916 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
11917 mSubtreeModifiedTargets.AppendObject(aTarget);
11922 void Document::MutationEventDispatched(nsINode* aTarget) {
11923 if (--mSubtreeModifiedDepth) {
11924 return;
11927 int32_t count = mSubtreeModifiedTargets.Count();
11928 if (!count) {
11929 return;
11932 nsPIDOMWindowInner* window = GetInnerWindow();
11933 if (window &&
11934 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
11935 mSubtreeModifiedTargets.Clear();
11936 return;
11939 nsCOMArray<nsINode> realTargets;
11940 for (nsINode* possibleTarget : mSubtreeModifiedTargets) {
11941 if (possibleTarget->ChromeOnlyAccess()) {
11942 continue;
11945 nsINode* commonAncestor = nullptr;
11946 int32_t realTargetCount = realTargets.Count();
11947 for (int32_t j = 0; j < realTargetCount; ++j) {
11948 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
11949 possibleTarget, realTargets[j]);
11950 if (commonAncestor) {
11951 realTargets.ReplaceObjectAt(commonAncestor, j);
11952 break;
11955 if (!commonAncestor) {
11956 realTargets.AppendObject(possibleTarget);
11960 mSubtreeModifiedTargets.Clear();
11962 for (const nsCOMPtr<nsINode>& target : realTargets) {
11963 InternalMutationEvent mutation(true, eLegacySubtreeModified);
11964 // MOZ_KnownLive due to bug 1620312
11965 AsyncEventDispatcher::RunDOMEventWhenSafe(MOZ_KnownLive(*target), mutation);
11969 void Document::DestroyElementMaps() {
11970 #ifdef DEBUG
11971 mStyledLinksCleared = true;
11972 #endif
11973 mStyledLinks.Clear();
11974 // Notify ID change listeners before clearing the identifier map.
11975 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
11976 iter.Get()->ClearAndNotify();
11978 mIdentifierMap.Clear();
11979 mComposedShadowRoots.Clear();
11980 mResponsiveContent.Clear();
11981 IncrementExpandoGeneration(*this);
11984 void Document::RefreshLinkHrefs() {
11985 // Get a list of all links we know about. We will reset them, which will
11986 // remove them from the document, so we need a copy of what is in the
11987 // hashtable.
11988 const nsTArray<Link*> linksToNotify = ToArray(mStyledLinks);
11990 // Reset all of our styled links.
11991 nsAutoScriptBlocker scriptBlocker;
11992 for (Link* link : linksToNotify) {
11993 link->ResetLinkState(true);
11997 nsresult Document::CloneDocHelper(Document* clone) const {
11998 clone->mIsStaticDocument = mCreatingStaticClone;
12000 // Init document
12001 nsresult rv = clone->Init(NodePrincipal(), mPartitionedPrincipal);
12002 NS_ENSURE_SUCCESS(rv, rv);
12004 if (mCreatingStaticClone) {
12005 if (mOriginalDocument) {
12006 clone->mOriginalDocument = mOriginalDocument;
12007 } else {
12008 clone->mOriginalDocument = const_cast<Document*>(this);
12010 clone->mOriginalDocument->mLatestStaticClone = clone;
12011 clone->mOriginalDocument->mStaticCloneCount++;
12013 nsCOMPtr<nsILoadGroup> loadGroup;
12015 // |mDocumentContainer| is the container of the document that is being
12016 // created and not the original container. See CreateStaticClone function().
12017 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
12018 if (docLoader) {
12019 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
12021 nsCOMPtr<nsIChannel> channel = GetChannel();
12022 nsCOMPtr<nsIURI> uri;
12023 if (channel) {
12024 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
12025 } else {
12026 uri = Document::GetDocumentURI();
12028 clone->mChannel = channel;
12029 clone->mShouldResistFingerprinting = mShouldResistFingerprinting;
12030 if (uri) {
12031 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
12034 clone->mIsSrcdocDocument = mIsSrcdocDocument;
12035 clone->SetContainer(mDocumentContainer);
12037 // Setup the navigation time. This will be needed by any animations in the
12038 // document, even if they are only paused.
12039 MOZ_ASSERT(!clone->GetNavigationTiming(),
12040 "Navigation time was already set?");
12041 if (mTiming) {
12042 RefPtr<nsDOMNavigationTiming> timing =
12043 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
12044 clone->SetNavigationTiming(timing);
12046 clone->SetCsp(mCSP);
12049 // Now ensure that our clone has the same URI, base URI, and principal as us.
12050 // We do this after the mCreatingStaticClone block above, because that block
12051 // can set the base URI to an incorrect value in cases when base URI
12052 // information came from the channel. So we override explicitly, and do it
12053 // for all these properties, in case ResetToURI messes with any of the rest of
12054 // them.
12055 clone->SetDocumentURI(Document::GetDocumentURI());
12056 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
12057 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
12058 clone->mActiveCookiePrincipal = mActiveCookiePrincipal;
12059 // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
12060 // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
12061 // when printed standalone via window.print() (where there won't be a parent
12062 // document to grab the URI from).
12063 clone->mDocumentBaseURI = GetDocBaseURI();
12064 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
12065 clone->mReferrerInfo =
12066 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
12067 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
12069 bool hasHadScriptObject = true;
12070 nsIScriptGlobalObject* scriptObject =
12071 GetScriptHandlingObject(hasHadScriptObject);
12072 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
12073 if (mCreatingStaticClone) {
12074 // If we're doing a static clone (print, print preview), then we're going to
12075 // be setting a scope object after the clone. It's better to set it only
12076 // once, so we don't do that here. However, we do want to act as if there is
12077 // a script handling object. So we set mHasHadScriptHandlingObject.
12078 clone->mHasHadScriptHandlingObject = true;
12079 } else if (scriptObject) {
12080 clone->SetScriptHandlingObject(scriptObject);
12081 } else {
12082 clone->SetScopeObject(GetScopeObject());
12084 // Make the clone a data document
12085 clone->SetLoadedAsData(
12086 true,
12087 /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
12089 // Misc state
12091 // State from Document
12092 clone->mCharacterSet = mCharacterSet;
12093 clone->mCharacterSetSource = mCharacterSetSource;
12094 clone->SetCompatibilityMode(mCompatMode);
12095 clone->mBidiOptions = mBidiOptions;
12096 clone->mContentLanguage = mContentLanguage;
12097 clone->SetContentType(GetContentTypeInternal());
12098 clone->mSecurityInfo = mSecurityInfo;
12100 // State from Document
12101 clone->mType = mType;
12102 clone->mXMLDeclarationBits = mXMLDeclarationBits;
12103 clone->mBaseTarget = mBaseTarget;
12105 return NS_OK;
12108 void Document::NotifyLoading(bool aNewParentIsLoading,
12109 const ReadyState& aCurrentState,
12110 ReadyState aNewState) {
12111 // Mirror the top-level loading state down to all subdocuments
12112 bool was_loading = mAncestorIsLoading ||
12113 aCurrentState == READYSTATE_LOADING ||
12114 aCurrentState == READYSTATE_INTERACTIVE;
12115 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
12116 aNewState == READYSTATE_INTERACTIVE; // new value for state
12117 bool set_load_state = was_loading != is_loading;
12119 MOZ_LOG(
12120 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12121 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
12122 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
12123 "set_load_state: %d",
12124 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
12125 (int)aNewState, was_loading, is_loading, set_load_state));
12127 mAncestorIsLoading = aNewParentIsLoading;
12128 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
12129 // Tell our innerwindow (and thus TimeoutManager)
12130 nsPIDOMWindowInner* inner = GetInnerWindow();
12131 if (inner) {
12132 inner->SetActiveLoadingState(is_loading);
12134 BrowsingContext* context = GetBrowsingContext();
12135 if (context) {
12136 // Don't use PreOrderWalk to mirror this down; go down one level as a
12137 // time so we can set mAncestorIsLoading and take into account the
12138 // readystates of the subdocument. In the child process it will call
12139 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
12140 // iterate it's children
12141 for (auto& child : context->Children()) {
12142 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12143 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
12144 // Setting ancestor loading on a discarded browsing context has no
12145 // effect.
12146 Unused << child->SetAncestorLoading(is_loading);
12152 void Document::SetReadyStateInternal(ReadyState aReadyState,
12153 bool aUpdateTimingInformation) {
12154 if (aReadyState == READYSTATE_UNINITIALIZED) {
12155 // Transition back to uninitialized happens only to keep assertions happy
12156 // right before readyState transitions to something else. Make this
12157 // transition undetectable by Web content.
12158 mReadyState = aReadyState;
12159 return;
12162 if (IsTopLevelContentDocument()) {
12163 if (aReadyState == READYSTATE_LOADING) {
12164 AddToplevelLoadingDocument(this);
12165 } else if (aReadyState == READYSTATE_COMPLETE) {
12166 RemoveToplevelLoadingDocument(this);
12170 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
12171 SetLoadingOrRestoredFromBFCacheTimeStampToNow();
12173 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
12174 mReadyState = aReadyState;
12175 if (aUpdateTimingInformation && mTiming) {
12176 switch (aReadyState) {
12177 case READYSTATE_LOADING:
12178 mTiming->NotifyDOMLoading(GetDocumentURI());
12179 break;
12180 case READYSTATE_INTERACTIVE:
12181 mTiming->NotifyDOMInteractive(GetDocumentURI());
12182 break;
12183 case READYSTATE_COMPLETE:
12184 mTiming->NotifyDOMComplete(GetDocumentURI());
12185 break;
12186 default:
12187 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
12188 break;
12191 // At the time of loading start, we don't have timing object, record time.
12193 if (READYSTATE_INTERACTIVE == aReadyState &&
12194 NodePrincipal()->IsSystemPrincipal()) {
12195 if (!mXULPersist) {
12196 mXULPersist = new XULPersist(this);
12197 mXULPersist->Init();
12199 if (!mChromeObserver) {
12200 mChromeObserver = new ChromeObserver(this);
12201 mChromeObserver->Init();
12205 if (aUpdateTimingInformation) {
12206 RecordNavigationTiming(aReadyState);
12209 AsyncEventDispatcher::RunDOMEventWhenSafe(
12210 *this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
12213 void Document::GetReadyState(nsAString& aReadyState) const {
12214 switch (mReadyState) {
12215 case READYSTATE_LOADING:
12216 aReadyState.AssignLiteral(u"loading");
12217 break;
12218 case READYSTATE_INTERACTIVE:
12219 aReadyState.AssignLiteral(u"interactive");
12220 break;
12221 case READYSTATE_COMPLETE:
12222 aReadyState.AssignLiteral(u"complete");
12223 break;
12224 default:
12225 aReadyState.AssignLiteral(u"uninitialized");
12229 void Document::SuppressEventHandling(uint32_t aIncrease) {
12230 mEventsSuppressed += aIncrease;
12231 if (mEventsSuppressed == aIncrease) {
12232 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
12233 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12236 UpdateFrameRequestCallbackSchedulingState();
12237 for (uint32_t i = 0; i < aIncrease; ++i) {
12238 ScriptLoader()->AddExecuteBlocker();
12241 auto suppressInSubDoc = [aIncrease](Document& aSubDoc) {
12242 aSubDoc.SuppressEventHandling(aIncrease);
12243 return CallState::Continue;
12246 EnumerateSubDocuments(suppressInSubDoc);
12249 void Document::NotifyAbortedLoad() {
12250 // If we still have outstanding work blocking DOMContentLoaded,
12251 // then don't try to change the readystate now, but wait until
12252 // they finish and then do so.
12253 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
12254 mSetCompleteAfterDOMContentLoaded = true;
12255 return;
12258 // Otherwise we're fully done at this point, so set the
12259 // readystate to complete.
12260 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
12261 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
12265 MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents(
12266 nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) {
12267 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
12268 if (MOZ_UNLIKELY(!fm)) {
12269 return;
12272 nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments);
12273 for (uint32_t i = 0; i < documents.Length(); ++i) {
12274 nsCOMPtr<Document> document = std::move(documents[i]);
12275 // NB: Don't bother trying to fire delayed events on documents that were
12276 // closed before this event ran.
12277 if (!document->EventHandlingSuppressed()) {
12278 fm->FireDelayedEvents(document);
12279 RefPtr<PresShell> presShell = document->GetPresShell();
12280 if (presShell) {
12281 // Only fire events for active documents.
12282 bool fire = aFireEvents && document->GetInnerWindow() &&
12283 document->GetInnerWindow()->IsCurrentInnerWindow();
12284 presShell->FireOrClearDelayedEvents(fire);
12286 document->FireOrClearPostMessageEvents(aFireEvents);
12291 void Document::PreloadPictureClosed() {
12292 MOZ_ASSERT(mPreloadPictureDepth > 0);
12293 mPreloadPictureDepth--;
12294 if (mPreloadPictureDepth == 0) {
12295 mPreloadPictureFoundSource.SetIsVoid(true);
12299 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
12300 const nsAString& aSizesAttr,
12301 const nsAString& aTypeAttr,
12302 const nsAString& aMediaAttr) {
12303 // Nested pictures are not valid syntax, so while we'll eventually load them,
12304 // it's not worth tracking sources mixed between nesting levels to preload
12305 // them effectively.
12306 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
12307 // <picture> selects the first matching source, so if this returns a URI we
12308 // needn't consider new sources until a new <picture> is encountered.
12309 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
12310 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
12311 aMediaAttr, mPreloadPictureFoundSource);
12312 if (found && mPreloadPictureFoundSource.IsVoid()) {
12313 // Found an empty source, which counts
12314 mPreloadPictureFoundSource.SetIsVoid(false);
12319 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
12320 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
12321 const nsAString& aSizesAttr, bool* aIsImgSet) {
12322 nsString sourceURL;
12323 bool isImgSet;
12324 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
12325 // We're in a <picture> element and found a URI from a source previous to
12326 // this image, use it.
12327 sourceURL = mPreloadPictureFoundSource;
12328 isImgSet = true;
12329 } else {
12330 // Otherwise try to use this <img> as a source
12331 HTMLImageElement::SelectSourceForTagWithAttrs(
12332 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
12333 VoidString(), sourceURL);
12334 isImgSet = !aSrcsetAttr.IsEmpty();
12337 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
12338 if (sourceURL.IsEmpty()) {
12339 return nullptr;
12342 // Construct into URI using passed baseURI (the parser may know of base URI
12343 // changes that have not reached us)
12344 nsresult rv;
12345 nsCOMPtr<nsIURI> uri;
12346 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
12347 this, aBaseURI);
12348 if (NS_FAILED(rv)) {
12349 return nullptr;
12352 *aIsImgSet = isImgSet;
12354 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
12355 // this this <picture> share the same <sources> (though this is not valid per
12356 // spec)
12357 return uri.forget();
12360 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
12361 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
12362 bool aLinkPreload, uint64_t aEarlyHintPreloaderId) {
12363 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
12364 nsContentUtils::CORSModeToLoadImageFlags(
12365 Element::StringToCORSMode(aCrossOriginAttr));
12367 nsContentPolicyType policyType =
12368 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
12369 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
12371 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12372 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12374 RefPtr<imgRequestProxy> request;
12376 nsLiteralString initiator = aEarlyHintPreloaderId
12377 ? u"early-hints"_ns
12378 : (aLinkPreload ? u"link"_ns : u"img"_ns);
12380 nsresult rv = nsContentUtils::LoadImage(
12381 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
12382 nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request),
12383 policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId);
12385 // Pin image-reference to avoid evicting it from the img-cache before
12386 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
12387 // unlink
12388 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
12389 mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
12393 void Document::MaybePreLoadImage(nsIURI* aUri,
12394 const nsAString& aCrossOriginAttr,
12395 ReferrerPolicyEnum aReferrerPolicy,
12396 bool aIsImgSet, bool aLinkPreload) {
12397 const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr);
12398 if (aLinkPreload) {
12399 // Check if the image was already preloaded in this document to avoid
12400 // duplicate preloading.
12401 PreloadHashKey key =
12402 PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode);
12403 if (!mPreloadService.PreloadExists(key)) {
12404 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12405 aLinkPreload, 0);
12407 return;
12410 // Early exit if the img is already present in the img-cache
12411 // which indicates that the "real" load has already started and
12412 // that we shouldn't preload it.
12413 if (nsContentUtils::IsImageAvailable(aUri, NodePrincipal(), corsMode, this)) {
12414 return;
12417 // Image not in cache - trigger preload
12418 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload,
12422 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
12423 if (!StaticPrefs::network_preconnect()) {
12424 return;
12427 NS_MutateURI mutator(aOrigURI);
12428 if (NS_FAILED(mutator.GetStatus())) {
12429 return;
12432 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
12433 // which ignores the path and uses only the origin. The other is for the
12434 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
12435 // non-Anonymous preconnects create different connections on the wire and
12436 // therefore should not be considred duplicates of each other and we
12437 // normalize the path before putting it in the hash to accomplish that.
12439 if (aCORSMode == CORS_ANONYMOUS) {
12440 mutator.SetPathQueryRef("/anonymous"_ns);
12441 } else {
12442 mutator.SetPathQueryRef("/"_ns);
12445 nsCOMPtr<nsIURI> uri;
12446 nsresult rv = mutator.Finalize(uri);
12447 if (NS_FAILED(rv)) {
12448 return;
12451 const bool existingEntryFound =
12452 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
12453 if (entry) {
12454 return true;
12456 entry.Insert(true);
12457 return false;
12459 if (existingEntryFound) {
12460 return;
12463 nsCOMPtr<nsISpeculativeConnect> speculator =
12464 mozilla::components::IO::Service();
12465 if (!speculator) {
12466 return;
12469 OriginAttributes oa;
12470 StoragePrincipalHelper::GetOriginAttributesForNetworkState(this, oa);
12471 speculator->SpeculativeConnectWithOriginAttributesNative(
12472 uri, std::move(oa), nullptr, aCORSMode == CORS_ANONYMOUS);
12475 void Document::ForgetImagePreload(nsIURI* aURI) {
12476 // Checking count is faster than hashing the URI in the common
12477 // case of empty table.
12478 if (mPreloadingImages.Count() != 0) {
12479 nsCOMPtr<imgIRequest> req;
12480 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
12481 if (req) {
12482 // Make sure to cancel the request so imagelib knows it's gone.
12483 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
12488 void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates,
12489 bool aNotify) {
12490 const DocumentState oldStates = mState;
12491 if (aMaybeChangedStates.HasAtLeastOneOfStates(
12492 DocumentState::ALL_LOCALEDIR_BITS)) {
12493 mState &= ~DocumentState::ALL_LOCALEDIR_BITS;
12494 if (IsDocumentRightToLeft()) {
12495 mState |= DocumentState::RTL_LOCALE;
12496 } else {
12497 mState |= DocumentState::LTR_LOCALE;
12501 if (aMaybeChangedStates.HasAtLeastOneOfStates(DocumentState::LWTHEME)) {
12502 if (ComputeDocumentLWTheme()) {
12503 mState |= DocumentState::LWTHEME;
12504 } else {
12505 mState &= ~DocumentState::LWTHEME;
12509 if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) {
12510 BrowsingContext* bc = GetBrowsingContext();
12511 if (!bc || !bc->GetIsActiveBrowserWindow()) {
12512 mState |= DocumentState::WINDOW_INACTIVE;
12513 } else {
12514 mState &= ~DocumentState::WINDOW_INACTIVE;
12518 const DocumentState changedStates = oldStates ^ mState;
12519 if (aNotify && !changedStates.IsEmpty()) {
12520 if (PresShell* ps = GetObservingPresShell()) {
12521 ps->DocumentStatesChanged(changedStates);
12526 namespace {
12529 * Stub for LoadSheet(), since all we want is to get the sheet into
12530 * the CSSLoader's style cache
12532 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
12533 ~StubCSSLoaderObserver() = default;
12535 public:
12536 NS_IMETHOD
12537 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
12538 NS_DECL_ISUPPORTS
12540 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
12542 } // namespace
12544 SheetPreloadStatus Document::PreloadStyle(
12545 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
12546 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aNonce,
12547 const nsAString& aIntegrity, css::StylePreloadKind aKind,
12548 uint64_t aEarlyHintPreloaderId, const nsAString& aFetchPriority) {
12549 MOZ_ASSERT(aKind != css::StylePreloadKind::None);
12551 // The CSSLoader will retain this object after we return.
12552 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
12554 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12555 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12557 // Charset names are always ASCII.
12558 auto result = CSSLoader()->LoadSheet(
12559 uri, aKind, aEncoding, referrerInfo, obs, aEarlyHintPreloaderId,
12560 Element::StringToCORSMode(aCrossOriginAttr), aNonce, aIntegrity,
12561 nsGenericHTMLElement::ToFetchPriority(aFetchPriority));
12562 if (result.isErr()) {
12563 return SheetPreloadStatus::Errored;
12565 RefPtr<StyleSheet> sheet = result.unwrap();
12566 if (sheet->IsComplete()) {
12567 return SheetPreloadStatus::AlreadyComplete;
12569 return SheetPreloadStatus::InProgress;
12572 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
12573 return CSSLoader()
12574 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
12575 .unwrapOr(nullptr);
12578 void Document::ResetDocumentDirection() {
12579 if (!nsContentUtils::IsChromeDoc(this)) {
12580 return;
12582 UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true);
12585 bool Document::IsDocumentRightToLeft() {
12586 if (!nsContentUtils::IsChromeDoc(this)) {
12587 return false;
12589 // setting the localedir attribute on the root element forces a
12590 // specific direction for the document.
12591 Element* element = GetRootElement();
12592 if (element) {
12593 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
12594 nullptr};
12595 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
12596 strings, eCaseMatters)) {
12597 case 0:
12598 return false;
12599 case 1:
12600 return true;
12601 default:
12602 break; // otherwise, not a valid value, so fall through
12606 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
12607 !mDocumentURI->SchemeIs("resource")) {
12608 return false;
12611 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
12614 class nsDelayedEventDispatcher : public Runnable {
12615 public:
12616 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
12617 : mozilla::Runnable("nsDelayedEventDispatcher"),
12618 mDocuments(std::move(aDocuments)) {}
12619 virtual ~nsDelayedEventDispatcher() = default;
12621 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
12622 // bug 1535398.
12623 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
12624 FireOrClearDelayedEvents(std::move(mDocuments), true);
12625 return NS_OK;
12628 private:
12629 nsTArray<nsCOMPtr<Document>> mDocuments;
12632 static void GetAndUnsuppressSubDocuments(
12633 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
12634 if (aDocument.EventHandlingSuppressed() > 0) {
12635 aDocument.DecreaseEventSuppression();
12636 aDocument.ScriptLoader()->RemoveExecuteBlocker();
12638 aDocuments.AppendElement(&aDocument);
12639 auto recurse = [&aDocuments](Document& aSubDoc) {
12640 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
12641 return CallState::Continue;
12643 aDocument.EnumerateSubDocuments(recurse);
12646 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
12647 nsTArray<nsCOMPtr<Document>> documents;
12648 GetAndUnsuppressSubDocuments(*this, documents);
12650 for (nsCOMPtr<Document>& doc : documents) {
12651 if (!doc->EventHandlingSuppressed()) {
12652 if (WindowGlobalChild* wgc = doc->GetWindowGlobalChild()) {
12653 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12656 MOZ_ASSERT(NS_IsMainThread());
12657 nsTArray<RefPtr<net::ChannelEventQueue>> queues =
12658 std::move(doc->mSuspendedQueues);
12659 for (net::ChannelEventQueue* queue : queues) {
12660 queue->Resume();
12663 // If there have been any events driven by the refresh driver which were
12664 // delayed due to events being suppressed in this document, make sure
12665 // there is a refresh scheduled soon so the events will run.
12666 if (doc->mHasDelayedRefreshEvent) {
12667 doc->mHasDelayedRefreshEvent = false;
12669 if (doc->mPresShell) {
12670 nsRefreshDriver* rd =
12671 doc->mPresShell->GetPresContext()->RefreshDriver();
12672 rd->RunDelayedEventsSoon();
12678 if (aFireEvents) {
12679 MOZ_RELEASE_ASSERT(NS_IsMainThread());
12680 nsCOMPtr<nsIRunnable> ded =
12681 new nsDelayedEventDispatcher(std::move(documents));
12682 Dispatch(ded.forget());
12683 } else {
12684 FireOrClearDelayedEvents(std::move(documents), false);
12688 bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
12689 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
12692 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
12693 MOZ_ASSERT(NS_IsMainThread());
12694 MOZ_ASSERT(EventHandlingSuppressed());
12695 mSuspendedQueues.AppendElement(aQueue);
12698 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
12699 MOZ_ASSERT(NS_IsMainThread());
12701 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
12702 mSuspendedPostMessageEvents.AppendElement(aEvent);
12703 return true;
12705 return false;
12708 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
12709 nsTArray<RefPtr<PostMessageEvent>> events =
12710 std::move(mSuspendedPostMessageEvents);
12712 if (aFireEvents) {
12713 for (PostMessageEvent* event : events) {
12714 event->Run();
12719 void Document::SetSuppressedEventListener(EventListener* aListener) {
12720 mSuppressedEventListener = aListener;
12721 auto setOnSubDocs = [&](Document& aDocument) {
12722 aDocument.SetSuppressedEventListener(aListener);
12723 return CallState::Continue;
12725 EnumerateSubDocuments(setOnSubDocs);
12728 bool Document::IsActive() const {
12729 return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
12730 !GetBrowsingContext()->IsInBFCache();
12733 nsISupports* Document::GetCurrentContentSink() {
12734 return mParser ? mParser->GetContentSink() : nullptr;
12737 Document* Document::GetTemplateContentsOwner() {
12738 if (!mTemplateContentsOwner) {
12739 bool hasHadScriptObject = true;
12740 nsIScriptGlobalObject* scriptObject =
12741 GetScriptHandlingObject(hasHadScriptObject);
12743 nsCOMPtr<Document> document;
12744 nsresult rv = NS_NewDOMDocument(
12745 getter_AddRefs(document),
12746 u""_ns, // aNamespaceURI
12747 u""_ns, // aQualifiedName
12748 nullptr, // aDoctype
12749 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
12750 true, // aLoadedAsData
12751 scriptObject, // aEventObject
12752 IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
12753 NS_ENSURE_SUCCESS(rv, nullptr);
12755 mTemplateContentsOwner = document;
12756 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
12758 if (!scriptObject) {
12759 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
12762 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
12764 // Set |mTemplateContentsOwner| as the template contents owner of itself so
12765 // that it is the template contents owner of nested template elements.
12766 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
12769 MOZ_ASSERT(mTemplateContentsOwner->IsTemplateContentsOwner());
12770 return mTemplateContentsOwner;
12773 // https://html.spec.whatwg.org/#the-autofocus-attribute
12774 void Document::ElementWithAutoFocusInserted(Element* aAutoFocusCandidate) {
12775 BrowsingContext* bc = GetBrowsingContext();
12776 if (!bc) {
12777 return;
12780 // If target is not fully active, then return.
12781 if (!IsCurrentActiveDocument()) {
12782 return;
12785 // If target's active sandboxing flag set has the sandboxed automatic features
12786 // browsing context flag, then return.
12787 if (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES) {
12788 return;
12791 // For each ancestorBC of target's browsing context's ancestor browsing
12792 // contexts: if ancestorBC's active document's origin is not same origin with
12793 // target's origin, then return.
12794 while (bc) {
12795 BrowsingContext* parent = bc->GetParent();
12796 if (!parent) {
12797 break;
12799 // AncestorBC is not the same site
12800 if (!parent->IsInProcess()) {
12801 return;
12804 Document* currentDocument = bc->GetDocument();
12805 if (!currentDocument) {
12806 return;
12809 Document* parentDocument = parent->GetDocument();
12810 if (!parentDocument) {
12811 return;
12814 // Not same origin
12815 if (!currentDocument->NodePrincipal()->Equals(
12816 parentDocument->NodePrincipal())) {
12817 return;
12820 bc = parent;
12822 MOZ_ASSERT(bc->IsTop());
12824 Document* topDocument = bc->GetDocument();
12825 MOZ_ASSERT(topDocument);
12826 topDocument->AppendAutoFocusCandidateToTopDocument(aAutoFocusCandidate);
12829 void Document::ScheduleFlushAutoFocusCandidates() {
12830 MOZ_ASSERT(mPresShell && mPresShell->DidInitialize());
12831 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12832 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
12833 rd->ScheduleAutoFocusFlush(this);
12837 void Document::AppendAutoFocusCandidateToTopDocument(
12838 Element* aAutoFocusCandidate) {
12839 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12840 if (mAutoFocusFired) {
12841 return;
12844 if (!HasAutoFocusCandidates()) {
12845 // PresShell may be initialized later
12846 if (mPresShell && mPresShell->DidInitialize()) {
12847 ScheduleFlushAutoFocusCandidates();
12851 nsWeakPtr element = do_GetWeakReference(aAutoFocusCandidate);
12852 mAutoFocusCandidates.RemoveElement(element);
12853 mAutoFocusCandidates.AppendElement(element);
12856 void Document::SetAutoFocusFired() {
12857 mAutoFocusCandidates.Clear();
12858 mAutoFocusFired = true;
12861 // https://html.spec.whatwg.org/#flush-autofocus-candidates
12862 void Document::FlushAutoFocusCandidates() {
12863 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12864 if (mAutoFocusFired) {
12865 return;
12868 if (!mPresShell) {
12869 return;
12872 MOZ_ASSERT(HasAutoFocusCandidates());
12873 MOZ_ASSERT(mPresShell->DidInitialize());
12875 nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetWindow();
12876 // We should be the top document
12877 if (!topWindow) {
12878 return;
12881 #ifdef DEBUG
12883 // Trying to find the top window (equivalent to window.top).
12884 nsCOMPtr<nsPIDOMWindowOuter> top = topWindow->GetInProcessTop();
12885 MOZ_ASSERT(topWindow == top);
12887 #endif
12889 // Don't steal the focus from the user
12890 if (topWindow->GetFocusedElement()) {
12891 SetAutoFocusFired();
12892 return;
12895 MOZ_ASSERT(mDocumentURI);
12896 nsAutoCString ref;
12897 // GetRef never fails
12898 nsresult rv = mDocumentURI->GetRef(ref);
12899 if (NS_SUCCEEDED(rv) &&
12900 nsContentUtils::GetTargetElement(this, NS_ConvertUTF8toUTF16(ref))) {
12901 SetAutoFocusFired();
12902 return;
12905 nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mAutoFocusCandidates);
12906 while (iter.HasMore()) {
12907 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(iter.GetNext());
12908 if (!autoFocusElement) {
12909 continue;
12911 RefPtr<Document> autoFocusElementDoc = autoFocusElement->OwnerDoc();
12912 // Get the latest info about the frame and allow scripts
12913 // to run which might affect the focusability of this element.
12914 autoFocusElementDoc->FlushPendingNotifications(FlushType::Frames);
12916 // Above layout flush may cause the PresShell to disappear.
12917 if (!mPresShell) {
12918 return;
12921 // Re-get the element because the ownerDoc() might have changed
12922 autoFocusElementDoc = autoFocusElement->OwnerDoc();
12923 BrowsingContext* bc = autoFocusElementDoc->GetBrowsingContext();
12924 if (!bc) {
12925 continue;
12928 // If doc is not fully active, then remove element from candidates, and
12929 // continue.
12930 if (!autoFocusElementDoc->IsCurrentActiveDocument()) {
12931 iter.Remove();
12932 continue;
12935 nsCOMPtr<nsIContentSink> sink =
12936 do_QueryInterface(autoFocusElementDoc->GetCurrentContentSink());
12937 if (sink) {
12938 nsHtml5TreeOpExecutor* executor =
12939 static_cast<nsHtml5TreeOpExecutor*>(sink->AsExecutor());
12940 if (executor) {
12941 // This is a HTML5 document
12942 MOZ_ASSERT(autoFocusElementDoc->IsHTMLDocument());
12943 // If doc's script-blocking style sheet counter is greater than 0, th
12944 // return.
12945 if (executor->WaitForPendingSheets()) {
12946 // In this case, element is the currently-best candidate, but doc is
12947 // not ready for autofocusing. We'll try again next time flush
12948 // autofocus candidates is called.
12949 ScheduleFlushAutoFocusCandidates();
12950 return;
12955 // The autofocus element could be moved to a different
12956 // top level BC.
12957 if (bc->Top()->GetDocument() != this) {
12958 continue;
12961 iter.Remove();
12963 // Let inclusiveAncestorDocuments be a list consisting of doc, plus the
12964 // active documents of each of doc's browsing context's ancestor browsing
12965 // contexts.
12966 // If any Document in inclusiveAncestorDocuments has non-null target
12967 // element, then continue.
12968 bool shouldFocus = true;
12969 while (bc) {
12970 Document* doc = bc->GetDocument();
12971 if (!doc) {
12972 shouldFocus = false;
12973 break;
12976 nsIURI* uri = doc->GetDocumentURI();
12977 if (!uri) {
12978 shouldFocus = false;
12979 break;
12982 nsAutoCString ref;
12983 nsresult rv = uri->GetRef(ref);
12984 // If there is an element in the document tree that has an ID equal to
12985 // fragment
12986 if (NS_SUCCEEDED(rv) &&
12987 nsContentUtils::GetTargetElement(doc, NS_ConvertUTF8toUTF16(ref))) {
12988 shouldFocus = false;
12989 break;
12991 bc = bc->GetParent();
12994 if (!shouldFocus) {
12995 continue;
12998 MOZ_ASSERT(topWindow);
12999 if (TryAutoFocusCandidate(*autoFocusElement)) {
13000 // We've successfully autofocused an element, don't
13001 // need to try to focus the rest.
13002 SetAutoFocusFired();
13003 break;
13007 if (HasAutoFocusCandidates()) {
13008 ScheduleFlushAutoFocusCandidates();
13012 bool Document::TryAutoFocusCandidate(Element& aElement) {
13013 const FocusOptions options;
13014 if (RefPtr<Element> target = nsFocusManager::GetTheFocusableArea(
13015 &aElement, nsFocusManager::ProgrammaticFocusFlags(options))) {
13016 target->Focus(options, CallerType::NonSystem, IgnoreErrors());
13017 return true;
13020 return false;
13023 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
13024 if (!aDocumentURI) {
13025 return;
13028 nsAutoCString ref;
13030 // Since all URI's that pass through here aren't URL's we can't
13031 // rely on the nsIURI implementation for providing a way for
13032 // finding the 'ref' part of the URI, we'll haveto revert to
13033 // string routines for finding the data past '#'
13035 nsresult rv = aDocumentURI->GetSpec(ref);
13036 if (NS_FAILED(rv)) {
13037 Unused << aDocumentURI->GetRef(mScrollToRef);
13038 return;
13041 nsReadingIterator<char> start, end;
13043 ref.BeginReading(start);
13044 ref.EndReading(end);
13046 if (FindCharInReadable('#', start, end)) {
13047 ++start; // Skip over the '#'
13049 mScrollToRef = Substring(start, end);
13053 // https://html.spec.whatwg.org/#scrolling-to-a-fragment
13054 void Document::ScrollToRef() {
13055 if (mScrolledToRefAlready) {
13056 RefPtr<PresShell> presShell = GetPresShell();
13057 if (presShell) {
13058 presShell->ScrollToAnchor();
13060 return;
13063 // 2. If fragment is the empty string, then return the special value top of
13064 // the document.
13065 if (mScrollToRef.IsEmpty()) {
13066 return;
13069 RefPtr<PresShell> presShell = GetPresShell();
13070 if (!presShell) {
13071 return;
13074 // 3. Let potentialIndicatedElement be the result of finding a potential
13075 // indicated element given document and fragment.
13076 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
13077 auto rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
13079 // 4. If potentialIndicatedElement is not null, then return
13080 // potentialIndicatedElement.
13081 if (NS_SUCCEEDED(rv)) {
13082 mScrolledToRefAlready = true;
13083 return;
13086 // 5. Let fragmentBytes be the result of percent-decoding fragment.
13087 nsAutoCString fragmentBytes;
13088 const bool unescaped =
13089 NS_UnescapeURL(mScrollToRef.Data(), mScrollToRef.Length(),
13090 /* aFlags = */ 0, fragmentBytes);
13092 if (!unescaped || fragmentBytes.IsEmpty()) {
13093 // Another attempt is only necessary if characters were unescaped.
13094 return;
13097 // 6. Let decodedFragment be the result of running UTF-8 decode without BOM on
13098 // fragmentBytes.
13099 nsAutoString decodedFragment;
13100 rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(fragmentBytes, decodedFragment);
13101 NS_ENSURE_SUCCESS_VOID(rv);
13103 // 7. Set potentialIndicatedElement to the result of finding a potential
13104 // indicated element given document and decodedFragment.
13105 rv = presShell->GoToAnchor(decodedFragment,
13106 mChangeScrollPosWhenScrollingToRef);
13107 if (NS_SUCCEEDED(rv)) {
13108 mScrolledToRefAlready = true;
13112 void Document::RegisterActivityObserver(nsISupports* aSupports) {
13113 if (!mActivityObservers) {
13114 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
13116 mActivityObservers->Insert(aSupports);
13119 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
13120 if (!mActivityObservers) {
13121 return false;
13123 return mActivityObservers->EnsureRemoved(aSupports);
13126 void Document::EnumerateActivityObservers(
13127 ActivityObserverEnumerator aEnumerator) {
13128 if (!mActivityObservers) {
13129 return;
13132 const auto keyArray =
13133 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
13134 for (auto& observer : keyArray) {
13135 aEnumerator(observer.get());
13139 void Document::RegisterPendingLinkUpdate(Link* aLink) {
13140 if (aLink->HasPendingLinkUpdate()) {
13141 return;
13144 aLink->SetHasPendingLinkUpdate();
13146 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
13147 nsCOMPtr<nsIRunnable> event =
13148 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
13149 &Document::FlushPendingLinkUpdates);
13150 // Do this work in a second in the worst case.
13151 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
13152 EventQueuePriority::Idle);
13153 if (NS_FAILED(rv)) {
13154 // If during shutdown posting a runnable doesn't succeed, we probably
13155 // don't need to update link states.
13156 return;
13158 mHasLinksToUpdateRunnable = true;
13161 mLinksToUpdate.InfallibleAppend(aLink);
13164 void Document::FlushPendingLinkUpdates() {
13165 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
13166 MOZ_ASSERT(mHasLinksToUpdateRunnable);
13167 mHasLinksToUpdateRunnable = false;
13169 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
13170 mFlushingPendingLinkUpdates = true;
13172 while (!mLinksToUpdate.IsEmpty()) {
13173 LinksToUpdateList links(std::move(mLinksToUpdate));
13174 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
13175 Link* link = iter.Get();
13176 Element* element = link->GetElement();
13177 if (element->OwnerDoc() == this) {
13178 link->ClearHasPendingLinkUpdate();
13179 if (element->IsInComposedDoc()) {
13180 link->TriggerLinkUpdate(/* aNotify = */ true);
13188 * Retrieves the node in a static-clone document that corresponds to aOrigNode,
13189 * which is a node in the original document from which aStaticClone was cloned.
13191 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
13192 Document& aStaticClone) {
13193 MOZ_ASSERT(aOrigNode);
13195 // Selections in anonymous subtrees aren't supported.
13196 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
13197 return nullptr;
13200 // If the node is disconnected, this is a bug in the selection code, but it
13201 // can happen with shadow DOM so handle it.
13202 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
13203 return nullptr;
13206 AutoTArray<Maybe<uint32_t>, 32> indexArray;
13207 const nsINode* current = aOrigNode;
13208 while (const nsINode* parent = current->GetParentNode()) {
13209 Maybe<uint32_t> index = parent->ComputeIndexOf(current);
13210 NS_ENSURE_TRUE(index.isSome(), nullptr);
13211 indexArray.AppendElement(std::move(index));
13212 current = parent;
13214 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
13215 nsINode* correspondingNode = [&]() -> nsINode* {
13216 if (current->IsDocument()) {
13217 return &aStaticClone;
13219 const auto* shadow = ShadowRoot::FromNode(*current);
13220 if (!shadow) {
13221 return nullptr;
13223 nsINode* correspondingHost =
13224 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
13225 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
13226 return nullptr;
13228 return correspondingHost->AsElement()->GetShadowRoot();
13229 }();
13231 if (NS_WARN_IF(!correspondingNode)) {
13232 return nullptr;
13234 for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
13235 correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
13236 NS_ENSURE_TRUE(correspondingNode, nullptr);
13238 return correspondingNode;
13242 * Caches the selection ranges from the source document onto the static clone in
13243 * case the "Print Selection Only" functionality is invoked.
13245 * Note that we cannot use the selection obtained from GetOriginalDocument()
13246 * since that selection may have mutated after the print was invoked.
13248 * Note also that because nsRange objects point into a specific document's
13249 * nodes, we cannot reuse an array of nsRange objects across multiple static
13250 * clone documents. For that reason we cache a new array of ranges on each
13251 * static clone that we create.
13253 * TODO(emilio): This can be simplified once we don't re-clone from static
13254 * documents.
13256 * @param aSourceDoc the document from which we are caching selection ranges
13257 * @param aStaticClone the document that will hold the cache
13258 * @return true if a selection range was cached
13260 static void CachePrintSelectionRanges(const Document& aSourceDoc,
13261 Document& aStaticClone) {
13262 MOZ_ASSERT(aStaticClone.IsStaticDocument());
13263 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc));
13264 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
13266 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
13268 // When the user opts to "Print Selection Only", the print code prefers any
13269 // selection in the static clone corresponding to the focused frame. If this
13270 // is that static clone, flag it for the printing code:
13271 const bool isFocusedDoc = [&] {
13272 if (sourceDocIsStatic) {
13273 return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc));
13275 nsPIDOMWindowOuter* window = aSourceDoc.GetWindow();
13276 if (!window) {
13277 return false;
13279 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
13280 if (!rootWindow) {
13281 return false;
13283 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
13284 nsFocusManager::GetFocusedDescendant(rootWindow,
13285 nsFocusManager::eIncludeAllDescendants,
13286 getter_AddRefs(focusedWindow));
13287 return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc;
13288 }();
13289 if (isFocusedDoc) {
13290 aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc,
13291 reinterpret_cast<void*>(true));
13294 const Selection* origSelection = nullptr;
13295 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
13297 if (sourceDocIsStatic) {
13298 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
13299 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
13300 } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
13301 origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
13304 if (!origSelection && !origRanges) {
13305 return;
13308 const uint32_t rangeCount =
13309 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
13310 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
13312 for (const uint32_t i : IntegerRange(rangeCount)) {
13313 MOZ_ASSERT_IF(!sourceDocIsStatic,
13314 origSelection->RangeCount() == rangeCount);
13315 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
13316 : origSelection->GetRangeAt(i);
13317 MOZ_ASSERT(range);
13318 nsINode* startContainer = range->GetStartContainer();
13319 nsINode* endContainer = range->GetEndContainer();
13321 if (!startContainer || !endContainer) {
13322 continue;
13325 nsINode* startNode =
13326 GetCorrespondingNodeInDocument(startContainer, aStaticClone);
13327 nsINode* endNode =
13328 GetCorrespondingNodeInDocument(endContainer, aStaticClone);
13330 if (NS_WARN_IF(!startNode || !endNode)) {
13331 continue;
13334 RefPtr<nsRange> clonedRange =
13335 nsRange::Create(startNode, range->StartOffset(), endNode,
13336 range->EndOffset(), IgnoreErrors());
13337 if (clonedRange && !clonedRange->Collapsed()) {
13338 printRanges->AppendElement(std::move(clonedRange));
13342 if (printRanges->IsEmpty()) {
13343 return;
13346 aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
13347 printRanges.release(),
13348 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
13351 already_AddRefed<Document> Document::CreateStaticClone(
13352 nsIDocShell* aCloneContainer, nsIDocumentViewer* aViewer,
13353 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
13354 MOZ_ASSERT(!mCreatingStaticClone);
13355 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
13356 MOZ_DIAGNOSTIC_ASSERT(aViewer);
13358 mCreatingStaticClone = true;
13359 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
13360 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
13362 auto raii = MakeScopeExit([&] {
13363 RemoveProperty(nsGkAtoms::adoptedsheetclones);
13364 mCreatingStaticClone = false;
13367 // Make document use different container during cloning.
13369 // FIXME(emilio): Why is this needed?
13370 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
13371 SetContainer(nsDocShell::Cast(aCloneContainer));
13372 IgnoredErrorResult rv;
13373 nsCOMPtr<nsINode> clonedNode = CloneNode(true, rv);
13374 SetContainer(originalShell);
13375 if (rv.Failed()) {
13376 return nullptr;
13379 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
13380 if (!clonedDoc) {
13381 return nullptr;
13384 size_t sheetsCount = SheetCount();
13385 for (size_t i = 0; i < sheetsCount; ++i) {
13386 RefPtr<StyleSheet> sheet = SheetAt(i);
13387 if (sheet) {
13388 if (sheet->IsApplicable()) {
13389 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13390 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13391 if (clonedSheet) {
13392 clonedDoc->AddStyleSheet(clonedSheet);
13397 clonedDoc->CloneAdoptedSheetsFrom(*this);
13399 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
13400 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
13401 for (StyleSheet* sheet : sheets) {
13402 if (sheet->IsApplicable()) {
13403 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13404 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13405 if (clonedSheet) {
13406 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
13407 clonedSheet);
13413 // Font faces created with the JS API will not be reflected in the
13414 // stylesheets and need to be copied over to the cloned document.
13415 if (const FontFaceSet* set = GetFonts()) {
13416 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
13419 clonedDoc->mReferrerInfo =
13420 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
13421 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
13422 CachePrintSelectionRanges(*this, *clonedDoc);
13424 // We're done with the clone, embed ourselves into the document viewer and
13425 // clone our children. The order here is pretty important, because our
13426 // document our document needs to have an owner global before we can create
13427 // the frame loaders for subdocuments.
13428 aViewer->SetDocument(clonedDoc);
13430 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
13432 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
13433 for (const auto& clone : pendingClones) {
13434 RefPtr<Element> element = do_QueryObject(clone.mElement);
13435 RefPtr<nsFrameLoader> frameLoader =
13436 nsFrameLoader::Create(element, /* aNetworkCreated */ false);
13438 if (NS_WARN_IF(!frameLoader)) {
13439 continue;
13442 clone.mElement->SetFrameLoader(frameLoader);
13444 nsresult rv = frameLoader->FinishStaticClone(
13445 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
13446 Unused << NS_WARN_IF(NS_FAILED(rv));
13449 return clonedDoc.forget();
13452 void Document::UnlinkOriginalDocumentIfStatic() {
13453 if (IsStaticDocument() && mOriginalDocument) {
13454 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
13455 mOriginalDocument->mStaticCloneCount--;
13456 mOriginalDocument = nullptr;
13458 MOZ_ASSERT(!mOriginalDocument);
13461 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
13462 int32_t* aHandle) {
13463 nsresult rv = mFrameRequestManager.Schedule(aCallback, aHandle);
13464 if (NS_FAILED(rv)) {
13465 return rv;
13468 UpdateFrameRequestCallbackSchedulingState();
13469 return NS_OK;
13472 void Document::CancelFrameRequestCallback(int32_t aHandle) {
13473 if (mFrameRequestManager.Cancel(aHandle)) {
13474 UpdateFrameRequestCallbackSchedulingState();
13478 bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
13479 return mFrameRequestManager.IsCanceled(aHandle);
13482 nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
13483 // Get the document's current state object. This is the object backing both
13484 // history.state and popStateEvent.state.
13486 // mStateObjectContainer may be null; this just means that there's no
13487 // current state object.
13489 if (!mCachedStateObjectValid) {
13490 if (mStateObjectContainer) {
13491 AutoJSAPI jsapi;
13492 // Init with null is "OK" in the sense that it will just fail.
13493 if (!jsapi.Init(GetScopeObject())) {
13494 return NS_ERROR_UNEXPECTED;
13496 JS::Rooted<JS::Value> value(jsapi.cx());
13497 nsresult rv =
13498 mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value);
13499 NS_ENSURE_SUCCESS(rv, rv);
13501 mCachedStateObject = value;
13502 if (!value.isNullOrUndefined()) {
13503 mozilla::HoldJSObjects(this);
13505 } else {
13506 mCachedStateObject = JS::NullValue();
13508 mCachedStateObjectValid = true;
13511 aState.set(mCachedStateObject);
13512 return NS_OK;
13515 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
13516 mTiming = aTiming;
13517 if (!mLoadingOrRestoredFromBFCacheTimeStamp.IsNull() && mTiming) {
13518 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(),
13519 mLoadingOrRestoredFromBFCacheTimeStamp);
13522 // If there's already the DocumentTimeline instance, tell it since the
13523 // DocumentTimeline is based on both the navigation start time stamp and the
13524 // refresh driver timestamp.
13525 if (mDocumentTimeline) {
13526 mDocumentTimeline->UpdateLastRefreshDriverTime();
13530 nsContentList* Document::ImageMapList() {
13531 if (!mImageMaps) {
13532 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
13533 nsGkAtoms::map);
13536 return mImageMaps;
13539 #define DEPRECATED_OPERATION(_op) #_op "Warning",
13540 static const char* kDeprecationWarnings[] = {
13541 #include "nsDeprecatedOperationList.h"
13542 nullptr};
13543 #undef DEPRECATED_OPERATION
13545 #define DOCUMENT_WARNING(_op) #_op "Warning",
13546 static const char* kDocumentWarnings[] = {
13547 #include "nsDocumentWarningList.h"
13548 nullptr};
13549 #undef DOCUMENT_WARNING
13551 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
13552 switch (aOperation) {
13553 #define DEPRECATED_OPERATION(_op) \
13554 case DeprecatedOperations::e##_op: \
13555 return eUseCounter_##_op;
13556 #include "nsDeprecatedOperationList.h"
13557 #undef DEPRECATED_OPERATION
13558 default:
13559 MOZ_CRASH();
13563 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
13564 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
13567 void Document::WarnOnceAbout(
13568 DeprecatedOperations aOperation, bool asError /* = false */,
13569 const nsTArray<nsString>& aParams /* = empty array */) const {
13570 MOZ_ASSERT(NS_IsMainThread());
13571 if (HasWarnedAbout(aOperation)) {
13572 return;
13574 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
13575 // Don't count deprecated operations for about pages since those pages
13576 // are almost in our control, and we always need to remove uses there
13577 // before we remove the operation itself anyway.
13578 if (!IsAboutPage()) {
13579 const_cast<Document*>(this)->SetUseCounter(
13580 OperationToUseCounter(aOperation));
13582 uint32_t flags =
13583 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13584 nsContentUtils::ReportToConsole(
13585 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
13586 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
13589 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
13590 return mDocWarningWarnedAbout[aWarning];
13593 void Document::WarnOnceAbout(
13594 DocumentWarnings aWarning, bool asError /* = false */,
13595 const nsTArray<nsString>& aParams /* = empty array */) const {
13596 MOZ_ASSERT(NS_IsMainThread());
13597 if (HasWarnedAbout(aWarning)) {
13598 return;
13600 mDocWarningWarnedAbout[aWarning] = true;
13601 uint32_t flags =
13602 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13603 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
13604 nsContentUtils::eDOM_PROPERTIES,
13605 kDocumentWarnings[aWarning], aParams);
13608 mozilla::dom::ImageTracker* Document::ImageTracker() {
13609 if (!mImageTracker) {
13610 mImageTracker = new mozilla::dom::ImageTracker;
13612 return mImageTracker;
13615 void Document::ScheduleSVGUseElementShadowTreeUpdate(
13616 SVGUseElement& aUseElement) {
13617 MOZ_ASSERT(aUseElement.IsInComposedDoc());
13619 if (MOZ_UNLIKELY(mIsStaticDocument)) {
13620 // Printing doesn't deal well with dynamic DOM mutations.
13621 return;
13624 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
13626 if (PresShell* presShell = GetPresShell()) {
13627 presShell->EnsureStyleFlush();
13631 void Document::DoUpdateSVGUseElementShadowTrees() {
13632 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13634 MOZ_ASSERT(!mCloningForSVGUse);
13635 mCloningForSVGUse = true;
13637 do {
13638 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
13639 mSVGUseElementsNeedingShadowTreeUpdate);
13640 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
13642 for (const auto& useElement : useElementsToUpdate) {
13643 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
13644 // The element was in another <use> shadow tree which we processed
13645 // already and also needed an update, and is removed from the document
13646 // now, so nothing to do here.
13647 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
13648 continue;
13650 useElement->UpdateShadowTree();
13652 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13654 mCloningForSVGUse = false;
13657 void Document::NotifyMediaFeatureValuesChanged() {
13658 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
13659 imageElement->MediaFeatureValuesChanged();
13663 already_AddRefed<Touch> Document::CreateTouch(
13664 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
13665 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
13666 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
13667 float aRotationAngle, float aForce) {
13668 RefPtr<Touch> touch =
13669 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
13670 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
13671 return touch.forget();
13674 already_AddRefed<TouchList> Document::CreateTouchList() {
13675 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13676 return retval.forget();
13679 already_AddRefed<TouchList> Document::CreateTouchList(
13680 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
13681 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13682 retval->Append(&aTouch);
13683 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13684 retval->Append(aTouches[i].get());
13686 return retval.forget();
13689 already_AddRefed<TouchList> Document::CreateTouchList(
13690 const Sequence<OwningNonNull<Touch>>& aTouches) {
13691 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13692 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13693 retval->Append(aTouches[i].get());
13695 return retval.forget();
13698 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
13699 float aX, float aY) {
13700 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
13702 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
13703 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
13704 nsPoint pt(x, y);
13706 FlushPendingNotifications(FlushType::Layout);
13708 PresShell* presShell = GetPresShell();
13709 if (!presShell) {
13710 return nullptr;
13713 nsIFrame* rootFrame = presShell->GetRootFrame();
13715 // XUL docs, unlike HTML, have no frame tree until everything's done loading
13716 if (!rootFrame) {
13717 return nullptr;
13720 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
13721 RelativeTo{rootFrame}, pt,
13722 {{FrameForPointOption::IgnorePaintSuppression,
13723 FrameForPointOption::IgnoreCrossDoc}});
13724 if (!ptFrame) {
13725 return nullptr;
13728 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
13729 nsPoint adjustedPoint = pt;
13730 if (nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
13731 adjustedPoint) !=
13732 nsLayoutUtils::TRANSFORM_SUCCEEDED) {
13733 return nullptr;
13736 nsIFrame::ContentOffsets offsets =
13737 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
13739 nsCOMPtr<nsIContent> node = offsets.content;
13740 uint32_t offset = offsets.offset;
13741 nsCOMPtr<nsIContent> anonNode = node;
13742 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
13743 if (nodeIsAnonymous) {
13744 node = ptFrame->GetContent();
13745 nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
13746 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
13747 nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
13748 if (textFrame) {
13749 // If the anonymous content node has a child, then we need to make sure
13750 // that we get the appropriate child, as otherwise the offset may not be
13751 // correct when we construct a range for it.
13752 nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
13753 if (firstChild) {
13754 anonNode = firstChild;
13757 if (textArea) {
13758 offset =
13759 nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
13762 node = nonanon;
13763 } else {
13764 node = nullptr;
13765 offset = 0;
13769 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
13770 if (nodeIsAnonymous) {
13771 aCaretPos->SetAnonymousContentNode(anonNode);
13773 return aCaretPos.forget();
13776 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
13777 // We rely on correct frame information here, so need to flush frames.
13778 FlushPendingNotifications(FlushType::Frames);
13780 // An element that is the HTML body element is potentially scrollable if all
13781 // of the following conditions are true:
13783 // The element has an associated CSS layout box.
13784 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
13785 if (!bodyFrame) {
13786 return false;
13789 // The element's parent element's computed value of the overflow-x and
13790 // overflow-y properties are visible.
13791 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
13792 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
13793 if (parentFrame &&
13794 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
13795 return false;
13798 // The element's computed value of the overflow-x or overflow-y properties is
13799 // not visible.
13800 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
13803 Element* Document::GetScrollingElement() {
13804 // Keep this in sync with IsScrollingElement.
13805 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
13806 RefPtr<HTMLBodyElement> body = GetBodyElement();
13807 if (body && !IsPotentiallyScrollable(body)) {
13808 return body;
13811 return nullptr;
13814 return GetRootElement();
13817 bool Document::IsScrollingElement(Element* aElement) {
13818 // Keep this in sync with GetScrollingElement.
13819 MOZ_ASSERT(aElement);
13821 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
13822 return aElement == GetRootElement();
13825 // In the common case when aElement != body, avoid refcounting.
13826 HTMLBodyElement* body = GetBodyElement();
13827 if (aElement != body) {
13828 return false;
13831 // Now we know body is non-null, since aElement is not null. It's the
13832 // scrolling element for the document if it itself is not potentially
13833 // scrollable.
13834 RefPtr<HTMLBodyElement> strongBody(body);
13835 return !IsPotentiallyScrollable(strongBody);
13838 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
13839 public:
13840 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
13841 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
13843 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
13844 const BlockParsingOptions& aOptions)
13845 : mPromise(aPromise) {
13846 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
13847 if (parser &&
13848 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
13849 parser->BlockParser();
13850 mParser = do_GetWeakReference(parser);
13851 mDocument = aDocument;
13852 mDocument->BlockOnload();
13853 mDocument->BlockDOMContentLoaded();
13857 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13858 ErrorResult& aRv) override {
13859 MaybeUnblockParser();
13861 mPromise->MaybeResolve(aValue);
13864 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13865 ErrorResult& aRv) override {
13866 MaybeUnblockParser();
13868 mPromise->MaybeReject(aValue);
13871 protected:
13872 virtual ~UnblockParsingPromiseHandler() {
13873 // If we're being cleaned up by the cycle collector, our mDocument reference
13874 // may have been unlinked while our mParser weak reference is still alive.
13875 if (mDocument) {
13876 MaybeUnblockParser();
13880 private:
13881 void MaybeUnblockParser() {
13882 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
13883 if (parser) {
13884 MOZ_DIAGNOSTIC_ASSERT(mDocument);
13885 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
13886 if (parser == docParser) {
13887 parser->UnblockParser();
13888 parser->ContinueInterruptedParsingAsync();
13891 if (mDocument) {
13892 // We blocked DOMContentLoaded and load events on this document. Unblock
13893 // them. Note that we want to do that no matter what's going on with the
13894 // parser state for this document. Maybe someone caused it to stop being
13895 // parsed, so CreatorParserOrNull() is returning null, but we still want
13896 // to unblock these.
13897 mDocument->UnblockDOMContentLoaded();
13898 mDocument->UnblockOnload(false);
13900 mParser = nullptr;
13901 mDocument = nullptr;
13904 nsWeakPtr mParser;
13905 RefPtr<Promise> mPromise;
13906 RefPtr<Document> mDocument;
13909 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
13911 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
13912 NS_INTERFACE_MAP_ENTRY(nsISupports)
13913 NS_INTERFACE_MAP_END
13915 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
13916 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
13918 already_AddRefed<Promise> Document::BlockParsing(
13919 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
13920 RefPtr<Promise> resultPromise =
13921 Promise::Create(aPromise.GetParentObject(), aRv);
13922 if (aRv.Failed()) {
13923 return nullptr;
13926 RefPtr<PromiseNativeHandler> promiseHandler =
13927 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
13928 aPromise.AppendNativeHandler(promiseHandler);
13930 return resultPromise.forget();
13933 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
13934 if (mFailedChannel) {
13935 nsCOMPtr<nsIURI> failedURI;
13936 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
13937 return failedURI.forget();
13941 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
13942 if (!uri) {
13943 return nullptr;
13946 return uri.forget();
13949 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
13950 if (mIsGoingAway) {
13951 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13952 return nullptr;
13955 if (!mReadyForIdle) {
13956 nsIGlobalObject* global = GetScopeObject();
13957 if (!global) {
13958 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13959 return nullptr;
13962 mReadyForIdle = Promise::Create(global, aRv);
13963 if (aRv.Failed()) {
13964 return nullptr;
13968 return mReadyForIdle;
13971 void Document::MaybeResolveReadyForIdle() {
13972 IgnoredErrorResult rv;
13973 Promise* readyPromise = GetDocumentReadyForIdle(rv);
13974 if (readyPromise) {
13975 readyPromise->MaybeResolveWithUndefined();
13979 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
13980 // The policy is created when the document is initialized. We _must_ have a
13981 // policy here even if the featurePolicy pref is off. If this assertion fails,
13982 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
13983 MOZ_ASSERT(mFeaturePolicy);
13984 return mFeaturePolicy;
13987 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
13988 // Only chrome documents are allowed to use command dispatcher.
13989 if (!nsContentUtils::IsChromeDoc(this)) {
13990 return nullptr;
13992 if (!mCommandDispatcher) {
13993 // Create our command dispatcher and hook it up.
13994 mCommandDispatcher = new nsXULCommandDispatcher(this);
13996 return mCommandDispatcher;
13999 void Document::InitializeXULBroadcastManager() {
14000 if (mXULBroadcastManager) {
14001 return;
14003 mXULBroadcastManager = new XULBroadcastManager(this);
14006 namespace {
14008 class DevToolsMutationObserver final : public nsStubMutationObserver {
14009 NS_DECL_ISUPPORTS
14010 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
14011 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
14012 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
14014 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
14015 // relies on the event firing _before_ the removal happens.
14016 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
14018 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
14019 // data changes right now (maybe intentionally?).
14020 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
14022 DevToolsMutationObserver() = default;
14024 private:
14025 void FireEvent(nsINode* aTarget, const nsAString& aType);
14027 ~DevToolsMutationObserver() = default;
14030 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
14032 void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
14033 const nsAString& aType) {
14034 AsyncEventDispatcher::RunDOMEventWhenSafe(*aTarget, aType, CanBubble::eNo,
14035 ChromeOnlyDispatch::eYes,
14036 Composed::eYes);
14039 void DevToolsMutationObserver::AttributeChanged(Element* aElement,
14040 int32_t aNamespaceID,
14041 nsAtom* aAttribute,
14042 int32_t aModType,
14043 const nsAttrValue* aOldValue) {
14044 FireEvent(aElement, u"devtoolsattrmodified"_ns);
14047 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
14048 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
14049 ContentInserted(c);
14053 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
14054 FireEvent(aChild, u"devtoolschildinserted"_ns);
14057 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
14059 } // namespace
14061 void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
14062 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
14063 return;
14065 mDevToolsWatchingDOMMutations = aValue;
14066 if (aValue) {
14067 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
14068 sDevToolsMutationObserver = new DevToolsMutationObserver();
14069 ClearOnShutdown(&sDevToolsMutationObserver);
14071 AddMutationObserver(sDevToolsMutationObserver);
14072 } else if (sDevToolsMutationObserver) {
14073 RemoveMutationObserver(sDevToolsMutationObserver);
14077 void EvaluateMediaQueryLists(nsTArray<RefPtr<MediaQueryList>>& aListsToNotify,
14078 Document& aDocument, bool aRecurse) {
14079 if (nsPresContext* pc = aDocument.GetPresContext()) {
14080 pc->FlushPendingMediaFeatureValuesChanged();
14083 for (MediaQueryList* mql : aDocument.MediaQueryLists()) {
14084 if (mql->EvaluateOnRenderingUpdate()) {
14085 aListsToNotify.AppendElement(mql);
14088 if (!aRecurse) {
14089 return;
14091 auto recurse = [&](Document& aSubDoc) {
14092 EvaluateMediaQueryLists(aListsToNotify, aSubDoc, true);
14093 return CallState::Continue;
14095 aDocument.EnumerateSubDocuments(recurse);
14098 void Document::EvaluateMediaQueriesAndReportChanges(bool aRecurse) {
14099 AutoTArray<RefPtr<MediaQueryList>, 32> mqls;
14100 EvaluateMediaQueryLists(mqls, *this, aRecurse);
14101 for (auto& mql : mqls) {
14102 mql->FireChangeEvent();
14106 void Document::MaybeWarnAboutZoom() {
14107 if (mHasWarnedAboutZoom) {
14108 return;
14110 const bool usedZoom = Servo_IsPropertyIdRecordedInUseCounter(
14111 mStyleUseCounters.get(), eCSSProperty_zoom);
14112 if (!usedZoom) {
14113 return;
14116 mHasWarnedAboutZoom = true;
14117 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
14118 this, nsContentUtils::eLAYOUT_PROPERTIES,
14119 "ZoomPropertyWarning");
14122 nsIHTMLCollection* Document::Children() {
14123 if (!mChildrenCollection) {
14124 mChildrenCollection =
14125 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
14126 nsGkAtoms::_asterisk, false);
14129 return mChildrenCollection;
14132 uint32_t Document::ChildElementCount() { return Children()->Length(); }
14134 // Singleton class to manage the list of fullscreen documents which are the
14135 // root of a branch which contains fullscreen documents. We maintain this list
14136 // so that we can easily exit all windows from fullscreen when the user
14137 // presses the escape key.
14138 class FullscreenRoots {
14139 public:
14140 // Adds the root of given document to the manager. Calling this method
14141 // with a document whose root is already contained has no effect.
14142 static void Add(Document* aDoc);
14144 // Iterates over every root in the root list, and calls aFunction, passing
14145 // each root once to aFunction. It is safe to call Add() and Remove() while
14146 // iterating over the list (i.e. in aFunction). Documents that are removed
14147 // from the manager during traversal are not traversed, and documents that
14148 // are added to the manager during traversal are also not traversed.
14149 static void ForEach(void (*aFunction)(Document* aDoc));
14151 // Removes the root of a specific document from the manager.
14152 static void Remove(Document* aDoc);
14154 // Returns true if all roots added to the list have been removed.
14155 static bool IsEmpty();
14157 private:
14158 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
14159 MOZ_COUNTED_DTOR(FullscreenRoots)
14161 enum : uint32_t { NotFound = uint32_t(-1) };
14162 // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
14163 static uint32_t Find(Document* aRoot);
14165 // Returns true if aRoot is in the list of fullscreen roots.
14166 static bool Contains(Document* aRoot);
14168 // Singleton instance of the FullscreenRoots. This is instantiated when a
14169 // root is added, and it is deleted when the last root is removed.
14170 static FullscreenRoots* sInstance;
14172 // List of weak pointers to roots.
14173 nsTArray<nsWeakPtr> mRoots;
14176 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
14178 /* static */
14179 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
14180 if (!sInstance) {
14181 return;
14183 // Create a copy of the roots array, and iterate over the copy. This is so
14184 // that if an element is removed from mRoots we don't mess up our iteration.
14185 nsTArray<nsWeakPtr> roots(sInstance->mRoots.Clone());
14186 // Call aFunction on all entries.
14187 for (uint32_t i = 0; i < roots.Length(); i++) {
14188 nsCOMPtr<Document> root = do_QueryReferent(roots[i]);
14189 // Check that the root isn't in the manager. This is so that new additions
14190 // while we were running don't get traversed.
14191 if (root && FullscreenRoots::Contains(root)) {
14192 aFunction(root);
14197 /* static */
14198 bool FullscreenRoots::Contains(Document* aRoot) {
14199 return FullscreenRoots::Find(aRoot) != NotFound;
14202 /* static */
14203 void FullscreenRoots::Add(Document* aDoc) {
14204 nsCOMPtr<Document> root =
14205 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14206 if (!FullscreenRoots::Contains(root)) {
14207 if (!sInstance) {
14208 sInstance = new FullscreenRoots();
14210 sInstance->mRoots.AppendElement(do_GetWeakReference(root));
14214 /* static */
14215 uint32_t FullscreenRoots::Find(Document* aRoot) {
14216 if (!sInstance) {
14217 return NotFound;
14219 nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
14220 for (uint32_t i = 0; i < roots.Length(); i++) {
14221 nsCOMPtr<Document> otherRoot(do_QueryReferent(roots[i]));
14222 if (otherRoot == aRoot) {
14223 return i;
14226 return NotFound;
14229 /* static */
14230 void FullscreenRoots::Remove(Document* aDoc) {
14231 nsCOMPtr<Document> root =
14232 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14233 uint32_t index = Find(root);
14234 NS_ASSERTION(index != NotFound,
14235 "Should only try to remove roots which are still added!");
14236 if (index == NotFound || !sInstance) {
14237 return;
14239 sInstance->mRoots.RemoveElementAt(index);
14240 if (sInstance->mRoots.IsEmpty()) {
14241 delete sInstance;
14242 sInstance = nullptr;
14246 /* static */
14247 bool FullscreenRoots::IsEmpty() { return !sInstance; }
14249 // Any fullscreen change waiting for the widget to finish transition
14250 // is queued here. This is declared static instead of a member of
14251 // Document because in the majority of time, there would be at most
14252 // one document requesting or exiting fullscreen. We shouldn't waste
14253 // the space to hold for it in every document.
14254 class PendingFullscreenChangeList {
14255 public:
14256 PendingFullscreenChangeList() = delete;
14258 template <typename T>
14259 static void Add(UniquePtr<T> aChange) {
14260 sList.insertBack(aChange.release());
14263 static const FullscreenChange* GetLast() { return sList.getLast(); }
14265 enum IteratorOption {
14266 // When we are committing fullscreen changes or preparing for
14267 // that, we generally want to iterate all requests in the same
14268 // window with eDocumentsWithSameRoot option.
14269 eDocumentsWithSameRoot,
14270 // If we are removing a document from the tree, we would only
14271 // want to remove the requests from the given document and its
14272 // descendants. For that case, use eInclusiveDescendants.
14273 eInclusiveDescendants
14276 template <typename T>
14277 class Iterator {
14278 public:
14279 explicit Iterator(Document* aDoc, IteratorOption aOption)
14280 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
14281 if (mCurrent) {
14282 if (aDoc->GetBrowsingContext()) {
14283 mRootBCForIteration = aDoc->GetBrowsingContext();
14284 if (aOption == eDocumentsWithSameRoot) {
14285 RefPtr<BrowsingContext> bc =
14286 GetParentIgnoreChromeBoundary(mRootBCForIteration);
14287 while (bc) {
14288 mRootBCForIteration = bc;
14289 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
14293 SkipToNextMatch();
14297 UniquePtr<T> TakeAndNext() {
14298 auto thisChange = TakeAndNextInternal();
14299 SkipToNextMatch();
14300 return thisChange;
14302 bool AtEnd() const { return mCurrent == nullptr; }
14304 private:
14305 already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
14306 BrowsingContext* aBC) {
14307 // Chrome BrowsingContexts are only available in the parent process, so if
14308 // we're in a content process, we only worry about the context tree.
14309 if (XRE_IsParentProcess()) {
14310 return aBC->Canonical()->GetParentCrossChromeBoundary();
14312 return do_AddRef(aBC->GetParent());
14315 UniquePtr<T> TakeAndNextInternal() {
14316 FullscreenChange* thisChange = mCurrent;
14317 MOZ_ASSERT(thisChange->Type() == T::kType);
14318 mCurrent = mCurrent->removeAndGetNext();
14319 return WrapUnique(static_cast<T*>(thisChange));
14321 void SkipToNextMatch() {
14322 while (mCurrent) {
14323 if (mCurrent->Type() == T::kType) {
14324 RefPtr<BrowsingContext> bc =
14325 mCurrent->Document()->GetBrowsingContext();
14326 if (!bc) {
14327 // Always automatically drop fullscreen changes which are
14328 // from a document detached from the doc shell.
14329 UniquePtr<T> change = TakeAndNextInternal();
14330 change->MayRejectPromise("Document is not active");
14331 continue;
14333 while (bc && bc != mRootBCForIteration) {
14334 bc = GetParentIgnoreChromeBoundary(bc);
14336 if (bc) {
14337 break;
14340 // The current one either don't have matched type, or isn't
14341 // inside the given subtree, so skip this item.
14342 mCurrent = mCurrent->getNext();
14346 FullscreenChange* mCurrent;
14347 RefPtr<BrowsingContext> mRootBCForIteration;
14350 private:
14351 static LinkedList<FullscreenChange> sList;
14354 /* static */
14355 LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
14357 Document* Document::GetFullscreenRoot() {
14358 nsCOMPtr<Document> root = do_QueryReferent(mFullscreenRoot);
14359 return root;
14362 size_t Document::CountFullscreenElements() const {
14363 size_t count = 0;
14364 for (const nsWeakPtr& ptr : mTopLayer) {
14365 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14366 if (elem->State().HasState(ElementState::FULLSCREEN)) {
14367 count++;
14371 return count;
14374 void Document::SetFullscreenRoot(Document* aRoot) {
14375 mFullscreenRoot = do_GetWeakReference(aRoot);
14378 // https://github.com/whatwg/html/issues/9143
14379 // We need to consider the precedence between active modal dialog, topmost auto
14380 // popover and fullscreen element once it's specified.
14381 void Document::HandleEscKey() {
14382 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14383 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14384 if (RefPtr popoverHTMLEl = nsGenericHTMLElement::FromNodeOrNull(element)) {
14385 if (element->IsAutoPopover() && element->IsPopoverOpen()) {
14386 popoverHTMLEl->HidePopover(IgnoreErrors());
14387 break;
14390 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14391 dialog->QueueCancelDialog();
14392 break;
14397 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
14398 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
14399 RefPtr<Promise> promise = exit->GetPromise();
14400 RestorePreviousFullscreenState(std::move(exit));
14401 return promise.forget();
14404 static void AskWindowToExitFullscreen(Document* aDoc) {
14405 if (XRE_GetProcessType() == GeckoProcessType_Content) {
14406 nsContentUtils::DispatchEventOnlyToChrome(
14407 aDoc, aDoc, u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
14408 Cancelable::eNo, /* DefaultAction */ nullptr);
14409 } else {
14410 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
14411 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
14416 class nsCallExitFullscreen : public Runnable {
14417 public:
14418 explicit nsCallExitFullscreen(Document* aDoc)
14419 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
14421 NS_IMETHOD Run() final {
14422 if (!mDoc) {
14423 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
14424 } else {
14425 AskWindowToExitFullscreen(mDoc);
14427 return NS_OK;
14430 private:
14431 nsCOMPtr<Document> mDoc;
14434 /* static */
14435 void Document::AsyncExitFullscreen(Document* aDoc) {
14436 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14437 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
14438 NS_DispatchToCurrentThread(exit.forget());
14441 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
14442 uint32_t count = 0;
14443 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
14444 auto subDoc = [&count](Document& aSubDoc) {
14445 if (aSubDoc.Fullscreen()) {
14446 count++;
14448 return CallState::Continue;
14450 aDoc.EnumerateSubDocuments(subDoc);
14451 return count;
14454 bool Document::IsFullscreenLeaf() {
14455 // A fullscreen leaf document is fullscreen, and has no fullscreen
14456 // subdocuments.
14458 // FIXME(emilio): This doesn't seem to account for fission iframes, is that
14459 // ok?
14460 return Fullscreen() && CountFullscreenSubDocuments(*this) == 0;
14463 static Document* GetFullscreenLeaf(Document& aDoc) {
14464 if (aDoc.IsFullscreenLeaf()) {
14465 return &aDoc;
14467 if (!aDoc.Fullscreen()) {
14468 return nullptr;
14470 Document* leaf = nullptr;
14471 auto recurse = [&leaf](Document& aSubDoc) {
14472 leaf = GetFullscreenLeaf(aSubDoc);
14473 return leaf ? CallState::Stop : CallState::Continue;
14475 aDoc.EnumerateSubDocuments(recurse);
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 mTopLayer.AppendElement(do_GetWeakReference(&aElement));
14755 NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
14757 if (modal) {
14758 aElement.AddStates(ElementState::TOPMOST_MODAL);
14760 bool foundExistingModalElement = false;
14761 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14762 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14763 if (element && element != &aElement &&
14764 element->State().HasState(ElementState::TOPMOST_MODAL)) {
14765 element->RemoveStates(ElementState::TOPMOST_MODAL);
14766 foundExistingModalElement = true;
14767 break;
14771 if (!foundExistingModalElement) {
14772 Element* root = GetRootElement();
14773 MOZ_RELEASE_ASSERT(root, "top layer element outside of document?");
14774 if (&aElement != root) {
14775 // Add inert to the root element so that the inertness is applied to the
14776 // entire document.
14777 root->AddStates(ElementState::INERT);
14783 void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
14784 aDialogElement.AddStates(ElementState::MODAL);
14785 TopLayerPush(aDialogElement);
14788 void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
14789 DebugOnly<Element*> removedElement = TopLayerPop(aDialogElement);
14790 MOZ_ASSERT(removedElement == &aDialogElement);
14791 aDialogElement.RemoveStates(ElementState::MODAL);
14794 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
14795 if (mTopLayer.IsEmpty()) {
14796 return nullptr;
14799 // Remove the topmost element that qualifies aPredicate; This
14800 // is required is because the top layer contains not only
14801 // fullscreen elements, but also dialog elements.
14802 Element* removedElement = nullptr;
14803 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
14804 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
14805 if (element && aPredicate(element)) {
14806 removedElement = element;
14807 mTopLayer.RemoveElementAt(i);
14808 break;
14812 // Pop from the stack null elements (references to elements which have
14813 // been GC'd since they were added to the stack) and elements which are
14814 // no longer in this document.
14816 // FIXME(emilio): If this loop does something, it'd violate the assertions
14817 // from GetTopLayerTop()... What gives?
14818 while (!mTopLayer.IsEmpty()) {
14819 Element* element = GetTopLayerTop();
14820 if (!element || element->GetComposedDoc() != this) {
14821 mTopLayer.RemoveLastElement();
14822 } else {
14823 // The top element of the stack is now an in-doc element. Return here.
14824 break;
14828 if (!removedElement) {
14829 return nullptr;
14832 const bool modal = removedElement->State().HasState(ElementState::MODAL);
14834 if (modal) {
14835 removedElement->RemoveStates(ElementState::TOPMOST_MODAL);
14836 bool foundExistingModalElement = false;
14837 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14838 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14839 if (element && element->State().HasState(ElementState::MODAL)) {
14840 element->AddStates(ElementState::TOPMOST_MODAL);
14841 foundExistingModalElement = true;
14842 break;
14845 // No more modal elements, make the document not inert anymore.
14846 if (!foundExistingModalElement) {
14847 Element* root = GetRootElement();
14848 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
14849 root->RemoveStates(ElementState::INERT);
14854 return removedElement;
14857 Element* Document::TopLayerPop(Element& aElement) {
14858 auto predictFunc = [&aElement](Element* element) {
14859 return element == &aElement;
14861 return TopLayerPop(predictFunc);
14864 void Document::GetWireframe(bool aIncludeNodes,
14865 Nullable<Wireframe>& aWireframe) {
14866 FlushPendingNotifications(FlushType::Layout);
14867 GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
14870 void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
14871 Nullable<Wireframe>& aWireframe) {
14872 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
14873 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
14875 PresShell* shell = GetPresShell();
14876 if (!shell) {
14877 return;
14880 nsPresContext* pc = shell->GetPresContext();
14881 if (!pc) {
14882 return;
14885 nsIFrame* rootFrame = shell->GetRootFrame();
14886 if (!rootFrame) {
14887 return;
14890 auto& wireframe = aWireframe.SetValue();
14891 wireframe.mCanvasBackground = shell->ComputeCanvasBackground().mViewportColor;
14893 FrameForPointOptions options;
14894 options.mBits += FrameForPointOption::IgnoreCrossDoc;
14895 options.mBits += FrameForPointOption::IgnorePaintSuppression;
14896 options.mBits += FrameForPointOption::OnlyVisible;
14898 AutoTArray<nsIFrame*, 32> frames;
14899 const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
14900 nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
14901 options);
14903 // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
14904 // something perhaps, but seems hard / like it'd involve at least some extra
14905 // copying around, since they don't outlive GetFramesForArea.
14906 auto& rects = wireframe.mRects.Construct();
14907 if (!rects.SetCapacity(frames.Length(), fallible)) {
14908 return;
14910 for (nsIFrame* frame : Reversed(frames)) {
14911 auto [rectColor,
14912 rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> {
14913 if (frame->IsTextFrame()) {
14914 return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
14915 WireframeRectType::Text};
14917 if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
14918 return {0, WireframeRectType::Image};
14920 if (frame->IsThemed()) {
14921 return {0, WireframeRectType::Background};
14923 bool drawImage = false;
14924 bool drawColor = false;
14925 if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) {
14926 const nscolor color = nsCSSRendering::DetermineBackgroundColor(
14927 pc, bgStyle, frame, drawImage, drawColor);
14928 if (drawImage &&
14929 !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
14930 return {color, WireframeRectType::Image};
14932 if (drawColor && !frame->IsCanvasFrame()) {
14933 // Canvas frame background already accounted for in mCanvasBackground.
14934 return {color, WireframeRectType::Background};
14937 return {0, WireframeRectType::Unknown};
14938 }();
14940 if (rectType == WireframeRectType::Unknown) {
14941 continue;
14944 const auto r =
14945 CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
14946 frame, frame->GetRectRelativeToSelf(), relativeTo));
14947 if ((uint32_t)r.Area() <
14948 StaticPrefs::browser_history_wireframeAreaThreshold()) {
14949 continue;
14952 // Can't really fail because SetCapacity succeeded.
14953 auto& taggedRect = *rects.AppendElement(fallible);
14955 if (aIncludeNodes) {
14956 if (nsIContent* c = frame->GetContent()) {
14957 taggedRect.mNode.Construct(c);
14960 taggedRect.mX = r.x;
14961 taggedRect.mY = r.y;
14962 taggedRect.mWidth = r.width;
14963 taggedRect.mHeight = r.height;
14964 taggedRect.mColor = rectColor;
14965 taggedRect.mType.Construct(rectType);
14969 Element* Document::GetTopLayerTop() {
14970 if (mTopLayer.IsEmpty()) {
14971 return nullptr;
14973 uint32_t last = mTopLayer.Length() - 1;
14974 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
14975 NS_ASSERTION(element, "Should have a top layer element!");
14976 NS_ASSERTION(element->IsInComposedDoc(),
14977 "Top layer element should be in doc");
14978 NS_ASSERTION(element->OwnerDoc() == this,
14979 "Top layer element should be in this doc");
14980 return element;
14983 Element* Document::GetUnretargetedFullscreenElement() const {
14984 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14985 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14986 // Per spec, the fullscreen element is the topmost element in the document’s
14987 // top layer whose fullscreen flag is set, if any, and null otherwise.
14988 if (element && element->State().HasState(ElementState::FULLSCREEN)) {
14989 return element;
14992 return nullptr;
14995 nsTArray<Element*> Document::GetTopLayer() const {
14996 nsTArray<Element*> elements;
14997 for (const nsWeakPtr& ptr : mTopLayer) {
14998 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14999 elements.AppendElement(elem);
15002 return elements;
15005 bool Document::TopLayerContains(Element& aElement) const {
15006 if (mTopLayer.IsEmpty()) {
15007 return false;
15009 nsWeakPtr weakElement = do_GetWeakReference(&aElement);
15010 return mTopLayer.Contains(weakElement);
15013 void Document::HideAllPopoversUntil(nsINode& aEndpoint,
15014 bool aFocusPreviousElement,
15015 bool aFireEvents) {
15016 auto closeAllOpenPopovers = [&aFocusPreviousElement, &aFireEvents,
15017 this]() MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
15018 while (RefPtr<Element> topmost = GetTopmostAutoPopover()) {
15019 HidePopover(*topmost, aFocusPreviousElement, aFireEvents, IgnoreErrors());
15023 if (&aEndpoint == this) {
15024 closeAllOpenPopovers();
15025 return;
15028 // https://github.com/whatwg/html/pull/9198
15029 auto needRepeatingHide = [&]() {
15030 auto autoList = AutoPopoverList();
15031 return autoList.Contains(&aEndpoint) &&
15032 &aEndpoint != autoList.LastElement();
15035 MOZ_ASSERT((&aEndpoint)->IsElement() &&
15036 (&aEndpoint)->AsElement()->IsAutoPopover());
15037 bool repeatingHide = false;
15038 bool fireEvents = aFireEvents;
15039 do {
15040 RefPtr<const Element> lastToHide = nullptr;
15041 bool foundEndpoint = false;
15042 for (const Element* popover : AutoPopoverList()) {
15043 if (popover == &aEndpoint) {
15044 foundEndpoint = true;
15045 } else if (foundEndpoint) {
15046 lastToHide = popover;
15047 break;
15051 if (!foundEndpoint) {
15052 closeAllOpenPopovers();
15053 return;
15056 while (lastToHide && lastToHide->IsPopoverOpen()) {
15057 RefPtr<Element> topmost = GetTopmostAutoPopover();
15058 if (!topmost) {
15059 break;
15061 HidePopover(*topmost, aFocusPreviousElement, fireEvents, IgnoreErrors());
15064 repeatingHide = needRepeatingHide();
15065 if (repeatingHide) {
15066 fireEvents = false;
15068 } while (repeatingHide);
15071 MOZ_CAN_RUN_SCRIPT_BOUNDARY void
15072 Document::HideAllPopoversWithoutRunningScript() {
15073 return HideAllPopoversUntil(*this, false, false);
15076 void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement,
15077 bool aFireEvents, ErrorResult& aRv) {
15078 RefPtr<nsGenericHTMLElement> popoverHTMLEl =
15079 nsGenericHTMLElement::FromNode(aPopover);
15080 NS_ASSERTION(popoverHTMLEl, "Not a HTML element");
15082 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15083 nullptr, aRv)) {
15084 return;
15087 bool wasShowingOrHiding =
15088 popoverHTMLEl->GetPopoverData()->IsShowingOrHiding();
15089 popoverHTMLEl->GetPopoverData()->SetIsShowingOrHiding(true);
15090 const bool fireEvents = aFireEvents && !wasShowingOrHiding;
15091 auto cleanupHidingFlag = MakeScopeExit([&]() {
15092 if (auto* popoverData = popoverHTMLEl->GetPopoverData()) {
15093 popoverData->SetIsShowingOrHiding(wasShowingOrHiding);
15097 if (popoverHTMLEl->IsAutoPopover()) {
15098 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, fireEvents);
15099 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15100 nullptr, aRv)) {
15101 return;
15103 // TODO: we can't always guarantee:
15104 // The last item in document's auto popover list is popoverHTMLEl.
15105 // See, https://github.com/whatwg/html/issues/9197
15106 // If popoverHTMLEl is not on top, hide popovers again without firing
15107 // events.
15108 if (NS_WARN_IF(GetTopmostAutoPopover() != popoverHTMLEl)) {
15109 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
15110 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15111 nullptr, aRv)) {
15112 return;
15114 MOZ_ASSERT(GetTopmostAutoPopover() == popoverHTMLEl,
15115 "popoverHTMLEl should be on top of auto popover list");
15119 auto* data = popoverHTMLEl->GetPopoverData();
15120 MOZ_ASSERT(data, "Should have popover data");
15121 data->SetInvoker(nullptr);
15123 // Fire beforetoggle event and re-check popover validity.
15124 if (fireEvents) {
15125 // Intentionally ignore the return value here as only on open event for
15126 // beforetoggle the cancelable attribute is initialized to true.
15127 popoverHTMLEl->FireToggleEvent(PopoverVisibilityState::Showing,
15128 PopoverVisibilityState::Hidden,
15129 u"beforetoggle"_ns);
15130 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15131 nullptr, aRv)) {
15132 return;
15136 RemovePopoverFromTopLayer(aPopover);
15138 popoverHTMLEl->PopoverPseudoStateUpdate(false, true);
15139 popoverHTMLEl->GetPopoverData()->SetPopoverVisibilityState(
15140 PopoverVisibilityState::Hidden);
15142 // Queue popover toggle event task.
15143 if (fireEvents) {
15144 popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing);
15147 if (aFocusPreviousElement) {
15148 popoverHTMLEl->FocusPreviousElementAfterHidingPopover();
15149 } else {
15150 popoverHTMLEl->ForgetPreviouslyFocusedElementAfterHidingPopover();
15154 nsTArray<Element*> Document::AutoPopoverList() const {
15155 nsTArray<Element*> elements;
15156 for (const nsWeakPtr& ptr : mTopLayer) {
15157 if (nsCOMPtr<Element> element = do_QueryReferent(ptr)) {
15158 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15159 elements.AppendElement(element);
15163 return elements;
15166 Element* Document::GetTopmostAutoPopover() const {
15167 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15168 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15169 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15170 return element;
15173 return nullptr;
15176 void Document::AddToAutoPopoverList(Element& aElement) {
15177 MOZ_ASSERT(aElement.IsAutoPopover());
15178 TopLayerPush(aElement);
15181 void Document::RemoveFromAutoPopoverList(Element& aElement) {
15182 MOZ_ASSERT(aElement.IsAutoPopover());
15183 TopLayerPop(aElement);
15186 void Document::AddPopoverToTopLayer(Element& aElement) {
15187 MOZ_ASSERT(aElement.GetPopoverData());
15188 TopLayerPush(aElement);
15191 void Document::RemovePopoverFromTopLayer(Element& aElement) {
15192 MOZ_ASSERT(aElement.GetPopoverData());
15193 TopLayerPop(aElement);
15196 // Returns true if aDoc browsing context is focused.
15197 bool IsInFocusedTab(Document* aDoc) {
15198 BrowsingContext* bc = aDoc->GetBrowsingContext();
15199 if (!bc) {
15200 return false;
15203 nsFocusManager* fm = nsFocusManager::GetFocusManager();
15204 if (!fm) {
15205 return false;
15208 if (XRE_IsParentProcess()) {
15209 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
15210 // by retaining the old code path for the parent process.
15211 nsIDocShell* docshell = aDoc->GetDocShell();
15212 if (!docshell) {
15213 return false;
15215 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15216 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15217 if (!rootItem) {
15218 return false;
15220 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
15221 if (!rootWin) {
15222 return false;
15225 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
15226 activeWindow = fm->GetActiveWindow();
15227 if (!activeWindow) {
15228 return false;
15231 return activeWindow == rootWin;
15234 return fm->GetActiveBrowsingContext() == bc->Top();
15237 // Returns true if aDoc browsing context is focused and is also active.
15238 bool IsInActiveTab(Document* aDoc) {
15239 if (!IsInFocusedTab(aDoc)) {
15240 return false;
15243 BrowsingContext* bc = aDoc->GetBrowsingContext();
15244 MOZ_ASSERT(bc, "With no BrowsingContext, we should have failed earlier.");
15245 return bc->IsActive();
15248 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
15249 // Ensure the frame element is the fullscreen element in this document.
15250 // If the frame element is already the fullscreen element in this document,
15251 // this has no effect.
15252 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
15253 RequestFullscreen(std::move(request), XRE_IsContentProcess());
15256 void Document::RemoteFrameFullscreenReverted() {
15257 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
15258 RestorePreviousFullscreenState(std::move(exit));
15261 static bool HasFullscreenSubDocument(Document& aDoc) {
15262 uint32_t count = CountFullscreenSubDocuments(aDoc);
15263 NS_ASSERTION(count <= 1,
15264 "Fullscreen docs should have at most 1 fullscreen child!");
15265 return count >= 1;
15268 // Returns nullptr if a request for Fullscreen API is currently enabled
15269 // in the given document. Returns a static string indicates the reason
15270 // why it is not enabled otherwise.
15271 const char* Document::GetFullscreenError(CallerType aCallerType) {
15272 if (!StaticPrefs::full_screen_api_enabled()) {
15273 return "FullscreenDeniedDisabled";
15276 if (aCallerType == CallerType::System) {
15277 // Chrome code can always use the fullscreen API, provided it's not
15278 // explicitly disabled.
15279 return nullptr;
15282 if (!IsVisible()) {
15283 return "FullscreenDeniedHidden";
15286 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
15287 return "FullscreenDeniedFeaturePolicy";
15290 // Ensure that all containing elements are <iframe> and have allowfullscreen
15291 // attribute set.
15292 BrowsingContext* bc = GetBrowsingContext();
15293 if (!bc || !bc->FullscreenAllowed()) {
15294 return "FullscreenDeniedContainerNotAllowed";
15297 return nullptr;
15300 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
15301 Element* elem = aRequest.Element();
15302 // Strictly speaking, this isn't part of the fullscreen element ready
15303 // check in the spec, but per steps in the spec, when an element which
15304 // is already the fullscreen element requests fullscreen, nothing
15305 // should change and no event should be dispatched, but we still need
15306 // to resolve the returned promise.
15307 Element* fullscreenElement = GetUnretargetedFullscreenElement();
15308 if (elem == fullscreenElement) {
15309 aRequest.MayResolvePromise();
15310 return false;
15312 if (!elem->IsInComposedDoc()) {
15313 aRequest.Reject("FullscreenDeniedNotInDocument");
15314 return false;
15316 if (elem->IsPopoverOpen()) {
15317 aRequest.Reject("FullscreenDeniedPopoverOpen");
15318 return false;
15320 if (elem->OwnerDoc() != this) {
15321 aRequest.Reject("FullscreenDeniedMovedDocument");
15322 return false;
15324 if (!GetWindow()) {
15325 aRequest.Reject("FullscreenDeniedLostWindow");
15326 return false;
15328 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
15329 aRequest.Reject(msg);
15330 return false;
15332 if (HasFullscreenSubDocument(*this)) {
15333 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
15334 return false;
15336 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
15337 aRequest.Reject("FullscreenDeniedHTMLDialog");
15338 return false;
15340 if (!nsContentUtils::IsChromeDoc(this) && !IsInFocusedTab(this)) {
15341 aRequest.Reject("FullscreenDeniedNotFocusedTab");
15342 return false;
15344 return true;
15347 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
15348 MOZ_ASSERT(XRE_IsParentProcess());
15349 nsIDocShell* docShell = aDoc->GetDocShell();
15350 if (!docShell) {
15351 return nullptr;
15353 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15354 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15355 return rootItem ? rootItem->GetWindow() : nullptr;
15358 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
15359 nsPIDOMWindowOuter* aRootWin) {
15360 MOZ_ASSERT(XRE_IsParentProcess());
15361 // If we are in the chrome process, and the window has not been in
15362 // fullscreen, we certainly need to make that fullscreen first.
15363 if (!aRootWin->GetFullScreen()) {
15364 return false;
15366 // The iterator not being at end indicates there is still some
15367 // pending fullscreen request relates to this document. We have to
15368 // push the request to the pending queue so requests are handled
15369 // in the correct order.
15370 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15371 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15372 if (!iter.AtEnd()) {
15373 return false;
15376 // Same thing for exits. If we have any pending, we have to push
15377 // to the pending queue.
15378 PendingFullscreenChangeList::Iterator<FullscreenExit> iterExit(
15379 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15380 if (!iterExit.AtEnd()) {
15381 return false;
15384 // We have to apply the fullscreen state directly in this case,
15385 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
15386 // if it is already in fullscreen. If we do not apply the state but
15387 // instead add it to the queue and wait for the window as normal,
15388 // we would get stuck.
15389 return true;
15392 static bool CheckFullscreenAllowedElementType(const Element* elem) {
15393 // Per spec only HTML, <svg>, and <math> should be allowed, but
15394 // we also need to allow XUL elements right now.
15395 return elem->IsHTMLElement() || elem->IsXULElement() ||
15396 elem->IsSVGElement(nsGkAtoms::svg) ||
15397 elem->IsMathMLElement(nsGkAtoms::math);
15400 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
15401 bool aApplyFullscreenDirectly) {
15402 if (XRE_IsContentProcess()) {
15403 RequestFullscreenInContentProcess(std::move(aRequest),
15404 aApplyFullscreenDirectly);
15405 } else {
15406 RequestFullscreenInParentProcess(std::move(aRequest),
15407 aApplyFullscreenDirectly);
15411 void Document::RequestFullscreenInContentProcess(
15412 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15413 MOZ_ASSERT(XRE_IsContentProcess());
15415 // If we are in the content process, we can apply the fullscreen
15416 // state directly only if we have been in DOM fullscreen, because
15417 // otherwise we always need to notify the chrome.
15418 if (aApplyFullscreenDirectly ||
15419 nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) {
15420 ApplyFullscreen(std::move(aRequest));
15421 return;
15424 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15425 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15426 return;
15429 // We don't need to check element ready before this point, because
15430 // if we called ApplyFullscreen, it would check that for us.
15431 if (!FullscreenElementReadyCheck(*aRequest)) {
15432 return;
15435 PendingFullscreenChangeList::Add(std::move(aRequest));
15436 // If we are not the top level process, dispatch an event to make
15437 // our parent process go fullscreen first.
15438 Dispatch(NS_NewRunnableFunction(
15439 "Document::RequestFullscreenInContentProcess", [self = RefPtr{this}] {
15440 if (!self->HasPendingFullscreenRequests()) {
15441 return;
15443 nsContentUtils::DispatchEventOnlyToChrome(
15444 self, self, u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
15445 Cancelable::eNo, /* DefaultAction */ nullptr);
15446 }));
15449 void Document::RequestFullscreenInParentProcess(
15450 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15451 MOZ_ASSERT(XRE_IsParentProcess());
15452 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
15453 if (!rootWin) {
15454 aRequest->MayRejectPromise("No active window");
15455 return;
15458 if (aApplyFullscreenDirectly ||
15459 ShouldApplyFullscreenDirectly(this, rootWin)) {
15460 ApplyFullscreen(std::move(aRequest));
15461 return;
15464 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15465 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15466 return;
15469 // See if we're waiting on an exit. If so, just make this one pending.
15470 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
15471 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15472 if (!iter.AtEnd()) {
15473 PendingFullscreenChangeList::Add(std::move(aRequest));
15474 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15475 return;
15478 // We don't need to check element ready before this point, because
15479 // if we called ApplyFullscreen, it would check that for us.
15480 if (!FullscreenElementReadyCheck(*aRequest)) {
15481 return;
15484 PendingFullscreenChangeList::Add(std::move(aRequest));
15485 // Make the window fullscreen.
15486 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15489 /* static */
15490 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
15491 bool handled = false;
15492 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15493 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15494 while (!iter.AtEnd()) {
15495 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15496 Document* doc = request->Document();
15497 if (doc->ApplyFullscreen(std::move(request))) {
15498 handled = true;
15501 return handled;
15504 /* static */
15505 void Document::ClearPendingFullscreenRequests(Document* aDoc) {
15506 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15507 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
15508 while (!iter.AtEnd()) {
15509 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15510 request->MayRejectPromise("Fullscreen request aborted");
15514 bool Document::HasPendingFullscreenRequests() {
15515 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15516 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15517 return !iter.AtEnd();
15520 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
15521 if (!FullscreenElementReadyCheck(*aRequest)) {
15522 return false;
15525 RefPtr<Document> doc = aRequest->Document();
15526 doc->HideAllPopoversWithoutRunningScript();
15528 // Stash a reference to any existing fullscreen doc, we'll use this later
15529 // to detect if the origin which is fullscreen has changed.
15530 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
15532 // Stores a list of documents which we must dispatch "fullscreenchange"
15533 // too. We're required by the spec to dispatch the events in root-to-leaf
15534 // order, but we traverse the doctree in a leaf-to-root order, so we save
15535 // references to the documents we must dispatch to so that we get the order
15536 // as specified.
15537 AutoTArray<Document*, 8> changed;
15539 // Remember the root document, so that if a fullscreen document is hidden
15540 // we can reset fullscreen state in the remaining visible fullscreen
15541 // documents.
15542 Document* fullScreenRootDoc =
15543 nsContentUtils::GetInProcessSubtreeRootDocument(this);
15545 // If a document is already in fullscreen, then unlock the mouse pointer
15546 // before setting a new document to fullscreen
15547 PointerLockManager::Unlock();
15549 // Set the fullscreen element. This sets the fullscreen style on the
15550 // element, and the fullscreen-ancestor styles on ancestors of the element
15551 // in this document.
15552 Element* elem = aRequest->Element();
15553 SetFullscreenElement(*elem);
15554 // Set the iframe fullscreen flag.
15555 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
15556 iframe->SetFullscreenFlag(true);
15558 changed.AppendElement(this);
15560 // Propagate up the document hierarchy, setting the fullscreen element as
15561 // the element's container in ancestor documents. This also sets the
15562 // appropriate css styles as well. Note we don't propagate down the
15563 // document hierarchy, the fullscreen element (or its container) is not
15564 // visible there. Stop when we reach the root document.
15565 Document* child = this;
15566 while (true) {
15567 child->SetFullscreenRoot(fullScreenRootDoc);
15569 // When entering fullscreen, reset the RCD's resolution to the intrinsic
15570 // resolution, otherwise the fullscreen content could be sized larger than
15571 // the screen (since fullscreen is implemented using position:fixed and
15572 // fixed elements are sized to the layout viewport).
15573 // This also ensures that things like video controls aren't zoomed in
15574 // when in fullscreen mode.
15575 if (PresShell* presShell = child->GetPresShell()) {
15576 if (RefPtr<MobileViewportManager> manager =
15577 presShell->GetMobileViewportManager()) {
15578 // Save the previous resolution so it can be restored.
15579 child->mSavedResolution = presShell->GetResolution();
15580 presShell->SetResolutionAndScaleTo(
15581 manager->ComputeIntrinsicResolution(),
15582 ResolutionChangeOrigin::MainThreadRestore);
15586 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
15587 "Fullscreen root should be set!");
15588 if (child == fullScreenRootDoc) {
15589 break;
15592 Element* element = child->GetEmbedderElement();
15593 if (!element) {
15594 // We've reached the root.No more changes need to be made
15595 // to the top layer stacks of documents further up the tree.
15596 break;
15599 Document* parent = child->GetInProcessParentDocument();
15600 parent->SetFullscreenElement(*element);
15601 changed.AppendElement(parent);
15602 child = parent;
15605 FullscreenRoots::Add(this);
15607 // If it is the first entry of the fullscreen, trigger an event so
15608 // that the UI can response to this change, e.g. hide chrome, or
15609 // notifying parent process to enter fullscreen. Note that chrome
15610 // code may also want to listen to MozDOMFullscreen:NewOrigin event
15611 // to pop up warning UI.
15612 if (!previousFullscreenDoc) {
15613 nsContentUtils::DispatchEventOnlyToChrome(
15614 this, elem, u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
15615 Cancelable::eNo, /* DefaultAction */ nullptr);
15618 // The origin which is fullscreen gets changed. Trigger an event so
15619 // that the chrome knows to pop up a warning UI. Note that
15620 // previousFullscreenDoc == nullptr upon first entry, we show the warning UI
15621 // directly as soon as chrome document goes into fullscreen state. Also note
15622 // that, in a multi-process browser, the code in content process is
15623 // responsible for sending message with the origin to its parent, and the
15624 // parent shouldn't rely on this event itself.
15625 if (aRequest->mShouldNotifyNewOrigin && previousFullscreenDoc &&
15626 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
15627 DispatchFullscreenNewOriginEvent(this);
15630 // Dispatch "fullscreenchange" events. Note that the loop order is
15631 // reversed so that events are dispatched in the tree order as
15632 // indicated in the spec.
15633 for (Document* d : Reversed(changed)) {
15634 DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement());
15636 aRequest->MayResolvePromise();
15637 return true;
15640 void Document::ClearOrientationPendingPromise() {
15641 mOrientationPendingPromise = nullptr;
15644 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
15645 if (mIsGoingAway) {
15646 return false;
15649 mOrientationPendingPromise = aPromise;
15650 return true;
15653 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
15654 dom::VisibilityState oldState = mVisibilityState;
15655 mVisibilityState = ComputeVisibilityState();
15656 if (oldState != mVisibilityState) {
15657 if (aDispatchEvent == DispatchVisibilityChange::Yes) {
15658 nsContentUtils::DispatchTrustedEvent(this, this, u"visibilitychange"_ns,
15659 CanBubble::eYes, Cancelable::eNo);
15661 NotifyActivityChanged();
15662 if (mVisibilityState == dom::VisibilityState::Visible) {
15663 MaybeActiveMediaComponents();
15666 bool visible = !Hidden();
15667 for (auto* listener : mWorkerListeners) {
15668 listener->OnVisible(visible);
15673 void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
15674 mWorkerListeners.Insert(aListener);
15675 aListener->OnVisible(!Hidden());
15678 void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
15679 mWorkerListeners.Remove(aListener);
15682 VisibilityState Document::ComputeVisibilityState() const {
15683 // We have to check a few pieces of information here:
15684 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
15685 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
15686 // want to use GetWindow here because it does weird groveling for windows
15687 // in some cases.
15688 // 3) Is our outer window background? If so, we're hidden.
15689 // Otherwise, we're visible.
15690 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
15691 mWindow->GetOuterWindow()->IsBackground()) {
15692 return dom::VisibilityState::Hidden;
15695 return dom::VisibilityState::Visible;
15698 void Document::PostVisibilityUpdateEvent() {
15699 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
15700 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
15701 DispatchVisibilityChange::Yes);
15702 Dispatch(event.forget());
15705 void Document::MaybeActiveMediaComponents() {
15706 auto* window = GetWindow();
15707 if (!window || !window->ShouldDelayMediaFromStart()) {
15708 return;
15710 window->ActivateMediaComponents();
15713 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
15714 nsINode::AddSizeOfExcludingThis(aWindowSizes,
15715 &aWindowSizes.mDOMSizes.mDOMOtherSize);
15717 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
15718 AddSizeOfNodeTree(*kid, aWindowSizes);
15721 // IMPORTANT: for our ComputedValues measurements, we want to measure
15722 // ComputedValues accessible from DOM elements before ComputedValues not
15723 // accessible from DOM elements (i.e. accessible only from the frame tree).
15725 // Therefore, the measurement of the Document superclass must happen after
15726 // the measurement of DOM nodes (above), because Document contains the
15727 // PresShell, which contains the frame tree.
15728 if (mPresShell) {
15729 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
15732 if (mStyleSet) {
15733 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
15736 aWindowSizes.mPropertyTablesSize +=
15737 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15739 if (EventListenerManager* elm = GetExistingListenerManager()) {
15740 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15743 if (mNodeInfoManager) {
15744 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
15747 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15748 mDOMMediaQueryLists.sizeOfExcludingThis(
15749 aWindowSizes.mState.mMallocSizeOf);
15751 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
15752 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15753 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15756 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
15758 for (auto& sheetArray : mAdditionalSheets) {
15759 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
15761 // Lumping in the loader with the style-sheets size is not ideal,
15762 // but most of the things in there are in fact stylesheets, so it
15763 // doesn't seem worthwhile to separate it out.
15764 // This can be null if we've already been unlinked.
15765 if (mCSSLoader) {
15766 aWindowSizes.mLayoutStyleSheetsSize +=
15767 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
15770 aWindowSizes.mDOMSizes.mDOMResizeObserverControllerSize +=
15771 mResizeObservers.ShallowSizeOfExcludingThis(
15772 aWindowSizes.mState.mMallocSizeOf);
15774 if (mAttributeStyles) {
15775 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15776 mAttributeStyles->DOMSizeOfIncludingThis(
15777 aWindowSizes.mState.mMallocSizeOf);
15780 if (mRadioGroupContainer) {
15781 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15782 mRadioGroupContainer->SizeOfIncludingThis(
15783 aWindowSizes.mState.mMallocSizeOf);
15786 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15787 mStyledLinks.ShallowSizeOfExcludingThis(
15788 aWindowSizes.mState.mMallocSizeOf);
15790 // Measurement of the following members may be added later if DMD finds it
15791 // is worthwhile:
15792 // - mMidasCommandManager
15793 // - many!
15796 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
15797 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15798 aWindowSizes.mState.mMallocSizeOf(this);
15799 DocAddSizeOfExcludingThis(aWindowSizes);
15802 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
15803 size_t* aNodeSize) const {
15804 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
15805 // nsDocuments can only appear at the top of the DOM tree, and we use the
15806 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
15807 // be called.
15808 MOZ_CRASH();
15811 /* static */
15812 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
15813 size_t nodeSize = 0;
15814 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
15816 // This is where we transfer the nodeSize obtained from
15817 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
15818 switch (aNode.NodeType()) {
15819 case nsINode::ELEMENT_NODE:
15820 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
15821 break;
15822 case nsINode::TEXT_NODE:
15823 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
15824 break;
15825 case nsINode::CDATA_SECTION_NODE:
15826 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
15827 break;
15828 case nsINode::COMMENT_NODE:
15829 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
15830 break;
15831 default:
15832 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
15833 break;
15836 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
15837 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15840 if (aNode.IsContent()) {
15841 nsTArray<nsIContent*> anonKids;
15842 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
15843 nsIContent::eAllChildren);
15844 for (nsIContent* anonKid : anonKids) {
15845 AddSizeOfNodeTree(*anonKid, aWindowSizes);
15848 if (auto* element = Element::FromNode(aNode)) {
15849 if (ShadowRoot* shadow = element->GetShadowRoot()) {
15850 AddSizeOfNodeTree(*shadow, aWindowSizes);
15855 // NOTE(emilio): If you feel smart and want to change this function to use
15856 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
15857 // sane way, and kids of <content> won't point to the parent, so we'd never
15858 // find the root node where we should stop at.
15859 for (nsIContent* kid = aNode.GetFirstChild(); kid;
15860 kid = kid->GetNextSibling()) {
15861 AddSizeOfNodeTree(*kid, aWindowSizes);
15865 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
15866 ErrorResult& rv) {
15867 nsCOMPtr<nsIScriptGlobalObject> global =
15868 do_QueryInterface(aGlobal.GetAsSupports());
15869 if (!global) {
15870 rv.Throw(NS_ERROR_UNEXPECTED);
15871 return nullptr;
15874 nsCOMPtr<nsIScriptObjectPrincipal> prin =
15875 do_QueryInterface(aGlobal.GetAsSupports());
15876 if (!prin) {
15877 rv.Throw(NS_ERROR_UNEXPECTED);
15878 return nullptr;
15881 nsCOMPtr<nsIURI> uri;
15882 NS_NewURI(getter_AddRefs(uri), "about:blank");
15883 if (!uri) {
15884 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
15885 return nullptr;
15888 nsCOMPtr<Document> doc;
15889 nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
15890 nullptr, uri, uri, prin->GetPrincipal(),
15891 true, global, DocumentFlavorPlain);
15892 if (NS_FAILED(res)) {
15893 rv.Throw(res);
15894 return nullptr;
15897 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
15899 return doc.forget();
15902 UniquePtr<XPathExpression> Document::CreateExpression(
15903 const nsAString& aExpression, XPathNSResolver* aResolver, ErrorResult& rv) {
15904 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
15907 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
15908 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
15911 already_AddRefed<XPathResult> Document::Evaluate(
15912 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
15913 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
15914 ErrorResult& rv) {
15915 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
15916 aType, aResult, rv);
15919 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
15920 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
15921 if (!item) {
15922 return nullptr;
15924 nsCOMPtr<nsIDocShellTreeOwner> owner;
15925 item->GetTreeOwner(getter_AddRefs(owner));
15926 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
15927 if (!appWin) {
15928 return nullptr;
15930 nsCOMPtr<nsIDocShell> appWinShell;
15931 appWin->GetDocShell(getter_AddRefs(appWinShell));
15932 if (!SameCOMIdentity(appWinShell, item)) {
15933 return nullptr;
15935 return appWin.forget();
15938 WindowContext* Document::GetTopLevelWindowContext() const {
15939 WindowContext* windowContext = GetWindowContext();
15940 return windowContext ? windowContext->TopWindowContext() : nullptr;
15943 Document* Document::GetTopLevelContentDocumentIfSameProcess() {
15944 Document* parent;
15946 if (!mLoadedAsData) {
15947 parent = this;
15948 } else {
15949 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15950 if (!window) {
15951 return nullptr;
15954 parent = window->GetExtantDoc();
15955 if (!parent) {
15956 return nullptr;
15960 do {
15961 if (parent->IsTopLevelContentDocument()) {
15962 break;
15965 // If we ever have a non-content parent before we hit a toplevel content
15966 // parent, then we're never going to find one. Just bail.
15967 if (!parent->IsContentDocument()) {
15968 return nullptr;
15971 parent = parent->GetInProcessParentDocument();
15972 } while (parent);
15974 return parent;
15977 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
15978 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
15981 void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
15982 MOZ_ASSERT(IsBeingUsedAsImage());
15983 MOZ_ASSERT(aReferencingDocument);
15985 if (!aReferencingDocument->mShouldReportUseCounters) {
15986 // No need to propagate use counters to a document that itself won't report
15987 // use counters.
15988 return;
15991 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15992 ("PropagateImageUseCounters from %s to %s",
15993 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
15994 nsContentUtils::TruncatedURLForDisplay(
15995 aReferencingDocument->mDocumentURI)
15996 .get()));
15998 if (aReferencingDocument->IsBeingUsedAsImage()) {
15999 NS_WARNING(
16000 "Page use counters from nested image documents may not "
16001 "propagate to the top-level document (bug 1657805)");
16004 SetCssUseCounterBits();
16005 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
16006 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
16009 bool Document::HasScriptsBlockedBySandbox() const {
16010 return mSandboxFlags & SANDBOXED_SCRIPTS;
16013 // Some use-counter sanity-checking.
16014 static_assert(size_t(eUseCounter_EndCSSProperties) -
16015 size_t(eUseCounter_FirstCSSProperty) ==
16016 size_t(eCSSProperty_COUNT_with_aliases),
16017 "We should have the right amount of CSS property use counters");
16018 static_assert(size_t(eUseCounter_Count) -
16019 size_t(eUseCounter_FirstCountedUnknownProperty) ==
16020 size_t(CountedUnknownProperty::Count),
16021 "We should have the right amount of counted unknown properties"
16022 " use counters");
16023 static_assert(size_t(eUseCounter_Count) * 2 ==
16024 size_t(Telemetry::HistogramUseCounterCount),
16025 "There should be two histograms (document and page)"
16026 " for each use counter");
16028 #define ASSERT_CSS_COUNTER(id_, method_) \
16029 static_assert(size_t(eUseCounter_property_##method_) - \
16030 size_t(eUseCounter_FirstCSSProperty) == \
16031 size_t(id_), \
16032 "Order for CSS counters and CSS property id should match");
16033 #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
16034 #define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
16035 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
16036 #define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
16037 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
16038 #define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
16039 ASSERT_CSS_COUNTER(eCSSPropertyAlias_##aliasid_, method_)
16040 #include "mozilla/ServoCSSPropList.h"
16041 #undef CSS_PROP_ALIAS
16042 #undef CSS_PROP_SHORTHAND
16043 #undef CSS_PROP_LONGHAND
16044 #undef CSS_PROP_PUBLIC_OR_PRIVATE
16045 #undef ASSERT_CSS_COUNTER
16047 void Document::SetCssUseCounterBits() {
16048 if (StaticPrefs::layout_css_use_counters_enabled()) {
16049 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
16050 auto id = nsCSSPropertyID(i);
16051 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
16052 SetUseCounter(nsCSSProps::UseCounterFor(id));
16057 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
16058 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
16059 auto id = CountedUnknownProperty(i);
16060 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
16061 id)) {
16062 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
16068 void Document::InitUseCounters() {
16069 // We can be called more than once, e.g. when session history navigation shows
16070 // us a second time.
16071 if (mUseCountersInitialized) {
16072 return;
16074 mUseCountersInitialized = true;
16076 static_assert(Telemetry::HistogramUseCounterCount > 0);
16078 if (!ShouldIncludeInTelemetry(/* aAllowExtensionURIs = */ true)) {
16079 return;
16082 // Now we know for sure that we should report use counters from this document.
16083 mShouldReportUseCounters = true;
16085 WindowContext* top = GetWindowContextForPageUseCounters();
16086 if (!top) {
16087 // This is the case for SVG image documents. They are not displayed in a
16088 // window, but we still do want to record document use counters for them.
16090 // Page use counter propagation is handled in PropagateImageUseCounters,
16091 // so there is no need to use the cross-process machinery to send them.
16092 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16093 ("InitUseCounters for a non-displayed document [%s]",
16094 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16095 return;
16098 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16099 if (!wgc) {
16100 return;
16103 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16104 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
16105 " [from %s]",
16106 wgc->InnerWindowId(), top->Id(),
16107 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16109 // Inform the parent process that we will send it page use counters later on.
16110 wgc->SendExpectPageUseCounters(top);
16111 mShouldSendPageUseCounters = true;
16114 // We keep separate counts for individual documents and top-level
16115 // pages to more accurately track how many web pages might break if
16116 // certain features were removed. Consider the case of a single
16117 // HTML document with several SVG images and/or iframes with
16118 // sub-documents of their own. If we maintained a single set of use
16119 // counters and all the sub-documents use a particular feature, then
16120 // telemetry would indicate that we would be breaking N documents if
16121 // that feature were removed. Whereas with a document/top-level
16122 // page split, we can see that N documents would be affected, but
16123 // only a single web page would be affected.
16125 // The difference between the values of these two histograms and the
16126 // related use counters below tell us how many pages did *not* use
16127 // the feature in question. For instance, if we see that a given
16128 // session has destroyed 30 content documents, but a particular use
16129 // counter shows only a count of 5, we can infer that the use
16130 // counter was *not* used in 25 of those 30 documents.
16132 // We do things this way, rather than accumulating a boolean flag
16133 // for each use counter, to avoid sending histograms for features
16134 // that don't get widely used. Doing things in this fashion means
16135 // smaller telemetry payloads and faster processing on the server
16136 // side.
16137 void Document::ReportDocumentUseCounters() {
16138 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
16139 return;
16142 mReportedDocumentUseCounters = true;
16144 // Note that a document is being destroyed. See the comment above for how
16145 // use counter histograms are interpreted relative to this measurement.
16146 // TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED is recorded in
16147 // WindowGlobalParent::FinishAccumulatingPageUseCounters.
16148 Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
16149 glean::use_counter::content_documents_destroyed.Add();
16151 // Ask all of our resource documents to report their own document use
16152 // counters.
16153 EnumerateExternalResources([](Document& aDoc) {
16154 aDoc.ReportDocumentUseCounters();
16155 return CallState::Continue;
16158 // Copy StyleUseCounters into our document use counters.
16159 SetCssUseCounterBits();
16161 Maybe<nsCString> urlForLogging;
16162 const bool dumpCounters = StaticPrefs::dom_use_counters_dump_document();
16163 if (dumpCounters) {
16164 urlForLogging.emplace(
16165 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()));
16168 // Report our per-document use counters.
16169 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
16170 auto uc = static_cast<UseCounter>(c);
16171 if (!mUseCounters[uc]) {
16172 continue;
16175 auto id = static_cast<Telemetry::HistogramID>(
16176 Telemetry::HistogramFirstUseCounter + uc * 2);
16177 if (dumpCounters) {
16178 printf_stderr("USE_COUNTER_DOCUMENT: %s - %s\n",
16179 Telemetry::GetHistogramName(id), urlForLogging->get());
16181 Telemetry::Accumulate(id, 1);
16182 IncrementUseCounter(uc, /* aIsPage = */ false);
16186 void Document::ReportLCP() {
16187 const nsDOMNavigationTiming* timing = GetNavigationTiming();
16189 if (!timing) {
16190 return;
16193 TimeStamp lcpTime = timing->GetLargestContentfulRenderTimeStamp();
16195 if (!lcpTime) {
16196 return;
16199 mozilla::glean::perf::largest_contentful_paint.AccumulateRawDuration(
16200 lcpTime - timing->GetNavigationStartTimeStamp());
16202 if (!GetChannel()) {
16203 return;
16206 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
16207 if (!timedChannel) {
16208 return;
16211 TimeStamp responseStart;
16212 timedChannel->GetResponseStart(&responseStart);
16214 if (!responseStart) {
16215 return;
16218 mozilla::glean::perf::largest_contentful_paint_from_response_start
16219 .AccumulateRawDuration(lcpTime - responseStart);
16221 if (profiler_thread_is_being_profiled_for_markers()) {
16222 MarkerInnerWindowId innerWindowID =
16223 MarkerInnerWindowIdFromDocShell(GetDocShell());
16224 GetNavigationTiming()->MaybeAddLCPProfilerMarker(innerWindowID);
16228 void Document::SendPageUseCounters() {
16229 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
16230 return;
16233 // Ask all of our resource documents to send their own document use
16234 // counters to the parent process to be counted as page use counters.
16235 EnumerateExternalResources([](Document& aDoc) {
16236 aDoc.SendPageUseCounters();
16237 return CallState::Continue;
16240 // Send our use counters to the parent process to accumulate them towards the
16241 // page use counters for the top-level document.
16243 // We take our own document use counters (those in mUseCounters) and any child
16244 // document use counters (those in mChildDocumentUseCounters) that have been
16245 // explicitly propagated up to us, which includes resource documents, static
16246 // clones, and SVG images.
16247 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16248 if (!wgc) {
16249 MOZ_ASSERT_UNREACHABLE(
16250 "SendPageUseCounters should be called while we still have access "
16251 "to our WindowContext");
16252 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16253 (" > too late to send page use counters"));
16254 return;
16257 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16258 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
16259 wgc->WindowContext()->Id(),
16260 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
16262 // Copy StyleUseCounters into our document use counters.
16263 SetCssUseCounterBits();
16265 UseCounters counters = mUseCounters | mChildDocumentUseCounters;
16266 wgc->SendAccumulatePageUseCounters(counters);
16269 bool Document::RecomputeResistFingerprinting() {
16270 mOverriddenFingerprintingSettings.reset();
16271 const bool previous = mShouldResistFingerprinting;
16273 RefPtr<BrowsingContext> opener =
16274 GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
16275 // If we have a parent or opener document, defer to it only when we have a
16276 // null principal (e.g. a sandboxed iframe or a data: uri) or when the
16277 // document's principal matches. This means we will defer about:blank,
16278 // about:srcdoc, blob and same-origin iframes/popups to the parent/opener,
16279 // but not cross-origin ones. Cross-origin iframes/popups may inherit a
16280 // CookieJarSettings.mShouldRFP = false bit however, which will be respected.
16281 auto shouldInheritFrom = [this](Document* aDoc) {
16282 return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
16283 this->NodePrincipal()->GetIsNullPrincipal());
16286 if (shouldInheritFrom(mParentDocument)) {
16287 MOZ_LOG(
16288 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16289 ("Inside RecomputeResistFingerprinting with URI %s and deferring "
16290 "to parent document %s",
16291 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16292 mParentDocument->GetDocumentURI()->GetSpecOrDefault().get()));
16293 mShouldResistFingerprinting = mParentDocument->ShouldResistFingerprinting(
16294 RFPTarget::IsAlwaysEnabledForPrecompute);
16295 mOverriddenFingerprintingSettings =
16296 mParentDocument->mOverriddenFingerprintingSettings;
16297 } else if (opener && shouldInheritFrom(opener->GetDocument())) {
16298 MOZ_LOG(
16299 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16300 ("Inside RecomputeResistFingerprinting with URI %s and deferring to "
16301 "opener document %s",
16302 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16303 opener->GetDocument()->GetDocumentURI()->GetSpecOrDefault().get()));
16304 mShouldResistFingerprinting =
16305 opener->GetDocument()->ShouldResistFingerprinting(
16306 RFPTarget::IsAlwaysEnabledForPrecompute);
16307 mOverriddenFingerprintingSettings =
16308 opener->GetDocument()->mOverriddenFingerprintingSettings;
16309 } else if (nsContentUtils::IsChromeDoc(this)) {
16310 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16311 ("Inside RecomputeResistFingerprinting with a ChromeDoc"));
16313 mShouldResistFingerprinting = false;
16314 mOverriddenFingerprintingSettings.reset();
16315 } else if (mChannel) {
16316 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16317 ("Inside RecomputeResistFingerprinting with URI %s",
16318 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get()
16319 : "null"));
16320 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
16321 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16323 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16324 mOverriddenFingerprintingSettings =
16325 loadInfo->GetOverriddenFingerprintingSettings();
16326 } else {
16327 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16328 ("Inside RecomputeResistFingerprinting fallback case."));
16329 // We still preserve the behavior in the fallback case. But, it means there
16330 // might be some cases we haven't considered yet and we need to investigate
16331 // them.
16332 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
16333 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16334 mOverriddenFingerprintingSettings.reset();
16337 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16338 ("Finished RecomputeResistFingerprinting with result %x",
16339 mShouldResistFingerprinting));
16341 bool changed = previous != mShouldResistFingerprinting;
16342 if (changed) {
16343 if (auto win = nsGlobalWindowInner::Cast(GetInnerWindow())) {
16344 win->RefreshReduceTimerPrecisionCallerType();
16347 return changed;
16350 bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const {
16351 return mShouldResistFingerprinting &&
16352 nsRFPService::IsRFPEnabledFor(aTarget,
16353 mOverriddenFingerprintingSettings);
16356 void Document::RecordCanvasUsage(CanvasUsage& aUsage) {
16357 // Limit the number of recent canvas extraction uses that are tracked.
16358 const size_t kTrackedCanvasLimit = 8;
16359 // Timeout between different canvas extractions.
16360 const uint64_t kTimeoutUsec = 3000 * 1000;
16362 uint64_t now = PR_Now();
16363 if ((mCanvasUsage.Length() > kTrackedCanvasLimit) ||
16364 ((now - mLastCanvasUsage) > kTimeoutUsec)) {
16365 mCanvasUsage.ClearAndRetainStorage();
16368 mCanvasUsage.AppendElement(aUsage);
16369 mLastCanvasUsage = now;
16371 nsCString originNoSuffix;
16372 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
16373 return;
16376 nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsage, GetChannel(),
16377 originNoSuffix);
16380 void Document::RecordFontFingerprinting() {
16381 nsCString originNoSuffix;
16382 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
16383 return;
16386 nsRFPService::MaybeReportFontFingerprinter(GetChannel(), originNoSuffix);
16389 WindowContext* Document::GetWindowContextForPageUseCounters() const {
16390 if (mDisplayDocument) {
16391 // If we are a resource document, then go through it to find the
16392 // top-level document.
16393 return mDisplayDocument->GetWindowContextForPageUseCounters();
16396 if (mOriginalDocument) {
16397 // For static clones (print preview documents), contribute page use counters
16398 // towards the original document.
16399 return mOriginalDocument->GetWindowContextForPageUseCounters();
16402 WindowContext* wc = GetTopLevelWindowContext();
16403 if (!wc || !wc->GetBrowsingContext()->IsContent()) {
16404 return nullptr;
16407 return wc;
16410 void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
16411 if (mIntersectionObservers.IsEmpty()) {
16412 return;
16415 DOMHighResTimeStamp time = 0;
16416 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
16417 if (Performance* perf = win->GetPerformance()) {
16418 time = perf->TimeStampToDOMHighResForRendering(aNowTime);
16422 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16423 mIntersectionObservers);
16424 for (const auto& observer : observers) {
16425 if (observer) {
16426 observer->Update(*this, time);
16431 void Document::ScheduleIntersectionObserverNotification() {
16432 if (mIntersectionObservers.IsEmpty()) {
16433 return;
16435 MOZ_RELEASE_ASSERT(NS_IsMainThread());
16436 nsCOMPtr<nsIRunnable> notification =
16437 NewRunnableMethod("Document::NotifyIntersectionObservers", this,
16438 &Document::NotifyIntersectionObservers);
16439 Dispatch(notification.forget());
16442 void Document::NotifyIntersectionObservers() {
16443 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16444 mIntersectionObservers);
16445 for (const auto& observer : observers) {
16446 if (observer) {
16447 // MOZ_KnownLive because the 'observers' array guarantees to keep it
16448 // alive.
16449 MOZ_KnownLive(observer)->Notify();
16454 DOMIntersectionObserver& Document::EnsureLazyLoadObserver() {
16455 if (!mLazyLoadObserver) {
16456 mLazyLoadObserver = DOMIntersectionObserver::CreateLazyLoadObserver(*this);
16458 return *mLazyLoadObserver;
16461 ResizeObserver& Document::EnsureLastRememberedSizeObserver() {
16462 if (!mLastRememberedSizeObserver) {
16463 mLastRememberedSizeObserver =
16464 ResizeObserver::CreateLastRememberedSizeObserver(*this);
16466 return *mLastRememberedSizeObserver;
16469 void Document::ObserveForLastRememberedSize(Element& aElement) {
16470 if (NS_WARN_IF(!IsActive())) {
16471 return;
16473 // Options are initialized with ResizeObserverBoxOptions::Content_box by
16474 // default, which is what we want.
16475 static ResizeObserverOptions options;
16476 EnsureLastRememberedSizeObserver().Observe(aElement, options);
16479 void Document::UnobserveForLastRememberedSize(Element& aElement) {
16480 if (mLastRememberedSizeObserver) {
16481 mLastRememberedSizeObserver->Unobserve(aElement);
16485 void Document::NotifyLayerManagerRecreated() {
16486 NotifyActivityChanged();
16487 EnumerateSubDocuments([](Document& aSubDoc) {
16488 aSubDoc.NotifyLayerManagerRecreated();
16489 return CallState::Continue;
16493 XPathEvaluator* Document::XPathEvaluator() {
16494 if (!mXPathEvaluator) {
16495 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
16497 return mXPathEvaluator.get();
16500 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
16501 return mCachedEncoder.forget();
16504 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
16505 mCachedEncoder = aEncoder;
16508 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
16510 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
16512 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
16513 mStateObjectContainer = scContainer;
16514 mCachedStateObject = JS::UndefinedValue();
16515 mCachedStateObjectValid = false;
16518 bool Document::ComputeDocumentLWTheme() const {
16519 if (!NodePrincipal()->IsSystemPrincipal()) {
16520 return false;
16523 Element* element = GetRootElement();
16524 return element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
16525 nsGkAtoms::_true, eCaseMatters);
16528 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
16529 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
16530 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
16531 ELEMENT_NODE);
16532 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
16534 nsCOMPtr<Element> element;
16535 DebugOnly<nsresult> rv =
16536 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
16537 mozilla::dom::NOT_FROM_PARSER);
16539 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
16540 return element.forget();
16543 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
16544 BrowsingContext* aContext) {
16545 aContext->PreOrderWalk([&](BrowsingContext* aBC) {
16546 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
16547 if (RefPtr<Document> doc = win->GetExtantDoc()) {
16548 SuppressDocument(doc);
16549 mDocuments.AppendElement(doc);
16555 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
16556 BrowsingContextGroup* aGroup) {
16557 for (const auto& bc : aGroup->Toplevels()) {
16558 SuppressBrowsingContext(bc);
16562 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
16563 SyncOperationBehavior aSyncBehavior)
16564 : mSyncBehavior(aSyncBehavior) {
16565 mMicroTaskLevel = 0;
16566 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16567 mMicroTaskLevel = ccjs->MicroTaskLevel();
16568 ccjs->SetMicroTaskLevel(0);
16570 if (aDoc) {
16571 mBrowsingContext = aDoc->GetBrowsingContext();
16572 if (InputTaskManager::CanSuspendInputEvent()) {
16573 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
16574 SuppressBrowsingContextGroup(bcg);
16576 } else if (mBrowsingContext) {
16577 SuppressBrowsingContext(mBrowsingContext->Top());
16579 if (mBrowsingContext &&
16580 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16581 InputTaskManager::CanSuspendInputEvent()) {
16582 mBrowsingContext->Group()->IncInputEventSuspensionLevel();
16587 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
16588 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16589 win->TimeoutManager().BeginSyncOperation();
16591 aDoc->SetIsInSyncOperation(true);
16594 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
16595 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16596 win->TimeoutManager().EndSyncOperation();
16598 aDoc->SetIsInSyncOperation(false);
16601 nsAutoSyncOperation::~nsAutoSyncOperation() {
16602 UnsuppressDocuments();
16603 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
16604 if (ccjs) {
16605 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
16607 if (mBrowsingContext &&
16608 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16609 InputTaskManager::CanSuspendInputEvent()) {
16610 mBrowsingContext->Group()->DecInputEventSuspensionLevel();
16614 void Document::SetIsInSyncOperation(bool aSync) {
16615 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16616 ccjs->UpdateMicroTaskSuppressionGeneration();
16619 if (aSync) {
16620 ++mInSyncOperationCount;
16621 } else {
16622 --mInSyncOperationCount;
16626 gfxUserFontSet* Document::GetUserFontSet() {
16627 if (!mFontFaceSet) {
16628 return nullptr;
16631 return mFontFaceSet->GetImpl();
16634 void Document::FlushUserFontSet() {
16635 if (!mFontFaceSetDirty) {
16636 return;
16639 mFontFaceSetDirty = false;
16641 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
16642 nsTArray<nsFontFaceRuleContainer> rules;
16643 RefPtr<PresShell> presShell = GetPresShell();
16644 if (presShell) {
16645 MOZ_ASSERT(mStyleSetFilled);
16646 EnsureStyleSet().AppendFontFaceRules(rules);
16649 if (!mFontFaceSet && !rules.IsEmpty()) {
16650 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16653 bool changed = false;
16654 if (mFontFaceSet) {
16655 changed = mFontFaceSet->UpdateRules(rules);
16658 // We need to enqueue a style change reflow (for later) to
16659 // reflect that we're modifying @font-face rules. (However,
16660 // without a reflow, nothing will happen to start any downloads
16661 // that are needed.)
16662 if (changed && presShell) {
16663 if (nsPresContext* presContext = presShell->GetPresContext()) {
16664 presContext->UserFontSetUpdated();
16670 void Document::MarkUserFontSetDirty() {
16671 if (mFontFaceSetDirty) {
16672 return;
16674 mFontFaceSetDirty = true;
16675 if (PresShell* presShell = GetPresShell()) {
16676 presShell->EnsureStyleFlush();
16680 FontFaceSet* Document::Fonts() {
16681 if (!mFontFaceSet) {
16682 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16683 FlushUserFontSet();
16685 return mFontFaceSet;
16688 void Document::ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp) {
16689 MOZ_ASSERT(!aTimeStamp.IsNull());
16691 if (!mLastScrollLinkedEffectDetectionTime.IsNull() &&
16692 mLastScrollLinkedEffectDetectionTime >= aTimeStamp) {
16693 return;
16696 if (mLastScrollLinkedEffectDetectionTime.IsNull()) {
16697 // Report to console just once.
16698 nsContentUtils::ReportToConsole(
16699 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
16700 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
16703 mLastScrollLinkedEffectDetectionTime = aTimeStamp;
16706 bool Document::HasScrollLinkedEffect() const {
16707 if (nsPresContext* pc = GetPresContext()) {
16708 return mLastScrollLinkedEffectDetectionTime ==
16709 pc->RefreshDriver()->MostRecentRefresh();
16712 return false;
16715 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
16716 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16717 // Setting has user interction on a discarded browsing context has
16718 // no effect.
16719 Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
16723 bool Document::GetSHEntryHasUserInteraction() {
16724 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16725 return topWc->GetSHEntryHasUserInteraction();
16727 return false;
16730 void Document::SetUserHasInteracted() {
16731 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
16732 ("Document %p has been interacted by user.", this));
16734 // We maybe need to update the user-interaction permission.
16735 MaybeStoreUserInteractionAsPermission();
16737 // For purposes of reducing irrelevant session history entries on
16738 // the back button, we annotate entries with whether they had user
16739 // interaction. This is gated on its own flag on the WindowContext
16740 // (instead of mUserHasInteracted) to account for the fact that multiple
16741 // top-level SH entries can be associated with the same document.
16742 // Thus, whenever we create a new SH entry for this document,
16743 // this flag is reset.
16744 if (!GetSHEntryHasUserInteraction()) {
16745 nsIDocShell* docShell = GetDocShell();
16746 if (docShell) {
16747 nsCOMPtr<nsISHEntry> currentEntry;
16748 bool oshe;
16749 nsresult rv =
16750 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
16751 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
16752 currentEntry->SetHasUserInteraction(true);
16755 SetSHEntryHasUserInteraction(true);
16758 if (mUserHasInteracted) {
16759 return;
16762 mUserHasInteracted = true;
16764 if (mChannel) {
16765 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16766 loadInfo->SetDocumentHasUserInteracted(true);
16768 // Tell the parent process about user interaction
16769 if (auto* wgc = GetWindowGlobalChild()) {
16770 wgc->SendUpdateDocumentHasUserInteracted(true);
16773 MaybeAllowStorageForOpenerAfterUserInteraction();
16776 BrowsingContext* Document::GetBrowsingContext() const {
16777 return mDocumentContainer ? mDocumentContainer->GetBrowsingContext()
16778 : nullptr;
16781 void Document::NotifyUserGestureActivation() {
16782 // https://html.spec.whatwg.org/multipage/interaction.html#activation-notification
16783 // 1. "Assert: document is fully active."
16784 RefPtr<BrowsingContext> currentBC = GetBrowsingContext();
16785 if (!currentBC) {
16786 return;
16789 RefPtr<WindowContext> currentWC = GetWindowContext();
16790 if (!currentWC) {
16791 return;
16794 // 2. "Let windows be « document's relevant global object"
16795 // Instead of assembling a list, we just call notify for wanted windows as we
16796 // find them
16797 currentWC->NotifyUserGestureActivation();
16799 // 3. "...windows with the active window of each of document's ancestor
16800 // navigables."
16801 for (WindowContext* wc = currentWC; wc; wc = wc->GetParentWindowContext()) {
16802 wc->NotifyUserGestureActivation();
16805 // 4. "windows with the active window of each of document's descendant
16806 // navigables, filtered to include only those navigables whose active
16807 // document's origin is same origin with document's origin"
16808 currentBC->PreOrderWalk([&](BrowsingContext* bc) {
16809 WindowContext* wc = bc->GetCurrentWindowContext();
16810 if (!wc) {
16811 return;
16814 // Check same-origin as current document
16815 WindowGlobalChild* wgc = wc->GetWindowGlobalChild();
16816 if (!wgc || !wgc->IsSameOriginWith(currentWC)) {
16817 return;
16820 wc->NotifyUserGestureActivation();
16824 bool Document::HasBeenUserGestureActivated() {
16825 RefPtr<WindowContext> wc = GetWindowContext();
16826 return wc && wc->HasBeenUserGestureActivated();
16829 DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
16830 if (RefPtr<WindowContext> wc = GetWindowContext()) {
16831 if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) {
16832 if (Performance* perf = innerWindow->GetPerformance()) {
16833 return perf->GetDOMTiming()->TimeStampToDOMHighRes(
16834 wc->GetUserGestureStart());
16839 NS_WARNING(
16840 "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
16841 return 0;
16844 void Document::ClearUserGestureActivation() {
16845 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16846 bc = bc->Top();
16847 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16848 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
16849 windowContext->NotifyResetUserGestureActivation();
16855 bool Document::HasValidTransientUserGestureActivation() const {
16856 RefPtr<WindowContext> wc = GetWindowContext();
16857 return wc && wc->HasValidTransientUserGestureActivation();
16860 bool Document::ConsumeTransientUserGestureActivation() {
16861 RefPtr<WindowContext> wc = GetWindowContext();
16862 return wc && wc->ConsumeTransientUserGestureActivation();
16865 void Document::SetDocTreeHadMedia() {
16866 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
16867 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
16868 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
16872 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
16873 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
16874 return;
16877 // This will probably change for project fission, but currently this document
16878 // and the opener are on the same process. In the future, we should make this
16879 // part async.
16880 nsPIDOMWindowInner* inner = GetInnerWindow();
16881 if (NS_WARN_IF(!inner)) {
16882 return;
16885 // We care about first-party tracking resources only.
16886 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
16887 return;
16890 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16891 if (NS_WARN_IF(!outer)) {
16892 return;
16895 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
16896 if (!openerBC) {
16897 // No opener.
16898 return;
16901 // We want to ensure the following check works for both fission mode and
16902 // non-fission mode:
16903 // "If the opener is not a 3rd party and if this window is not a 3rd party
16904 // with respect to the opener, we should not continue."
16906 // In non-fission mode, the opener and the opened window are in the same
16907 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
16908 // In fission mode, if this window is not a 3rd party with respect to the
16909 // opener, they must be in the same process, so we can still use
16910 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
16911 // party.
16912 if (openerBC->IsInProcess()) {
16913 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
16914 if (NS_WARN_IF(!outerOpener)) {
16915 return;
16918 nsCOMPtr<nsPIDOMWindowInner> openerInner =
16919 outerOpener->GetCurrentInnerWindow();
16920 if (NS_WARN_IF(!openerInner)) {
16921 return;
16924 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
16925 if (NS_WARN_IF(!openerDocument)) {
16926 return;
16929 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
16930 if (NS_WARN_IF(!openerURI)) {
16931 return;
16934 // If the opener is not a 3rd party and if this window is not
16935 // a 3rd party with respect to the opener, we should not continue.
16936 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
16937 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
16938 return;
16942 // We don't care when the asynchronous work finishes here.
16943 Unused << StorageAccessAPIHelper::AllowAccessFor(
16944 NodePrincipal(), openerBC,
16945 ContentBlockingNotifier::eOpenerAfterUserInteraction);
16948 namespace {
16950 // Documents can stay alive for days. We don't want to update the permission
16951 // value at any user-interaction, and, using a timer triggered any X seconds
16952 // should be good enough. 'X' is taken from
16953 // privacy.userInteraction.document.interval pref.
16954 // We also want to store the user-interaction before shutting down, and, for
16955 // this reason, this class implements nsIAsyncShutdownBlocker interface.
16956 class UserInteractionTimer final : public Runnable,
16957 public nsITimerCallback,
16958 public nsIAsyncShutdownBlocker {
16959 public:
16960 NS_DECL_ISUPPORTS_INHERITED
16962 explicit UserInteractionTimer(Document* aDocument)
16963 : Runnable("UserInteractionTimer"),
16964 mPrincipal(aDocument->NodePrincipal()),
16965 mDocument(do_GetWeakReference(aDocument)) {
16966 static int32_t userInteractionTimerId = 0;
16967 // Blocker names must be unique. Let's create it now because when needed,
16968 // the document could be already gone.
16969 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
16970 ++userInteractionTimerId, aDocument);
16973 // Runnable interface
16975 NS_IMETHOD
16976 Run() override {
16977 uint32_t interval =
16978 StaticPrefs::privacy_userInteraction_document_interval();
16979 if (!interval) {
16980 return NS_OK;
16983 RefPtr<UserInteractionTimer> self = this;
16984 auto raii =
16985 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
16987 nsresult rv = NS_NewTimerWithCallback(
16988 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
16989 NS_ENSURE_SUCCESS(rv, NS_OK);
16991 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16992 NS_ENSURE_TRUE(!!phase, NS_OK);
16994 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
16995 __LINE__, u"UserInteractionTimer shutdown"_ns);
16996 NS_ENSURE_SUCCESS(rv, NS_OK);
16998 raii.release();
16999 return NS_OK;
17002 // nsITimerCallback interface
17004 NS_IMETHOD
17005 Notify(nsITimer* aTimer) override {
17006 StoreUserInteraction();
17007 return NS_OK;
17010 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
17011 using nsINamed::GetName;
17012 #endif
17014 // nsIAsyncShutdownBlocker interface
17016 NS_IMETHOD
17017 GetName(nsAString& aName) override {
17018 aName = mBlockerName;
17019 return NS_OK;
17022 NS_IMETHOD
17023 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
17024 CancelTimerAndStoreUserInteraction();
17025 return NS_OK;
17028 NS_IMETHOD
17029 GetState(nsIPropertyBag**) override { return NS_OK; }
17031 private:
17032 ~UserInteractionTimer() = default;
17034 void StoreUserInteraction() {
17035 // Remove the shutting down blocker
17036 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
17037 if (phase) {
17038 phase->RemoveBlocker(this);
17041 // If the document is not gone, let's reset its timer flag.
17042 nsCOMPtr<Document> document = do_QueryReferent(mDocument);
17043 if (document) {
17044 ContentBlockingUserInteraction::Observe(mPrincipal);
17045 document->ResetUserInteractionTimer();
17049 void CancelTimerAndStoreUserInteraction() {
17050 if (mTimer) {
17051 mTimer->Cancel();
17052 mTimer = nullptr;
17055 StoreUserInteraction();
17058 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
17059 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
17060 NS_ENSURE_TRUE(!!svc, nullptr);
17062 nsCOMPtr<nsIAsyncShutdownClient> phase;
17063 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
17064 NS_ENSURE_SUCCESS(rv, nullptr);
17066 return phase.forget();
17069 nsCOMPtr<nsIPrincipal> mPrincipal;
17070 nsWeakPtr mDocument;
17072 nsCOMPtr<nsITimer> mTimer;
17074 nsString mBlockerName;
17077 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
17078 nsIAsyncShutdownBlocker)
17080 } // namespace
17082 void Document::MaybeStoreUserInteractionAsPermission() {
17083 // We care about user-interaction stored only for top-level documents
17084 // and documents with access to the Storage Access API
17085 if (!IsTopLevelContentDocument()) {
17086 bool hasSA;
17087 nsresult rv = HasStorageAccessSync(hasSA);
17088 if (NS_FAILED(rv) || !hasSA) {
17089 return;
17093 if (!mUserHasInteracted) {
17094 // First interaction, let's store this info now.
17095 ContentBlockingUserInteraction::Observe(NodePrincipal());
17096 return;
17099 if (mHasUserInteractionTimerScheduled) {
17100 return;
17103 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
17104 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
17105 EventQueuePriority::Idle);
17106 if (NS_WARN_IF(NS_FAILED(rv))) {
17107 return;
17110 // This value will be reset by the timer.
17111 mHasUserInteractionTimerScheduled = true;
17114 void Document::ResetUserInteractionTimer() {
17115 mHasUserInteractionTimerScheduled = false;
17118 bool Document::IsExtensionPage() const {
17119 return BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
17122 void Document::AddResizeObserver(ResizeObserver& aObserver) {
17123 MOZ_ASSERT(!mResizeObservers.Contains(&aObserver));
17124 // Insert internal ResizeObservers before scripted ones, since they may have
17125 // observable side-effects and we don't want to expose the insertion time.
17126 if (aObserver.HasNativeCallback()) {
17127 mResizeObservers.InsertElementAt(0, &aObserver);
17128 } else {
17129 mResizeObservers.AppendElement(&aObserver);
17133 void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
17134 MOZ_ASSERT(mResizeObservers.Contains(&aObserver));
17135 mResizeObservers.RemoveElement(&aObserver);
17138 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
17139 if (!mPermissionDelegateHandler) {
17140 mPermissionDelegateHandler = MakeAndAddRef<PermissionDelegateHandler>(this);
17143 if (!mPermissionDelegateHandler->Initialize()) {
17144 mPermissionDelegateHandler = nullptr;
17147 return mPermissionDelegateHandler;
17150 void Document::ScheduleResizeObserversNotification() const {
17151 if (!mPresShell) {
17152 return;
17154 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
17155 rd->EnsureResizeObserverUpdateHappens();
17159 static void FlushLayoutForWholeBrowsingContextTree(Document& aDoc) {
17160 BrowsingContext* bc = aDoc.GetBrowsingContext();
17161 if (bc && bc->GetExtantDocument() == &aDoc) {
17162 RefPtr<BrowsingContext> top = bc->Top();
17163 top->PreOrderWalk([](BrowsingContext* aCur) {
17164 if (Document* doc = aCur->GetExtantDocument()) {
17165 doc->FlushPendingNotifications(FlushType::Layout);
17168 } else {
17169 // If there is no browsing context, or we're not the current document of the
17170 // browsing context, then we just flush this document itself.
17171 aDoc.FlushPendingNotifications(FlushType::Layout);
17175 void Document::DetermineProximityToViewportAndNotifyResizeObservers() {
17176 uint32_t shallowestTargetDepth = 0;
17177 bool initialResetOfScrolledIntoViewFlagsDone = false;
17178 while (true) {
17179 // Flush layout, so that any callback functions' style changes / resizes
17180 // get a chance to take effect. The callback functions may do changes in its
17181 // sub-documents or ancestors, so flushing layout for the whole browsing
17182 // context tree makes sure we don't miss anyone.
17183 FlushLayoutForWholeBrowsingContextTree(*this);
17184 if (PresShell* presShell = GetPresShell()) {
17185 auto result = presShell->DetermineProximityToViewport();
17186 if (result.mHadInitialDetermination) {
17187 continue;
17189 if (result.mAnyScrollIntoViewFlag) {
17190 // Not defined in the spec: It's possible that some elements with
17191 // content-visibility: auto were forced to be visible in order to
17192 // perform scrollIntoView() so clear their flags now and restart the
17193 // loop.
17194 // See https://github.com/w3c/csswg-drafts/issues/9337
17195 presShell->ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags();
17196 presShell->ScheduleContentRelevancyUpdate(
17197 ContentRelevancyReason::Visible);
17198 if (!initialResetOfScrolledIntoViewFlagsDone) {
17199 initialResetOfScrolledIntoViewFlagsDone = true;
17200 continue;
17205 // To avoid infinite resize loop, we only gather all active observations
17206 // that have the depth of observed target element more than current
17207 // shallowestTargetDepth.
17208 GatherAllActiveResizeObservations(shallowestTargetDepth);
17210 if (!HasAnyActiveResizeObservations()) {
17211 break;
17214 DebugOnly<uint32_t> oldShallowestTargetDepth = shallowestTargetDepth;
17215 shallowestTargetDepth = BroadcastAllActiveResizeObservations();
17216 NS_ASSERTION(oldShallowestTargetDepth < shallowestTargetDepth,
17217 "shallowestTargetDepth should be getting strictly deeper");
17220 if (HasAnySkippedResizeObservations()) {
17221 if (nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow()) {
17222 // Per spec, we deliver an error if the document has any skipped
17223 // observations. Also, we re-register via ScheduleNotification().
17224 RootedDictionary<ErrorEventInit> init(RootingCx());
17225 init.mMessage.AssignLiteral(
17226 "ResizeObserver loop completed with undelivered notifications.");
17227 init.mBubbles = false;
17228 init.mCancelable = false;
17230 nsEventStatus status = nsEventStatus_eIgnore;
17231 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
17232 MOZ_ASSERT(sgo);
17233 if (NS_WARN_IF(sgo->HandleScriptError(init, &status))) {
17234 status = nsEventStatus_eIgnore;
17236 } else {
17237 // We don't fire error events at any global for non-window JS on the main
17238 // thread.
17241 // We need to deliver pending notifications in next cycle.
17242 ScheduleResizeObserversNotification();
17246 void Document::GatherAllActiveResizeObservations(uint32_t aDepth) {
17247 for (ResizeObserver* observer : mResizeObservers) {
17248 observer->GatherActiveObservations(aDepth);
17252 uint32_t Document::BroadcastAllActiveResizeObservations() {
17253 uint32_t shallowestTargetDepth = std::numeric_limits<uint32_t>::max();
17255 // Copy the observers as this invokes the callbacks and could register and
17256 // unregister observers at will.
17257 const auto observers =
17258 ToTArray<nsTArray<RefPtr<ResizeObserver>>>(mResizeObservers);
17259 for (const auto& observer : observers) {
17260 // MOZ_KnownLive because 'observers' is guaranteed to keep it
17261 // alive.
17263 // This can go away once
17264 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
17265 uint32_t targetDepth =
17266 MOZ_KnownLive(observer)->BroadcastActiveObservations();
17267 if (targetDepth < shallowestTargetDepth) {
17268 shallowestTargetDepth = targetDepth;
17272 return shallowestTargetDepth;
17275 bool Document::HasAnySkippedResizeObservations() const {
17276 for (const auto& observer : mResizeObservers) {
17277 if (observer->HasSkippedObservations()) {
17278 return true;
17281 return false;
17284 bool Document::HasAnyActiveResizeObservations() const {
17285 for (const auto& observer : mResizeObservers) {
17286 if (observer->HasActiveObservations()) {
17287 return true;
17290 return false;
17293 void Document::ClearStaleServoData() {
17294 DocumentStyleRootIterator iter(this);
17295 while (Element* root = iter.GetNextStyleRoot()) {
17296 RestyleManager::ClearServoDataFromSubtree(root);
17300 Selection* Document::GetSelection(ErrorResult& aRv) {
17301 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
17302 if (!window) {
17303 return nullptr;
17306 if (!window->IsCurrentInnerWindow()) {
17307 return nullptr;
17310 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
17313 void Document::MakeBrowsingContextNonSynthetic() {
17314 if (nsContentUtils::ShouldHideObjectOrEmbedImageDocument()) {
17315 if (BrowsingContext* bc = GetBrowsingContext()) {
17316 if (bc->GetSyntheticDocumentContainer()) {
17317 Unused << bc->SetSyntheticDocumentContainer(false);
17323 nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
17324 // Step 1: check if cookie permissions are available or denied to this
17325 // document's principal
17326 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17327 if (!inner) {
17328 aHasStorageAccess = false;
17329 return NS_OK;
17331 Maybe<bool> resultBecauseCookiesApproved =
17332 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17333 CookieJarSettings(), NodePrincipal());
17334 if (resultBecauseCookiesApproved.isSome()) {
17335 if (resultBecauseCookiesApproved.value()) {
17336 aHasStorageAccess = true;
17337 return NS_OK;
17338 } else {
17339 aHasStorageAccess = false;
17340 return NS_OK;
17344 // Step 2: Check if the browser settings determine whether or not this
17345 // document has access to its unpartitioned cookies.
17346 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17347 bool isOnThirdPartySkipList = false;
17348 if (mChannel) {
17349 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17350 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17351 nsILoadInfo::StoragePermissionAllowListed;
17353 bool isThirdPartyTracker =
17354 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17355 Maybe<bool> resultBecauseBrowserSettings =
17356 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17357 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
17358 isThirdPartyTracker);
17359 if (resultBecauseBrowserSettings.isSome()) {
17360 if (resultBecauseBrowserSettings.value()) {
17361 aHasStorageAccess = true;
17362 return NS_OK;
17363 } else {
17364 aHasStorageAccess = false;
17365 return NS_OK;
17369 // Step 3: Check if the location of this call (embedded, top level, same-site)
17370 // determines if cookies are permitted or not.
17371 Maybe<bool> resultBecauseCallContext =
17372 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17373 false);
17374 if (resultBecauseCallContext.isSome()) {
17375 if (resultBecauseCallContext.value()) {
17376 aHasStorageAccess = true;
17377 return NS_OK;
17378 } else {
17379 aHasStorageAccess = false;
17380 return NS_OK;
17384 // Step 4: Check if the permissions for this document determine if if has
17385 // access or is denied cookies.
17386 Maybe<bool> resultBecausePreviousPermission =
17387 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17388 this, false);
17389 if (resultBecausePreviousPermission.isSome()) {
17390 if (resultBecausePreviousPermission.value()) {
17391 aHasStorageAccess = true;
17392 return NS_OK;
17393 } else {
17394 aHasStorageAccess = false;
17395 return NS_OK;
17398 // If you get here, we default to not giving you permission.
17399 aHasStorageAccess = false;
17400 return NS_OK;
17403 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
17404 mozilla::ErrorResult& aRv) {
17405 nsIGlobalObject* global = GetScopeObject();
17406 if (!global) {
17407 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17408 return nullptr;
17411 RefPtr<Promise> promise =
17412 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
17413 if (aRv.Failed()) {
17414 return nullptr;
17417 if (!IsCurrentActiveDocument()) {
17418 promise->MaybeRejectWithInvalidStateError(
17419 "hasStorageAccess requires an active document");
17420 return promise.forget();
17423 bool hasStorageAccess;
17424 nsresult rv = HasStorageAccessSync(hasStorageAccess);
17425 if (NS_FAILED(rv)) {
17426 promise->MaybeRejectWithUndefined();
17427 } else {
17428 promise->MaybeResolve(hasStorageAccess);
17431 return promise.forget();
17434 RefPtr<Document::GetContentBlockingEventsPromise>
17435 Document::GetContentBlockingEvents() {
17436 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
17437 if (!wgc) {
17438 return nullptr;
17441 return wgc->SendGetContentBlockingEvents()->Then(
17442 GetCurrentSerialEventTarget(), __func__,
17443 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
17444 ResolveOrRejectValue& aValue) {
17445 if (aValue.IsResolve()) {
17446 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
17447 aValue.ResolveValue(), __func__);
17450 return Document::GetContentBlockingEventsPromise::CreateAndReject(
17451 false, __func__);
17455 StorageAccessAPIHelper::PerformPermissionGrant
17456 Document::CreatePermissionGrantPromise(
17457 nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal,
17458 bool aHasUserInteraction, bool aRequireUserInteraction,
17459 const Maybe<nsCString>& aTopLevelBaseDomain, bool aFrameOnly) {
17460 MOZ_ASSERT(aInnerWindow);
17461 MOZ_ASSERT(aPrincipal);
17462 RefPtr<Document> self(this);
17463 RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
17464 RefPtr<nsIPrincipal> principal(aPrincipal);
17466 return [inner, self, principal, aHasUserInteraction, aRequireUserInteraction,
17467 aTopLevelBaseDomain, aFrameOnly]() {
17468 RefPtr<StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::Private>
17469 p = new StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
17470 Private(__func__);
17472 RefPtr<PWindowGlobalChild::GetStorageAccessPermissionPromise> promise;
17473 // Test the permission
17474 MOZ_ASSERT(XRE_IsContentProcess());
17476 WindowGlobalChild* wgc = inner->GetWindowGlobalChild();
17477 MOZ_ASSERT(wgc);
17479 promise = wgc->SendGetStorageAccessPermission();
17480 MOZ_ASSERT(promise);
17481 promise->Then(
17482 GetCurrentSerialEventTarget(), __func__,
17483 [self, p, inner, principal, aHasUserInteraction,
17484 aRequireUserInteraction, aTopLevelBaseDomain,
17485 aFrameOnly](uint32_t aAction) {
17486 if (aAction == nsIPermissionManager::ALLOW_ACTION) {
17487 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17488 return;
17490 if (aAction == nsIPermissionManager::DENY_ACTION) {
17491 p->Reject(false, __func__);
17492 return;
17495 // We require user activation before conducting a permission request
17496 // See
17497 // https://privacycg.github.io/storage-access/#dom-document-requeststorageaccess
17498 // where we "If has transient activation is false: ..." immediately
17499 // before we "Let permissionState be the result of requesting
17500 // permission to use "storage-access"" from in parallel.
17501 if (!aHasUserInteraction && aRequireUserInteraction) {
17502 // Report an error to the console for this case
17503 nsContentUtils::ReportToConsole(
17504 nsIScriptError::errorFlag,
17505 nsLiteralCString("requestStorageAccess"), self,
17506 nsContentUtils::eDOM_PROPERTIES,
17507 "RequestStorageAccessUserGesture");
17508 p->Reject(false, __func__);
17509 return;
17512 // Create the user prompt
17513 RefPtr<StorageAccessPermissionRequest> sapr =
17514 StorageAccessPermissionRequest::Create(
17515 inner, principal, aTopLevelBaseDomain, aFrameOnly,
17516 // Allow
17517 [p] {
17518 Telemetry::AccumulateCategorical(
17519 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
17520 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17522 // Block
17523 [p] {
17524 Telemetry::AccumulateCategorical(
17525 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
17526 p->Reject(false, __func__);
17529 using PromptResult = ContentPermissionRequestBase::PromptResult;
17530 PromptResult pr = sapr->CheckPromptPrefs();
17532 if (pr == PromptResult::Pending) {
17533 // We're about to show a prompt, record the request attempt
17534 Telemetry::AccumulateCategorical(
17535 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
17538 // Try to auto-grant the storage access so the user doesn't see the
17539 // prompt.
17540 self->AutomaticStorageAccessPermissionCanBeGranted(
17541 aHasUserInteraction)
17542 ->Then(
17543 GetCurrentSerialEventTarget(), __func__,
17544 // If the autogrant check didn't fail, call this function
17545 [p, pr, sapr,
17546 inner](const Document::
17547 AutomaticStorageAccessPermissionGrantPromise::
17548 ResolveOrRejectValue& aValue) -> void {
17549 // Make a copy because we can't modified copy-captured
17550 // lambda variables.
17551 PromptResult pr2 = pr;
17553 // If the user didn't already click "allow" and we can
17554 // autogrant, do that!
17555 bool storageAccessCanBeGrantedAutomatically =
17556 aValue.IsResolve() && aValue.ResolveValue();
17557 bool autoGrant = false;
17558 if (pr2 == PromptResult::Pending &&
17559 storageAccessCanBeGrantedAutomatically) {
17560 pr2 = PromptResult::Granted;
17561 autoGrant = true;
17563 Telemetry::AccumulateCategorical(
17564 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
17565 AllowAutomatically);
17568 // If we can complete the permission request, do so.
17569 if (pr2 != PromptResult::Pending) {
17570 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
17571 pr2 == PromptResult::Denied);
17572 if (pr2 == PromptResult::Granted) {
17573 StorageAccessAPIHelper::StorageAccessPromptChoices
17574 choice = StorageAccessAPIHelper::eAllow;
17575 if (autoGrant) {
17576 choice = StorageAccessAPIHelper::eAllowAutoGrant;
17578 if (!autoGrant) {
17579 p->Resolve(choice, __func__);
17580 } else {
17581 // We capture sapr here to prevent it from destructing
17582 // before the callbacks complete.
17583 sapr->MaybeDelayAutomaticGrants()->Then(
17584 GetCurrentSerialEventTarget(), __func__,
17585 [p, sapr, choice] {
17586 p->Resolve(choice, __func__);
17588 [p, sapr] { p->Reject(false, __func__); });
17590 return;
17592 p->Reject(false, __func__);
17593 return;
17596 // If we get here, the auto-decision failed and we need to
17597 // wait for the user prompt to complete.
17598 sapr->RequestDelayedTask(
17599 GetMainThreadSerialEventTarget(),
17600 ContentPermissionRequestBase::DelayedTaskType::Request);
17603 [p](mozilla::ipc::ResponseRejectReason aError) {
17604 p->Reject(false, __func__);
17605 return p;
17608 return p;
17612 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
17613 mozilla::ErrorResult& aRv) {
17614 nsIGlobalObject* global = GetScopeObject();
17615 if (!global) {
17616 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17617 return nullptr;
17620 RefPtr<Promise> promise = Promise::Create(global, aRv);
17621 if (aRv.Failed()) {
17622 return nullptr;
17625 if (!IsCurrentActiveDocument()) {
17626 promise->MaybeRejectWithInvalidStateError(
17627 "requestStorageAccess requires an active document");
17628 return promise.forget();
17631 // Get a pointer to the inner window- We need this for convenience sake
17632 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17633 if (!inner) {
17634 ConsumeTransientUserGestureActivation();
17635 promise->MaybeRejectWithNotAllowedError(
17636 "requestStorageAccess not allowed"_ns);
17637 return promise.forget();
17640 // Step 1: Check if the principal calling this has a permission that lets
17641 // them use cookies or forbids them from using cookies.
17642 // This is outside of the spec of the StorageAccess API, but makes the return
17643 // values to have proper semantics.
17644 Maybe<bool> resultBecauseCookiesApproved =
17645 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17646 CookieJarSettings(), NodePrincipal());
17647 if (resultBecauseCookiesApproved.isSome()) {
17648 if (resultBecauseCookiesApproved.value()) {
17649 promise->MaybeResolveWithUndefined();
17650 return promise.forget();
17651 } else {
17652 ConsumeTransientUserGestureActivation();
17653 promise->MaybeRejectWithNotAllowedError(
17654 "requestStorageAccess not allowed"_ns);
17655 return promise.forget();
17659 // Step 2: Check if the browser settings always allow or deny cookies.
17660 // We should always return a resolved promise if the cookieBehavior is ACCEPT.
17661 // This is outside of the spec of the StorageAccess API, but makes the return
17662 // values to have proper semantics.
17663 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17664 bool isOnThirdPartySkipList = false;
17665 if (mChannel) {
17666 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17667 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17668 nsILoadInfo::StoragePermissionAllowListed;
17670 bool isThirdPartyTracker =
17671 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17672 Maybe<bool> resultBecauseBrowserSettings =
17673 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17674 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
17675 isThirdPartyTracker);
17676 if (resultBecauseBrowserSettings.isSome()) {
17677 if (resultBecauseBrowserSettings.value()) {
17678 promise->MaybeResolveWithUndefined();
17679 return promise.forget();
17680 } else {
17681 ConsumeTransientUserGestureActivation();
17682 promise->MaybeRejectWithNotAllowedError(
17683 "requestStorageAccess not allowed"_ns);
17684 return promise.forget();
17688 // Step 3: Check if the Document calling requestStorageAccess has anything to
17689 // gain from storage access. It should be embedded, non-null, etc.
17690 Maybe<bool> resultBecauseCallContext =
17691 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17692 true);
17693 if (resultBecauseCallContext.isSome()) {
17694 if (resultBecauseCallContext.value()) {
17695 promise->MaybeResolveWithUndefined();
17696 return promise.forget();
17697 } else {
17698 ConsumeTransientUserGestureActivation();
17699 promise->MaybeRejectWithNotAllowedError(
17700 "requestStorageAccess not allowed"_ns);
17701 return promise.forget();
17705 // Step 4: Check if we already allowed or denied storage access for this
17706 // document's storage key.
17707 Maybe<bool> resultBecausePreviousPermission =
17708 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17709 this, true);
17710 if (resultBecausePreviousPermission.isSome()) {
17711 if (resultBecausePreviousPermission.value()) {
17712 promise->MaybeResolveWithUndefined();
17713 return promise.forget();
17714 } else {
17715 ConsumeTransientUserGestureActivation();
17716 promise->MaybeRejectWithNotAllowedError(
17717 "requestStorageAccess not allowed"_ns);
17718 return promise.forget();
17722 // Get pointers to some objects that will be used in the async portion
17723 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17724 RefPtr<nsGlobalWindowOuter> outer =
17725 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17726 if (!outer) {
17727 ConsumeTransientUserGestureActivation();
17728 promise->MaybeRejectWithNotAllowedError(
17729 "requestStorageAccess not allowed"_ns);
17730 return promise.forget();
17732 RefPtr<Document> self(this);
17734 // Step 5. Start an async call to request storage access. This will either
17735 // perform an automatic decision or notify the user, then perform some follow
17736 // on work changing state to reflect the result of the API. If it resolves,
17737 // the request was granted. If it rejects it was denied.
17738 StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17739 this, inner, bc, NodePrincipal(),
17740 self->HasValidTransientUserGestureActivation(), true, true,
17741 ContentBlockingNotifier::eStorageAccessAPI, true)
17742 ->Then(
17743 GetCurrentSerialEventTarget(), __func__,
17744 [inner, promise] {
17745 inner->SaveStorageAccessPermissionGranted();
17746 promise->MaybeResolveWithUndefined();
17748 [self, promise] {
17749 self->ConsumeTransientUserGestureActivation();
17750 promise->MaybeRejectWithNotAllowedError(
17751 "requestStorageAccess not allowed"_ns);
17754 return promise.forget();
17757 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
17758 const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
17759 mozilla::ErrorResult& aRv) {
17760 nsIGlobalObject* global = GetScopeObject();
17761 if (!global) {
17762 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17763 return nullptr;
17765 RefPtr<Promise> promise = Promise::Create(global, aRv);
17766 if (aRv.Failed()) {
17767 return nullptr;
17770 // Step 0: Check that we have user activation before proceeding to prevent
17771 // rapid calls to the API to leak information.
17772 if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) {
17773 // Report an error to the console for this case
17774 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17775 nsLiteralCString("requestStorageAccess"),
17776 this, nsContentUtils::eDOM_PROPERTIES,
17777 "RequestStorageAccessUserGesture");
17778 ConsumeTransientUserGestureActivation();
17779 promise->MaybeRejectWithNotAllowedError(
17780 "requestStorageAccess not allowed"_ns);
17781 return promise.forget();
17784 // Step 1: Check if the provided URI is different-site to this Document
17785 nsCOMPtr<nsIURI> thirdPartyURI;
17786 nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
17787 if (NS_WARN_IF(NS_FAILED(rv))) {
17788 aRv.Throw(rv);
17789 return nullptr;
17791 bool isThirdPartyDocument;
17792 rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument);
17793 if (NS_WARN_IF(NS_FAILED(rv))) {
17794 aRv.Throw(rv);
17795 return nullptr;
17797 Maybe<bool> resultBecauseBrowserSettings =
17798 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17799 CookieJarSettings(), isThirdPartyDocument, false, true);
17800 if (resultBecauseBrowserSettings.isSome()) {
17801 if (resultBecauseBrowserSettings.value()) {
17802 promise->MaybeResolveWithUndefined();
17803 return promise.forget();
17805 ConsumeTransientUserGestureActivation();
17806 promise->MaybeRejectWithNotAllowedError(
17807 "requestStorageAccess not allowed"_ns);
17808 return promise.forget();
17811 // Step 2: Check that this Document is same-site to the top, and check that
17812 // we have user activation if we require it.
17813 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17814 CheckSameSiteCallingContextDecidesStorageAccessAPI(
17815 this, aRequireUserActivation);
17816 if (resultBecauseCallContext.isSome()) {
17817 if (resultBecauseCallContext.value()) {
17818 promise->MaybeResolveWithUndefined();
17819 return promise.forget();
17821 ConsumeTransientUserGestureActivation();
17822 promise->MaybeRejectWithNotAllowedError(
17823 "requestStorageAccess not allowed"_ns);
17824 return promise.forget();
17827 // Step 3: Get some useful variables that can be captured by the lambda for
17828 // the asynchronous portion
17829 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17830 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17831 if (!inner) {
17832 ConsumeTransientUserGestureActivation();
17833 promise->MaybeRejectWithNotAllowedError(
17834 "requestStorageAccess not allowed"_ns);
17835 return promise.forget();
17837 RefPtr<nsGlobalWindowOuter> outer =
17838 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17839 if (!outer) {
17840 ConsumeTransientUserGestureActivation();
17841 promise->MaybeRejectWithNotAllowedError(
17842 "requestStorageAccess not allowed"_ns);
17843 return promise.forget();
17845 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17846 thirdPartyURI, NodePrincipal()->OriginAttributesRef());
17847 if (!principal) {
17848 ConsumeTransientUserGestureActivation();
17849 promise->MaybeRejectWithNotAllowedError(
17850 "requestStorageAccess not allowed"_ns);
17851 return promise.forget();
17854 RefPtr<Document> self(this);
17855 bool hasUserActivation = HasValidTransientUserGestureActivation();
17857 // Consume user activation before entering the async part of this method.
17858 // This prevents usage of other transient activation-gated APIs.
17859 ConsumeTransientUserGestureActivation();
17861 // Step 4a: Start the async part of this function. Check the cookie
17862 // permission, but this can't be done in this process. We needs the cookie
17863 // permission of the URL as if it were embedded on this page, so we need to
17864 // make this check in the ContentParent.
17865 StorageAccessAPIHelper::AsyncCheckCookiesPermittedDecidesStorageAccessAPI(
17866 GetBrowsingContext(), principal)
17867 ->Then(
17868 GetCurrentSerialEventTarget(), __func__,
17869 [inner, thirdPartyURI, bc, principal, hasUserActivation,
17870 aRequireUserActivation, self, promise](Maybe<bool> cookieResult) {
17871 // Handle the result of the cookie permission check that took place
17872 // in the ContentParent.
17873 if (cookieResult.isSome()) {
17874 if (cookieResult.value()) {
17875 return MozPromise<int, bool, true>::CreateAndResolve(true,
17876 __func__);
17878 return MozPromise<int, bool, true>::CreateAndReject(false,
17879 __func__);
17882 // Step 4b: Check for the existing storage access permission
17883 nsAutoCString type;
17884 bool ok =
17885 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
17886 if (!ok) {
17887 return MozPromise<int, bool, true>::CreateAndReject(false,
17888 __func__);
17890 if (AntiTrackingUtils::CheckStoragePermission(
17891 self->NodePrincipal(), type,
17892 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
17893 return MozPromise<int, bool, true>::CreateAndResolve(true,
17894 __func__);
17897 // Step 4c: Try to request storage access, either automatically or
17898 // 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::ePrivilegeStorageAccessForOriginAPI,
17904 true);
17906 // If the IPC rejects, we should reject our promise here which will
17907 // cause a rejection of the promise we already returned
17908 [promise]() {
17909 return MozPromise<int, bool, true>::CreateAndReject(false,
17910 __func__);
17912 ->Then(
17913 GetCurrentSerialEventTarget(), __func__,
17914 // If the previous handlers resolved, we should reinstate user
17915 // activation and resolve the promise we returned in Step 5.
17916 [self, inner, promise] {
17917 inner->SaveStorageAccessPermissionGranted();
17918 self->NotifyUserGestureActivation();
17919 promise->MaybeResolveWithUndefined();
17921 // If the previous handler rejected, we should reject the promise
17922 // returned by this function.
17923 [promise] {
17924 promise->MaybeRejectWithNotAllowedError(
17925 "requestStorageAccess not allowed"_ns);
17928 // Step 5: While the async stuff is happening, we should return the promise so
17929 // our caller can continue executing.
17930 return promise.forget();
17933 already_AddRefed<Promise> Document::RequestStorageAccessUnderSite(
17934 const nsAString& aSerializedSite, ErrorResult& aRv) {
17935 nsIGlobalObject* global = GetScopeObject();
17936 if (!global) {
17937 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17938 return nullptr;
17940 RefPtr<Promise> promise = Promise::Create(global, aRv);
17941 if (aRv.Failed()) {
17942 return nullptr;
17945 // Check that we have user activation before proceeding to prevent
17946 // rapid calls to the API to leak information.
17947 if (!ConsumeTransientUserGestureActivation()) {
17948 // Report an error to the console for this case
17949 nsContentUtils::ReportToConsole(
17950 nsIScriptError::errorFlag, "requestStorageAccess"_ns, this,
17951 nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture");
17952 promise->MaybeRejectWithUndefined();
17953 return promise.forget();
17956 // Check if the provided URI is different-site to this Document
17957 nsCOMPtr<nsIURI> siteURI;
17958 nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite);
17959 if (NS_WARN_IF(NS_FAILED(rv))) {
17960 promise->MaybeRejectWithUndefined();
17961 return promise.forget();
17963 bool isCrossSiteArgument;
17964 rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument);
17965 if (NS_WARN_IF(NS_FAILED(rv))) {
17966 aRv.Throw(rv);
17967 return nullptr;
17969 if (!isCrossSiteArgument) {
17970 promise->MaybeRejectWithUndefined();
17971 return promise.forget();
17974 // Check if this party has broad cookie permissions.
17975 Maybe<bool> resultBecauseCookiesApproved =
17976 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17977 CookieJarSettings(), NodePrincipal());
17978 if (resultBecauseCookiesApproved.isSome()) {
17979 if (resultBecauseCookiesApproved.value()) {
17980 promise->MaybeResolveWithUndefined();
17981 return promise.forget();
17983 promise->MaybeRejectWithUndefined();
17984 return promise.forget();
17987 // Check if browser settings preclude this document getting storage
17988 // access under the provided site
17989 Maybe<bool> resultBecauseBrowserSettings =
17990 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17991 CookieJarSettings(), true, false, true);
17992 if (resultBecauseBrowserSettings.isSome()) {
17993 if (resultBecauseBrowserSettings.value()) {
17994 promise->MaybeResolveWithUndefined();
17995 return promise.forget();
17997 promise->MaybeRejectWithUndefined();
17998 return promise.forget();
18001 // Check that this Document is same-site to the top
18002 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
18003 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
18004 if (resultBecauseCallContext.isSome()) {
18005 if (resultBecauseCallContext.value()) {
18006 promise->MaybeResolveWithUndefined();
18007 return promise.forget();
18009 promise->MaybeRejectWithUndefined();
18010 return promise.forget();
18013 nsCOMPtr<nsIPrincipal> principal(NodePrincipal());
18015 // Test if the permission this is requesting is already set
18016 nsCOMPtr<nsIPrincipal> argumentPrincipal =
18017 BasePrincipal::CreateContentPrincipal(
18018 siteURI, NodePrincipal()->OriginAttributesRef());
18019 if (!argumentPrincipal) {
18020 ConsumeTransientUserGestureActivation();
18021 promise->MaybeRejectWithUndefined();
18022 return promise.forget();
18024 nsCString originNoSuffix;
18025 rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix);
18026 if (NS_WARN_IF(NS_FAILED(rv))) {
18027 promise->MaybeRejectWithUndefined();
18028 return promise.forget();
18031 ContentChild* cc = ContentChild::GetSingleton();
18032 MOZ_ASSERT(cc);
18033 RefPtr<Document> self(this);
18034 cc->SendTestStorageAccessPermission(argumentPrincipal, originNoSuffix)
18035 ->Then(
18036 GetCurrentSerialEventTarget(), __func__,
18037 [promise, siteURI,
18038 self](const ContentChild::TestStorageAccessPermissionPromise::
18039 ResolveValueType& aResult) {
18040 if (aResult) {
18041 return StorageAccessAPIHelper::
18042 StorageAccessPermissionGrantPromise::CreateAndResolve(
18043 StorageAccessAPIHelper::eAllow, __func__);
18045 // Get a grant for the storage access permission that will be set
18046 // when this is completed in the embedding context
18047 nsCString serializedSite;
18048 RefPtr<nsEffectiveTLDService> etld =
18049 nsEffectiveTLDService::GetInstance();
18050 if (!etld) {
18051 return StorageAccessAPIHelper::
18052 StorageAccessPermissionGrantPromise::CreateAndReject(
18053 false, __func__);
18055 nsresult rv = etld->GetSite(siteURI, serializedSite);
18056 if (NS_FAILED(rv)) {
18057 return StorageAccessAPIHelper::
18058 StorageAccessPermissionGrantPromise::CreateAndReject(
18059 false, __func__);
18061 return self->CreatePermissionGrantPromise(
18062 self->GetInnerWindow(), self->NodePrincipal(), true, true,
18063 Some(serializedSite), false)();
18065 [](const ContentChild::TestStorageAccessPermissionPromise::
18066 RejectValueType& aResult) {
18067 return StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
18068 CreateAndReject(false, __func__);
18070 ->Then(
18071 GetCurrentSerialEventTarget(), __func__,
18072 [promise, principal, siteURI](int result) {
18073 ContentChild* cc = ContentChild::GetSingleton();
18074 if (!cc) {
18075 // TODO(bug 1778561): Make this work in non-content processes.
18076 promise->MaybeRejectWithUndefined();
18077 return;
18079 // Set a permission in the parent process that this document wants
18080 // storage access under the argument's site, resolving our returned
18081 // promise on success
18082 cc->SendSetAllowStorageAccessRequestFlag(principal, siteURI)
18083 ->Then(
18084 GetCurrentSerialEventTarget(), __func__,
18085 [promise](bool success) {
18086 if (success) {
18087 promise->MaybeResolveWithUndefined();
18088 } else {
18089 promise->MaybeRejectWithUndefined();
18092 [promise](mozilla::ipc::ResponseRejectReason reason) {
18093 promise->MaybeRejectWithUndefined();
18096 [promise](bool result) { promise->MaybeRejectWithUndefined(); });
18098 // Return the promise that is resolved in the async handler above
18099 return promise.forget();
18102 already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite(
18103 const nsAString& aSerializedOrigin, ErrorResult& aRv) {
18104 nsIGlobalObject* global = GetScopeObject();
18105 if (!global) {
18106 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
18107 return nullptr;
18109 RefPtr<Promise> promise = Promise::Create(global, aRv);
18110 if (aRv.Failed()) {
18111 return nullptr;
18114 // Check that the provided URI is different-site to this Document
18115 nsCOMPtr<nsIURI> argumentURI;
18116 nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin);
18117 if (NS_WARN_IF(NS_FAILED(rv))) {
18118 promise->MaybeRejectWithUndefined();
18119 return promise.forget();
18121 bool isCrossSiteArgument;
18122 rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument);
18123 if (NS_WARN_IF(NS_FAILED(rv))) {
18124 aRv.Throw(rv);
18125 return nullptr;
18127 if (!isCrossSiteArgument) {
18128 promise->MaybeRejectWithUndefined();
18129 return promise.forget();
18132 // Check if browser settings preclude this document getting storage
18133 // access under the provided site
18134 Maybe<bool> resultBecauseBrowserSettings =
18135 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
18136 CookieJarSettings(), true, false, true);
18137 if (resultBecauseBrowserSettings.isSome()) {
18138 if (resultBecauseBrowserSettings.value()) {
18139 promise->MaybeResolveWithUndefined();
18140 return promise.forget();
18142 promise->MaybeRejectWithUndefined();
18143 return promise.forget();
18146 // Check that this Document is same-site to the top
18147 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
18148 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
18149 if (resultBecauseCallContext.isSome()) {
18150 if (resultBecauseCallContext.value()) {
18151 promise->MaybeResolveWithUndefined();
18152 return promise.forget();
18154 promise->MaybeRejectWithUndefined();
18155 return promise.forget();
18158 // Create principal of the embedded site requesting storage access
18159 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
18160 argumentURI, NodePrincipal()->OriginAttributesRef());
18161 if (!principal) {
18162 promise->MaybeRejectWithUndefined();
18163 return promise.forget();
18166 // Get versions of these objects that we can use in lambdas for callbacks
18167 RefPtr<Document> self(this);
18168 RefPtr<BrowsingContext> bc = GetBrowsingContext();
18169 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
18171 // Test that the permission was set by a call to RequestStorageAccessUnderSite
18172 // from a top level document that is same-site with the argument
18173 ContentChild* cc = ContentChild::GetSingleton();
18174 if (!cc) {
18175 // TODO(bug 1778561): Make this work in non-content processes.
18176 promise->MaybeRejectWithUndefined();
18177 return promise.forget();
18179 cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI)
18180 ->Then(
18181 GetCurrentSerialEventTarget(), __func__,
18182 [inner, bc, self, principal](bool success) {
18183 if (success) {
18184 // If that resolved with true, check that we don't already have a
18185 // permission that gives cookie access.
18186 return StorageAccessAPIHelper::
18187 AsyncCheckCookiesPermittedDecidesStorageAccessAPI(bc,
18188 principal);
18190 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18191 NS_ERROR_FAILURE, __func__);
18193 [](mozilla::ipc::ResponseRejectReason reason) {
18194 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18195 NS_ERROR_FAILURE, __func__);
18197 ->Then(
18198 GetCurrentSerialEventTarget(), __func__,
18199 [inner, bc, principal, self, promise](Maybe<bool> cookieResult) {
18200 // Handle the result of the cookie permission check that took place
18201 // in the ContentParent.
18202 if (cookieResult.isSome()) {
18203 if (cookieResult.value()) {
18204 return StorageAccessAPIHelper::
18205 StorageAccessPermissionGrantPromise::CreateAndResolve(
18206 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18208 return StorageAccessAPIHelper::
18209 StorageAccessPermissionGrantPromise::CreateAndReject(
18210 false, __func__);
18213 // Check for the existing storage access permission
18214 nsAutoCString type;
18215 bool ok =
18216 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
18217 if (!ok) {
18218 return StorageAccessAPIHelper::
18219 StorageAccessPermissionGrantPromise::CreateAndReject(
18220 false, __func__);
18222 if (AntiTrackingUtils::CheckStoragePermission(
18223 self->NodePrincipal(), type,
18224 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
18225 return StorageAccessAPIHelper::
18226 StorageAccessPermissionGrantPromise::CreateAndResolve(
18227 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18230 // Try to request storage access, ignoring the final checks.
18231 // We ignore the final checks because this is where the "grant"
18232 // either by prompt doorhanger or autogrant takes place. We already
18233 // gathered an equivalent grant in requestStorageAccessUnderSite.
18234 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
18235 self, inner, bc, principal, true, true, false,
18236 ContentBlockingNotifier::eStorageAccessAPI, false);
18238 // If the IPC rejects, we should reject our promise here which will
18239 // cause a rejection of the promise we already returned
18240 [promise]() {
18241 return MozPromise<int, bool, true>::CreateAndReject(false,
18242 __func__);
18244 ->Then(
18245 GetCurrentSerialEventTarget(), __func__,
18246 // If the previous handlers resolved, we should reinstate user
18247 // activation and resolve the promise we returned in Step 5.
18248 [self, inner, promise] {
18249 inner->SaveStorageAccessPermissionGranted();
18250 promise->MaybeResolveWithUndefined();
18252 // If the previous handler rejected, we should reject the promise
18253 // returned by this function.
18254 [promise] { promise->MaybeRejectWithUndefined(); });
18256 return promise.forget();
18259 nsTHashSet<RefPtr<WakeLockSentinel>>& Document::ActiveWakeLocks(
18260 WakeLockType aType) {
18261 return mActiveLocks.LookupOrInsert(aType);
18264 class UnlockAllWakeLockRunnable final : public Runnable {
18265 public:
18266 UnlockAllWakeLockRunnable(WakeLockType aType, Document* aDoc)
18267 : Runnable("UnlockAllWakeLocks"), mType(aType), mDoc(aDoc) {}
18269 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
18270 // bug 1535398.
18271 MOZ_CAN_RUN_SCRIPT_BOUNDARY
18272 NS_IMETHOD Run() override {
18273 // Move, as ReleaseWakeLock will try to remove from and possibly allow
18274 // scripts via onrelease to add to document.[[ActiveLocks]]["screen"]
18275 nsCOMPtr<Document> doc = mDoc;
18276 nsTHashSet<RefPtr<WakeLockSentinel>> locks =
18277 std::move(doc->ActiveWakeLocks(mType));
18278 for (const auto& lock : locks) {
18279 // ReleaseWakeLock runs script, which could release other locks
18280 if (!lock->Released()) {
18281 ReleaseWakeLock(doc, MOZ_KnownLive(lock), mType);
18284 return NS_OK;
18287 protected:
18288 ~UnlockAllWakeLockRunnable() = default;
18290 private:
18291 WakeLockType mType;
18292 nsCOMPtr<Document> mDoc;
18295 void Document::UnlockAllWakeLocks(WakeLockType aType) {
18296 // Perform unlock in a runnable to prevent UnlockAll being MOZ_CAN_RUN_SCRIPT
18297 RefPtr<UnlockAllWakeLockRunnable> runnable =
18298 MakeRefPtr<UnlockAllWakeLockRunnable>(aType, this);
18299 NS_DispatchToMainThread(runnable);
18302 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
18303 Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
18304 // requestStorageAccessForOrigin may not require user activation. If we don't
18305 // have user activation at this point we should always show the prompt.
18306 if (!hasUserActivation ||
18307 !StaticPrefs::privacy_antitracking_enableWebcompat()) {
18308 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18309 false, __func__);
18311 if (XRE_IsContentProcess()) {
18312 // In the content process, we need to ask the parent process to compute
18313 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
18314 // isn't accessible in the content process.
18315 ContentChild* cc = ContentChild::GetSingleton();
18316 MOZ_ASSERT(cc);
18318 return cc->SendAutomaticStorageAccessPermissionCanBeGranted(NodePrincipal())
18319 ->Then(GetCurrentSerialEventTarget(), __func__,
18320 [](const ContentChild::
18321 AutomaticStorageAccessPermissionCanBeGrantedPromise::
18322 ResolveOrRejectValue& aValue) {
18323 if (aValue.IsResolve()) {
18324 return AutomaticStorageAccessPermissionGrantPromise::
18325 CreateAndResolve(aValue.ResolveValue(), __func__);
18328 return AutomaticStorageAccessPermissionGrantPromise::
18329 CreateAndReject(false, __func__);
18333 if (XRE_IsParentProcess()) {
18334 // In the parent process, we can directly compute this.
18335 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18336 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
18337 __func__);
18340 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
18341 false, __func__);
18344 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
18345 nsIPrincipal* aPrincipal) {
18346 if (!StaticPrefs::dom_storage_access_auto_grants()) {
18347 return false;
18350 if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
18351 return false;
18354 nsCOMPtr<nsIBrowserUsage> bu = do_ImportESModule(
18355 "resource:///modules/BrowserUsageTelemetry.sys.mjs", fallible);
18356 if (NS_WARN_IF(!bu)) {
18357 return false;
18360 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
18361 nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours(
18362 &uniqueDomainsVisitedInPast24Hours);
18363 if (NS_WARN_IF(NS_FAILED(rv))) {
18364 return false;
18367 Maybe<size_t> maybeOriginsThirdPartyHasAccessTo =
18368 AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal);
18369 if (maybeOriginsThirdPartyHasAccessTo.isNothing()) {
18370 return false;
18372 size_t originsThirdPartyHasAccessTo =
18373 maybeOriginsThirdPartyHasAccessTo.value();
18375 // one percent of the number of top-levels origins visited in the current
18376 // session (but not to exceed 24 hours), or the value of the
18377 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
18378 // higher.
18379 size_t maxConcurrentAutomaticGrants = std::max(
18380 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
18381 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
18384 return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
18387 void Document::RecordNavigationTiming(ReadyState aReadyState) {
18388 if (!XRE_IsContentProcess()) {
18389 return;
18391 if (!IsTopLevelContentDocument()) {
18392 return;
18394 // If we dont have the timing yet (mostly because the doc is still loading),
18395 // get it from docshell.
18396 RefPtr<nsDOMNavigationTiming> timing = mTiming;
18397 if (!timing) {
18398 if (!mDocumentContainer) {
18399 return;
18401 timing = mDocumentContainer->GetNavigationTiming();
18402 if (!timing) {
18403 return;
18406 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
18407 switch (aReadyState) {
18408 case READYSTATE_LOADING:
18409 if (!mDOMLoadingSet) {
18410 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
18411 startTime);
18412 mDOMLoadingSet = true;
18414 break;
18415 case READYSTATE_INTERACTIVE:
18416 if (!mDOMInteractiveSet) {
18417 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
18418 startTime);
18419 mDOMInteractiveSet = true;
18421 break;
18422 case READYSTATE_COMPLETE:
18423 if (!mDOMCompleteSet) {
18424 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
18425 startTime);
18426 mDOMCompleteSet = true;
18428 break;
18429 default:
18430 NS_WARNING("Unexpected ReadyState value");
18431 break;
18435 bool Document::ImportMapsEnabled() const {
18436 return nsContentUtils::IsChromeDoc(this) ||
18437 StaticPrefs::dom_importMaps_enabled();
18440 void Document::ReportShadowDOMUsage() {
18441 nsPIDOMWindowInner* inner = GetInnerWindow();
18442 if (NS_WARN_IF(!inner)) {
18443 return;
18446 WindowContext* wc = inner->GetWindowContext();
18447 if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
18448 return;
18451 WindowContext* topWc = wc->TopWindowContext();
18452 if (topWc->GetHasReportedShadowDOMUsage()) {
18453 return;
18456 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
18459 // static
18460 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
18461 return StaticPrefs::dom_storage_access_enabled() &&
18462 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
18465 bool Document::StorageAccessSandboxed() const {
18466 return Document::StorageAccessSandboxed(GetSandboxFlags());
18469 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
18470 if (mCachedTabSizeGeneration == 0 ||
18471 GetGeneration() != mCachedTabSizeGeneration) {
18472 return false;
18474 aSizes->mDom += mCachedTabSizes.mDom;
18475 aSizes->mStyle += mCachedTabSizes.mStyle;
18476 aSizes->mOther += mCachedTabSizes.mOther;
18477 return true;
18480 void Document::SetCachedSizes(nsTabSizes* aSizes) {
18481 mCachedTabSizes.mDom = aSizes->mDom;
18482 mCachedTabSizes.mStyle = aSizes->mStyle;
18483 mCachedTabSizes.mOther = aSizes->mOther;
18484 mCachedTabSizeGeneration = GetGeneration();
18487 nsAtom* Document::GetContentLanguageAsAtomForStyle() const {
18488 // Content-Language may be a comma-separated list of language codes,
18489 // in which case the HTML5 spec says to treat it as unknown
18490 if (mContentLanguage &&
18491 !nsDependentAtomString(mContentLanguage).Contains(char16_t(','))) {
18492 return GetContentLanguage();
18495 return nullptr;
18498 nsAtom* Document::GetLanguageForStyle() const {
18499 if (nsAtom* lang = GetContentLanguageAsAtomForStyle()) {
18500 return lang;
18502 return mLanguageFromCharset.get();
18505 void Document::GetContentLanguageForBindings(DOMString& aString) const {
18506 aString.SetKnownLiveAtom(mContentLanguage, DOMString::eTreatNullAsEmpty);
18509 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
18510 nsAtom* aLanguage, bool* aNeedsToCache) const {
18511 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
18512 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
18515 void Document::DoCacheAllKnownLangPrefs() {
18516 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
18517 RefPtr<nsAtom> lang = GetLanguageForStyle();
18518 StaticPresData* data = StaticPresData::Get();
18519 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
18520 data->GetFontPrefsForLang(nsGkAtoms::x_math);
18521 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
18522 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
18523 for (const auto& key : mLanguagesUsed) {
18524 data->GetFontPrefsForLang(key);
18526 mMayNeedFontPrefsUpdate = false;
18529 void Document::RecomputeLanguageFromCharset() {
18530 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
18531 RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
18532 if (language == nsGkAtoms::Unicode) {
18533 language = service->GetLocaleLanguage();
18536 if (language == mLanguageFromCharset) {
18537 return;
18540 mMayNeedFontPrefsUpdate = true;
18541 mLanguageFromCharset = std::move(language);
18544 nsICookieJarSettings* Document::CookieJarSettings() {
18545 // If we are here, this is probably a javascript: URL document. In any case,
18546 // we must have a nsCookieJarSettings. Let's create it.
18547 if (!mCookieJarSettings) {
18548 Document* inProcessParent = GetInProcessParentDocument();
18550 if (inProcessParent) {
18551 mCookieJarSettings = net::CookieJarSettings::Create(
18552 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
18553 mozilla::net::CookieJarSettings::Cast(
18554 inProcessParent->CookieJarSettings())
18555 ->GetPartitionKey(),
18556 inProcessParent->CookieJarSettings()->GetIsFirstPartyIsolated(),
18557 inProcessParent->CookieJarSettings()
18558 ->GetIsOnContentBlockingAllowList(),
18559 inProcessParent->CookieJarSettings()
18560 ->GetShouldResistFingerprinting());
18562 // Inherit the fingerprinting random key from the parent.
18563 nsTArray<uint8_t> randomKey;
18564 nsresult rv = inProcessParent->CookieJarSettings()
18565 ->GetFingerprintingRandomizationKey(randomKey);
18567 if (NS_SUCCEEDED(rv)) {
18568 net::CookieJarSettings::Cast(mCookieJarSettings)
18569 ->SetFingerprintingRandomizationKey(randomKey);
18571 } else {
18572 mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
18575 if (auto* wgc = GetWindowGlobalChild()) {
18576 net::CookieJarSettingsArgs csArgs;
18577 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
18578 // Update cookie settings in the parent process
18579 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
18580 NS_WARNING(
18581 "Failed to update document's cookie jar settings on the "
18582 "WindowGlobalParent");
18587 return mCookieJarSettings;
18590 bool Document::UsingStorageAccess() {
18591 if (WindowContext* wc = GetWindowContext()) {
18592 return wc->GetUsingStorageAccess();
18595 // If we don't yet have a window context, we have to use the decision
18596 // from the Document's Channel's LoadInfo directly.
18597 if (!mChannel) {
18598 return false;
18601 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18602 return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
18605 bool Document::HasStorageAccessPermissionGrantedByAllowList() {
18606 // We only care about if the document gets the storage permission via the
18607 // allow list here. So we don't check the storage access cache in the inner
18608 // window.
18610 if (!mChannel) {
18611 return false;
18614 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18615 return loadInfo->GetStoragePermission() ==
18616 nsILoadInfo::StoragePermissionAllowListed;
18619 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
18620 if (!StaticPrefs::
18621 privacy_partition_always_partition_third_party_non_cookie_storage()) {
18622 return EffectiveCookiePrincipal();
18625 nsPIDOMWindowInner* inner = GetInnerWindow();
18626 if (!inner) {
18627 return NodePrincipal();
18630 // Return our cached storage principal if one exists.
18631 if (mActiveStoragePrincipal) {
18632 return mActiveStoragePrincipal;
18635 // Calling StorageAllowedForDocument will notify the ContentBlockLog. This
18636 // loads TrackingDBService.jsm, which in turn pulls in osfile.jsm, making us
18637 // fail // browser/base/content/test/performance/browser_startup.js. To avoid
18638 // that, we short-circuit the check here by allowing storage access to system
18639 // and addon principles, avoiding the test-failure.
18640 nsIPrincipal* principal = NodePrincipal();
18641 if (principal && (principal->IsSystemPrincipal() ||
18642 principal->GetIsAddonOrExpandedAddonPrincipal())) {
18643 return mActiveStoragePrincipal = NodePrincipal();
18646 auto cookieJarSettings = const_cast<Document*>(this)->CookieJarSettings();
18647 if (cookieJarSettings->GetIsOnContentBlockingAllowList()) {
18648 return mActiveStoragePrincipal = NodePrincipal();
18651 StorageAccess storageAccess = StorageAllowedForDocument(this);
18652 if (!ShouldPartitionStorage(storageAccess) ||
18653 !StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
18654 return mActiveStoragePrincipal = NodePrincipal();
18657 Unused << NS_WARN_IF(NS_FAILED(StoragePrincipalHelper::GetPrincipal(
18658 nsGlobalWindowInner::Cast(inner),
18659 StoragePrincipalHelper::eForeignPartitionedPrincipal,
18660 getter_AddRefs(mActiveStoragePrincipal))));
18661 return mActiveStoragePrincipal;
18664 nsIPrincipal* Document::EffectiveCookiePrincipal() const {
18665 nsPIDOMWindowInner* inner = GetInnerWindow();
18666 if (!inner) {
18667 return NodePrincipal();
18670 // Return our cached storage principal if one exists.
18671 if (mActiveCookiePrincipal) {
18672 return mActiveCookiePrincipal;
18675 // We use the lower-level ContentBlocking API here to ensure this
18676 // check doesn't send notifications.
18677 uint32_t rejectedReason = 0;
18678 if (ShouldAllowAccessFor(inner, GetDocumentURI(), &rejectedReason)) {
18679 return mActiveCookiePrincipal = NodePrincipal();
18682 // Let's use the storage principal only if we need to partition the cookie
18683 // jar. When the permission is granted, access will be different and the
18684 // normal principal will be used.
18685 if (ShouldPartitionStorage(rejectedReason) &&
18686 !StoragePartitioningEnabled(
18687 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
18688 return mActiveCookiePrincipal = NodePrincipal();
18691 return mActiveCookiePrincipal = mPartitionedPrincipal;
18694 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
18695 // If the document is sandboxed document or data: document, we should
18696 // get URI of the parent document.
18697 for (const Document* document = this;
18698 document && document->IsContentDocument();
18699 document = document->GetInProcessParentDocument()) {
18700 // The document URI may be about:blank even if it comes from actual web
18701 // site. Therefore, we need to check the URI of its principal.
18702 nsIPrincipal* principal = document->NodePrincipal();
18703 if (principal->GetIsNullPrincipal()) {
18704 continue;
18706 return principal;
18708 return nullptr;
18711 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
18712 mIsInitialDocumentInWindow = aIsInitialDocument;
18714 // Asynchronously tell the parent process that we are, or are no longer, the
18715 // initial document. This happens async.
18716 if (auto* wgc = GetWindowGlobalChild()) {
18717 wgc->SendSetIsInitialDocument(aIsInitialDocument);
18721 // static
18722 void Document::AddToplevelLoadingDocument(Document* aDoc) {
18723 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18724 // Currently we're interested in foreground documents only, so bail out early.
18725 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
18726 return;
18729 if (!sLoadingForegroundTopLevelContentDocument) {
18730 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
18731 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18732 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18733 if (idleScheduler) {
18734 idleScheduler->SendRunningPrioritizedOperation();
18737 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
18738 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
18742 // static
18743 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
18744 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18745 if (sLoadingForegroundTopLevelContentDocument) {
18746 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
18747 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
18748 delete sLoadingForegroundTopLevelContentDocument;
18749 sLoadingForegroundTopLevelContentDocument = nullptr;
18751 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18752 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18753 if (idleScheduler) {
18754 idleScheduler->SendPrioritizedOperationDone();
18760 ColorScheme Document::DefaultColorScheme() const {
18761 return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
18764 ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
18765 if (ShouldResistFingerprinting(RFPTarget::CSSPrefersColorScheme) &&
18766 aIgnoreRFP == IgnoreRFP::No) {
18767 return ColorScheme::Light;
18770 if (nsPresContext* pc = GetPresContext()) {
18771 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
18772 return *scheme;
18776 return PreferenceSheet::PrefsFor(*this).mColorScheme;
18779 bool Document::HasRecentlyStartedForegroundLoads() {
18780 if (!sLoadingForegroundTopLevelContentDocument) {
18781 return false;
18784 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
18785 ++i) {
18786 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
18787 // A page loaded in foreground could be in background now.
18788 if (!doc->IsInBackgroundWindow()) {
18789 nsPIDOMWindowInner* win = doc->GetInnerWindow();
18790 if (win) {
18791 Performance* perf = win->GetPerformance();
18792 if (perf &&
18793 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
18794 return true;
18800 // Didn't find any loading foreground documents, just clear the array.
18801 delete sLoadingForegroundTopLevelContentDocument;
18802 sLoadingForegroundTopLevelContentDocument = nullptr;
18804 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18805 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18806 if (idleScheduler) {
18807 idleScheduler->SendPrioritizedOperationDone();
18809 return false;
18812 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
18813 nsFrameLoader* aStaticCloneOf) {
18814 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
18815 clone->mElement = aElement;
18816 clone->mStaticCloneOf = aStaticCloneOf;
18819 bool Document::ShouldAvoidNativeTheme() const {
18820 return StaticPrefs::widget_non_native_theme_enabled() &&
18821 (!IsInChromeDocShell() || XRE_IsContentProcess());
18824 bool Document::UseRegularPrincipal() const {
18825 return EffectiveStoragePrincipal() == NodePrincipal();
18828 bool Document::HasThirdPartyChannel() {
18829 nsCOMPtr<nsIChannel> channel = GetChannel();
18830 if (channel) {
18831 // We assume that the channel is a third-party by default.
18832 bool thirdParty = true;
18834 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
18835 components::ThirdPartyUtil::Service();
18836 if (!thirdPartyUtil) {
18837 return thirdParty;
18840 // Check that if the channel is a third-party to its parent.
18841 nsresult rv =
18842 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
18843 if (NS_FAILED(rv)) {
18844 // Assume third-party in case of failure
18845 thirdParty = true;
18848 return thirdParty;
18851 if (mParentDocument) {
18852 return mParentDocument->HasThirdPartyChannel();
18855 return false;
18858 bool Document::IsLikelyContentInaccessibleTopLevelAboutBlank() const {
18859 if (!mDocumentURI || !NS_IsAboutBlank(mDocumentURI)) {
18860 return false;
18862 // FIXME(emilio): This is not quite edge-case free. See bug 1860098.
18864 // For stuff in frames, that makes our per-document telemetry probes not
18865 // really reliable but doesn't affect the correctness of our page probes, so
18866 // it's not too terrible.
18867 BrowsingContext* bc = GetBrowsingContext();
18868 return bc && bc->IsTop() && !bc->HadOriginalOpener();
18871 bool Document::ShouldIncludeInTelemetry(bool aAllowExtensionURIs) {
18872 if (!IsContentDocument() && !IsResourceDoc()) {
18873 return false;
18876 if (IsLikelyContentInaccessibleTopLevelAboutBlank()) {
18877 return false;
18880 nsIPrincipal* prin = NodePrincipal();
18881 if (!aAllowExtensionURIs && prin->GetIsAddonOrExpandedAddonPrincipal()) {
18882 return false;
18885 // TODO(emilio): Should this use GetIsContentPrincipal() +
18886 // GetPrecursorPrincipal() instead (accounting for add-ons separately)?
18887 if (prin->IsSystemPrincipal() || prin->SchemeIs("about") ||
18888 prin->SchemeIs("chrome") || prin->SchemeIs("resource")) {
18889 return false;
18892 return true;
18895 void Document::GetConnectedShadowRoots(
18896 nsTArray<RefPtr<ShadowRoot>>& aOut) const {
18897 AppendToArray(aOut, mComposedShadowRoots);
18900 void Document::AddMediaElementWithMSE() {
18901 if (mMediaElementWithMSECount++ == 0) {
18902 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18903 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18908 void Document::RemoveMediaElementWithMSE() {
18909 MOZ_ASSERT(mMediaElementWithMSECount > 0);
18910 if (--mMediaElementWithMSECount == 0) {
18911 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18912 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18917 void Document::UnregisterFromMemoryReportingForDataDocument() {
18918 if (!mAddedToMemoryReportingAsDataDocument) {
18919 return;
18921 mAddedToMemoryReportingAsDataDocument = false;
18922 nsIGlobalObject* global = GetScopeObject();
18923 if (global) {
18924 if (nsPIDOMWindowInner* win = global->GetAsInnerWindow()) {
18925 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
18926 this);
18930 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
18931 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
18932 mOOPChildrenLoading.AppendElement(aChild);
18933 if (mOOPChildrenLoading.Length() == 1) {
18934 // Let's block unload so that we're blocked from going into the BFCache
18935 // until the child has actually notified us that it has done loading.
18936 BlockOnload();
18940 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
18941 // aChild will not be in the list if nsDocLoader::Stop() was called, since
18942 // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
18943 // so we don't need to call DocLoaderIsEmpty in that case.
18944 if (mOOPChildrenLoading.RemoveElement(aChild)) {
18945 if (mOOPChildrenLoading.IsEmpty()) {
18946 UnblockOnload(false);
18948 RefPtr<nsDocLoader> docLoader(mDocumentContainer);
18949 if (docLoader) {
18950 docLoader->OOPChildrenLoadingIsEmpty();
18955 void Document::ClearOOPChildrenLoading() {
18956 nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
18957 mOOPChildrenLoading.SwapElements(oopChildrenLoading);
18958 if (!oopChildrenLoading.IsEmpty()) {
18959 UnblockOnload(false);
18963 bool Document::MayHaveDOMActivateListeners() const {
18964 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
18965 return inner->HasDOMActivateEventListeners();
18968 // If we can't get information from the window object, default to true.
18969 return true;
18972 HighlightRegistry& Document::HighlightRegistry() {
18973 if (!mHighlightRegistry) {
18974 mHighlightRegistry = MakeRefPtr<class HighlightRegistry>(this);
18976 return *mHighlightRegistry;
18979 RadioGroupContainer& Document::OwnedRadioGroupContainer() {
18980 if (!mRadioGroupContainer) {
18981 mRadioGroupContainer = MakeUnique<RadioGroupContainer>();
18983 return *mRadioGroupContainer;
18986 void Document::UpdateHiddenByContentVisibilityForAnimations() {
18987 for (AnimationTimeline* timeline : Timelines()) {
18988 timeline->UpdateHiddenByContentVisibility();
18991 } // namespace mozilla::dom