Backed out changeset 31ce1e9b688c (bug 1939787) for causing rust related BR bustage...
[gecko.git] / dom / base / Document.cpp
blob96d64cdef4404020a2a3fc4c23664481c3cbaf38
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/BounceTrackingProtection.h"
50 #include "mozilla/CSSEnabledState.h"
51 #include "mozilla/ContentBlockingAllowList.h"
52 #include "mozilla/ContentBlockingNotifier.h"
53 #include "mozilla/ContentBlockingUserInteraction.h"
54 #include "mozilla/ContentPrincipal.h"
55 #include "mozilla/CycleCollectedJSContext.h"
56 #include "mozilla/DebugOnly.h"
57 #include "mozilla/ProfilerMarkers.h"
58 #include "mozilla/AttributeStyles.h"
59 #include "mozilla/DocumentStyleRootIterator.h"
60 #include "mozilla/EditorBase.h"
61 #include "mozilla/EditorCommands.h"
62 #include "mozilla/Encoding.h"
63 #include "mozilla/ErrorResult.h"
64 #include "mozilla/EventDispatcher.h"
65 #include "mozilla/EventListenerManager.h"
66 #include "mozilla/EventQueue.h"
67 #include "mozilla/EventStateManager.h"
68 #include "mozilla/ExtensionPolicyService.h"
69 #include "mozilla/FullscreenChange.h"
70 #include "mozilla/GlobalStyleSheetCache.h"
71 #include "mozilla/MappedDeclarationsBuilder.h"
72 #include "mozilla/HTMLEditor.h"
73 #include "mozilla/HoldDropJSObjects.h"
74 #include "mozilla/IdentifierMapEntry.h"
75 #include "mozilla/InputTaskManager.h"
76 #include "mozilla/IntegerRange.h"
77 #include "mozilla/InternalMutationEvent.h"
78 #include "mozilla/Likely.h"
79 #include "mozilla/Logging.h"
80 #include "mozilla/LookAndFeel.h"
81 #include "mozilla/MacroForEach.h"
82 #include "mozilla/Maybe.h"
83 #include "mozilla/MediaFeatureChange.h"
84 #include "mozilla/MediaManager.h"
85 #include "mozilla/MemoryReporting.h"
86 #include "mozilla/NullPrincipal.h"
87 #include "mozilla/OriginAttributes.h"
88 #include "mozilla/OwningNonNull.h"
89 #include "mozilla/PendingFullscreenEvent.h"
90 #include "mozilla/PermissionDelegateHandler.h"
91 #include "mozilla/PermissionManager.h"
92 #include "mozilla/Preferences.h"
93 #include "mozilla/PreloadHashKey.h"
94 #include "mozilla/PresShell.h"
95 #include "mozilla/PresShellForwards.h"
96 #include "mozilla/PresShellInlines.h"
97 #include "mozilla/PseudoStyleType.h"
98 #include "mozilla/RefCountType.h"
99 #include "mozilla/RelativeTo.h"
100 #include "mozilla/RestyleManager.h"
101 #include "mozilla/ReverseIterator.h"
102 #include "mozilla/SchedulerGroup.h"
103 #include "mozilla/ScrollTimelineAnimationTracker.h"
104 #include "mozilla/SMILAnimationController.h"
105 #include "mozilla/SMILTimeContainer.h"
106 #include "mozilla/ScopeExit.h"
107 #include "mozilla/Components.h"
108 #include "mozilla/SVGUtils.h"
109 #include "mozilla/ServoStyleConsts.h"
110 #include "mozilla/ServoTypes.h"
111 #include "mozilla/SizeOfState.h"
112 #include "mozilla/Span.h"
113 #include "mozilla/Sprintf.h"
114 #include "mozilla/StaticAnalysisFunctions.h"
115 #include "mozilla/StaticPrefs_apz.h"
116 #include "mozilla/StaticPrefs_browser.h"
117 #include "mozilla/StaticPrefs_docshell.h"
118 #include "mozilla/StaticPrefs_dom.h"
119 #include "mozilla/StaticPrefs_fission.h"
120 #include "mozilla/StaticPrefs_full_screen_api.h"
121 #include "mozilla/StaticPrefs_layout.h"
122 #include "mozilla/StaticPrefs_network.h"
123 #include "mozilla/StaticPrefs_page_load.h"
124 #include "mozilla/StaticPrefs_privacy.h"
125 #include "mozilla/StaticPrefs_security.h"
126 #include "mozilla/StaticPrefs_widget.h"
127 #include "mozilla/StaticPresData.h"
128 #include "mozilla/StorageAccess.h"
129 #include "mozilla/StoragePrincipalHelper.h"
130 #include "mozilla/StyleSheet.h"
131 #include "mozilla/Telemetry.h"
132 #include "mozilla/TelemetryScalarEnums.h"
133 #include "mozilla/TextControlElement.h"
134 #include "mozilla/TextEditor.h"
135 #include "mozilla/TypedEnumBits.h"
136 #include "mozilla/URLDecorationStripper.h"
137 #include "mozilla/URLExtraData.h"
138 #include "mozilla/Unused.h"
139 #include "mozilla/css/ImageLoader.h"
140 #include "mozilla/css/Loader.h"
141 #include "mozilla/css/Rule.h"
142 #include "mozilla/css/SheetParsingMode.h"
143 #include "mozilla/dom/AnonymousContent.h"
144 #include "mozilla/dom/BlobURLProtocolHandler.h"
145 #include "mozilla/dom/BrowserChild.h"
146 #include "mozilla/dom/BrowsingContext.h"
147 #include "mozilla/dom/BrowsingContextGroup.h"
148 #include "mozilla/dom/CanonicalBrowsingContext.h"
149 #include "mozilla/dom/CanvasRenderingContextHelper.h"
150 #include "mozilla/dom/CDATASection.h"
151 #include "mozilla/dom/CSPDictionariesBinding.h"
152 #include "mozilla/dom/ChromeObserver.h"
153 #include "mozilla/dom/ClientInfo.h"
154 #include "mozilla/dom/ClientState.h"
155 #include "mozilla/dom/CloseWatcherManager.h"
156 #include "mozilla/dom/Comment.h"
157 #include "mozilla/dom/ContentChild.h"
158 #include "mozilla/dom/CSSBinding.h"
159 #include "mozilla/dom/CSSCustomPropertyRegisteredEvent.h"
160 #include "mozilla/dom/DOMImplementation.h"
161 #include "mozilla/dom/DOMIntersectionObserver.h"
162 #include "mozilla/dom/DOMStringList.h"
163 #include "mozilla/dom/DocGroup.h"
164 #include "mozilla/dom/DocumentBinding.h"
165 #include "mozilla/dom/DocumentFragment.h"
166 #include "mozilla/dom/DocumentL10n.h"
167 #include "mozilla/dom/DocumentTimeline.h"
168 #include "mozilla/dom/DocumentType.h"
169 #include "mozilla/dom/ElementBinding.h"
170 #include "mozilla/dom/ErrorEvent.h"
171 #include "mozilla/dom/Event.h"
172 #include "mozilla/dom/EventListenerBinding.h"
173 #include "mozilla/dom/FailedCertSecurityInfoBinding.h"
174 #include "mozilla/dom/FeaturePolicy.h"
175 #include "mozilla/dom/FeaturePolicyUtils.h"
176 #include "mozilla/dom/FontFaceSet.h"
177 #include "mozilla/dom/FragmentDirective.h"
178 #include "mozilla/dom/fragmentdirectives_ffi_generated.h"
179 #include "mozilla/dom/FromParser.h"
180 #include "mozilla/dom/HighlightRegistry.h"
181 #include "mozilla/dom/HTMLAllCollection.h"
182 #include "mozilla/dom/HTMLBodyElement.h"
183 #include "mozilla/dom/HTMLCollectionBinding.h"
184 #include "mozilla/dom/HTMLDialogElement.h"
185 #include "mozilla/dom/HTMLEmbedElement.h"
186 #include "mozilla/dom/HTMLFormElement.h"
187 #include "mozilla/dom/HTMLIFrameElement.h"
188 #include "mozilla/dom/HTMLImageElement.h"
189 #include "mozilla/dom/HTMLInputElement.h"
190 #include "mozilla/dom/HTMLLinkElement.h"
191 #include "mozilla/dom/HTMLMediaElement.h"
192 #include "mozilla/dom/HTMLMetaElement.h"
193 #include "mozilla/dom/HTMLObjectElement.h"
194 #include "mozilla/dom/HTMLSharedElement.h"
195 #include "mozilla/dom/HTMLTextAreaElement.h"
196 #include "mozilla/dom/ImageTracker.h"
197 #include "mozilla/dom/InspectorUtils.h"
198 #include "mozilla/dom/InteractiveWidget.h"
199 #include "mozilla/dom/Link.h"
200 #include "mozilla/dom/MediaQueryList.h"
201 #include "mozilla/dom/MediaSource.h"
202 #include "mozilla/dom/MutationObservers.h"
203 #include "mozilla/dom/NameSpaceConstants.h"
204 #include "mozilla/dom/Navigator.h"
205 #include "mozilla/dom/NetErrorInfoBinding.h"
206 #include "mozilla/dom/NodeInfo.h"
207 #include "mozilla/dom/NodeIterator.h"
208 #include "mozilla/dom/nsHTTPSOnlyUtils.h"
209 #include "mozilla/dom/PContentChild.h"
210 #include "mozilla/dom/PWindowGlobalChild.h"
211 #include "mozilla/dom/PageTransitionEvent.h"
212 #include "mozilla/dom/PageTransitionEventBinding.h"
213 #include "mozilla/dom/Performance.h"
214 #include "mozilla/dom/PermissionMessageUtils.h"
215 #include "mozilla/dom/PostMessageEvent.h"
216 #include "mozilla/dom/ProcessingInstruction.h"
217 #include "mozilla/dom/Promise.h"
218 #include "mozilla/dom/PromiseNativeHandler.h"
219 #include "mozilla/dom/RemoteBrowser.h"
220 #include "mozilla/dom/ResizeObserver.h"
221 #include "mozilla/dom/RustTypes.h"
222 #include "mozilla/dom/SVGElement.h"
223 #include "mozilla/dom/SVGDocument.h"
224 #include "mozilla/dom/SVGSVGElement.h"
225 #include "mozilla/dom/SVGUseElement.h"
226 #include "mozilla/dom/ScriptLoader.h"
227 #include "mozilla/dom/ScriptSettings.h"
228 #include "mozilla/dom/Selection.h"
229 #include "mozilla/dom/ServiceWorkerContainer.h"
230 #include "mozilla/dom/ServiceWorkerDescriptor.h"
231 #include "mozilla/dom/ServiceWorkerManager.h"
232 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
233 #include "mozilla/dom/ShadowRoot.h"
234 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
235 #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
236 #include "mozilla/dom/StyleSheetList.h"
237 #include "mozilla/dom/StyleSheetRemovedEvent.h"
238 #include "mozilla/dom/StyleSheetRemovedEventBinding.h"
239 #include "mozilla/dom/TimeoutManager.h"
240 #include "mozilla/dom/ToggleEvent.h"
241 #include "mozilla/dom/Touch.h"
242 #include "mozilla/dom/TouchEvent.h"
243 #include "mozilla/dom/TreeOrderedArrayInlines.h"
244 #include "mozilla/dom/TreeWalker.h"
245 #include "mozilla/dom/TrustedHTML.h"
246 #include "mozilla/dom/TrustedTypeUtils.h"
247 #include "mozilla/dom/TrustedTypesConstants.h"
248 #include "mozilla/dom/URL.h"
249 #include "mozilla/dom/UseCounterMetrics.h"
250 #include "mozilla/dom/UserActivation.h"
251 #include "mozilla/dom/ViewTransition.h"
252 #include "mozilla/dom/WakeLockJS.h"
253 #include "mozilla/dom/WakeLockSentinel.h"
254 #include "mozilla/dom/WindowBinding.h"
255 #include "mozilla/dom/WindowContext.h"
256 #include "mozilla/dom/WindowGlobalChild.h"
257 #include "mozilla/dom/WindowProxyHolder.h"
258 #include "mozilla/dom/WorkerDocumentListener.h"
259 #include "mozilla/dom/XPathEvaluator.h"
260 #include "mozilla/dom/XPathExpression.h"
261 #include "mozilla/dom/nsCSPContext.h"
262 #include "mozilla/dom/nsCSPUtils.h"
263 #include "mozilla/extensions/WebExtensionPolicy.h"
264 #include "mozilla/fallible.h"
265 #include "mozilla/gfx/BaseCoord.h"
266 #include "mozilla/gfx/BaseSize.h"
267 #include "mozilla/gfx/Coord.h"
268 #include "mozilla/gfx/Point.h"
269 #include "mozilla/gfx/ScaleFactor.h"
270 #include "mozilla/glean/GleanMetrics.h"
271 #include "mozilla/intl/LocaleService.h"
272 #include "mozilla/ipc/IdleSchedulerChild.h"
273 #include "mozilla/ipc/MessageChannel.h"
274 #include "mozilla/net/ChannelEventQueue.h"
275 #include "mozilla/net/Cookie.h"
276 #include "mozilla/net/CookieCommons.h"
277 #include "mozilla/net/CookieJarSettings.h"
278 #include "mozilla/net/CookieParser.h"
279 #include "mozilla/net/NeckoChannelParams.h"
280 #include "mozilla/net/RequestContextService.h"
281 #include "nsAboutProtocolUtils.h"
282 #include "nsAttrValue.h"
283 #include "nsAttrValueInlines.h"
284 #include "nsBaseHashtable.h"
285 #include "nsBidiUtils.h"
286 #include "nsCRT.h"
287 #include "nsCSSPropertyID.h"
288 #include "nsCSSProps.h"
289 #include "nsCSSPseudoElements.h"
290 #include "nsCSSRendering.h"
291 #include "nsCanvasFrame.h"
292 #include "nsCaseTreatment.h"
293 #include "nsCharsetSource.h"
294 #include "nsCommandManager.h"
295 #include "nsCommandParams.h"
296 #include "nsComponentManagerUtils.h"
297 #include "nsContentCreatorFunctions.h"
298 #include "nsContentList.h"
299 #include "nsContentPermissionHelper.h"
300 #include "nsContentSecurityUtils.h"
301 #include "nsContentUtils.h"
302 #include "nsCoord.h"
303 #include "nsCycleCollectionNoteChild.h"
304 #include "nsCycleCollectionTraversalCallback.h"
305 #include "nsDOMAttributeMap.h"
306 #include "nsDOMCaretPosition.h"
307 #include "nsDOMNavigationTiming.h"
308 #include "nsDOMString.h"
309 #include "nsDeviceContext.h"
310 #include "nsDocShell.h"
311 #include "nsDocShellLoadTypes.h"
312 #include "nsEffectiveTLDService.h"
313 #include "nsError.h"
314 #include "nsEscape.h"
315 #include "nsFocusManager.h"
316 #include "nsFrameLoader.h"
317 #include "nsFrameLoaderOwner.h"
318 #include "nsGenericHTMLElement.h"
319 #include "nsGlobalWindowInner.h"
320 #include "nsGlobalWindowOuter.h"
321 #include "nsHTMLDocument.h"
322 #include "nsHtml5Module.h"
323 #include "nsHtml5Parser.h"
324 #include "nsHtml5TreeOpExecutor.h"
325 #include "nsIAsyncShutdown.h"
326 #include "nsIAuthPrompt.h"
327 #include "nsIAuthPrompt2.h"
328 #include "nsIBFCacheEntry.h"
329 #include "nsIBaseWindow.h"
330 #include "nsIBrowserChild.h"
331 #include "nsIBrowserUsage.h"
332 #include "nsICSSLoaderObserver.h"
333 #include "nsICategoryManager.h"
334 #include "nsICertOverrideService.h"
335 #include "nsIContent.h"
336 #include "nsIContentInlines.h"
337 #include "nsIContentPolicy.h"
338 #include "nsIContentSecurityPolicy.h"
339 #include "nsIContentSink.h"
340 #include "nsICookieJarSettings.h"
341 #include "nsICookieService.h"
342 #include "nsIDOMXULCommandDispatcher.h"
343 #include "nsIDocShell.h"
344 #include "nsIDocShellTreeItem.h"
345 #include "nsIDocumentActivity.h"
346 #include "nsIDocumentEncoder.h"
347 #include "nsIDocumentLoader.h"
348 #include "nsIDocumentLoaderFactory.h"
349 #include "nsIDocumentObserver.h"
350 #include "nsIDNSService.h"
351 #include "nsIEditingSession.h"
352 #include "nsIEditor.h"
353 #include "nsIEffectiveTLDService.h"
354 #include "nsIFile.h"
355 #include "nsIFileChannel.h"
356 #include "nsIFrame.h"
357 #include "nsIGlobalObject.h"
358 #include "nsIHTMLCollection.h"
359 #include "nsIHttpChannel.h"
360 #include "nsIHttpChannelInternal.h"
361 #include "nsIIOService.h"
362 #include "nsIImageLoadingContent.h"
363 #include "nsIInlineSpellChecker.h"
364 #include "nsIInputStreamChannel.h"
365 #include "nsIInterfaceRequestorUtils.h"
366 #include "nsILayoutHistoryState.h"
367 #include "nsIMultiPartChannel.h"
368 #include "nsIMutationObserver.h"
369 #include "nsINSSErrorsService.h"
370 #include "nsINamed.h"
371 #include "nsINodeList.h"
372 #include "nsIObjectLoadingContent.h"
373 #include "nsIObserverService.h"
374 #include "nsIPermission.h"
375 #include "nsIPrompt.h"
376 #include "nsIPropertyBag2.h"
377 #include "nsIPublicKeyPinningService.h"
378 #include "nsIReferrerInfo.h"
379 #include "nsIRefreshURI.h"
380 #include "nsIRequest.h"
381 #include "nsIRequestContext.h"
382 #include "nsIRunnable.h"
383 #include "nsISHEntry.h"
384 #include "nsIScriptElement.h"
385 #include "nsIScriptError.h"
386 #include "nsIScriptGlobalObject.h"
387 #include "nsIScriptSecurityManager.h"
388 #include "nsISecurityConsoleMessage.h"
389 #include "nsISelectionController.h"
390 #include "nsISerialEventTarget.h"
391 #include "nsISimpleEnumerator.h"
392 #include "nsISiteSecurityService.h"
393 #include "nsISocketProvider.h"
394 #include "nsISpeculativeConnect.h"
395 #include "nsIStructuredCloneContainer.h"
396 #include "nsIThread.h"
397 #include "nsITimedChannel.h"
398 #include "nsITimer.h"
399 #include "nsITransportSecurityInfo.h"
400 #include "nsIURIMutator.h"
401 #include "nsIVariant.h"
402 #include "nsIWeakReference.h"
403 #include "nsIWebNavigation.h"
404 #include "nsIWidget.h"
405 #include "nsIX509Cert.h"
406 #include "nsIX509CertValidity.h"
407 #include "nsIXMLContentSink.h"
408 #include "nsIHTMLContentSink.h"
409 #include "nsIXULRuntime.h"
410 #include "nsImageLoadingContent.h"
411 #include "nsImportModule.h"
412 #include "nsLanguageAtomService.h"
413 #include "nsLayoutUtils.h"
414 #include "nsMimeTypes.h"
415 #include "nsNetCID.h"
416 #include "nsNetUtil.h"
417 #include "nsNodeInfoManager.h"
418 #include "nsObjectLoadingContent.h"
419 #include "nsPIDOMWindowInlines.h"
420 #include "nsPIWindowRoot.h"
421 #include "nsPoint.h"
422 #include "nsPointerHashKeys.h"
423 #include "nsPresContext.h"
424 #include "nsQueryFrame.h"
425 #include "nsQueryObject.h"
426 #include "nsRange.h"
427 #include "nsRect.h"
428 #include "nsRefreshDriver.h"
429 #include "nsSandboxFlags.h"
430 #include "nsSerializationHelper.h"
431 #include "nsServiceManagerUtils.h"
432 #include "nsStringFlags.h"
433 #include "nsStyleUtil.h"
434 #include "nsStringIterator.h"
435 #include "nsStyleSheetService.h"
436 #include "nsStyleStruct.h"
437 #include "nsTextControlFrame.h"
438 #include "nsSubDocumentFrame.h"
439 #include "nsTextNode.h"
440 #include "nsUnicharUtils.h"
441 #include "nsWrapperCache.h"
442 #include "nsWrapperCacheInlines.h"
443 #include "nsXPCOMCID.h"
444 #include "nsXULAppAPI.h"
445 #include "prthread.h"
446 #include "prtime.h"
447 #include "prtypes.h"
448 #include "xpcpublic.h"
450 // XXX Must be included after mozilla/Encoding.h
451 #include "encoding_rs.h"
453 #include "mozilla/dom/XULBroadcastManager.h"
454 #include "mozilla/dom/XULPersist.h"
455 #include "nsIAppWindow.h"
456 #include "nsXULPrototypeDocument.h"
457 #include "nsXULCommandDispatcher.h"
458 #include "nsXULPopupManager.h"
459 #include "nsIDocShellTreeOwner.h"
461 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
462 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
463 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
464 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
466 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
468 mozilla::LazyLogModule gPageCacheLog("PageCache");
469 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
470 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
471 mozilla::LazyLogModule gUseCountersLog("UseCounters");
473 namespace mozilla {
475 using namespace net;
477 namespace dom {
479 class Document::HeaderData {
480 public:
481 HeaderData(nsAtom* aField, const nsAString& aData)
482 : mField(aField), mData(aData) {}
484 ~HeaderData() {
485 // Delete iteratively to avoid blowing up the stack, though it shouldn't
486 // happen in practice.
487 UniquePtr<HeaderData> next = std::move(mNext);
488 while (next) {
489 next = std::move(next->mNext);
493 RefPtr<nsAtom> mField;
494 nsString mData;
495 UniquePtr<HeaderData> mNext;
498 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
499 nullptr;
501 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
502 static LazyLogModule gCspPRLog("CSP");
503 LazyLogModule gUserInteractionPRLog("UserInteraction");
505 static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
506 nsIHttpChannel** aHttpChannel) {
507 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
508 if (httpChannel) {
509 httpChannel.forget(aHttpChannel);
510 return NS_OK;
513 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
514 if (!multipart) {
515 *aHttpChannel = nullptr;
516 return NS_OK;
519 nsCOMPtr<nsIChannel> baseChannel;
520 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
521 if (NS_WARN_IF(NS_FAILED(rv))) {
522 return rv;
525 httpChannel = do_QueryInterface(baseChannel);
526 httpChannel.forget(aHttpChannel);
528 return NS_OK;
531 } // namespace dom
533 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
535 IdentifierMapEntry::IdentifierMapEntry(
536 const IdentifierMapEntry::DependentAtomOrString* aKey)
537 : mKey(aKey ? *aKey : nullptr) {}
539 void IdentifierMapEntry::Traverse(
540 nsCycleCollectionTraversalCallback* aCallback) {
541 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
542 "mIdentifierMap mNameContentList");
543 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
545 if (mImageElement) {
546 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
547 "mIdentifierMap mImageElement element");
548 nsIContent* imageElement = mImageElement;
549 aCallback->NoteXPCOMChild(imageElement);
553 bool IdentifierMapEntry::IsEmpty() {
554 return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
555 !mImageElement;
558 bool IdentifierMapEntry::HasNameElement() const {
559 return mNameContentList && mNameContentList->Length() != 0;
562 void IdentifierMapEntry::AddContentChangeCallback(
563 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
564 if (!mChangeCallbacks) {
565 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
568 ChangeCallback cc = {aCallback, aData, aForImage};
569 mChangeCallbacks->PutEntry(cc);
572 void IdentifierMapEntry::RemoveContentChangeCallback(
573 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
574 if (!mChangeCallbacks) return;
575 ChangeCallback cc = {aCallback, aData, aForImage};
576 mChangeCallbacks->RemoveEntry(cc);
577 if (mChangeCallbacks->Count() == 0) {
578 mChangeCallbacks = nullptr;
582 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
583 Element* aNewElement,
584 bool aImageOnly) {
585 if (!mChangeCallbacks) return;
587 for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) {
588 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
589 // Don't fire image changes for non-image observers, and don't fire element
590 // changes for image observers when an image override is active.
591 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
592 continue;
595 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
596 iter.Remove();
601 void IdentifierMapEntry::AddIdElement(Element* aElement) {
602 MOZ_ASSERT(aElement, "Must have element");
603 MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
605 size_t index = mIdContentList.Insert(*aElement);
606 if (index == 0) {
607 Element* oldElement = mIdContentList->SafeElementAt(1);
608 FireChangeCallbacks(oldElement, aElement);
612 void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
613 MOZ_ASSERT(aElement, "Missing element");
615 // This should only be called while the document is in an update.
616 // Assertions near the call to this method guarantee this.
618 // This could fire in OOM situations
619 // Only assert this in HTML documents for now as XUL does all sorts of weird
620 // crap.
621 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
622 mIdContentList->Contains(aElement),
623 "Removing id entry that doesn't exist");
625 // XXXbz should this ever Compact() I guess when all the content is gone
626 // we'll just get cleaned up in the natural order of things...
627 Element* currentElement = mIdContentList->SafeElementAt(0);
628 mIdContentList.RemoveElement(*aElement);
629 if (currentElement == aElement) {
630 FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
634 void IdentifierMapEntry::SetImageElement(Element* aElement) {
635 Element* oldElement = GetImageIdElement();
636 mImageElement = aElement;
637 Element* newElement = GetImageIdElement();
638 if (oldElement != newElement) {
639 FireChangeCallbacks(oldElement, newElement, true);
643 void IdentifierMapEntry::ClearAndNotify() {
644 Element* currentElement = mIdContentList->SafeElementAt(0);
645 mIdContentList.Clear();
646 if (currentElement) {
647 FireChangeCallbacks(currentElement, nullptr);
649 mNameContentList = nullptr;
650 if (mImageElement) {
651 SetImageElement(nullptr);
653 mChangeCallbacks = nullptr;
656 namespace dom {
658 class SimpleHTMLCollection final : public nsSimpleContentList,
659 public nsIHTMLCollection {
660 public:
661 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
663 NS_DECL_ISUPPORTS_INHERITED
665 virtual nsINode* GetParentObject() override {
666 return nsSimpleContentList::GetParentObject();
668 virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
669 virtual Element* GetElementAt(uint32_t aIndex) override {
670 return mElements.SafeElementAt(aIndex)->AsElement();
673 virtual Element* GetFirstNamedElement(const nsAString& aName,
674 bool& aFound) override {
675 aFound = false;
676 RefPtr<nsAtom> name = NS_Atomize(aName);
677 for (uint32_t i = 0; i < mElements.Length(); i++) {
678 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
679 Element* element = mElements[i]->AsElement();
680 if (element->GetID() == name ||
681 (element->HasName() &&
682 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
683 aFound = true;
684 return element;
687 return nullptr;
690 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
691 AutoTArray<nsAtom*, 8> atoms;
692 for (uint32_t i = 0; i < mElements.Length(); i++) {
693 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
694 Element* element = mElements[i]->AsElement();
696 nsAtom* id = element->GetID();
697 MOZ_ASSERT(id != nsGkAtoms::_empty);
698 if (id && !atoms.Contains(id)) {
699 atoms.AppendElement(id);
702 if (element->HasName()) {
703 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
704 MOZ_ASSERT(name && name != nsGkAtoms::_empty);
705 if (name && !atoms.Contains(name)) {
706 atoms.AppendElement(name);
711 nsString* names = aNames.AppendElements(atoms.Length());
712 for (uint32_t i = 0; i < atoms.Length(); i++) {
713 atoms[i]->ToString(names[i]);
717 virtual JSObject* GetWrapperPreserveColorInternal() override {
718 return nsWrapperCache::GetWrapperPreserveColor();
720 virtual void PreserveWrapperInternal(
721 nsISupports* aScriptObjectHolder) override {
722 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
724 virtual JSObject* WrapObject(JSContext* aCx,
725 JS::Handle<JSObject*> aGivenProto) override {
726 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
729 using nsBaseContentList::Item;
731 private:
732 virtual ~SimpleHTMLCollection() = default;
735 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
736 nsIHTMLCollection)
738 } // namespace dom
740 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
741 if (!mNameContentList) {
742 mNameContentList = new dom::SimpleHTMLCollection(aNode);
745 mNameContentList->AppendElement(aElement);
748 void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
749 if (mNameContentList) {
750 mNameContentList->RemoveElement(aElement);
754 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const {
755 Element* idElement = GetIdElement();
756 return idElement &&
757 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
760 size_t IdentifierMapEntry::SizeOfExcludingThis(
761 MallocSizeOf aMallocSizeOf) const {
762 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
765 // Helper structs for the content->subdoc map
767 class SubDocMapEntry : public PLDHashEntryHdr {
768 public:
769 // Both of these are strong references
770 dom::Element* mKey; // must be first, to look like PLDHashEntryStub
771 dom::Document* mSubDocument;
774 class OnloadBlocker final : public nsIRequest {
775 public:
776 OnloadBlocker() = default;
778 NS_DECL_ISUPPORTS
779 NS_DECL_NSIREQUEST
781 private:
782 ~OnloadBlocker() = default;
785 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
787 NS_IMETHODIMP
788 OnloadBlocker::GetName(nsACString& aResult) {
789 aResult.AssignLiteral("about:document-onload-blocker");
790 return NS_OK;
793 NS_IMETHODIMP
794 OnloadBlocker::IsPending(bool* _retval) {
795 *_retval = true;
796 return NS_OK;
799 NS_IMETHODIMP
800 OnloadBlocker::GetStatus(nsresult* status) {
801 *status = NS_OK;
802 return NS_OK;
805 NS_IMETHODIMP OnloadBlocker::SetCanceledReason(const nsACString& aReason) {
806 return SetCanceledReasonImpl(aReason);
809 NS_IMETHODIMP OnloadBlocker::GetCanceledReason(nsACString& aReason) {
810 return GetCanceledReasonImpl(aReason);
813 NS_IMETHODIMP OnloadBlocker::CancelWithReason(nsresult aStatus,
814 const nsACString& aReason) {
815 return CancelWithReasonImpl(aStatus, aReason);
817 NS_IMETHODIMP
818 OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
819 NS_IMETHODIMP
820 OnloadBlocker::Suspend(void) { return NS_OK; }
821 NS_IMETHODIMP
822 OnloadBlocker::Resume(void) { return NS_OK; }
824 NS_IMETHODIMP
825 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
826 *aLoadGroup = nullptr;
827 return NS_OK;
830 NS_IMETHODIMP
831 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
833 NS_IMETHODIMP
834 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
835 *aLoadFlags = nsIRequest::LOAD_NORMAL;
836 return NS_OK;
839 NS_IMETHODIMP
840 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
841 return GetTRRModeImpl(aTRRMode);
844 NS_IMETHODIMP
845 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
846 return SetTRRModeImpl(aTRRMode);
849 NS_IMETHODIMP
850 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
852 // ==================================================================
854 namespace dom {
856 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
858 Document* ExternalResourceMap::RequestResource(
859 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
860 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
861 // If we ever start allowing non-same-origin loads here, we might need to do
862 // something interesting with aRequestingPrincipal even for the hashtable
863 // gets.
864 MOZ_ASSERT(aURI, "Must have a URI");
865 MOZ_ASSERT(aRequestingNode, "Must have a node");
866 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
867 *aPendingLoad = nullptr;
868 if (mHaveShutDown) {
869 return nullptr;
872 // First, make sure we strip the ref from aURI.
873 nsCOMPtr<nsIURI> clone;
874 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
875 if (NS_FAILED(rv) || !clone) {
876 return nullptr;
879 ExternalResource* resource;
880 mMap.Get(clone, &resource);
881 if (resource) {
882 return resource->mDocument;
885 bool loadStartSucceeded =
886 mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
887 if (!loadEntry) {
888 loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
890 if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
891 aRequestingNode))) {
892 return false;
896 RefPtr<PendingLoad> load(loadEntry.Data());
897 load.forget(aPendingLoad);
898 return true;
900 if (!loadStartSucceeded) {
901 // Make sure we don't thrash things by trying this load again, since
902 // chances are it failed for good reasons (security check, etc).
903 // This must be done outside the WithEntryHandle functor, as it accesses
904 // mPendingLoads.
905 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
908 return nullptr;
911 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
912 nsTArray<RefPtr<Document>> docs(mMap.Count());
913 for (const auto& entry : mMap.Values()) {
914 if (Document* doc = entry->mDocument) {
915 docs.AppendElement(doc);
919 for (auto& doc : docs) {
920 if (aCallback(*doc) == CallState::Stop) {
921 return;
926 void ExternalResourceMap::Traverse(
927 nsCycleCollectionTraversalCallback* aCallback) const {
928 // mPendingLoads will get cleared out as the requests complete, so
929 // no need to worry about those here.
930 for (const auto& entry : mMap) {
931 ExternalResourceMap::ExternalResource* resource = entry.GetWeak();
933 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
934 "mExternalResourceMap.mMap entry"
935 "->mDocument");
936 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
938 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
939 "mExternalResourceMap.mMap entry"
940 "->mViewer");
941 aCallback->NoteXPCOMChild(resource->mViewer);
943 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
944 "mExternalResourceMap.mMap entry"
945 "->mLoadGroup");
946 aCallback->NoteXPCOMChild(resource->mLoadGroup);
950 void ExternalResourceMap::HideViewers() {
951 for (const auto& entry : mMap) {
952 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
953 if (viewer) {
954 viewer->Hide();
959 void ExternalResourceMap::ShowViewers() {
960 for (const auto& entry : mMap) {
961 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
962 if (viewer) {
963 viewer->Show();
968 void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
969 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
971 if (aFromDoc->IsShowing()) {
972 aToDoc->OnPageShow(true, nullptr);
976 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
977 nsIDocumentViewer* aViewer,
978 nsILoadGroup* aLoadGroup,
979 Document* aDisplayDocument) {
980 MOZ_ASSERT(aURI, "Unexpected call");
981 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
982 "Must have both or neither");
984 RefPtr<PendingLoad> load;
985 mPendingLoads.Remove(aURI, getter_AddRefs(load));
987 nsresult rv = NS_OK;
989 nsCOMPtr<Document> doc;
990 if (aViewer) {
991 doc = aViewer->GetDocument();
992 NS_ASSERTION(doc, "Must have a document");
994 doc->SetDisplayDocument(aDisplayDocument);
996 // Make sure that hiding our viewer will tear down its presentation.
997 aViewer->SetSticky(false);
999 rv = aViewer->Init(nullptr, LayoutDeviceIntRect(), nullptr);
1000 if (NS_SUCCEEDED(rv)) {
1001 rv = aViewer->Open(nullptr, nullptr);
1004 if (NS_FAILED(rv)) {
1005 doc = nullptr;
1006 aViewer = nullptr;
1007 aLoadGroup = nullptr;
1011 ExternalResource* newResource =
1012 mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get();
1014 newResource->mDocument = doc;
1015 newResource->mViewer = aViewer;
1016 newResource->mLoadGroup = aLoadGroup;
1017 if (doc) {
1018 if (nsPresContext* pc = doc->GetPresContext()) {
1019 pc->RecomputeBrowsingContextDependentData();
1021 TransferShowingState(aDisplayDocument, doc);
1024 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
1025 for (uint32_t i = 0; i < obs.Length(); ++i) {
1026 obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
1027 nullptr);
1030 return rv;
1033 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
1034 nsIRequestObserver)
1036 NS_IMETHODIMP
1037 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
1038 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
1039 if (map.HaveShutDown()) {
1040 return NS_BINDING_ABORTED;
1043 nsCOMPtr<nsIDocumentViewer> viewer;
1044 nsCOMPtr<nsILoadGroup> loadGroup;
1045 nsresult rv =
1046 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
1048 // Make sure to do this no matter what
1049 nsresult rv2 =
1050 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
1051 if (NS_FAILED(rv)) {
1052 return rv;
1054 if (NS_FAILED(rv2)) {
1055 mTargetListener = nullptr;
1056 return rv2;
1059 return mTargetListener->OnStartRequest(aRequest);
1062 nsresult ExternalResourceMap::PendingLoad::SetupViewer(
1063 nsIRequest* aRequest, nsIDocumentViewer** aViewer,
1064 nsILoadGroup** aLoadGroup) {
1065 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
1066 *aViewer = nullptr;
1067 *aLoadGroup = nullptr;
1069 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
1070 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
1072 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
1073 if (httpChannel) {
1074 bool requestSucceeded;
1075 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
1076 !requestSucceeded) {
1077 // Bail out on this load, since it looks like we have an HTTP error page
1078 return NS_BINDING_ABORTED;
1082 nsAutoCString type;
1083 chan->GetContentType(type);
1085 nsCOMPtr<nsILoadGroup> loadGroup;
1086 chan->GetLoadGroup(getter_AddRefs(loadGroup));
1088 // Give this document its own loadgroup
1089 nsCOMPtr<nsILoadGroup> newLoadGroup =
1090 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1091 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
1092 newLoadGroup->SetLoadGroup(loadGroup);
1094 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1095 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1097 nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
1098 new LoadgroupCallbacks(callbacks);
1099 newLoadGroup->SetNotificationCallbacks(newCallbacks);
1101 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
1102 nsContentUtils::FindInternalDocumentViewer(type);
1103 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
1105 nsCOMPtr<nsIDocumentViewer> viewer;
1106 nsCOMPtr<nsIStreamListener> listener;
1107 nsresult rv = docLoaderFactory->CreateInstance(
1108 "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
1109 getter_AddRefs(listener), getter_AddRefs(viewer));
1110 NS_ENSURE_SUCCESS(rv, rv);
1111 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
1113 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
1114 if (!parser) {
1115 /// We don't want to deal with the various fake documents yet
1116 return NS_ERROR_NOT_IMPLEMENTED;
1119 // We can't handle HTML and other weird things here yet.
1120 nsIContentSink* sink = parser->GetContentSink();
1121 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
1122 if (!xmlSink) {
1123 return NS_ERROR_NOT_IMPLEMENTED;
1126 listener.swap(mTargetListener);
1127 viewer.forget(aViewer);
1128 newLoadGroup.forget(aLoadGroup);
1129 return NS_OK;
1132 NS_IMETHODIMP
1133 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
1134 nsIInputStream* aStream,
1135 uint64_t aOffset,
1136 uint32_t aCount) {
1137 // mTargetListener might be null if SetupViewer or AddExternalResource failed.
1138 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
1139 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
1140 return NS_BINDING_ABORTED;
1142 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1145 NS_IMETHODIMP
1146 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
1147 nsresult aStatus) {
1148 // mTargetListener might be null if SetupViewer or AddExternalResource failed
1149 if (mTargetListener) {
1150 nsCOMPtr<nsIStreamListener> listener;
1151 mTargetListener.swap(listener);
1152 return listener->OnStopRequest(aRequest, aStatus);
1155 return NS_OK;
1158 nsresult ExternalResourceMap::PendingLoad::StartLoad(
1159 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
1160 MOZ_ASSERT(aURI, "Must have a URI");
1161 MOZ_ASSERT(aRequestingNode, "Must have a node");
1162 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
1164 nsCOMPtr<nsILoadGroup> loadGroup =
1165 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
1167 nsresult rv = NS_OK;
1168 nsCOMPtr<nsIChannel> channel;
1169 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
1170 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1171 nsIContentPolicy::TYPE_INTERNAL_EXTERNAL_RESOURCE,
1172 nullptr, // aPerformanceStorage
1173 loadGroup);
1174 NS_ENSURE_SUCCESS(rv, rv);
1176 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1177 if (httpChannel) {
1178 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
1179 Unused << NS_WARN_IF(NS_FAILED(rv));
1182 mURI = aURI;
1184 return channel->AsyncOpen(this);
1187 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
1188 nsIInterfaceRequestor)
1190 #define IMPL_SHIM(_i) \
1191 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
1193 IMPL_SHIM(nsILoadContext)
1194 IMPL_SHIM(nsIProgressEventSink)
1195 IMPL_SHIM(nsIChannelEventSink)
1197 #undef IMPL_SHIM
1199 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
1201 #define TRY_SHIM(_i) \
1202 PR_BEGIN_MACRO \
1203 if (IID_IS(_i)) { \
1204 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
1205 if (!real) { \
1206 return NS_NOINTERFACE; \
1208 nsCOMPtr<_i> shim = new _i##Shim(this, real); \
1209 shim.forget(aSink); \
1210 return NS_OK; \
1212 PR_END_MACRO
1214 NS_IMETHODIMP
1215 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
1216 void** aSink) {
1217 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
1218 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
1219 return mCallbacks->GetInterface(aIID, aSink);
1222 *aSink = nullptr;
1224 TRY_SHIM(nsILoadContext);
1225 TRY_SHIM(nsIProgressEventSink);
1226 TRY_SHIM(nsIChannelEventSink);
1228 return NS_NOINTERFACE;
1231 #undef TRY_SHIM
1232 #undef IID_IS
1234 ExternalResourceMap::ExternalResource::~ExternalResource() {
1235 if (mViewer) {
1236 mViewer->Close(nullptr);
1237 mViewer->Destroy();
1241 // ==================================================================
1242 // =
1243 // ==================================================================
1245 // If we ever have an nsIDocumentObserver notification for stylesheet title
1246 // changes we should update the list from that instead of overriding
1247 // EnsureFresh.
1248 class DOMStyleSheetSetList final : public DOMStringList {
1249 public:
1250 explicit DOMStyleSheetSetList(Document* aDocument);
1252 void Disconnect() { mDocument = nullptr; }
1254 virtual void EnsureFresh() override;
1256 protected:
1257 Document* mDocument; // Our document; weak ref. It'll let us know if it
1258 // dies.
1261 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
1262 : mDocument(aDocument) {
1263 NS_ASSERTION(mDocument, "Must have document!");
1266 void DOMStyleSheetSetList::EnsureFresh() {
1267 MOZ_ASSERT(NS_IsMainThread());
1269 mNames.Clear();
1271 if (!mDocument) {
1272 return; // Spec says "no exceptions", and we have no style sets if we have
1273 // no document, for sure
1276 size_t count = mDocument->SheetCount();
1277 nsAutoString title;
1278 for (size_t index = 0; index < count; index++) {
1279 StyleSheet* sheet = mDocument->SheetAt(index);
1280 NS_ASSERTION(sheet, "Null sheet in sheet list!");
1281 sheet->GetTitle(title);
1282 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
1283 return;
1288 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1290 // ==================================================================
1291 // =
1292 // ==================================================================
1294 Document::InternalCommandDataHashtable*
1295 Document::sInternalCommandDataHashtable = nullptr;
1297 // static
1298 void Document::Shutdown() {
1299 if (sInternalCommandDataHashtable) {
1300 sInternalCommandDataHashtable->Clear();
1301 delete sInternalCommandDataHashtable;
1302 sInternalCommandDataHashtable = nullptr;
1306 Document::Document(const char* aContentType)
1307 : nsINode(nullptr),
1308 DocumentOrShadowRoot(this),
1309 mCharacterSet(WINDOWS_1252_ENCODING),
1310 mCharacterSetSource(0),
1311 mParentDocument(nullptr),
1312 mCachedRootElement(nullptr),
1313 mNodeInfoManager(nullptr),
1314 #ifdef DEBUG
1315 mStyledLinksCleared(false),
1316 #endif
1317 mCachedStateObjectValid(false),
1318 mBlockAllMixedContent(false),
1319 mBlockAllMixedContentPreloads(false),
1320 mUpgradeInsecureRequests(false),
1321 mUpgradeInsecurePreloads(false),
1322 mDevToolsWatchingDOMMutations(false),
1323 mBidiEnabled(false),
1324 mMayNeedFontPrefsUpdate(true),
1325 mMathMLEnabled(false),
1326 mIsInitialDocumentInWindow(false),
1327 mIsEverInitialDocumentInWindow(false),
1328 mIgnoreDocGroupMismatches(false),
1329 mLoadedAsData(false),
1330 mAddedToMemoryReportingAsDataDocument(false),
1331 mMayStartLayout(true),
1332 mHaveFiredTitleChange(false),
1333 mIsShowing(false),
1334 mVisible(true),
1335 mRemovedFromDocShell(false),
1336 // mAllowDNSPrefetch starts true, so that we can always reliably && it
1337 // with various values that might disable it. Since we never prefetch
1338 // unless we get a window, and in that case the docshell value will get
1339 // &&-ed in, this is safe.
1340 mAllowDNSPrefetch(true),
1341 mIsStaticDocument(false),
1342 mCreatingStaticClone(false),
1343 mHasPrintCallbacks(false),
1344 mInUnlinkOrDeletion(false),
1345 mHasHadScriptHandlingObject(false),
1346 mIsBeingUsedAsImage(false),
1347 mChromeRulesEnabled(false),
1348 mInChromeDocShell(false),
1349 mIsSyntheticDocument(false),
1350 mHasLinksToUpdateRunnable(false),
1351 mFlushingPendingLinkUpdates(false),
1352 mMayHaveDOMMutationObservers(false),
1353 mMayHaveAnimationObservers(false),
1354 mHasCSPDeliveredThroughHeader(false),
1355 mBFCacheDisallowed(false),
1356 mHasHadDefaultView(false),
1357 mStyleSheetChangeEventsEnabled(false),
1358 mDevToolsAnonymousAndShadowEventsEnabled(false),
1359 mIsSrcdocDocument(false),
1360 mHasDisplayDocument(false),
1361 mFontFaceSetDirty(true),
1362 mDidFireDOMContentLoaded(true),
1363 mIsTopLevelContentDocument(false),
1364 mIsContentDocument(false),
1365 mDidCallBeginLoad(false),
1366 mEncodingMenuDisabled(false),
1367 mLinksEnabled(true),
1368 mIsSVGGlyphsDocument(false),
1369 mInDestructor(false),
1370 mIsGoingAway(false),
1371 mStyleSetFilled(false),
1372 mQuirkSheetAdded(false),
1373 mContentEditableSheetAdded(false),
1374 mMayHaveTitleElement(false),
1375 mDOMLoadingSet(false),
1376 mDOMInteractiveSet(false),
1377 mDOMCompleteSet(false),
1378 mAutoFocusFired(false),
1379 mScrolledToRefAlready(false),
1380 mChangeScrollPosWhenScrollingToRef(false),
1381 mDelayFrameLoaderInitialization(false),
1382 mSynchronousDOMContentLoaded(false),
1383 mMaybeServiceWorkerControlled(false),
1384 mAllowZoom(false),
1385 mValidScaleFloat(false),
1386 mValidMinScale(false),
1387 mValidMaxScale(false),
1388 mWidthStrEmpty(false),
1389 mParserAborted(false),
1390 mReportedDocumentUseCounters(false),
1391 mHasReportedShadowDOMUsage(false),
1392 mHasDelayedRefreshEvent(false),
1393 mLoadEventFiring(false),
1394 mSkipLoadEventAfterClose(false),
1395 mDisableCookieAccess(false),
1396 mDisableDocWrite(false),
1397 mTooDeepWriteRecursion(false),
1398 mPendingMaybeEditingStateChanged(false),
1399 mHasBeenEditable(false),
1400 mIsRunningExecCommandByContent(false),
1401 mIsRunningExecCommandByChromeOrAddon(false),
1402 mSetCompleteAfterDOMContentLoaded(false),
1403 mDidHitCompleteSheetCache(false),
1404 mUseCountersInitialized(false),
1405 mShouldReportUseCounters(false),
1406 mShouldSendPageUseCounters(false),
1407 mUserHasInteracted(false),
1408 mHasUserInteractionTimerScheduled(false),
1409 mShouldResistFingerprinting(false),
1410 mIsInPrivateBrowsing(false),
1411 mCloningForSVGUse(false),
1412 mAllowDeclarativeShadowRoots(false),
1413 mSuspendDOMNotifications(false),
1414 mForceLoadAtTop(false),
1415 mFireMutationEvents(true),
1416 mHasPolicyWithRequireTrustedTypesForDirective(false),
1417 mClipboardCopyTriggered(false),
1418 mXMLDeclarationBits(0),
1419 mOnloadBlockCount(0),
1420 mWriteLevel(0),
1421 mContentEditableCount(0),
1422 mEditingState(EditingState::eOff),
1423 mCompatMode(eCompatibility_FullStandards),
1424 mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
1425 mAncestorIsLoading(false),
1426 mVisibilityState(dom::VisibilityState::Hidden),
1427 mType(eUnknown),
1428 mDefaultElementType(0),
1429 mAllowXULXBL(eTriUnset),
1430 mSkipDTDSecurityChecks(false),
1431 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
1432 mSandboxFlags(0),
1433 mPartID(0),
1434 mMarkedCCGeneration(0),
1435 mPresShell(nullptr),
1436 mSubtreeModifiedDepth(0),
1437 mPreloadPictureDepth(0),
1438 mEventsSuppressed(0),
1439 mIgnoreDestructiveWritesCounter(0),
1440 mStaticCloneCount(0),
1441 mWindow(nullptr),
1442 mBFCacheEntry(nullptr),
1443 mInSyncOperationCount(0),
1444 mBlockDOMContentLoaded(0),
1445 mUpdateNestLevel(0),
1446 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
1447 mViewportType(Unknown),
1448 mViewportFit(ViewportFitType::Auto),
1449 mInteractiveWidgetMode(
1450 InteractiveWidgetUtils::DefaultInteractiveWidgetMode()),
1451 mHeaderData(nullptr),
1452 mServoRestyleRootDirtyBits(0),
1453 mThrowOnDynamicMarkupInsertionCounter(0),
1454 mIgnoreOpensDuringUnloadCounter(0),
1455 mSavedResolution(1.0f),
1456 mGeneration(0),
1457 mCachedTabSizeGeneration(0),
1458 mNextFormNumber(0),
1459 mNextControlNumber(0),
1460 mPreloadService(this),
1461 mShouldNotifyFetchSuccess(false),
1462 mShouldNotifyFormOrPasswordRemoved(false) {
1463 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
1465 SetIsInDocument();
1466 SetIsConnected(true);
1468 // Create these unconditionally, they will be used to warn about the `zoom`
1469 // property, even if use counters are disabled.
1470 mStyleUseCounters.reset(Servo_UseCounters_Create());
1472 SetContentType(nsDependentCString(aContentType));
1474 // Start out mLastStyleSheetSet as null, per spec
1475 SetDOMStringToNull(mLastStyleSheetSet);
1477 // void state used to differentiate an empty source from an unselected source
1478 mPreloadPictureFoundSource.SetIsVoid(true);
1480 RecomputeLanguageFromCharset();
1482 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
1483 mReferrerInfo = new dom::ReferrerInfo(nullptr);
1486 #ifndef ANDROID
1487 // unused by GeckoView
1488 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
1489 if (NS_WARN_IF(!aWin)) {
1490 return false;
1493 nsIURI* uri = aWin->GetDocumentURI();
1494 if (NS_WARN_IF(!uri)) {
1495 return false;
1497 // getSpec is an expensive operation, hence we first check the scheme
1498 // to see if the caller is actually an about: page.
1499 if (!uri->SchemeIs("about")) {
1500 return false;
1503 nsAutoCString aboutSpec;
1504 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
1505 NS_ENSURE_SUCCESS(rv, false);
1507 return aboutSpec.EqualsASCII(aSpec);
1509 #endif
1511 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
1512 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1513 #ifdef ANDROID
1514 // GeckoView uses data URLs for error pages, so for now just check for any
1515 // error page
1516 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1517 #else
1518 return win && IsAboutErrorPage(win, "neterror");
1519 #endif
1522 bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
1523 JSObject* aObject) {
1524 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1525 #ifdef ANDROID
1526 // GeckoView uses data URLs for error pages, so for now just check for any
1527 // error page
1528 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1529 #else
1530 return win && IsAboutErrorPage(win, "httpsonlyerror");
1531 #endif
1534 already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
1535 bool aIsTemporary, ErrorResult& aError) {
1536 RefPtr<Promise> promise = Promise::Create(GetScopeObject(), aError,
1537 Promise::ePropagateUserInteraction);
1538 if (aError.Failed()) {
1539 return nullptr;
1542 nsresult rv = NS_OK;
1543 if (NS_WARN_IF(!mFailedChannel)) {
1544 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1545 return promise.forget();
1548 nsCOMPtr<nsIURI> failedChannelURI;
1549 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
1550 if (!failedChannelURI) {
1551 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1552 return promise.forget();
1555 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI);
1556 if (!innerURI) {
1557 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1558 return promise.forget();
1561 nsAutoCString host;
1562 innerURI->GetAsciiHost(host);
1563 int32_t port;
1564 innerURI->GetPort(&port);
1566 nsCOMPtr<nsITransportSecurityInfo> tsi;
1567 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1568 if (NS_WARN_IF(NS_FAILED(rv))) {
1569 promise->MaybeReject(rv);
1570 return promise.forget();
1572 if (NS_WARN_IF(!tsi)) {
1573 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1574 return promise.forget();
1577 nsCOMPtr<nsIX509Cert> cert;
1578 rv = tsi->GetServerCert(getter_AddRefs(cert));
1579 if (NS_WARN_IF(NS_FAILED(rv))) {
1580 promise->MaybeReject(rv);
1581 return promise.forget();
1583 if (NS_WARN_IF(!cert)) {
1584 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1585 return promise.forget();
1588 if (XRE_IsContentProcess()) {
1589 ContentChild* cc = ContentChild::GetSingleton();
1590 MOZ_ASSERT(cc);
1591 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1592 cc->SendAddCertException(cert, host, port, attrs, aIsTemporary)
1593 ->Then(GetCurrentSerialEventTarget(), __func__,
1594 [promise](const mozilla::MozPromise<
1595 nsresult, mozilla::ipc::ResponseRejectReason,
1596 true>::ResolveOrRejectValue& aValue) {
1597 if (aValue.IsResolve()) {
1598 promise->MaybeResolve(aValue.ResolveValue());
1599 } else {
1600 promise->MaybeRejectWithUndefined();
1603 return promise.forget();
1606 if (XRE_IsParentProcess()) {
1607 nsCOMPtr<nsICertOverrideService> overrideService =
1608 do_GetService(NS_CERTOVERRIDE_CONTRACTID);
1609 if (!overrideService) {
1610 promise->MaybeReject(NS_ERROR_FAILURE);
1611 return promise.forget();
1614 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1615 rv = overrideService->RememberValidityOverride(host, port, attrs, cert,
1616 aIsTemporary);
1617 if (NS_WARN_IF(NS_FAILED(rv))) {
1618 promise->MaybeReject(rv);
1619 return promise.forget();
1622 promise->MaybeResolveWithUndefined();
1623 return promise.forget();
1626 promise->MaybeReject(NS_ERROR_FAILURE);
1627 return promise.forget();
1630 void Document::ReloadWithHttpsOnlyException() {
1631 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
1632 wgc->SendReloadWithHttpsOnlyException();
1636 // Given an nsresult that is assumed to be synthesized by PSM and describes a
1637 // certificate or TLS error, attempts to convert it into a string
1638 // representation of the underlying NSS error.
1639 // `aErrorCodeString` will be an empty string if `aResult` is not an error from
1640 // PSM or it does not represent a valid NSS error.
1641 void GetErrorCodeStringFromNSResult(nsresult aResult,
1642 nsAString& aErrorCodeString) {
1643 aErrorCodeString.Truncate();
1645 if (NS_ERROR_GET_MODULE(aResult) != NS_ERROR_MODULE_SECURITY ||
1646 NS_ERROR_GET_SEVERITY(aResult) != NS_ERROR_SEVERITY_ERROR) {
1647 return;
1650 PRErrorCode errorCode = -1 * NS_ERROR_GET_CODE(aResult);
1651 if (!mozilla::psm::IsNSSErrorCode(errorCode)) {
1652 return;
1655 const char* errorCodeString = PR_ErrorToName(errorCode);
1656 if (!errorCodeString) {
1657 return;
1660 aErrorCodeString.AssignASCII(errorCodeString);
1663 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
1664 nsresult rv = NS_OK;
1665 if (NS_WARN_IF(!mFailedChannel)) {
1666 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1667 return;
1670 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mFailedChannel));
1672 // We don't throw even if httpChannel is null, we just keep responseStatus and
1673 // responseStatusText empty
1674 if (httpChannel) {
1675 uint32_t responseStatus;
1676 nsAutoCString responseStatusText;
1677 rv = httpChannel->GetResponseStatus(&responseStatus);
1678 if (NS_SUCCEEDED(rv)) {
1679 aInfo.mResponseStatus = responseStatus;
1682 rv = httpChannel->GetResponseStatusText(responseStatusText);
1683 if (NS_SUCCEEDED(rv)) {
1684 aInfo.mResponseStatusText.AssignASCII(responseStatusText);
1688 nsCOMPtr<nsITransportSecurityInfo> tsi;
1689 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1690 if (NS_WARN_IF(NS_FAILED(rv))) {
1691 aRv.Throw(rv);
1692 return;
1695 nsresult channelStatus;
1696 rv = mFailedChannel->GetStatus(&channelStatus);
1697 if (NS_WARN_IF(NS_FAILED(rv))) {
1698 aRv.Throw(rv);
1699 return;
1701 aInfo.mChannelStatus = static_cast<uint32_t>(channelStatus);
1703 // If nsITransportSecurityInfo is not set, simply keep the remaining fields
1704 // empty (to make responseStatus and responseStatusText accessible).
1705 if (!tsi) {
1706 return;
1709 // TransportSecurityInfo::GetErrorCodeString always returns NS_OK
1710 (void)tsi->GetErrorCodeString(aInfo.mErrorCodeString);
1711 if (aInfo.mErrorCodeString.IsEmpty()) {
1712 GetErrorCodeStringFromNSResult(channelStatus, aInfo.mErrorCodeString);
1716 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
1717 JSObject* aObject) {
1718 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1719 #ifdef ANDROID
1720 // GeckoView uses data URLs for error pages, so for now just check for any
1721 // error page
1722 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1723 #else
1724 return win && IsAboutErrorPage(win, "certerror");
1725 #endif
1728 bool Document::CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject) {
1729 RefPtr<BasePrincipal> principal =
1730 BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx));
1732 if (!principal) {
1733 return false;
1736 // We allow the privilege SSA to be called from system principal.
1737 if (principal->IsSystemPrincipal()) {
1738 return true;
1741 // We only allow calling the privilege SSA from the content script of the
1742 // webcompat extension.
1743 if (auto* policy = principal->ContentScriptAddonPolicy()) {
1744 nsAutoString addonID;
1745 policy->GetId(addonID);
1747 return addonID.EqualsLiteral("webcompat@mozilla.org");
1750 return false;
1753 bool Document::IsErrorPage() const {
1754 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
1755 return loadInfo && loadInfo->GetLoadErrorPage();
1758 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
1759 ErrorResult& aRv) {
1760 nsresult rv = NS_OK;
1761 if (NS_WARN_IF(!mFailedChannel)) {
1762 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1763 return;
1766 nsCOMPtr<nsITransportSecurityInfo> tsi;
1767 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1768 if (NS_WARN_IF(NS_FAILED(rv))) {
1769 aRv.Throw(rv);
1770 return;
1772 if (NS_WARN_IF(!tsi)) {
1773 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1774 return;
1777 nsresult channelStatus;
1778 rv = mFailedChannel->GetStatus(&channelStatus);
1779 if (NS_WARN_IF(NS_FAILED(rv))) {
1780 aRv.Throw(rv);
1781 return;
1783 aInfo.mChannelStatus = static_cast<uint32_t>(channelStatus);
1785 // TransportSecurityInfo::GetErrorCodeString always returns NS_OK
1786 (void)tsi->GetErrorCodeString(aInfo.mErrorCodeString);
1787 if (aInfo.mErrorCodeString.IsEmpty()) {
1788 GetErrorCodeStringFromNSResult(channelStatus, aInfo.mErrorCodeString);
1791 nsITransportSecurityInfo::OverridableErrorCategory errorCategory;
1792 rv = tsi->GetOverridableErrorCategory(&errorCategory);
1793 if (NS_WARN_IF(NS_FAILED(rv))) {
1794 aRv.Throw(rv);
1795 return;
1797 switch (errorCategory) {
1798 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST:
1799 aInfo.mOverridableErrorCategory =
1800 dom::OverridableErrorCategory::Trust_error;
1801 break;
1802 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN:
1803 aInfo.mOverridableErrorCategory =
1804 dom::OverridableErrorCategory::Domain_mismatch;
1805 break;
1806 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME:
1807 aInfo.mOverridableErrorCategory =
1808 dom::OverridableErrorCategory::Expired_or_not_yet_valid;
1809 break;
1810 default:
1811 aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Unset;
1812 break;
1815 nsCOMPtr<nsIX509Cert> cert;
1816 nsCOMPtr<nsIX509CertValidity> validity;
1817 rv = tsi->GetServerCert(getter_AddRefs(cert));
1818 if (NS_WARN_IF(NS_FAILED(rv))) {
1819 aRv.Throw(rv);
1820 return;
1822 if (NS_WARN_IF(!cert)) {
1823 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1824 return;
1827 rv = cert->GetValidity(getter_AddRefs(validity));
1828 if (NS_WARN_IF(NS_FAILED(rv))) {
1829 aRv.Throw(rv);
1830 return;
1832 if (NS_WARN_IF(!validity)) {
1833 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1834 return;
1837 PRTime validityResult;
1838 rv = validity->GetNotBefore(&validityResult);
1839 if (NS_WARN_IF(NS_FAILED(rv))) {
1840 aRv.Throw(rv);
1841 return;
1843 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1845 rv = validity->GetNotAfter(&validityResult);
1846 if (NS_WARN_IF(NS_FAILED(rv))) {
1847 aRv.Throw(rv);
1848 return;
1850 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1852 nsAutoString issuerCommonName;
1853 nsAutoString certChainPEMString;
1854 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
1855 int64_t maxValidity = std::numeric_limits<int64_t>::max();
1856 int64_t minValidity = 0;
1857 PRTime notBefore, notAfter;
1858 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
1859 rv = tsi->GetFailedCertChain(failedCertArray);
1860 if (NS_WARN_IF(NS_FAILED(rv))) {
1861 aRv.Throw(rv);
1862 return;
1865 if (NS_WARN_IF(failedCertArray.IsEmpty())) {
1866 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1867 return;
1870 for (const auto& certificate : failedCertArray) {
1871 rv = certificate->GetIssuerCommonName(issuerCommonName);
1872 if (NS_WARN_IF(NS_FAILED(rv))) {
1873 aRv.Throw(rv);
1874 return;
1877 rv = certificate->GetValidity(getter_AddRefs(validity));
1878 if (NS_WARN_IF(NS_FAILED(rv))) {
1879 aRv.Throw(rv);
1880 return;
1882 if (NS_WARN_IF(!validity)) {
1883 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1884 return;
1887 rv = validity->GetNotBefore(&notBefore);
1888 if (NS_WARN_IF(NS_FAILED(rv))) {
1889 aRv.Throw(rv);
1890 return;
1893 rv = validity->GetNotAfter(&notAfter);
1894 if (NS_WARN_IF(NS_FAILED(rv))) {
1895 aRv.Throw(rv);
1896 return;
1899 notBefore = std::max(minValidity, notBefore);
1900 notAfter = std::min(maxValidity, notAfter);
1901 nsTArray<uint8_t> certArray;
1902 rv = certificate->GetRawDER(certArray);
1903 if (NS_WARN_IF(NS_FAILED(rv))) {
1904 aRv.Throw(rv);
1905 return;
1908 nsAutoString der64;
1909 rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()),
1910 certArray.Length(), der64);
1911 if (NS_WARN_IF(NS_FAILED(rv))) {
1912 aRv.Throw(rv);
1913 return;
1915 if (!certChainStrings.AppendElement(der64, fallible)) {
1916 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1917 return;
1921 aInfo.mIssuerCommonName.Assign(issuerCommonName);
1922 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
1923 aInfo.mCertValidityRangeNotBefore =
1924 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
1926 int32_t errorCode;
1927 rv = tsi->GetErrorCode(&errorCode);
1928 if (NS_WARN_IF(NS_FAILED(rv))) {
1929 aRv.Throw(rv);
1930 return;
1933 nsCOMPtr<nsINSSErrorsService> nsserr =
1934 do_GetService("@mozilla.org/nss_errors_service;1");
1935 if (NS_WARN_IF(!nsserr)) {
1936 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1937 return;
1939 nsresult res;
1940 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
1941 if (NS_WARN_IF(NS_FAILED(rv))) {
1942 aRv.Throw(rv);
1943 return;
1945 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
1946 if (NS_WARN_IF(NS_FAILED(rv))) {
1947 aRv.Throw(rv);
1948 return;
1951 OriginAttributes attrs;
1952 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1953 nsCOMPtr<nsIURI> aURI;
1954 mFailedChannel->GetURI(getter_AddRefs(aURI));
1955 if (XRE_IsContentProcess()) {
1956 ContentChild* cc = ContentChild::GetSingleton();
1957 MOZ_ASSERT(cc);
1958 cc->SendIsSecureURI(aURI, attrs, &aInfo.mHasHSTS);
1959 } else {
1960 nsCOMPtr<nsISiteSecurityService> sss =
1961 do_GetService(NS_SSSERVICE_CONTRACTID);
1962 if (NS_WARN_IF(!sss)) {
1963 return;
1965 Unused << NS_WARN_IF(
1966 NS_FAILED(sss->IsSecureURI(aURI, attrs, &aInfo.mHasHSTS)));
1968 nsCOMPtr<nsIPublicKeyPinningService> pkps =
1969 do_GetService(NS_PKPSERVICE_CONTRACTID);
1970 if (NS_WARN_IF(!pkps)) {
1971 return;
1973 Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP)));
1976 bool Document::IsAboutPage() const {
1977 return NodePrincipal()->SchemeIs("about");
1980 void Document::ConstructUbiNode(void* storage) {
1981 JS::ubi::Concrete<Document>::construct(storage, this);
1984 void Document::LoadEventFired() {
1985 // Object used to collect some telemetry data so we don't need to query for it
1986 // twice.
1987 glean::perf::PageLoadExtra pageLoadEventData;
1989 // Accumulate timing data located in each document's realm and report to
1990 // telemetry.
1991 AccumulateJSTelemetry(pageLoadEventData);
1993 // Collect page load timings
1994 AccumulatePageLoadTelemetry(pageLoadEventData);
1996 // Record page load event
1997 RecordPageLoadEventTelemetry(pageLoadEventData);
1999 // Release the JS bytecode cache from its wait on the load event, and
2000 // potentially dispatch the encoding of the bytecode.
2001 if (ScriptLoader()) {
2002 ScriptLoader()->LoadEventFired();
2006 void Document::RecordPageLoadEventTelemetry(
2007 glean::perf::PageLoadExtra& aEventTelemetryData) {
2008 // If the page load time is empty, then the content wasn't something we want
2009 // to report (i.e. not a top level document).
2010 if (!aEventTelemetryData.loadTime) {
2011 return;
2013 MOZ_ASSERT(IsTopLevelContentDocument());
2015 nsPIDOMWindowOuter* window = GetWindow();
2016 if (!window) {
2017 return;
2020 nsIDocShell* docshell = window->GetDocShell();
2021 if (!docshell) {
2022 return;
2025 nsAutoCString loadTypeStr;
2026 switch (docshell->GetLoadType()) {
2027 case LOAD_NORMAL:
2028 case LOAD_NORMAL_REPLACE:
2029 case LOAD_NORMAL_BYPASS_CACHE:
2030 case LOAD_NORMAL_BYPASS_PROXY:
2031 case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
2032 loadTypeStr.Append("NORMAL");
2033 break;
2034 case LOAD_HISTORY:
2035 loadTypeStr.Append("HISTORY");
2036 break;
2037 case LOAD_RELOAD_NORMAL:
2038 case LOAD_RELOAD_BYPASS_CACHE:
2039 case LOAD_RELOAD_BYPASS_PROXY:
2040 case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
2041 case LOAD_REFRESH:
2042 case LOAD_REFRESH_REPLACE:
2043 case LOAD_RELOAD_CHARSET_CHANGE:
2044 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
2045 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
2046 loadTypeStr.Append("RELOAD");
2047 break;
2048 case LOAD_LINK:
2049 loadTypeStr.Append("LINK");
2050 break;
2051 case LOAD_STOP_CONTENT:
2052 case LOAD_STOP_CONTENT_AND_REPLACE:
2053 loadTypeStr.Append("STOP");
2054 break;
2055 case LOAD_ERROR_PAGE:
2056 loadTypeStr.Append("ERROR");
2057 break;
2058 default:
2059 loadTypeStr.Append("OTHER");
2060 break;
2063 nsCOMPtr<nsIEffectiveTLDService> tldService =
2064 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
2065 if (tldService && mReferrerInfo &&
2066 (docshell->GetLoadType() & nsIDocShell::LOAD_CMD_NORMAL)) {
2067 nsAutoCString currentBaseDomain, referrerBaseDomain;
2068 nsCOMPtr<nsIURI> referrerURI = mReferrerInfo->GetComputedReferrer();
2069 if (referrerURI) {
2070 auto result = NS_SUCCEEDED(
2071 tldService->GetBaseDomain(referrerURI, 0, referrerBaseDomain));
2072 if (result) {
2073 bool sameOrigin = false;
2074 NodePrincipal()->IsSameOrigin(referrerURI, &sameOrigin);
2075 aEventTelemetryData.sameOriginNav = mozilla::Some(sameOrigin);
2080 aEventTelemetryData.loadType = mozilla::Some(loadTypeStr);
2082 // Sending a glean ping must be done on the parent process.
2083 if (ContentChild* cc = ContentChild::GetSingleton()) {
2084 cc->SendRecordPageLoadEvent(aEventTelemetryData);
2088 #ifndef ANDROID
2089 static void AccumulateHttp3FcpGleanPref(const nsCString& http3Key,
2090 const TimeDuration& duration) {
2091 if (http3Key == "http3"_ns) {
2092 glean::performance_pageload::http3_fcp_http3.AccumulateRawDuration(
2093 duration);
2094 } else if (http3Key == "supports_http3"_ns) {
2095 glean::performance_pageload::http3_fcp_supports_http3.AccumulateRawDuration(
2096 duration);
2097 } else {
2098 MOZ_ASSERT_UNREACHABLE("Unknown value for http3Key");
2102 static void AccumulatePriorityFcpGleanPref(
2103 const nsCString& http3WithPriorityKey, const TimeDuration& duration) {
2104 if (http3WithPriorityKey == "with_priority"_ns) {
2105 glean::performance_pageload::h3p_fcp_with_priority.AccumulateRawDuration(
2106 duration);
2107 } else if (http3WithPriorityKey == "without_priority"_ns) {
2108 glean::performance_pageload::http3_fcp_without_priority
2109 .AccumulateRawDuration(duration);
2110 } else {
2111 MOZ_ASSERT_UNREACHABLE("Unknown value for http3WithPriorityKey");
2114 #endif
2116 void Document::AccumulatePageLoadTelemetry(
2117 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2118 // Interested only in top level documents for real websites that are in the
2119 // foreground.
2120 if (!ShouldIncludeInTelemetry() || !IsTopLevelContentDocument() ||
2121 !GetNavigationTiming() ||
2122 !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
2123 return;
2126 if (!GetChannel()) {
2127 return;
2130 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
2131 if (!timedChannel) {
2132 return;
2135 // Default duration is 0, use this to check for bogus negative values.
2136 const TimeDuration zeroDuration;
2138 TimeStamp responseStart;
2139 timedChannel->GetResponseStart(&responseStart);
2141 TimeStamp redirectStart, redirectEnd;
2142 timedChannel->GetRedirectStart(&redirectStart);
2143 timedChannel->GetRedirectEnd(&redirectEnd);
2145 uint8_t redirectCount;
2146 timedChannel->GetRedirectCount(&redirectCount);
2147 if (redirectCount) {
2148 aEventTelemetryDataOut.redirectCount =
2149 mozilla::Some(static_cast<uint32_t>(redirectCount));
2152 if (!redirectStart.IsNull() && !redirectEnd.IsNull()) {
2153 TimeDuration redirectTime = redirectEnd - redirectStart;
2154 if (redirectTime > zeroDuration) {
2155 aEventTelemetryDataOut.redirectTime =
2156 mozilla::Some(static_cast<uint32_t>(redirectTime.ToMilliseconds()));
2160 TimeStamp dnsLookupStart, dnsLookupEnd;
2161 timedChannel->GetDomainLookupStart(&dnsLookupStart);
2162 timedChannel->GetDomainLookupEnd(&dnsLookupEnd);
2164 if (!dnsLookupStart.IsNull() && !dnsLookupEnd.IsNull()) {
2165 TimeDuration dnsLookupTime = dnsLookupEnd - dnsLookupStart;
2166 if (dnsLookupTime > zeroDuration) {
2167 aEventTelemetryDataOut.dnsLookupTime =
2168 mozilla::Some(static_cast<uint32_t>(dnsLookupTime.ToMilliseconds()));
2172 TimeStamp navigationStart =
2173 GetNavigationTiming()->GetNavigationStartTimeStamp();
2175 if (!responseStart || !navigationStart) {
2176 return;
2179 nsAutoCString dnsKey("Native");
2180 nsAutoCString http3Key;
2181 nsAutoCString http3WithPriorityKey;
2182 nsAutoCString earlyHintKey;
2183 nsCOMPtr<nsIHttpChannelInternal> httpChannel =
2184 do_QueryInterface(GetChannel());
2185 if (httpChannel) {
2186 bool resolvedByTRR = false;
2187 Unused << httpChannel->GetIsResolvedByTRR(&resolvedByTRR);
2188 if (resolvedByTRR) {
2189 if (nsCOMPtr<nsIDNSService> dns =
2190 do_GetService(NS_DNSSERVICE_CONTRACTID)) {
2191 dns->GetTRRDomainKey(dnsKey);
2192 } else {
2193 // Failed to get the DNS service.
2194 dnsKey = "(fail)"_ns;
2196 aEventTelemetryDataOut.trrDomain = mozilla::Some(dnsKey);
2199 uint32_t major;
2200 uint32_t minor;
2201 if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
2202 if (major == 3) {
2203 http3Key = "http3"_ns;
2204 nsCOMPtr<nsIHttpChannel> httpChannel2 = do_QueryInterface(GetChannel());
2205 nsCString header;
2206 if (httpChannel2 &&
2207 NS_SUCCEEDED(
2208 httpChannel2->GetResponseHeader("priority"_ns, header)) &&
2209 !header.IsEmpty()) {
2210 http3WithPriorityKey = "with_priority"_ns;
2211 } else {
2212 http3WithPriorityKey = "without_priority"_ns;
2214 } else if (major == 2) {
2215 bool supportHttp3 = false;
2216 if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
2217 supportHttp3 = false;
2219 if (supportHttp3) {
2220 http3Key = "supports_http3"_ns;
2224 aEventTelemetryDataOut.httpVer = mozilla::Some(major);
2227 uint32_t earlyHintType = 0;
2228 Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
2229 if (earlyHintType & LinkStyle::ePRECONNECT) {
2230 earlyHintKey.Append("preconnect_"_ns);
2232 if (earlyHintType & LinkStyle::ePRELOAD) {
2233 earlyHintKey.Append("preload_"_ns);
2234 earlyHintKey.Append(mPreloadService.GetEarlyHintUsed() ? "1"_ns : "0"_ns);
2238 TimeStamp asyncOpen;
2239 timedChannel->GetAsyncOpen(&asyncOpen);
2240 if (asyncOpen) {
2241 Telemetry::AccumulateTimeDelta(Telemetry::DNS_PERF_FIRST_BYTE_MS, dnsKey,
2242 asyncOpen, responseStart);
2245 // First Contentful Composite
2246 if (TimeStamp firstContentfulComposite =
2247 GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) {
2248 glean::performance_pageload::fcp.AccumulateRawDuration(
2249 firstContentfulComposite - navigationStart);
2251 if (!http3Key.IsEmpty()) {
2252 Telemetry::AccumulateTimeDelta(
2253 Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key,
2254 navigationStart, firstContentfulComposite);
2255 #ifndef ANDROID
2256 AccumulateHttp3FcpGleanPref(http3Key,
2257 firstContentfulComposite - navigationStart);
2258 #endif
2261 if (!http3WithPriorityKey.IsEmpty()) {
2262 Telemetry::AccumulateTimeDelta(
2263 Telemetry::H3P_PERF_FIRST_CONTENTFUL_PAINT_MS, http3WithPriorityKey,
2264 navigationStart, firstContentfulComposite);
2265 #ifndef ANDROID
2266 AccumulatePriorityFcpGleanPref(
2267 http3WithPriorityKey, firstContentfulComposite - navigationStart);
2268 #endif
2271 Telemetry::AccumulateTimeDelta(
2272 Telemetry::DNS_PERF_FIRST_CONTENTFUL_PAINT_MS, dnsKey, navigationStart,
2273 firstContentfulComposite);
2275 glean::performance_pageload::fcp_responsestart.AccumulateRawDuration(
2276 firstContentfulComposite - responseStart);
2278 TimeDuration fcpTime = firstContentfulComposite - navigationStart;
2279 if (fcpTime > zeroDuration) {
2280 aEventTelemetryDataOut.fcpTime =
2281 mozilla::Some(static_cast<uint32_t>(fcpTime.ToMilliseconds()));
2285 // Report the most up to date LCP time. For our histogram we actually report
2286 // this on page unload.
2287 if (TimeStamp lcpTime =
2288 GetNavigationTiming()->GetLargestContentfulRenderTimeStamp()) {
2289 aEventTelemetryDataOut.lcpTime = mozilla::Some(
2290 static_cast<uint32_t>((lcpTime - navigationStart).ToMilliseconds()));
2293 // Load event
2294 if (TimeStamp loadEventStart =
2295 GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
2296 glean::performance_pageload::load_time.AccumulateRawDuration(
2297 loadEventStart - navigationStart);
2298 if (!http3Key.IsEmpty()) {
2299 Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
2300 http3Key, navigationStart, loadEventStart);
2303 if (!http3WithPriorityKey.IsEmpty()) {
2304 Telemetry::AccumulateTimeDelta(Telemetry::H3P_PERF_PAGE_LOAD_TIME_MS,
2305 http3WithPriorityKey, navigationStart,
2306 loadEventStart);
2309 glean::performance_pageload::load_time_responsestart.AccumulateRawDuration(
2310 loadEventStart - responseStart);
2312 TimeDuration responseTime = responseStart - navigationStart;
2313 if (responseTime > zeroDuration) {
2314 aEventTelemetryDataOut.responseTime =
2315 mozilla::Some(static_cast<uint32_t>(responseTime.ToMilliseconds()));
2318 TimeDuration loadTime = loadEventStart - navigationStart;
2319 if (loadTime > zeroDuration) {
2320 aEventTelemetryDataOut.loadTime =
2321 mozilla::Some(static_cast<uint32_t>(loadTime.ToMilliseconds()));
2324 TimeStamp requestStart;
2325 timedChannel->GetRequestStart(&requestStart);
2326 if (requestStart) {
2327 TimeDuration timeToRequestStart = requestStart - navigationStart;
2328 if (timeToRequestStart > zeroDuration) {
2329 aEventTelemetryDataOut.timeToRequestStart = mozilla::Some(
2330 static_cast<uint32_t>(timeToRequestStart.ToMilliseconds()));
2335 aEventTelemetryDataOut.features = mozilla::Some(mPageloadEventFeatures);
2338 void Document::AccumulateJSTelemetry(
2339 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2340 if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry()) {
2341 return;
2344 if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
2345 return;
2348 AutoJSContext cx;
2349 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
2350 JSAutoRealm ar(cx, globalObject);
2351 JS::JSTimers timers = JS::GetJSTimers(cx);
2353 if (!timers.executionTime.IsZero()) {
2354 glean::javascript_pageload::execution_time.AccumulateRawDuration(
2355 timers.executionTime);
2356 aEventTelemetryDataOut.jsExecTime = mozilla::Some(
2357 static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
2360 if (!timers.delazificationTime.IsZero()) {
2361 glean::javascript_pageload::delazification_time.AccumulateRawDuration(
2362 timers.delazificationTime);
2365 if (!timers.xdrEncodingTime.IsZero()) {
2366 glean::javascript_pageload::xdr_encode_time.AccumulateRawDuration(
2367 timers.xdrEncodingTime);
2370 if (!timers.baselineCompileTime.IsZero()) {
2371 glean::javascript_pageload::baseline_compile_time.AccumulateRawDuration(
2372 timers.baselineCompileTime);
2375 if (!timers.gcTime.IsZero()) {
2376 glean::javascript_pageload::gc_time.AccumulateRawDuration(timers.gcTime);
2379 if (!timers.protectTime.IsZero()) {
2380 glean::javascript_pageload::protect_time.AccumulateRawDuration(
2381 timers.protectTime);
2385 Document::~Document() {
2386 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
2387 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
2388 "Can't be top-level and a resource doc at the same time");
2390 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
2392 if (IsTopLevelContentDocument()) {
2393 RemoveToplevelLoadingDocument(this);
2395 // don't report for about: pages
2396 if (!IsAboutPage()) {
2397 if (MOZ_UNLIKELY(mMathMLEnabled)) {
2398 glean::mathml::doc_count.Add(1);
2403 mInDestructor = true;
2404 mInUnlinkOrDeletion = true;
2406 mozilla::DropJSObjects(this);
2408 // Clear mObservers to keep it in sync with the mutationobserver list
2409 mObservers.Clear();
2411 mIntersectionObservers.Clear();
2413 if (mStyleSheetSetList) {
2414 mStyleSheetSetList->Disconnect();
2417 if (mAnimationController) {
2418 mAnimationController->Disconnect();
2421 MOZ_ASSERT(mTimelines.isEmpty());
2423 mParentDocument = nullptr;
2425 // Kill the subdocument map, doing this will release its strong
2426 // references, if any.
2427 mSubDocuments = nullptr;
2429 nsAutoScriptBlocker scriptBlocker;
2431 // Destroy link map now so we don't waste time removing
2432 // links one by one
2433 DestroyElementMaps();
2435 // Invalidate cached array of child nodes
2436 InvalidateChildNodes();
2438 // We should not have child nodes when destructor is called,
2439 // since child nodes keep their owner document alive.
2440 MOZ_ASSERT(!HasChildren());
2442 mCachedRootElement = nullptr;
2444 for (auto& sheets : mAdditionalSheets) {
2445 UnlinkStyleSheets(sheets);
2448 if (mAttributeStyles) {
2449 mAttributeStyles->SetOwningDocument(nullptr);
2452 if (mListenerManager) {
2453 mListenerManager->Disconnect();
2454 UnsetFlags(NODE_HAS_LISTENERMANAGER);
2457 if (mScriptLoader) {
2458 mScriptLoader->DropDocumentReference();
2461 if (mCSSLoader) {
2462 // Could be null here if Init() failed or if we have been unlinked.
2463 mCSSLoader->DropDocumentReference();
2466 if (mStyleImageLoader) {
2467 mStyleImageLoader->DropDocumentReference();
2470 if (mXULBroadcastManager) {
2471 mXULBroadcastManager->DropDocumentReference();
2474 if (mXULPersist) {
2475 mXULPersist->DropDocumentReference();
2478 if (mPermissionDelegateHandler) {
2479 mPermissionDelegateHandler->DropDocumentReference();
2482 mHeaderData = nullptr;
2484 mPendingTitleChangeEvent.Revoke();
2486 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
2487 "must not have media query lists left");
2489 if (mNodeInfoManager) {
2490 mNodeInfoManager->DropDocumentReference();
2493 if (mDocGroup) {
2494 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
2495 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
2498 UnlinkOriginalDocumentIfStatic();
2500 UnregisterFromMemoryReportingForDataDocument();
2503 void Document::DropStyleSet() { mStyleSet = nullptr; }
2505 NS_INTERFACE_TABLE_HEAD(Document)
2506 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
2507 NS_INTERFACE_TABLE_BEGIN
2508 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
2509 NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
2510 NS_INTERFACE_TABLE_ENTRY(Document, Document)
2511 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
2512 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
2513 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
2514 NS_INTERFACE_TABLE_END
2515 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
2516 NS_INTERFACE_MAP_END
2518 NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
2519 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Document, LastRelease())
2521 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2522 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2523 EventListenerManager* elm = tmp->GetExistingListenerManager();
2524 if (elm) {
2525 elm->MarkForCC();
2527 return true;
2529 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2531 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2532 return Element::CanSkipInCC(tmp);
2533 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2535 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2536 return Element::CanSkipThis(tmp);
2537 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2539 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2540 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2541 char name[512];
2542 nsAutoCString loadedAsData;
2543 if (tmp->IsLoadedAsData()) {
2544 loadedAsData.AssignLiteral("data");
2545 } else {
2546 loadedAsData.AssignLiteral("normal");
2548 uint32_t nsid = tmp->GetDefaultNamespaceID();
2549 nsAutoCString uri;
2550 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2551 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2552 "(xhtml)", "(XLink)", "(XSLT)",
2553 "(MathML)", "(RDF)", "(XUL)"};
2554 if (nsid < std::size(kNSURIs)) {
2555 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2556 kNSURIs[nsid], uri.get());
2557 } else {
2558 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2560 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2561 } else {
2562 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2565 if (!nsINode::Traverse(tmp, cb)) {
2566 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2569 tmp->mExternalResourceMap.Traverse(&cb);
2571 // Traverse all Document pointer members.
2572 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2573 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2574 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2575 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2576 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2577 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFragmentDirective)
2578 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry)
2580 // Traverse all Document nsCOMPtrs.
2581 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2582 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2583 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2584 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2585 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2587 DocumentOrShadowRoot::Traverse(tmp, cb);
2589 if (tmp->mRadioGroupContainer) {
2590 RadioGroupContainer::Traverse(tmp->mRadioGroupContainer.get(), cb);
2593 for (auto& sheets : tmp->mAdditionalSheets) {
2594 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2597 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2598 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadObserver)
2599 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementsObservedForLastRememberedSize)
2600 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2601 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2602 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2603 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2604 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2605 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2606 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker)
2607 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2608 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2609 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2610 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2611 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2612 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2613 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2614 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2615 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2616 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2617 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2618 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2619 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPermissionDelegateHandler)
2620 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2621 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2622 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2623 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2624 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActiveViewTransition)
2625 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2626 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
2628 // Traverse all our nsCOMArrays.
2629 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2631 // Traverse animation components
2632 if (tmp->mAnimationController) {
2633 tmp->mAnimationController->Traverse(&cb);
2636 if (tmp->mSubDocuments) {
2637 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2638 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2640 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2641 cb.NoteXPCOMChild(entry->mKey);
2642 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2643 "mSubDocuments entry->mSubDocument");
2644 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2648 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2650 // We own only the items in mDOMMediaQueryLists that have listeners;
2651 // this reference is managed by their AddListener and RemoveListener
2652 // methods.
2653 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2654 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2655 if (mql->HasListeners() &&
2656 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2657 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2658 cb.NoteXPCOMChild(static_cast<EventTarget*>(mql));
2662 // XXX: This should be not needed once bug 1569185 lands.
2663 for (const auto& entry : tmp->mL10nProtoElements) {
2664 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2665 cb.NoteXPCOMChild(entry.GetKey());
2666 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
2669 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2670 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2671 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2672 mPendingFrameStaticClones[i].mStaticCloneOf);
2675 for (auto& tableEntry : tmp->mActiveLocks) {
2676 ImplCycleCollectionTraverse(cb, *tableEntry.GetModifiableData(),
2677 "mActiveLocks entry", 0);
2679 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2681 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2683 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document)
2684 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
2685 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject)
2686 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2688 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2689 tmp->mInUnlinkOrDeletion = true;
2691 tmp->SetStateObject(nullptr);
2693 // Clear out our external resources
2694 tmp->mExternalResourceMap.Shutdown();
2696 nsAutoScriptBlocker scriptBlocker;
2698 nsINode::Unlink(tmp);
2700 while (tmp->HasChildren()) {
2701 // Hold a strong ref to the node when we remove it, because we may be
2702 // the last reference to it.
2703 // If this code changes, change the corresponding code in Document's
2704 // unlink impl and ContentUnbinder::UnbindSubtree.
2705 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2706 tmp->DisconnectChild(child);
2707 child->UnbindFromTree();
2710 tmp->UnlinkOriginalDocumentIfStatic();
2712 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2714 tmp->SetScriptGlobalObject(nullptr);
2716 for (auto& sheets : tmp->mAdditionalSheets) {
2717 tmp->UnlinkStyleSheets(sheets);
2720 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2721 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2722 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadObserver)
2723 NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementsObservedForLastRememberedSize);
2724 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2725 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2726 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2727 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFragmentDirective)
2728 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry)
2729 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2730 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2731 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2732 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2733 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2734 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2735 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2736 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2737 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker)
2738 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2739 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2740 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2741 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2742 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2743 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2744 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2745 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2746 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2747 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2748 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2749 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2750 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPermissionDelegateHandler)
2751 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2752 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2753 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2754 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2755 NS_IMPL_CYCLE_COLLECTION_UNLINK(mActiveViewTransition)
2756 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2757 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2759 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
2760 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
2761 tmp->mDocGroup);
2763 tmp->mDocGroup = nullptr;
2765 if (tmp->IsTopLevelContentDocument()) {
2766 RemoveToplevelLoadingDocument(tmp);
2769 tmp->mParentDocument = nullptr;
2771 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2773 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2775 if (tmp->mListenerManager) {
2776 tmp->mListenerManager->Disconnect();
2777 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2778 tmp->mListenerManager = nullptr;
2781 if (tmp->mStyleSheetSetList) {
2782 tmp->mStyleSheetSetList->Disconnect();
2783 tmp->mStyleSheetSetList = nullptr;
2786 tmp->mSubDocuments = nullptr;
2788 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
2790 DocumentOrShadowRoot::Unlink(tmp);
2792 tmp->mRadioGroupContainer = nullptr;
2794 // Document has a pretty complex destructor, so we're going to
2795 // assume that *most* cycles you actually want to break somewhere
2796 // else, and not unlink an awful lot here.
2798 tmp->mExpandoAndGeneration.OwnerUnlinked();
2800 if (tmp->mAnimationController) {
2801 tmp->mAnimationController->Unlink();
2804 tmp->mPendingTitleChangeEvent.Revoke();
2806 if (tmp->mCSSLoader) {
2807 tmp->mCSSLoader->DropDocumentReference();
2808 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2811 if (tmp->mScriptLoader) {
2812 tmp->mScriptLoader->DropDocumentReference();
2813 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptLoader)
2816 // We own only the items in mDOMMediaQueryLists that have listeners;
2817 // this reference is managed by their AddListener and RemoveListener
2818 // methods.
2819 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2820 MediaQueryList* next =
2821 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2822 mql->Disconnect();
2823 mql = next;
2826 tmp->mPendingFrameStaticClones.Clear();
2828 tmp->mActiveLocks.Clear();
2830 tmp->mInUnlinkOrDeletion = false;
2832 tmp->UnregisterFromMemoryReportingForDataDocument();
2834 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2835 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2836 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2837 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2839 nsresult Document::Init(nsIPrincipal* aPrincipal,
2840 nsIPrincipal* aPartitionedPrincipal) {
2841 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2842 return NS_ERROR_ALREADY_INITIALIZED;
2845 // Force initialization.
2846 mOnloadBlocker = new OnloadBlocker();
2847 mStyleImageLoader = new css::ImageLoader(this);
2849 mNodeInfoManager = new nsNodeInfoManager(this, aPrincipal);
2851 // mNodeInfo keeps NodeInfoManager alive!
2852 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2853 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2854 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2855 "Bad NodeType in aNodeInfo");
2857 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2859 mCSSLoader = new css::Loader(this);
2860 // Assume we're not quirky, until we know otherwise
2861 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2863 // If after creation the owner js global is not set for a document
2864 // we use the default compartment for this document, instead of creating
2865 // wrapper in some random compartment when the document is exposed to js
2866 // via some events.
2867 nsCOMPtr<nsIGlobalObject> global =
2868 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2869 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2870 mScopeObject = do_GetWeakReference(global);
2871 MOZ_ASSERT(mScopeObject);
2873 mScriptLoader = new dom::ScriptLoader(this);
2875 // we need to create a policy here so getting the policy within
2876 // ::Policy() can *always* return a non null policy
2877 mFeaturePolicy = new dom::FeaturePolicy(this);
2878 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2880 if (aPrincipal) {
2881 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2882 } else {
2883 RecomputeResistFingerprinting();
2886 return NS_OK;
2889 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2891 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2892 PropertyTable().RemoveAllPropertiesFor(aNode);
2895 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2896 nsCOMPtr<nsIURI> uri;
2897 nsCOMPtr<nsIPrincipal> principal;
2898 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2899 if (aChannel) {
2900 mIsInPrivateBrowsing = NS_UsePrivateBrowsing(aChannel);
2902 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2903 // nsScriptSecurityManager::GetChannelResultPrincipals.
2904 // Note: this should match the uri used for the OnNewURI call in
2905 // nsDocShell::CreateDocumentViewer.
2906 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2908 nsIScriptSecurityManager* securityManager =
2909 nsContentUtils::GetSecurityManager();
2910 if (securityManager) {
2911 securityManager->GetChannelResultPrincipals(
2912 aChannel, getter_AddRefs(principal),
2913 getter_AddRefs(partitionedPrincipal));
2917 bool equal = principal->Equals(partitionedPrincipal);
2919 principal = MaybeDowngradePrincipal(principal);
2920 if (equal) {
2921 partitionedPrincipal = principal;
2922 } else {
2923 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2926 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2928 // Note that, since mTiming does not change during a reset, the
2929 // navigationStart time remains unchanged and therefore any future new
2930 // timeline will have the same global clock time as the old one.
2931 mDocumentTimeline = nullptr;
2933 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
2934 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
2935 mDocumentBaseURI = baseURI.forget();
2936 mChromeXHRDocBaseURI = nullptr;
2940 mChannel = aChannel;
2941 RecomputeResistFingerprinting();
2944 void Document::DisconnectNodeTree() {
2945 // Delete references to sub-documents and kill the subdocument map,
2946 // if any. This is not strictly needed, but makes the node tree
2947 // teardown a bit faster.
2948 mSubDocuments = nullptr;
2950 bool oldVal = mInUnlinkOrDeletion;
2951 mInUnlinkOrDeletion = true;
2952 { // Scope for update
2953 MOZ_AUTO_DOC_UPDATE(this, true);
2955 // Destroy link map now so we don't waste time removing
2956 // links one by one
2957 DestroyElementMaps();
2959 // Invalidate cached array of child nodes
2960 InvalidateChildNodes();
2962 while (nsCOMPtr<nsIContent> content = GetLastChild()) {
2963 nsMutationGuard::DidMutate();
2964 MutationObservers::NotifyContentWillBeRemoved(this, content);
2965 DisconnectChild(content);
2966 if (content == mCachedRootElement) {
2967 // Immediately clear mCachedRootElement, now that it's been removed
2968 // from mChildren, so that GetRootElement() will stop returning this
2969 // now-stale value.
2970 mCachedRootElement = nullptr;
2972 content->UnbindFromTree();
2974 MOZ_ASSERT(!mCachedRootElement,
2975 "After removing all children, there should be no root elem");
2977 mInUnlinkOrDeletion = oldVal;
2980 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2981 nsIPrincipal* aPrincipal,
2982 nsIPrincipal* aPartitionedPrincipal) {
2983 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2984 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2986 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2987 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2989 mSecurityInfo = nullptr;
2991 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2992 if (!aLoadGroup || group != aLoadGroup) {
2993 mDocumentLoadGroup = nullptr;
2996 DisconnectNodeTree();
2998 // Reset our stylesheets
2999 ResetStylesheetsToURI(aURI);
3001 // Release the listener manager
3002 if (mListenerManager) {
3003 mListenerManager->Disconnect();
3004 mListenerManager = nullptr;
3007 // Release the stylesheets list.
3008 mDOMStyleSheets = nullptr;
3010 // Release our principal after tearing down the document, rather than before.
3011 // This ensures that, during teardown, the document and the dying window
3012 // (which already nulled out its document pointer and cached the principal)
3013 // have matching principals.
3014 SetPrincipals(nullptr, nullptr);
3016 // Clear the original URI so SetDocumentURI sets it.
3017 mOriginalURI = nullptr;
3019 SetDocumentURI(aURI);
3020 mChromeXHRDocURI = nullptr;
3021 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
3022 // mDocumentURI.
3023 mDocumentBaseURI = nullptr;
3024 mChromeXHRDocBaseURI = nullptr;
3026 if (aLoadGroup) {
3027 nsCOMPtr<nsIInterfaceRequestor> callbacks;
3028 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
3029 if (callbacks) {
3030 nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
3031 if (loadContext) {
3032 // This is asserting that if we previously set mIsInPrivateBrowsing
3033 // to true from the channel in Document::Reset, that the loadContext
3034 // also believes it to be true.
3035 MOZ_ASSERT(!mIsInPrivateBrowsing ||
3036 mIsInPrivateBrowsing == loadContext->UsePrivateBrowsing());
3037 mIsInPrivateBrowsing = loadContext->UsePrivateBrowsing();
3041 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
3042 // there was an assertion here that aLoadGroup was not null. This
3043 // is no longer valid: nsDocShell::SetDocument does not create a
3044 // load group, and it works just fine
3046 // XXXbz what does "just fine" mean exactly? And given that there
3047 // is no nsDocShell::SetDocument, what is this talking about?
3049 if (IsContentDocument()) {
3050 // Inform the associated request context about this load start so
3051 // any of its internal load progress flags gets reset.
3052 nsCOMPtr<nsIRequestContextService> rcsvc =
3053 net::RequestContextService::GetOrCreate();
3054 if (rcsvc) {
3055 nsCOMPtr<nsIRequestContext> rc;
3056 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
3057 if (rc) {
3058 rc->BeginLoad();
3064 mLastModified.Truncate();
3065 // XXXbz I guess we're assuming that the caller will either pass in
3066 // a channel with a useful type or call SetContentType?
3067 SetContentType(""_ns);
3068 mContentLanguage = nullptr;
3069 mBaseTarget.Truncate();
3071 mXMLDeclarationBits = 0;
3073 // Now get our new principal
3074 if (aPrincipal) {
3075 SetPrincipals(aPrincipal, aPartitionedPrincipal);
3076 } else {
3077 nsIScriptSecurityManager* securityManager =
3078 nsContentUtils::GetSecurityManager();
3079 if (securityManager) {
3080 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
3082 if (!loadContext && aLoadGroup) {
3083 nsCOMPtr<nsIInterfaceRequestor> cbs;
3084 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
3085 loadContext = do_GetInterface(cbs);
3088 MOZ_ASSERT(loadContext,
3089 "must have a load context or pass in an explicit principal");
3091 nsCOMPtr<nsIPrincipal> principal;
3092 nsresult rv = securityManager->GetLoadContextContentPrincipal(
3093 mDocumentURI, loadContext, getter_AddRefs(principal));
3094 if (NS_SUCCEEDED(rv)) {
3095 SetPrincipals(principal, principal);
3100 if (mFontFaceSet) {
3101 mFontFaceSet->RefreshStandardFontLoadPrincipal();
3104 // Refresh the principal on the realm.
3105 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
3106 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
3110 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
3111 nsIPrincipal* aPrincipal) {
3112 if (!aPrincipal) {
3113 return nullptr;
3116 // We can't load a document with an expanded principal. If we're given one,
3117 // automatically downgrade it to the last principal it subsumes (which is the
3118 // extension principal, in the case of extension content scripts).
3119 auto* basePrin = BasePrincipal::Cast(aPrincipal);
3120 if (basePrin->Is<ExpandedPrincipal>()) {
3121 MOZ_DIAGNOSTIC_CRASH(
3122 "Should never try to create a document with "
3123 "an expanded principal");
3125 auto* expanded = basePrin->As<ExpandedPrincipal>();
3126 return do_AddRef(expanded->AllowList().LastElement());
3129 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
3130 // We basically want the parent document here, but because this is very
3131 // early in the load, GetInProcessParentDocument() returns null, so we use
3132 // the docshell hierarchy to get this information instead.
3133 if (RefPtr<BrowsingContext> parent =
3134 mDocumentContainer->GetBrowsingContext()->GetParent()) {
3135 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
3136 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
3137 nsCOMPtr<nsIPrincipal> nullPrincipal =
3138 NullPrincipal::CreateWithoutOriginAttributes();
3139 return nullPrincipal.forget();
3143 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
3144 return principal.forget();
3147 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
3148 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3149 ServoStyleSet& styleSet = EnsureStyleSet();
3151 // lowest index first
3152 int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
3154 size_t count = styleSet.SheetCount(StyleOrigin::Author);
3155 size_t index = 0;
3156 for (; index < count; index++) {
3157 auto* sheet = styleSet.SheetAt(StyleOrigin::Author, index);
3158 MOZ_ASSERT(sheet);
3159 int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
3160 if (sheetDocIndex > newDocIndex) {
3161 break;
3164 // If the sheet is not owned by the document it can be an author
3165 // sheet registered at nsStyleSheetService or an additional author
3166 // sheet on the document, which means the new
3167 // doc sheet should end up before it.
3168 if (sheetDocIndex < 0) {
3169 if (sheetService) {
3170 auto& authorSheets = *sheetService->AuthorStyleSheets();
3171 if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
3172 break;
3175 if (sheet == GetFirstAdditionalAuthorSheet()) {
3176 break;
3181 return index;
3184 void Document::ResetStylesheetsToURI(nsIURI* aURI) {
3185 MOZ_ASSERT(aURI);
3187 ClearAdoptedStyleSheets();
3188 ServoStyleSet& styleSet = EnsureStyleSet();
3190 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
3191 for (auto& sheet : Reversed(aSheetList)) {
3192 sheet->ClearAssociatedDocumentOrShadowRoot();
3193 if (mStyleSetFilled) {
3194 styleSet.RemoveStyleSheet(*sheet);
3197 aSheetList.Clear();
3199 ClearSheetList(mStyleSheets);
3200 for (auto& sheets : mAdditionalSheets) {
3201 ClearSheetList(sheets);
3203 if (mStyleSetFilled) {
3204 if (auto* ss = nsStyleSheetService::GetInstance()) {
3205 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
3206 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
3207 if (sheet->IsApplicable()) {
3208 styleSet.RemoveStyleSheet(*sheet);
3214 // Now reset our inline style and attribute sheets.
3215 if (mAttributeStyles) {
3216 mAttributeStyles->Reset();
3217 mAttributeStyles->SetOwningDocument(this);
3218 } else {
3219 mAttributeStyles = new AttributeStyles(this);
3222 if (mStyleSetFilled) {
3223 FillStyleSetDocumentSheets();
3225 if (styleSet.StyleSheetsHaveChanged()) {
3226 ApplicableStylesChanged();
3231 static void AppendSheetsToStyleSet(
3232 ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
3233 for (StyleSheet* sheet : Reversed(aSheets)) {
3234 aStyleSet->AppendStyleSheet(*sheet);
3238 void Document::FillStyleSetUserAndUASheets() {
3239 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
3240 // ordering.
3242 // The document will fill in the document sheets when we create the presshell
3243 auto* cache = GlobalStyleSheetCache::Singleton();
3245 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3246 MOZ_ASSERT(sheetService,
3247 "should never be creating a StyleSet after the style sheet "
3248 "service has gone");
3250 ServoStyleSet& styleSet = EnsureStyleSet();
3251 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
3252 styleSet.AppendStyleSheet(*sheet);
3255 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
3256 : cache->GetUserContentSheet();
3257 if (sheet) {
3258 styleSet.AppendStyleSheet(*sheet);
3261 styleSet.AppendStyleSheet(*cache->UASheet());
3263 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
3264 styleSet.AppendStyleSheet(*cache->MathMLSheet());
3267 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
3268 styleSet.AppendStyleSheet(*cache->SVGSheet());
3271 styleSet.AppendStyleSheet(*cache->HTMLSheet());
3273 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
3274 styleSet.AppendStyleSheet(*cache->NoFramesSheet());
3277 styleSet.AppendStyleSheet(*cache->CounterStylesSheet());
3279 // Only load the full XUL sheet if we'll need it.
3280 if (LoadsFullXULStyleSheetUpFront()) {
3281 styleSet.AppendStyleSheet(*cache->XULSheet());
3284 styleSet.AppendStyleSheet(*cache->FormsSheet());
3285 styleSet.AppendStyleSheet(*cache->ScrollbarsSheet());
3287 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
3288 styleSet.AppendStyleSheet(*sheet);
3291 MOZ_ASSERT(!mQuirkSheetAdded);
3292 if (NeedsQuirksSheet()) {
3293 styleSet.AppendStyleSheet(*cache->QuirkSheet());
3294 mQuirkSheetAdded = true;
3298 void Document::FillStyleSet() {
3299 MOZ_ASSERT(!mStyleSetFilled);
3300 FillStyleSetUserAndUASheets();
3301 FillStyleSetDocumentSheets();
3302 mStyleSetFilled = true;
3305 void Document::RemoveContentEditableStyleSheet() {
3306 MOZ_ASSERT(IsHTMLOrXHTML());
3308 ServoStyleSet& styleSet = EnsureStyleSet();
3309 auto* cache = GlobalStyleSheetCache::Singleton();
3310 bool changed = false;
3311 if (mContentEditableSheetAdded) {
3312 styleSet.RemoveStyleSheet(*cache->ContentEditableSheet());
3313 mContentEditableSheetAdded = false;
3314 changed = true;
3316 if (changed) {
3317 MOZ_ASSERT(mStyleSetFilled);
3318 ApplicableStylesChanged();
3322 void Document::AddContentEditableStyleSheetToStyleSet() {
3323 MOZ_ASSERT(IsHTMLOrXHTML());
3324 MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
3325 "Caller should ensure we're being rendered");
3327 ServoStyleSet& styleSet = EnsureStyleSet();
3328 auto* cache = GlobalStyleSheetCache::Singleton();
3329 bool changed = false;
3330 if (!mContentEditableSheetAdded) {
3331 styleSet.AppendStyleSheet(*cache->ContentEditableSheet());
3332 mContentEditableSheetAdded = true;
3333 changed = true;
3335 if (changed) {
3336 ApplicableStylesChanged();
3340 void Document::FillStyleSetDocumentSheets() {
3341 ServoStyleSet& styleSet = EnsureStyleSet();
3342 MOZ_ASSERT(styleSet.SheetCount(StyleOrigin::Author) == 0,
3343 "Style set already has document sheets?");
3345 // Sheets are added in reverse order to avoid worst-case time complexity when
3346 // looking up the index of a sheet.
3348 // Note that usually appending is faster (rebuilds less stuff in the
3349 // styleset), but in this case it doesn't matter since we're filling the
3350 // styleset from scratch anyway.
3351 for (StyleSheet* sheet : Reversed(mStyleSheets)) {
3352 if (sheet->IsApplicable()) {
3353 styleSet.AddDocStyleSheet(*sheet);
3357 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
3358 if (aSheet.IsApplicable()) {
3359 styleSet.AddDocStyleSheet(aSheet);
3363 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3364 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
3365 styleSet.AppendStyleSheet(*sheet);
3368 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eAgentSheet]);
3369 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eUserSheet]);
3370 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eAuthorSheet]);
3373 void Document::CompatibilityModeChanged() {
3374 MOZ_ASSERT(IsHTMLOrXHTML());
3375 CSSLoader()->SetCompatibilityMode(mCompatMode);
3377 if (mStyleSet) {
3378 mStyleSet->CompatibilityModeChanged();
3380 if (!mStyleSetFilled) {
3381 MOZ_ASSERT(!mQuirkSheetAdded);
3382 return;
3385 MOZ_ASSERT(mStyleSet);
3386 if (PresShell* presShell = GetPresShell()) {
3387 // Selectors may have become case-sensitive / case-insensitive, the stylist
3388 // has already performed the relevant invalidation.
3389 presShell->EnsureStyleFlush();
3391 if (mQuirkSheetAdded == NeedsQuirksSheet()) {
3392 return;
3394 auto* cache = GlobalStyleSheetCache::Singleton();
3395 StyleSheet* sheet = cache->QuirkSheet();
3396 if (mQuirkSheetAdded) {
3397 mStyleSet->RemoveStyleSheet(*sheet);
3398 } else {
3399 mStyleSet->AppendStyleSheet(*sheet);
3401 mQuirkSheetAdded = !mQuirkSheetAdded;
3402 ApplicableStylesChanged();
3405 void Document::SetCompatibilityMode(nsCompatibility aMode) {
3406 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
3407 "Bad compat mode for XHTML document!");
3409 if (mCompatMode == aMode) {
3410 return;
3412 mCompatMode = aMode;
3413 CompatibilityModeChanged();
3414 // Trigger recomputation of the nsViewportInfo the next time it's queried.
3415 mViewportType = Unknown;
3418 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
3419 uint32_t aSandboxFlags,
3420 nsIChannel* aChannel) {
3421 // If the document permits allow-top-navigation and
3422 // allow-top-navigation-by-user-activation this will permit all top
3423 // navigation.
3424 if (aSandboxFlags != SANDBOXED_NONE &&
3425 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
3426 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
3427 nsContentUtils::ReportToConsole(
3428 nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
3429 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
3430 "BothAllowTopNavigationAndUserActivationPresent");
3432 // If the document is sandboxed (via the HTML5 iframe sandbox
3433 // attribute) and both the allow-scripts and allow-same-origin
3434 // keywords are supplied, the sandboxed document can call into its
3435 // parent document and remove its sandboxing entirely - we print a
3436 // warning to the web console in this case.
3437 if (aSandboxFlags & SANDBOXED_NAVIGATION &&
3438 !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
3439 !(aSandboxFlags & SANDBOXED_ORIGIN)) {
3440 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
3441 MOZ_ASSERT(bc->IsInProcess());
3443 RefPtr<BrowsingContext> parentBC = bc->GetParent();
3444 if (!parentBC || !parentBC->IsInProcess()) {
3445 // If parent document is not in process, then by construction it
3446 // cannot be same origin.
3447 return;
3450 // Don't warn if our parent is not the top-level document.
3451 if (!parentBC->IsTopContent()) {
3452 return;
3455 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
3456 MOZ_ASSERT(parentDocShell);
3458 nsCOMPtr<nsIChannel> parentChannel;
3459 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
3460 if (!parentChannel) {
3461 return;
3463 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
3464 if (NS_FAILED(rv)) {
3465 return;
3468 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
3469 nsCOMPtr<nsIURI> iframeUri;
3470 parentChannel->GetURI(getter_AddRefs(iframeUri));
3471 nsContentUtils::ReportToConsole(
3472 nsIScriptError::warningFlag, "Iframe Sandbox"_ns, parentDocument,
3473 nsContentUtils::eSECURITY_PROPERTIES,
3474 "BothAllowScriptsAndSameOriginPresent", nsTArray<nsString>(),
3475 SourceLocation(iframeUri.get()));
3479 bool Document::IsSynthesized() {
3480 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
3481 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
3484 // static
3485 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
3486 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
3487 return principal && (principal->IsSystemPrincipal() ||
3488 principal->GetIsAddonOrExpandedAddonPrincipal());
3491 static void CheckIsBadPolicy(nsILoadInfo::CrossOriginOpenerPolicy aPolicy,
3492 BrowsingContext* aContext, nsIChannel* aChannel) {
3493 #if defined(EARLY_BETA_OR_EARLIER)
3494 auto requireCORP =
3495 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
3497 if (aContext->GetOpenerPolicy() == aPolicy ||
3498 (aContext->GetOpenerPolicy() != requireCORP && aPolicy != requireCORP)) {
3499 return;
3502 nsCOMPtr<nsIURI> uri;
3503 bool hasURI = NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(uri)));
3505 bool isViewSource = hasURI && uri->SchemeIs("view-source");
3507 nsCString contentType;
3508 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3509 bool isPDFJS = bag &&
3510 NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3511 contentType)) &&
3512 contentType.EqualsLiteral(APPLICATION_PDF);
3514 MOZ_DIAGNOSTIC_ASSERT(!isViewSource,
3515 "Bug 1834864: Assert due to view-source.");
3516 MOZ_DIAGNOSTIC_ASSERT(!isPDFJS, "Bug 1834864: Assert due to pdfjs.");
3517 MOZ_DIAGNOSTIC_ASSERT(aPolicy == requireCORP,
3518 "Assert due to clearing REQUIRE_CORP.");
3519 MOZ_DIAGNOSTIC_ASSERT(aContext->GetOpenerPolicy() == requireCORP,
3520 "Assert due to setting REQUIRE_CORP.");
3521 #endif // defined(EARLY_BETA_OR_EARLIER)
3524 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
3525 nsILoadGroup* aLoadGroup,
3526 nsISupports* aContainer,
3527 nsIStreamListener** aDocListener,
3528 bool aReset) {
3529 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
3530 nsCOMPtr<nsIURI> uri;
3531 aChannel->GetURI(getter_AddRefs(uri));
3532 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
3533 ("DOCUMENT %p StartDocumentLoad %s", this,
3534 uri ? uri->GetSpecOrDefault().get() : ""));
3537 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
3538 "Bad readyState");
3539 SetReadyStateInternal(READYSTATE_LOADING);
3541 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
3542 mLoadedAsData = true;
3543 SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true);
3544 // We need to disable script & style loading in this case.
3545 // We leave them disabled even in EndLoad(), and let anyone
3546 // who puts the document on display to worry about enabling.
3548 // Do not load/process scripts when loading as data
3549 ScriptLoader()->SetEnabled(false);
3551 // styles
3552 CSSLoader()->SetEnabled(
3553 false); // Do not load/process styles when loading as data
3554 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
3555 // Allow CSS, but not scripts
3556 ScriptLoader()->SetEnabled(false);
3559 mMayStartLayout = false;
3560 MOZ_ASSERT(!mReadyForIdle,
3561 "We should never hit DOMContentLoaded before this point");
3563 if (aReset) {
3564 Reset(aChannel, aLoadGroup);
3567 nsAutoCString contentType;
3568 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3569 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3570 contentType))) ||
3571 NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
3572 // XXX this is only necessary for viewsource:
3573 nsACString::const_iterator start, end, semicolon;
3574 contentType.BeginReading(start);
3575 contentType.EndReading(end);
3576 semicolon = start;
3577 FindCharInReadable(';', semicolon, end);
3578 SetContentType(Substring(start, semicolon));
3581 RetrieveRelevantHeaders(aChannel);
3583 mChannel = aChannel;
3584 RecomputeResistFingerprinting();
3585 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
3586 if (inStrmChan) {
3587 bool isSrcdocChannel;
3588 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
3589 if (isSrcdocChannel) {
3590 mIsSrcdocDocument = true;
3594 if (mChannel) {
3595 nsLoadFlags loadFlags;
3596 mChannel->GetLoadFlags(&loadFlags);
3597 bool isDocument = false;
3598 mChannel->GetIsDocument(&isDocument);
3599 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
3600 IsSynthesized() && XRE_IsContentProcess()) {
3601 ContentChild::UpdateCookieStatus(mChannel);
3604 // Store the security info for future use.
3605 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
3608 // If this document is being loaded by a docshell, copy its sandbox flags
3609 // to the document, and store the fullscreen enabled flag. These are
3610 // immutable after being set here.
3611 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
3613 // If this is an error page, don't inherit sandbox flags
3614 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3615 if (docShell && !loadInfo->GetLoadErrorPage()) {
3616 mSandboxFlags = loadInfo->GetSandboxFlags();
3617 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
3620 // Set the opener policy for the top level content document.
3621 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
3622 nsILoadInfo::CrossOriginOpenerPolicy policy =
3623 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
3624 if (IsTopLevelContentDocument() && httpChan &&
3625 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
3626 docShell->GetBrowsingContext()) {
3627 CheckIsBadPolicy(policy, docShell->GetBrowsingContext(), aChannel);
3629 // Setting the opener policy on a discarded context has no effect.
3630 Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
3633 // The CSP directives upgrade-insecure-requests as well as
3634 // block-all-mixed-content not only apply to the toplevel document,
3635 // but also to nested documents. The loadInfo of a subdocument
3636 // load already holds the correct flag, so let's just set it here
3637 // on the document. Please note that we set the appropriate preload
3638 // bits just for the sake of completeness here, because the preloader
3639 // does not reach into subdocuments.
3640 mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
3641 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3642 mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
3643 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3645 // HTTPS-Only Mode flags
3646 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
3647 // sub-resources and sub-documents.
3648 mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
3650 nsresult rv = InitReferrerInfo(aChannel);
3651 NS_ENSURE_SUCCESS(rv, rv);
3653 rv = InitCOEP(aChannel);
3654 NS_ENSURE_SUCCESS(rv, rv);
3656 // HACK: Calling EnsureIPCPoliciesRead() here will parse the CSP using the
3657 // context's current mSelfURI (which is still the previous mSelfURI),
3658 // bypassing some internal bugs with 'self' and iframe inheritance.
3659 // Not calling it here results in the mSelfURI being the current mSelfURI and
3660 // not the previous which breaks said inheritance.
3661 // https://bugzilla.mozilla.org/show_bug.cgi?id=1793560#ch-8
3662 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3663 if (cspToInherit) {
3664 cspToInherit->EnsureIPCPoliciesRead();
3667 rv = InitCSP(aChannel);
3668 NS_ENSURE_SUCCESS(rv, rv);
3670 rv = InitDocPolicy(aChannel);
3671 NS_ENSURE_SUCCESS(rv, rv);
3673 // Initialize FeaturePolicy
3674 rv = InitFeaturePolicy(aChannel);
3675 NS_ENSURE_SUCCESS(rv, rv);
3677 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3678 NS_ENSURE_SUCCESS(rv, rv);
3680 // Generally XFO and CSP frame-ancestors is handled within
3681 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3682 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3683 nsContentPolicyType internalContentType =
3684 loadInfo->InternalContentPolicyType();
3685 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3686 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3687 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3689 nsresult status;
3690 aChannel->GetStatus(&status);
3691 if (status == NS_ERROR_XFO_VIOLATION) {
3692 // stop! ERROR page!
3693 // But before we have to reset the principal of the document
3694 // because the onload() event fires before the error page
3695 // is displayed and we do not want the enclosing document
3696 // to access the contentDocument.
3697 RefPtr<NullPrincipal> nullPrincipal =
3698 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3699 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3700 // and also GetInnerWindow() is still null at this point, before
3701 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3702 // within Document.cpp
3703 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3704 SetPrincipals(nullPrincipal, nullPrincipal);
3708 return NS_OK;
3711 void Document::SetLoadedAsData(bool aLoadedAsData,
3712 bool aConsiderForMemoryReporting) {
3713 mLoadedAsData = aLoadedAsData;
3714 if (aConsiderForMemoryReporting) {
3715 nsIGlobalObject* global = GetScopeObject();
3716 if (global) {
3717 if (nsPIDOMWindowInner* window = global->GetAsInnerWindow()) {
3718 nsGlobalWindowInner::Cast(window)
3719 ->RegisterDataDocumentForMemoryReporting(this);
3725 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3727 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) {
3728 mCSP = aCSP;
3729 mHasPolicyWithRequireTrustedTypesForDirective =
3730 aCSP && aCSP->GetHasPolicyWithRequireTrustedTypesForDirective();
3733 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3734 return mPreloadCSP;
3737 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3738 mPreloadCSP = aPreloadCSP;
3741 void Document::GetCspJSON(nsString& aJSON) {
3742 aJSON.Truncate();
3744 if (!mCSP) {
3745 dom::CSPPolicies jsonPolicies;
3746 jsonPolicies.ToJSON(aJSON);
3747 return;
3749 mCSP->ToJSON(aJSON);
3752 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3753 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3754 nsAutoString messageTag;
3755 aMessages[i]->GetTag(messageTag);
3757 nsAutoString category;
3758 aMessages[i]->GetCategory(category);
3760 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3761 NS_ConvertUTF16toUTF8(category), this,
3762 nsContentUtils::eSECURITY_PROPERTIES,
3763 NS_ConvertUTF16toUTF8(messageTag).get());
3767 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3768 nsresult rv = NS_OK;
3769 if (!aSpeculative) {
3770 // 1) apply settings from regular CSP
3771 if (mCSP) {
3772 // Set up 'block-all-mixed-content' if not already inherited
3773 // from the parent context or set by any other CSP.
3774 if (!mBlockAllMixedContent) {
3775 bool block = false;
3776 rv = mCSP->GetBlockAllMixedContent(&block);
3777 NS_ENSURE_SUCCESS_VOID(rv);
3778 mBlockAllMixedContent = block;
3780 if (!mBlockAllMixedContentPreloads) {
3781 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3784 // Set up 'upgrade-insecure-requests' if not already inherited
3785 // from the parent context or set by any other CSP.
3786 if (!mUpgradeInsecureRequests) {
3787 bool upgrade = false;
3788 rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
3789 NS_ENSURE_SUCCESS_VOID(rv);
3790 mUpgradeInsecureRequests = upgrade;
3792 if (!mUpgradeInsecurePreloads) {
3793 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3795 // Update csp settings in the parent process
3796 if (auto* wgc = GetWindowGlobalChild()) {
3797 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3798 mUpgradeInsecureRequests);
3801 return;
3804 // 2) apply settings from speculative csp
3805 if (mPreloadCSP) {
3806 if (!mBlockAllMixedContentPreloads) {
3807 bool block = false;
3808 rv = mPreloadCSP->GetBlockAllMixedContent(&block);
3809 NS_ENSURE_SUCCESS_VOID(rv);
3810 mBlockAllMixedContent = block;
3812 if (!mUpgradeInsecurePreloads) {
3813 bool upgrade = false;
3814 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
3815 NS_ENSURE_SUCCESS_VOID(rv);
3816 mUpgradeInsecurePreloads = upgrade;
3821 nsresult Document::InitCSP(nsIChannel* aChannel) {
3822 MOZ_ASSERT(!mScriptGlobalObject,
3823 "CSP must be initialized before mScriptGlobalObject is set!");
3825 // If this is a data document - no need to set CSP.
3826 if (mLoadedAsData) {
3827 return NS_OK;
3830 // If this is an image, no need to set a CSP. Otherwise SVG images
3831 // served with a CSP might block internally applied inline styles.
3832 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3833 if (loadInfo->GetExternalContentPolicyType() ==
3834 ExtContentPolicy::TYPE_IMAGE ||
3835 loadInfo->GetExternalContentPolicyType() ==
3836 ExtContentPolicy::TYPE_IMAGESET) {
3837 return NS_OK;
3840 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3842 // If there is a CSP that needs to be inherited from whatever
3843 // global is considered the client of the document fetch then
3844 // we query it here from the loadinfo in case the newly created
3845 // document needs to inherit the CSP. See:
3846 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3847 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
3848 if (inheritedCSP) {
3849 mCSP = loadInfo->GetCspToInherit();
3852 // If there is no CSP to inherit, then we create a new CSP here so
3853 // that history entries always have the right reference in case a
3854 // Meta CSP gets dynamically added after the history entry has
3855 // already been created.
3856 if (!mCSP) {
3857 mCSP = new nsCSPContext();
3858 mHasPolicyWithRequireTrustedTypesForDirective = false;
3859 } else {
3860 mHasPolicyWithRequireTrustedTypesForDirective =
3861 mCSP->GetHasPolicyWithRequireTrustedTypesForDirective();
3864 // Always overwrite the requesting context of the CSP so that any new
3865 // 'self' keyword added to an inherited CSP translates correctly.
3866 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3867 if (NS_WARN_IF(NS_FAILED(rv))) {
3868 return rv;
3871 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3873 nsCOMPtr<nsIHttpChannel> httpChannel;
3874 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3875 if (NS_WARN_IF(NS_FAILED(rv))) {
3876 return rv;
3879 if (httpChannel) {
3880 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3881 tCspHeaderValue);
3883 Unused << httpChannel->GetResponseHeader(
3884 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3886 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3887 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3889 // Check if this is a document from a WebExtension.
3890 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3891 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3893 // If there's no CSP to apply, go ahead and return early
3894 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
3895 cspROHeaderValue.IsEmpty()) {
3896 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3897 nsCOMPtr<nsIURI> chanURI;
3898 aChannel->GetURI(getter_AddRefs(chanURI));
3899 nsAutoCString aspec;
3900 chanURI->GetAsciiSpec(aspec);
3901 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3902 ("no CSP for document, %s", aspec.get()));
3905 return NS_OK;
3908 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3909 ("Document is an add-on or CSP header specified %p", this));
3911 // ----- if the doc is an addon, apply its CSP.
3912 if (addonPolicy) {
3913 mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
3915 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3916 // Bug 1548468: Move CSP off ExpandedPrincipal
3917 // Currently the LoadInfo holds the source of truth for every resource load
3918 // because LoadInfo::GetCsp() queries the CSP from an ExpandedPrincipal
3919 // (and not from the Client) if the load was triggered by an extension.
3920 auto* basePrin = BasePrincipal::Cast(principal);
3921 if (basePrin->Is<ExpandedPrincipal>()) {
3922 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3926 // ----- if there's a full-strength CSP header, apply it.
3927 if (!cspHeaderValue.IsEmpty()) {
3928 mHasCSPDeliveredThroughHeader = true;
3929 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3930 NS_ENSURE_SUCCESS(rv, rv);
3933 // ----- if there's a report-only CSP header, apply it.
3934 if (!cspROHeaderValue.IsEmpty()) {
3935 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3936 NS_ENSURE_SUCCESS(rv, rv);
3939 // ----- Enforce sandbox policy if supplied in CSP header
3940 // The document may already have some sandbox flags set (e.g. if the document
3941 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3942 // directive, intersect the CSP sandbox flags with the existing flags. This
3943 // corresponds to the _least_ permissive policy.
3944 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3945 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3946 NS_ENSURE_SUCCESS(rv, rv);
3948 // Probably the iframe sandbox attribute already caused the creation of a
3949 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3950 // and no one has been created yet.
3951 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3952 !(mSandboxFlags & SANDBOXED_ORIGIN);
3954 mSandboxFlags |= cspSandboxFlags;
3956 if (needNewNullPrincipal) {
3957 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3958 // Skip setting the content blocking allowlist principal to NullPrincipal.
3959 // The principal is only used to enable/disable trackingprotection via
3960 // permission and can be shared with the top level sandboxed site.
3961 // See Bug 1654546.
3962 SetPrincipals(principal, principal);
3965 ApplySettingsFromCSP(false);
3966 return NS_OK;
3969 static FeaturePolicy* GetFeaturePolicyFromElement(Element* aElement) {
3970 if (auto* iframe = HTMLIFrameElement::FromNodeOrNull(aElement)) {
3971 return iframe->FeaturePolicy();
3974 if (!HTMLObjectElement::FromNodeOrNull(aElement) &&
3975 !HTMLEmbedElement::FromNodeOrNull(aElement)) {
3976 return nullptr;
3979 return aElement->OwnerDoc()->FeaturePolicy();
3982 nsresult Document::InitDocPolicy(nsIChannel* aChannel) {
3983 // We only use document policy to implement the text fragments spec, so leave
3984 // everything at the default value if it isn't enabled. This includes the
3985 // behavior for element fragments.
3986 if (!StaticPrefs::dom_text_fragments_enabled()) {
3987 return NS_OK;
3990 nsCOMPtr<nsIHttpChannel> httpChannel;
3991 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3992 if (NS_WARN_IF(NS_FAILED(rv))) {
3993 return rv;
3996 nsAutoCString docPolicyString;
3997 if (httpChannel) {
3998 Unused << httpChannel->GetResponseHeader("Document-Policy"_ns,
3999 docPolicyString);
4002 if (docPolicyString.IsEmpty()) {
4003 return NS_OK;
4006 mForceLoadAtTop = NS_GetForceLoadAtTopFromHeader(docPolicyString);
4008 return NS_OK;
4011 void Document::InitFeaturePolicy(
4012 const Variant<Nothing, FeaturePolicyInfo, Element*>&
4013 aContainerFeaturePolicy) {
4014 MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created");
4016 mFeaturePolicy->ResetDeclaredPolicy();
4018 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
4020 RefPtr<dom::FeaturePolicy> featurePolicy = mFeaturePolicy;
4021 aContainerFeaturePolicy.match(
4022 [](const Nothing&) {},
4023 [featurePolicy](const FeaturePolicyInfo& aContainerFeaturePolicy) {
4024 // Let's inherit the policy from the possibly cross-origin container.
4025 featurePolicy->InheritPolicy(aContainerFeaturePolicy);
4026 featurePolicy->SetSrcOrigin(aContainerFeaturePolicy.mSrcOrigin);
4028 [featurePolicy](Element* aContainer) {
4029 // Let's inherit the policy from the parent container element if it
4030 // exists.
4031 if (RefPtr<dom::FeaturePolicy> containerFeaturePolicy =
4032 GetFeaturePolicyFromElement(aContainer)) {
4033 featurePolicy->InheritPolicy(containerFeaturePolicy);
4034 featurePolicy->SetSrcOrigin(containerFeaturePolicy->GetSrcOrigin());
4039 Element* GetEmbedderElementFrom(BrowsingContext* aBrowsingContext) {
4040 if (!aBrowsingContext) {
4041 return nullptr;
4043 if (!aBrowsingContext->IsContentSubframe()) {
4044 return nullptr;
4047 return aBrowsingContext->GetEmbedderElement();
4050 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
4051 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
4052 if (Element* embedderElement = GetEmbedderElementFrom(GetBrowsingContext())) {
4053 InitFeaturePolicy(AsVariant(embedderElement));
4054 } else if (Maybe<FeaturePolicyInfo> featurePolicyContainer =
4055 loadInfo->GetContainerFeaturePolicyInfo()) {
4056 InitFeaturePolicy(AsVariant(*featurePolicyContainer));
4057 } else {
4058 InitFeaturePolicy(AsVariant(Nothing{}));
4061 // We don't want to parse the http Feature-Policy header if this pref is off.
4062 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
4063 return NS_OK;
4066 nsCOMPtr<nsIHttpChannel> httpChannel;
4067 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4068 if (NS_WARN_IF(NS_FAILED(rv))) {
4069 return rv;
4072 if (!httpChannel) {
4073 return NS_OK;
4076 // query the policy from the header
4077 nsAutoCString value;
4078 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
4079 if (NS_SUCCEEDED(rv)) {
4080 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
4081 NodePrincipal(), nullptr);
4084 return NS_OK;
4087 void Document::EnsureNotEnteringAndExitFullscreen() {
4088 Document::ClearPendingFullscreenRequests(this);
4089 if (GetFullscreenElement()) {
4090 Document::AsyncExitFullscreen(this);
4094 void Document::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
4095 mReferrerInfo = aReferrerInfo;
4096 mCachedReferrerInfoForInternalCSSAndSVGResources = nullptr;
4097 mCachedURLData = nullptr;
4100 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
4101 MOZ_ASSERT(mReferrerInfo);
4102 MOZ_ASSERT(mPreloadReferrerInfo);
4104 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
4105 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
4106 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
4107 // they have an opaque origin.
4108 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
4109 if (BrowsingContext* bc = GetBrowsingContext()) {
4110 // At this point the document is not fully created and mParentDocument has
4111 // not been set yet,
4112 Document* parentDoc = bc->GetEmbedderElement()
4113 ? bc->GetEmbedderElement()->OwnerDoc()
4114 : nullptr;
4115 if (parentDoc) {
4116 SetReferrerInfo(parentDoc->GetReferrerInfo());
4117 mPreloadReferrerInfo = mReferrerInfo;
4118 return NS_OK;
4121 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
4122 "srcdoc without null principal as toplevel!");
4126 nsCOMPtr<nsIHttpChannel> httpChannel;
4127 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4128 if (NS_WARN_IF(NS_FAILED(rv))) {
4129 return rv;
4132 if (!httpChannel) {
4133 return NS_OK;
4136 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo()) {
4137 SetReferrerInfo(referrerInfo);
4140 // Override policy if we get one from Referrerr-Policy header
4141 mozilla::dom::ReferrerPolicy policy =
4142 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
4143 nsCOMPtr<nsIReferrerInfo> clone =
4144 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
4145 ->CloneWithNewPolicy(policy);
4146 SetReferrerInfo(clone);
4147 mPreloadReferrerInfo = mReferrerInfo;
4148 return NS_OK;
4151 nsresult Document::InitCOEP(nsIChannel* aChannel) {
4152 nsCOMPtr<nsIHttpChannel> httpChannel;
4153 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4154 if (NS_FAILED(rv)) {
4155 return NS_OK;
4158 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
4160 if (!intChannel) {
4161 return NS_OK;
4164 nsILoadInfo::CrossOriginEmbedderPolicy policy =
4165 nsILoadInfo::EMBEDDER_POLICY_NULL;
4166 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(
4167 mTrials.IsEnabled(OriginTrial::CoepCredentialless), &policy))) {
4168 mEmbedderPolicy = Some(policy);
4171 return NS_OK;
4174 void Document::StopDocumentLoad() {
4175 if (mParser) {
4176 mParserAborted = true;
4177 mParser->Terminate();
4181 void Document::SetDocumentURI(nsIURI* aURI) {
4182 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
4183 mDocumentURI = aURI;
4184 // This loosely implements §3.4.1 of Text Fragments
4185 // https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives
4186 // Unlike specified in the spec, the fragment directive is not stripped from
4187 // the URL in the session history entry. Instead it is removed when the URL is
4188 // set in the `Document`. Also, instead of storing the `uninvokedDirective` in
4189 // `Document` as mentioned in the spec, the extracted directives are moved to
4190 // the `FragmentDirective` object which deals with finding the ranges to
4191 // highlight in `ScrollToRef()`.
4192 // XXX(:jjaschke): This is only a temporary solution.
4193 // https://bugzil.la/1881429 is filed for revisiting this.
4194 nsTArray<TextDirective> textDirectives;
4195 FragmentDirective::ParseAndRemoveFragmentDirectiveFromFragment(
4196 mDocumentURI, &textDirectives);
4197 FragmentDirective()->SetTextDirectives(std::move(textDirectives));
4199 nsIURI* newBase = GetDocBaseURI();
4201 mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI);
4203 bool equalBases = false;
4204 // Changing just the ref of a URI does not change how relative URIs would
4205 // resolve wrt to it, so we can treat the bases as equal as long as they're
4206 // equal ignoring the ref.
4207 if (oldBase && newBase) {
4208 oldBase->EqualsExceptRef(newBase, &equalBases);
4209 } else {
4210 equalBases = !oldBase && !newBase;
4213 // If this is the first time we're setting the document's URI, set the
4214 // document's original URI.
4215 if (!mOriginalURI) mOriginalURI = mDocumentURI;
4217 // If changing the document's URI changed the base URI of the document, we
4218 // need to refresh the hrefs of all the links on the page.
4219 if (!equalBases) {
4220 mCachedURLData = nullptr;
4221 RefreshLinkHrefs();
4224 // Recalculate our base domain
4225 mBaseDomain.Truncate();
4226 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
4227 if (thirdPartyUtil) {
4228 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
4231 // Tell our WindowGlobalParent that the document's URI has been changed.
4232 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
4233 wgc->SetDocumentURI(mDocumentURI);
4237 static void GetFormattedTimeString(PRTime aTime, bool aUniversal,
4238 nsAString& aFormattedTimeString) {
4239 PRExplodedTime prtime;
4240 PR_ExplodeTime(aTime, aUniversal ? PR_GMTParameters : PR_LocalTimeParameters,
4241 &prtime);
4242 // "MM/DD/YYYY hh:mm:ss"
4243 char formatedTime[24];
4244 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
4245 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
4246 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
4247 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
4248 } else {
4249 // If we for whatever reason failed to find the last modified time
4250 // (or even the current time), fall back to what NS4.x returned.
4251 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
4255 void Document::GetLastModified(nsAString& aLastModified) const {
4256 if (!mLastModified.IsEmpty()) {
4257 aLastModified.Assign(mLastModified);
4258 } else {
4259 GetFormattedTimeString(PR_Now(),
4260 ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
4261 aLastModified);
4265 static void IncrementExpandoGeneration(Document& aDoc) {
4266 ++aDoc.mExpandoAndGeneration.generation;
4269 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
4270 MOZ_ASSERT(
4271 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
4272 "Only put elements that need to be exposed as document['name'] in "
4273 "the named table.");
4275 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
4277 // Null for out-of-memory
4278 if (entry) {
4279 if (!entry->HasNameElement() &&
4280 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4281 IncrementExpandoGeneration(*this);
4283 entry->AddNameElement(this, aElement);
4287 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
4288 // Speed up document teardown
4289 if (mIdentifierMap.Count() == 0) return;
4291 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
4292 if (!entry) // Could be false if the element was anonymous, hence never added
4293 return;
4295 entry->RemoveNameElement(aElement);
4296 if (!entry->HasNameElement() &&
4297 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4298 IncrementExpandoGeneration(*this);
4302 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
4303 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
4305 if (entry) { /* True except on OOM */
4306 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4307 !entry->HasNameElement() &&
4308 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4309 IncrementExpandoGeneration(*this);
4311 entry->AddIdElement(aElement);
4315 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
4316 NS_ASSERTION(aId, "huhwhatnow?");
4318 // Speed up document teardown
4319 if (mIdentifierMap.Count() == 0) {
4320 return;
4323 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
4324 if (!entry) // Can be null for XML elements with changing ids.
4325 return;
4327 entry->RemoveIdElement(aElement);
4328 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4329 !entry->HasNameElement() &&
4330 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4331 IncrementExpandoGeneration(*this);
4333 if (entry->IsEmpty()) {
4334 mIdentifierMap.RemoveEntry(entry);
4338 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
4339 bool aPreload) {
4340 ReferrerPolicyEnum policy =
4341 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
4342 // The empty string "" corresponds to no referrer policy, causing a fallback
4343 // to a referrer policy defined elsewhere.
4344 if (policy == ReferrerPolicy::_empty) {
4345 return;
4348 MOZ_ASSERT(mReferrerInfo);
4349 MOZ_ASSERT(mPreloadReferrerInfo);
4351 if (aPreload) {
4352 mPreloadReferrerInfo =
4353 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
4354 ->CloneWithNewPolicy(policy);
4355 } else {
4356 nsCOMPtr<nsIReferrerInfo> clone =
4357 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
4358 ->CloneWithNewPolicy(policy);
4359 SetReferrerInfo(clone);
4363 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
4364 nsIPrincipal* aNewPartitionedPrincipal) {
4365 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
4366 if (aNewPrincipal && mAllowDNSPrefetch &&
4367 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
4368 if (aNewPrincipal->SchemeIs("https")) {
4369 mAllowDNSPrefetch = false;
4373 mScriptLoader->DeregisterFromCache();
4374 mCSSLoader->DeregisterFromSheetCache();
4376 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
4377 mPartitionedPrincipal = aNewPartitionedPrincipal;
4379 mCachedURLData = nullptr;
4381 mCSSLoader->RegisterInSheetCache();
4382 mScriptLoader->RegisterToCache();
4384 RecomputeResistFingerprinting();
4386 #ifdef DEBUG
4387 // Validate that the docgroup is set correctly by calling its getter and
4388 // triggering its sanity check.
4390 // If we're setting the principal to null, we don't want to perform the check,
4391 // as the document is entering an intermediate state where it does not have a
4392 // principal. It will be given another real principal shortly which we will
4393 // check. It's not unsafe to have a document which has a null principal in the
4394 // same docgroup as another document, so this should not be a problem.
4395 if (aNewPrincipal) {
4396 GetDocGroup();
4398 #endif
4401 #ifdef DEBUG
4402 void Document::AssertDocGroupMatchesKey() const {
4403 // Sanity check that we have an up-to-date and accurate docgroup
4404 // We only check if the principal when we can get the browsing context.
4406 // Note that we can be invoked during cycle collection, so we need to handle
4407 // the browsingcontext being partially unlinked - normally you shouldn't
4408 // null-check `Group()` as it shouldn't return nullptr.
4409 if (!GetBrowsingContext() || !GetBrowsingContext()->Group()) {
4410 return;
4413 if (mDocGroup && mDocGroup->GetBrowsingContextGroup()) {
4414 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
4415 GetBrowsingContext()->Group());
4417 // GetKey() can fail, e.g. after the TLD service has shut down.
4418 nsAutoCString docGroupKey;
4419 nsresult rv = mozilla::dom::DocGroup::GetKey(
4420 NodePrincipal(),
4421 GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated(),
4422 docGroupKey);
4423 if (NS_SUCCEEDED(rv)) {
4424 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
4428 #endif
4430 nsresult Document::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) const {
4431 return SchedulerGroup::Dispatch(std::move(aRunnable));
4434 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
4435 bool aIsTracking) {
4436 if (aIsTracking) {
4437 mTrackingScripts.Insert(aURL);
4439 // Ideally, whether a given script is tracking or not should be consistent,
4440 // but there is a race so that it is not, when loading real sites in debug
4441 // builds. See bug 1925286.
4442 // MOZ_ASSERT_IF(!aIsTracking, !mTrackingScripts.Contains(aURL));
4445 bool Document::IsScriptTracking(JSContext* aCx) const {
4446 JS::AutoFilename filename;
4447 if (!JS::DescribeScriptedCaller(&filename, aCx)) {
4448 return false;
4450 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
4453 void Document::GetContentType(nsAString& aContentType) {
4454 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
4457 void Document::SetContentType(const nsACString& aContentType) {
4458 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
4459 aContentType.EqualsLiteral("application/xhtml+xml")) {
4460 mDefaultElementType = kNameSpaceID_XHTML;
4463 mCachedEncoder = nullptr;
4464 mContentType = aContentType;
4467 bool Document::HasPendingInitialTranslation() {
4468 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
4471 bool Document::HasPendingL10nMutations() const {
4472 return mDocumentL10n && mDocumentL10n->HasPendingMutations();
4475 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
4476 JS::Rooted<JSObject*> object(aCx, aObject);
4477 nsCOMPtr<nsIPrincipal> callerPrincipal =
4478 nsContentUtils::SubjectPrincipal(aCx);
4479 nsGlobalWindowInner* win = xpc::WindowOrNull(object);
4480 bool allowed = false;
4481 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
4482 &allowed);
4483 return allowed;
4486 void Document::LocalizationLinkAdded(Element* aLinkElement) {
4487 if (!AllowsL10n()) {
4488 return;
4491 nsAutoString href;
4492 aLinkElement->GetAttr(nsGkAtoms::href, href);
4494 if (!mDocumentL10n) {
4495 Element* elem = GetDocumentElement();
4496 MOZ_DIAGNOSTIC_ASSERT(elem);
4498 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
4499 mDocumentL10n = DocumentL10n::Create(this, isSync);
4500 if (NS_WARN_IF(!mDocumentL10n)) {
4501 return;
4505 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
4507 if (mReadyState >= READYSTATE_INTERACTIVE) {
4508 nsContentUtils::AddScriptRunner(NewRunnableMethod(
4509 "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
4510 &DocumentL10n::TriggerInitialTranslation));
4511 } else {
4512 if (!mDocumentL10n->mBlockingLayout) {
4513 // Our initial translation is going to block layout start. Make sure
4514 // we don't fire the load event until after that stops happening and
4515 // layout has a chance to start.
4516 BlockOnload();
4517 mDocumentL10n->mBlockingLayout = true;
4522 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
4523 if (!AllowsL10n()) {
4524 return;
4527 if (mDocumentL10n) {
4528 nsAutoString href;
4529 aLinkElement->GetAttr(nsGkAtoms::href, href);
4530 uint32_t remaining =
4531 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
4532 if (remaining == 0) {
4533 if (mDocumentL10n->mBlockingLayout) {
4534 mDocumentL10n->mBlockingLayout = false;
4535 UnblockOnload(/* aFireSync = */ false);
4537 mDocumentL10n = nullptr;
4543 * This method should be called once the end of the l10n
4544 * resource container has been parsed.
4546 * In XUL this is the end of the first </linkset>,
4547 * In XHTML/HTML this is the end of </head>.
4549 * This milestone is used to allow for batch
4550 * localization context I/O and building done
4551 * once when all resources in the document have been
4552 * collected.
4554 void Document::OnL10nResourceContainerParsed() {
4555 // XXX: This is a scaffolding for where we might inject prefetch
4556 // in bug 1717241.
4559 void Document::OnParsingCompleted() {
4560 // Let's call it again, in case the resource
4561 // container has not been closed, and only
4562 // now we're closing the document.
4563 OnL10nResourceContainerParsed();
4565 if (mDocumentL10n) {
4566 RefPtr<DocumentL10n> l10n = mDocumentL10n;
4567 l10n->TriggerInitialTranslation();
4571 void Document::InitialTranslationCompleted(bool aL10nCached) {
4572 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4573 // This means we blocked the load event in LocalizationLinkAdded. It's
4574 // important that the load blocker removal here be async, because our caller
4575 // will notify the content sink after us, and we want the content sync's
4576 // work to happen before the load event fires.
4577 mDocumentL10n->mBlockingLayout = false;
4578 UnblockOnload(/* aFireSync = */ false);
4581 mL10nProtoElements.Clear();
4583 nsXULPrototypeDocument* proto = GetPrototype();
4584 if (proto) {
4585 proto->SetIsL10nCached(aL10nCached);
4589 bool Document::AllowsL10n() const {
4590 if (IsStaticDocument()) {
4591 // We don't allow l10n on static documents, because the nodes are already
4592 // cloned translated, and static docs don't get parsed so we never
4593 // TriggerInitialTranslation, etc, so a load blocker would keep hanging
4594 // forever.
4595 return false;
4597 bool allowed = false;
4598 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4599 return allowed;
4602 DocumentTimeline* Document::Timeline() {
4603 if (!mDocumentTimeline) {
4604 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4607 return mDocumentTimeline;
4610 SVGSVGElement* Document::GetSVGRootElement() const {
4611 Element* root = GetRootElement();
4612 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4613 return nullptr;
4615 return static_cast<SVGSVGElement*>(root);
4618 /* Return true if the document is in the focused top-level window, and is an
4619 * ancestor of the focused DOMWindow. */
4620 bool Document::HasFocus(ErrorResult& rv) const {
4621 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4622 if (!fm) {
4623 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4624 return false;
4627 BrowsingContext* bc = GetBrowsingContext();
4628 if (!bc) {
4629 return false;
4632 if (!fm->IsInActiveWindow(bc)) {
4633 return false;
4636 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
4639 bool Document::ThisDocumentHasFocus() const {
4640 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4641 return fm && fm->GetFocusedWindow() &&
4642 fm->GetFocusedWindow()->GetExtantDoc() == this;
4645 void Document::GetDesignMode(nsAString& aDesignMode) {
4646 if (IsInDesignMode()) {
4647 aDesignMode.AssignLiteral("on");
4648 } else {
4649 aDesignMode.AssignLiteral("off");
4653 void Document::SetDesignMode(const nsAString& aDesignMode,
4654 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4655 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4658 static void NotifyEditableStateChange(Document& aDoc) {
4659 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4660 nsMutationGuard g;
4661 #endif
4662 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4663 node = node->GetNextNode(&aDoc)) {
4664 if (auto* element = Element::FromNode(node)) {
4665 element->UpdateEditableState(true);
4668 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4671 void Document::SetDocumentEditableFlag(bool aEditable) {
4672 if (HasFlag(NODE_IS_EDITABLE) == aEditable) {
4673 return;
4675 SetEditableFlag(aEditable);
4676 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4677 // state of all descendant elements of it. Update that now.
4678 NotifyEditableStateChange(*this);
4681 void Document::SetDesignMode(const nsAString& aDesignMode,
4682 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4683 ErrorResult& rv) {
4684 if (aSubjectPrincipal.isSome() &&
4685 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4686 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4687 return;
4689 const bool editableMode = IsInDesignMode();
4690 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4691 SetDocumentEditableFlag(!editableMode);
4692 rv = EditingStateChanged();
4696 nsCommandManager* Document::GetMidasCommandManager() {
4697 // check if we have it cached
4698 if (mMidasCommandManager) {
4699 return mMidasCommandManager;
4702 nsPIDOMWindowOuter* window = GetWindow();
4703 if (!window) {
4704 return nullptr;
4707 nsIDocShell* docshell = window->GetDocShell();
4708 if (!docshell) {
4709 return nullptr;
4712 mMidasCommandManager = docshell->GetCommandManager();
4713 return mMidasCommandManager;
4716 // static
4717 void Document::EnsureInitializeInternalCommandDataHashtable() {
4718 if (sInternalCommandDataHashtable) {
4719 return;
4721 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
4722 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4723 // clang-format off
4724 sInternalCommandDataHashtable->InsertOrUpdate(
4725 u"bold"_ns,
4726 InternalCommandData(
4727 "cmd_bold",
4728 Command::FormatBold,
4729 ExecCommandParam::Ignore,
4730 StyleUpdatingCommand::GetInstance,
4731 CommandOnTextEditor::Disabled));
4732 sInternalCommandDataHashtable->InsertOrUpdate(
4733 u"italic"_ns,
4734 InternalCommandData(
4735 "cmd_italic",
4736 Command::FormatItalic,
4737 ExecCommandParam::Ignore,
4738 StyleUpdatingCommand::GetInstance,
4739 CommandOnTextEditor::Disabled));
4740 sInternalCommandDataHashtable->InsertOrUpdate(
4741 u"underline"_ns,
4742 InternalCommandData(
4743 "cmd_underline",
4744 Command::FormatUnderline,
4745 ExecCommandParam::Ignore,
4746 StyleUpdatingCommand::GetInstance,
4747 CommandOnTextEditor::Disabled));
4748 sInternalCommandDataHashtable->InsertOrUpdate(
4749 u"strikethrough"_ns,
4750 InternalCommandData(
4751 "cmd_strikethrough",
4752 Command::FormatStrikeThrough,
4753 ExecCommandParam::Ignore,
4754 StyleUpdatingCommand::GetInstance,
4755 CommandOnTextEditor::Disabled));
4756 sInternalCommandDataHashtable->InsertOrUpdate(
4757 u"subscript"_ns,
4758 InternalCommandData(
4759 "cmd_subscript",
4760 Command::FormatSubscript,
4761 ExecCommandParam::Ignore,
4762 StyleUpdatingCommand::GetInstance,
4763 CommandOnTextEditor::Disabled));
4764 sInternalCommandDataHashtable->InsertOrUpdate(
4765 u"superscript"_ns,
4766 InternalCommandData(
4767 "cmd_superscript",
4768 Command::FormatSuperscript,
4769 ExecCommandParam::Ignore,
4770 StyleUpdatingCommand::GetInstance,
4771 CommandOnTextEditor::Disabled));
4772 sInternalCommandDataHashtable->InsertOrUpdate(
4773 u"cut"_ns,
4774 InternalCommandData(
4775 "cmd_cut",
4776 Command::Cut,
4777 ExecCommandParam::Ignore,
4778 CutCommand::GetInstance,
4779 CommandOnTextEditor::Enabled));
4780 sInternalCommandDataHashtable->InsertOrUpdate(
4781 u"copy"_ns,
4782 InternalCommandData(
4783 "cmd_copy",
4784 Command::Copy,
4785 ExecCommandParam::Ignore,
4786 CopyCommand::GetInstance,
4787 CommandOnTextEditor::Enabled));
4788 sInternalCommandDataHashtable->InsertOrUpdate(
4789 u"paste"_ns,
4790 InternalCommandData(
4791 "cmd_paste",
4792 Command::Paste,
4793 ExecCommandParam::Ignore,
4794 PasteCommand::GetInstance,
4795 CommandOnTextEditor::Enabled));
4796 sInternalCommandDataHashtable->InsertOrUpdate(
4797 u"delete"_ns,
4798 InternalCommandData(
4799 "cmd_deleteCharBackward",
4800 Command::DeleteCharBackward,
4801 ExecCommandParam::Ignore,
4802 DeleteCommand::GetInstance,
4803 CommandOnTextEditor::Enabled));
4804 sInternalCommandDataHashtable->InsertOrUpdate(
4805 u"forwarddelete"_ns,
4806 InternalCommandData(
4807 "cmd_deleteCharForward",
4808 Command::DeleteCharForward,
4809 ExecCommandParam::Ignore,
4810 DeleteCommand::GetInstance,
4811 CommandOnTextEditor::Enabled));
4812 sInternalCommandDataHashtable->InsertOrUpdate(
4813 u"selectall"_ns,
4814 InternalCommandData(
4815 "cmd_selectAll",
4816 Command::SelectAll,
4817 ExecCommandParam::Ignore,
4818 SelectAllCommand::GetInstance,
4819 CommandOnTextEditor::Enabled));
4820 sInternalCommandDataHashtable->InsertOrUpdate(
4821 u"undo"_ns,
4822 InternalCommandData(
4823 "cmd_undo",
4824 Command::HistoryUndo,
4825 ExecCommandParam::Ignore,
4826 UndoCommand::GetInstance,
4827 CommandOnTextEditor::Enabled));
4828 sInternalCommandDataHashtable->InsertOrUpdate(
4829 u"redo"_ns,
4830 InternalCommandData(
4831 "cmd_redo",
4832 Command::HistoryRedo,
4833 ExecCommandParam::Ignore,
4834 RedoCommand::GetInstance,
4835 CommandOnTextEditor::Enabled));
4836 sInternalCommandDataHashtable->InsertOrUpdate(
4837 u"indent"_ns,
4838 InternalCommandData("cmd_indent",
4839 Command::FormatIndent,
4840 ExecCommandParam::Ignore,
4841 IndentCommand::GetInstance,
4842 CommandOnTextEditor::Disabled));
4843 sInternalCommandDataHashtable->InsertOrUpdate(
4844 u"outdent"_ns,
4845 InternalCommandData(
4846 "cmd_outdent",
4847 Command::FormatOutdent,
4848 ExecCommandParam::Ignore,
4849 OutdentCommand::GetInstance,
4850 CommandOnTextEditor::Disabled));
4851 sInternalCommandDataHashtable->InsertOrUpdate(
4852 u"backcolor"_ns,
4853 InternalCommandData(
4854 "cmd_highlight",
4855 Command::FormatBackColor,
4856 ExecCommandParam::String,
4857 HighlightColorStateCommand::GetInstance,
4858 CommandOnTextEditor::Disabled));
4859 sInternalCommandDataHashtable->InsertOrUpdate(
4860 u"hilitecolor"_ns,
4861 InternalCommandData(
4862 "cmd_highlight",
4863 Command::FormatBackColor,
4864 ExecCommandParam::String,
4865 HighlightColorStateCommand::GetInstance,
4866 CommandOnTextEditor::Disabled));
4867 sInternalCommandDataHashtable->InsertOrUpdate(
4868 u"forecolor"_ns,
4869 InternalCommandData(
4870 "cmd_fontColor",
4871 Command::FormatFontColor,
4872 ExecCommandParam::String,
4873 FontColorStateCommand::GetInstance,
4874 CommandOnTextEditor::Disabled));
4875 sInternalCommandDataHashtable->InsertOrUpdate(
4876 u"fontname"_ns,
4877 InternalCommandData(
4878 "cmd_fontFace",
4879 Command::FormatFontName,
4880 ExecCommandParam::String,
4881 FontFaceStateCommand::GetInstance,
4882 CommandOnTextEditor::Disabled));
4883 sInternalCommandDataHashtable->InsertOrUpdate(
4884 u"fontsize"_ns,
4885 InternalCommandData(
4886 "cmd_fontSize",
4887 Command::FormatFontSize,
4888 ExecCommandParam::String,
4889 FontSizeStateCommand::GetInstance,
4890 CommandOnTextEditor::Disabled));
4891 sInternalCommandDataHashtable->InsertOrUpdate(
4892 u"inserthorizontalrule"_ns,
4893 InternalCommandData(
4894 "cmd_insertHR",
4895 Command::InsertHorizontalRule,
4896 ExecCommandParam::Ignore,
4897 InsertTagCommand::GetInstance,
4898 CommandOnTextEditor::Disabled));
4899 sInternalCommandDataHashtable->InsertOrUpdate(
4900 u"createlink"_ns,
4901 InternalCommandData(
4902 "cmd_insertLinkNoUI",
4903 Command::InsertLink,
4904 ExecCommandParam::String,
4905 InsertTagCommand::GetInstance,
4906 CommandOnTextEditor::Disabled));
4907 sInternalCommandDataHashtable->InsertOrUpdate(
4908 u"insertimage"_ns,
4909 InternalCommandData(
4910 "cmd_insertImageNoUI",
4911 Command::InsertImage,
4912 ExecCommandParam::String,
4913 InsertTagCommand::GetInstance,
4914 CommandOnTextEditor::Disabled));
4915 sInternalCommandDataHashtable->InsertOrUpdate(
4916 u"inserthtml"_ns,
4917 InternalCommandData(
4918 "cmd_insertHTML",
4919 Command::InsertHTML,
4920 ExecCommandParam::String,
4921 InsertHTMLCommand::GetInstance,
4922 // TODO: Chromium inserts text content of the document fragment
4923 // created from the param.
4924 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
4925 CommandOnTextEditor::Disabled));
4926 sInternalCommandDataHashtable->InsertOrUpdate(
4927 u"inserttext"_ns,
4928 InternalCommandData(
4929 "cmd_insertText",
4930 Command::InsertText,
4931 ExecCommandParam::String,
4932 InsertPlaintextCommand::GetInstance,
4933 CommandOnTextEditor::Enabled));
4934 sInternalCommandDataHashtable->InsertOrUpdate(
4935 u"justifyleft"_ns,
4936 InternalCommandData(
4937 "cmd_align",
4938 Command::FormatJustifyLeft,
4939 ExecCommandParam::Ignore, // Will be set to "left"
4940 AlignCommand::GetInstance,
4941 CommandOnTextEditor::Disabled));
4942 sInternalCommandDataHashtable->InsertOrUpdate(
4943 u"justifyright"_ns,
4944 InternalCommandData(
4945 "cmd_align",
4946 Command::FormatJustifyRight,
4947 ExecCommandParam::Ignore, // Will be set to "right"
4948 AlignCommand::GetInstance,
4949 CommandOnTextEditor::Disabled));
4950 sInternalCommandDataHashtable->InsertOrUpdate(
4951 u"justifycenter"_ns,
4952 InternalCommandData(
4953 "cmd_align",
4954 Command::FormatJustifyCenter,
4955 ExecCommandParam::Ignore, // Will be set to "center"
4956 AlignCommand::GetInstance,
4957 CommandOnTextEditor::Disabled));
4958 sInternalCommandDataHashtable->InsertOrUpdate(
4959 u"justifyfull"_ns,
4960 InternalCommandData(
4961 "cmd_align",
4962 Command::FormatJustifyFull,
4963 ExecCommandParam::Ignore, // Will be set to "justify"
4964 AlignCommand::GetInstance,
4965 CommandOnTextEditor::Disabled));
4966 sInternalCommandDataHashtable->InsertOrUpdate(
4967 u"removeformat"_ns,
4968 InternalCommandData(
4969 "cmd_removeStyles",
4970 Command::FormatRemove,
4971 ExecCommandParam::Ignore,
4972 RemoveStylesCommand::GetInstance,
4973 CommandOnTextEditor::Disabled));
4974 sInternalCommandDataHashtable->InsertOrUpdate(
4975 u"unlink"_ns,
4976 InternalCommandData(
4977 "cmd_removeLinks",
4978 Command::FormatRemoveLink,
4979 ExecCommandParam::Ignore,
4980 StyleUpdatingCommand::GetInstance,
4981 CommandOnTextEditor::Disabled));
4982 sInternalCommandDataHashtable->InsertOrUpdate(
4983 u"insertorderedlist"_ns,
4984 InternalCommandData(
4985 "cmd_ol",
4986 Command::InsertOrderedList,
4987 ExecCommandParam::Ignore,
4988 ListCommand::GetInstance,
4989 CommandOnTextEditor::Disabled));
4990 sInternalCommandDataHashtable->InsertOrUpdate(
4991 u"insertunorderedlist"_ns,
4992 InternalCommandData(
4993 "cmd_ul",
4994 Command::InsertUnorderedList,
4995 ExecCommandParam::Ignore,
4996 ListCommand::GetInstance,
4997 CommandOnTextEditor::Disabled));
4998 sInternalCommandDataHashtable->InsertOrUpdate(
4999 u"insertparagraph"_ns,
5000 InternalCommandData(
5001 "cmd_insertParagraph",
5002 Command::InsertParagraph,
5003 ExecCommandParam::Ignore,
5004 InsertParagraphCommand::GetInstance,
5005 CommandOnTextEditor::Enabled));
5006 sInternalCommandDataHashtable->InsertOrUpdate(
5007 u"insertlinebreak"_ns,
5008 InternalCommandData(
5009 "cmd_insertLineBreak",
5010 Command::InsertLineBreak,
5011 ExecCommandParam::Ignore,
5012 InsertLineBreakCommand::GetInstance,
5013 CommandOnTextEditor::Enabled));
5014 sInternalCommandDataHashtable->InsertOrUpdate(
5015 u"formatblock"_ns,
5016 InternalCommandData(
5017 "cmd_formatBlock",
5018 Command::FormatBlock,
5019 ExecCommandParam::String,
5020 FormatBlockStateCommand::GetInstance,
5021 CommandOnTextEditor::Disabled));
5022 sInternalCommandDataHashtable->InsertOrUpdate(
5023 u"styleWithCSS"_ns,
5024 InternalCommandData(
5025 "cmd_setDocumentUseCSS",
5026 Command::SetDocumentUseCSS,
5027 ExecCommandParam::Boolean,
5028 SetDocumentStateCommand::GetInstance,
5029 CommandOnTextEditor::FallThrough));
5030 sInternalCommandDataHashtable->InsertOrUpdate(
5031 u"usecss"_ns, // Legacy command
5032 InternalCommandData(
5033 "cmd_setDocumentUseCSS",
5034 Command::SetDocumentUseCSS,
5035 ExecCommandParam::InvertedBoolean,
5036 SetDocumentStateCommand::GetInstance,
5037 CommandOnTextEditor::FallThrough));
5038 sInternalCommandDataHashtable->InsertOrUpdate(
5039 u"contentReadOnly"_ns,
5040 InternalCommandData(
5041 "cmd_setDocumentReadOnly",
5042 Command::SetDocumentReadOnly,
5043 ExecCommandParam::Boolean,
5044 SetDocumentStateCommand::GetInstance,
5045 CommandOnTextEditor::Enabled));
5046 sInternalCommandDataHashtable->InsertOrUpdate(
5047 u"insertBrOnReturn"_ns,
5048 InternalCommandData(
5049 "cmd_insertBrOnReturn",
5050 Command::SetDocumentInsertBROnEnterKeyPress,
5051 ExecCommandParam::Boolean,
5052 SetDocumentStateCommand::GetInstance,
5053 CommandOnTextEditor::FallThrough));
5054 sInternalCommandDataHashtable->InsertOrUpdate(
5055 u"defaultParagraphSeparator"_ns,
5056 InternalCommandData(
5057 "cmd_defaultParagraphSeparator",
5058 Command::SetDocumentDefaultParagraphSeparator,
5059 ExecCommandParam::String,
5060 SetDocumentStateCommand::GetInstance,
5061 CommandOnTextEditor::FallThrough));
5062 sInternalCommandDataHashtable->InsertOrUpdate(
5063 u"enableObjectResizing"_ns,
5064 InternalCommandData(
5065 "cmd_enableObjectResizing",
5066 Command::ToggleObjectResizers,
5067 ExecCommandParam::Boolean,
5068 SetDocumentStateCommand::GetInstance,
5069 CommandOnTextEditor::FallThrough));
5070 sInternalCommandDataHashtable->InsertOrUpdate(
5071 u"enableInlineTableEditing"_ns,
5072 InternalCommandData(
5073 "cmd_enableInlineTableEditing",
5074 Command::ToggleInlineTableEditor,
5075 ExecCommandParam::Boolean,
5076 SetDocumentStateCommand::GetInstance,
5077 CommandOnTextEditor::FallThrough));
5078 sInternalCommandDataHashtable->InsertOrUpdate(
5079 u"enableAbsolutePositionEditing"_ns,
5080 InternalCommandData(
5081 "cmd_enableAbsolutePositionEditing",
5082 Command::ToggleAbsolutePositionEditor,
5083 ExecCommandParam::Boolean,
5084 SetDocumentStateCommand::GetInstance,
5085 CommandOnTextEditor::FallThrough));
5086 sInternalCommandDataHashtable->InsertOrUpdate(
5087 u"enableCompatibleJoinSplitDirection"_ns,
5088 InternalCommandData("cmd_enableCompatibleJoinSplitNodeDirection",
5089 Command::EnableCompatibleJoinSplitNodeDirection,
5090 ExecCommandParam::Boolean,
5091 SetDocumentStateCommand::GetInstance,
5092 CommandOnTextEditor::FallThrough));
5093 #if 0
5094 // with empty string
5095 sInternalCommandDataHashtable->InsertOrUpdate(
5096 u"justifynone"_ns,
5097 InternalCommandData(
5098 "cmd_align",
5099 Command::Undefined,
5100 ExecCommandParam::Ignore,
5101 nullptr,
5102 CommandOnTextEditor::Disabled)); // Not implemented yet.
5103 // REQUIRED SPECIAL REVIEW special review
5104 sInternalCommandDataHashtable->InsertOrUpdate(
5105 u"saveas"_ns,
5106 InternalCommandData(
5107 "cmd_saveAs",
5108 Command::Undefined,
5109 ExecCommandParam::Boolean,
5110 nullptr,
5111 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5112 // REQUIRED SPECIAL REVIEW special review
5113 sInternalCommandDataHashtable->InsertOrUpdate(
5114 u"print"_ns,
5115 InternalCommandData(
5116 "cmd_print",
5117 Command::Undefined,
5118 ExecCommandParam::Boolean,
5119 nullptr,
5120 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5121 #endif // #if 0
5122 // clang-format on
5125 Document::InternalCommandData Document::ConvertToInternalCommand(
5126 const nsAString& aHTMLCommandName,
5127 const TrustedHTMLOrString* aValue /* = nullptr */,
5128 ErrorResult* aRv /* = nullptr */,
5129 nsAString* aAdjustedValue /* = nullptr */) {
5130 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
5131 EnsureInitializeInternalCommandDataHashtable();
5132 InternalCommandData commandData;
5133 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
5134 return InternalCommandData();
5136 // Ignore if the command is disabled by a corresponding pref due to Gecko
5137 // specific.
5138 switch (commandData.mCommand) {
5139 case Command::SetDocumentReadOnly:
5140 if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() &&
5141 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) {
5142 return InternalCommandData();
5144 break;
5145 case Command::SetDocumentInsertBROnEnterKeyPress:
5146 MOZ_DIAGNOSTIC_ASSERT(
5147 aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn"));
5148 if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) {
5149 return InternalCommandData();
5151 break;
5152 default:
5153 break;
5155 if (!aAdjustedValue) {
5156 // No further work to do
5157 return commandData;
5159 MOZ_ASSERT(aValue);
5160 MOZ_ASSERT(aRv);
5161 Maybe<nsAutoString> compliantStringHolder;
5162 const nsAString* compliantString = nullptr;
5163 if (commandData.mCommand == Command::InsertHTML) {
5164 constexpr nsLiteralString sink = u"Document execCommand"_ns;
5165 compliantString = TrustedTypeUtils::GetTrustedTypesCompliantString(
5166 *aValue, sink, kTrustedTypesOnlySinkGroup, *this, compliantStringHolder,
5167 *aRv);
5168 if (aRv->Failed()) {
5169 return InternalCommandData();
5171 } else {
5172 compliantString = aValue->IsString() ? &aValue->GetAsString()
5173 : &aValue->GetAsTrustedHTML().mData;
5176 switch (commandData.mExecCommandParam) {
5177 case ExecCommandParam::Ignore:
5178 // Just have to copy it, no checking
5179 switch (commandData.mCommand) {
5180 case Command::FormatJustifyLeft:
5181 aAdjustedValue->AssignLiteral("left");
5182 break;
5183 case Command::FormatJustifyRight:
5184 aAdjustedValue->AssignLiteral("right");
5185 break;
5186 case Command::FormatJustifyCenter:
5187 aAdjustedValue->AssignLiteral("center");
5188 break;
5189 case Command::FormatJustifyFull:
5190 aAdjustedValue->AssignLiteral("justify");
5191 break;
5192 default:
5193 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
5194 EditorCommandParamType::None);
5195 break;
5197 return commandData;
5199 case ExecCommandParam::Boolean:
5200 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5201 EditorCommandParamType::Bool));
5202 // If this is a boolean value and it's not explicitly false (e.g. no
5203 // value). We default to "true" (see bug 301490).
5204 if (!compliantString->LowerCaseEqualsLiteral("false")) {
5205 aAdjustedValue->AssignLiteral("true");
5206 } else {
5207 aAdjustedValue->AssignLiteral("false");
5209 return commandData;
5211 case ExecCommandParam::InvertedBoolean:
5212 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5213 EditorCommandParamType::Bool));
5214 // For old backwards commands we invert the check.
5215 if (compliantString->LowerCaseEqualsLiteral("false")) {
5216 aAdjustedValue->AssignLiteral("true");
5217 } else {
5218 aAdjustedValue->AssignLiteral("false");
5220 return commandData;
5222 case ExecCommandParam::String:
5223 MOZ_ASSERT(!!(
5224 EditorCommand::GetParamType(commandData.mCommand) &
5225 (EditorCommandParamType::String | EditorCommandParamType::CString)));
5226 switch (commandData.mCommand) {
5227 case Command::FormatBlock: {
5228 const char16_t* start = compliantString->BeginReading();
5229 const char16_t* end = compliantString->EndReading();
5230 if (start != end && *start == '<' && *(end - 1) == '>') {
5231 ++start;
5232 --end;
5234 // XXX Should we reorder this array with actual usage?
5235 static const nsStaticAtom* kFormattableBlockTags[] = {
5236 // clang-format off
5237 nsGkAtoms::address,
5238 nsGkAtoms::article,
5239 nsGkAtoms::aside,
5240 nsGkAtoms::blockquote,
5241 nsGkAtoms::dd,
5242 nsGkAtoms::div,
5243 nsGkAtoms::dl,
5244 nsGkAtoms::dt,
5245 nsGkAtoms::footer,
5246 nsGkAtoms::h1,
5247 nsGkAtoms::h2,
5248 nsGkAtoms::h3,
5249 nsGkAtoms::h4,
5250 nsGkAtoms::h5,
5251 nsGkAtoms::h6,
5252 nsGkAtoms::header,
5253 nsGkAtoms::hgroup,
5254 nsGkAtoms::main,
5255 nsGkAtoms::nav,
5256 nsGkAtoms::p,
5257 nsGkAtoms::pre,
5258 nsGkAtoms::section,
5259 // clang-format on
5261 nsAutoString value(nsDependentSubstring(start, end));
5262 ToLowerCase(value);
5263 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
5264 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
5265 if (valueAtom == kTag) {
5266 kTag->ToString(*aAdjustedValue);
5267 return commandData;
5270 return InternalCommandData();
5272 case Command::FormatFontSize: {
5273 // Per editing spec as of April 23, 2012, we need to reject the value
5274 // if it's not a valid floating-point number surrounded by optional
5275 // whitespace. Otherwise, we parse it as a legacy font size. For
5276 // now, we just parse as a legacy font size regardless (matching
5277 // WebKit) -- bug 747879.
5278 int32_t size = nsContentUtils::ParseLegacyFontSize(*compliantString);
5279 if (!size) {
5280 return InternalCommandData();
5282 MOZ_ASSERT(aAdjustedValue->IsEmpty());
5283 aAdjustedValue->AppendInt(size);
5284 return commandData;
5286 case Command::InsertImage:
5287 case Command::InsertLink:
5288 if (compliantString->IsEmpty()) {
5289 // Invalid value, return false
5290 return InternalCommandData();
5292 aAdjustedValue->Assign(*compliantString);
5293 return commandData;
5294 case Command::SetDocumentDefaultParagraphSeparator:
5295 if (!compliantString->LowerCaseEqualsLiteral("div") &&
5296 !compliantString->LowerCaseEqualsLiteral("p") &&
5297 !compliantString->LowerCaseEqualsLiteral("br")) {
5298 // Invalid value
5299 return InternalCommandData();
5301 aAdjustedValue->Assign(*compliantString);
5302 return commandData;
5303 default:
5304 aAdjustedValue->Assign(*compliantString);
5305 return commandData;
5308 default:
5309 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
5310 return InternalCommandData();
5314 Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
5315 Document& aDocument, const InternalCommandData& aCommandData)
5316 : mCommandData(aCommandData) {
5317 // We'll retrieve an editor with current DOM tree and layout information.
5318 // However, JS may have already hidden or remove exposed root content of
5319 // the editor. Therefore, we need the latest layout information here.
5320 aDocument.FlushPendingNotifications(FlushType::Layout);
5321 if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) {
5322 mDoNothing = true;
5323 return;
5326 if (nsPresContext* presContext = aDocument.GetPresContext()) {
5327 // Consider context of command handling which is automatically resolved
5328 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5329 // The order is:
5330 // 1. TextEditor if there is an active element and it has TextEditor like
5331 // <input type="text"> or <textarea>.
5332 // 2. HTMLEditor for the document, if there is.
5333 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
5334 if (aCommandData.IsCutOrCopyCommand()) {
5335 // Note that we used to use DocShell to handle `cut` and `copy` command
5336 // for dispatching corresponding events for making possible web apps to
5337 // implement their own editor without editable elements but supports
5338 // standard shortcut keys, etc. In this case, we prefer to use active
5339 // element's editor to keep same behavior.
5340 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5341 } else {
5342 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5343 mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
5344 if (!mActiveEditor) {
5345 mActiveEditor = mHTMLEditor;
5350 // Then, retrieve editor command class instance which should handle it
5351 // and can handle it now.
5352 if (!mActiveEditor) {
5353 // If the command is available without editor, we should redirect the
5354 // command to focused descendant with DocShell.
5355 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5356 mDoNothing = true;
5357 return;
5359 return;
5362 // Otherwise, we should use EditorCommand instance (which is singleton
5363 // instance) when it's enabled.
5364 mEditorCommand = aCommandData.mGetEditorCommandFunc
5365 ? aCommandData.mGetEditorCommandFunc()
5366 : nullptr;
5367 if (!mEditorCommand) {
5368 mDoNothing = true;
5369 mActiveEditor = nullptr;
5370 mHTMLEditor = nullptr;
5371 return;
5374 if (IsCommandEnabled()) {
5375 return;
5378 // If the EditorCommand instance is disabled, we should do nothing if
5379 // the command requires an editor.
5380 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5381 // Do nothing if editor specific commands is disabled (bug 760052).
5382 mDoNothing = true;
5383 return;
5386 // Otherwise, we should redirect it to focused descendant with DocShell.
5387 mEditorCommand = nullptr;
5388 mActiveEditor = nullptr;
5389 mHTMLEditor = nullptr;
5392 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
5393 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
5394 switch (mCommandData.mCommandOnTextEditor) {
5395 case CommandOnTextEditor::Enabled:
5396 return mActiveEditor;
5397 case CommandOnTextEditor::Disabled:
5398 return mActiveEditor && mActiveEditor->IsTextEditor()
5399 ? nullptr
5400 : mActiveEditor.get();
5401 case CommandOnTextEditor::FallThrough:
5402 return mHTMLEditor;
5404 return nullptr;
5407 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
5408 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
5409 // Make sure frames are up to date, since that can affect whether
5410 // we're editable.
5411 doc->FlushPendingNotifications(FlushType::Frames);
5413 EditorBase* targetEditor = GetTargetEditor();
5414 if (targetEditor && targetEditor->IsTextEditor()) {
5415 // FYI: When `disabled` attribute is set, `TextEditor` treats it as
5416 // "readonly" too.
5417 return !targetEditor->IsReadonly();
5419 return aDocument->IsEditingOn();
5422 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
5423 EditorBase* targetEditor = GetTargetEditor();
5424 if (!targetEditor) {
5425 return false;
5427 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5428 return MOZ_KnownLive(mEditorCommand)
5429 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
5432 nsresult Document::AutoEditorCommandTarget::DoCommand(
5433 nsIPrincipal* aPrincipal) const {
5434 MOZ_ASSERT(!DoNothing());
5435 MOZ_ASSERT(mEditorCommand);
5436 EditorBase* targetEditor = GetTargetEditor();
5437 if (!targetEditor) {
5438 return NS_SUCCESS_DOM_NO_OPERATION;
5440 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5441 return MOZ_KnownLive(mEditorCommand)
5442 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
5443 aPrincipal);
5446 template <typename ParamType>
5447 nsresult Document::AutoEditorCommandTarget::DoCommandParam(
5448 const ParamType& aParam, nsIPrincipal* aPrincipal) const {
5449 MOZ_ASSERT(!DoNothing());
5450 MOZ_ASSERT(mEditorCommand);
5451 EditorBase* targetEditor = GetTargetEditor();
5452 if (!targetEditor) {
5453 return NS_SUCCESS_DOM_NO_OPERATION;
5455 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5456 return MOZ_KnownLive(mEditorCommand)
5457 ->DoCommandParam(mCommandData.mCommand, aParam,
5458 MOZ_KnownLive(*targetEditor), aPrincipal);
5461 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
5462 nsCommandParams& aParams) const {
5463 MOZ_ASSERT(mEditorCommand);
5464 EditorBase* targetEditor = GetTargetEditor();
5465 if (!targetEditor) {
5466 return NS_OK;
5468 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5469 return MOZ_KnownLive(mEditorCommand)
5470 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
5471 MOZ_KnownLive(targetEditor), nullptr);
5474 Document::AutoRunningExecCommandMarker::AutoRunningExecCommandMarker(
5475 Document& aDocument, nsIPrincipal* aPrincipal)
5476 : mDocument(aDocument),
5477 mTreatAsUserInput(EditorBase::TreatAsUserInput(aPrincipal)),
5478 mHasBeenRunningByContent(aDocument.mIsRunningExecCommandByContent),
5479 mHasBeenRunningByChromeOrAddon(
5480 aDocument.mIsRunningExecCommandByChromeOrAddon) {
5481 if (mTreatAsUserInput) {
5482 aDocument.mIsRunningExecCommandByChromeOrAddon = true;
5483 } else {
5484 aDocument.mIsRunningExecCommandByContent = true;
5488 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
5489 const TrustedHTMLOrString& aValue,
5490 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
5491 // Only allow on HTML documents.
5492 if (!IsHTMLOrXHTML()) {
5493 aRv.ThrowInvalidStateError(
5494 "execCommand is only supported on HTML documents");
5495 return false;
5497 // Otherwise, don't throw exception for compatibility with Chrome.
5499 // if they are requesting UI from us, let's fail since we have no UI
5500 if (aShowUI) {
5501 return false;
5504 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
5505 // this might add some ugly JS dependencies?
5507 nsAutoString adjustedValue;
5508 InternalCommandData commandData =
5509 ConvertToInternalCommand(aHTMLCommandName, &aValue, &aRv, &adjustedValue);
5510 switch (commandData.mCommand) {
5511 case Command::DoNothing:
5512 return false;
5513 case Command::SetDocumentReadOnly:
5514 SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly);
5515 break;
5516 case Command::EnableCompatibleJoinSplitNodeDirection:
5517 // We didn't allow to enable the legacy behavior once we've enabled the
5518 // new behavior by default. For keeping the behavior at supporting both
5519 // mode, we should keep returning `false` if the web app to enable the
5520 // legacy mode. Additionally, we don't support the legacy direction
5521 // anymore. Therefore, we can return `false` here even if the caller is
5522 // an addon or chrome script.
5523 if (!adjustedValue.EqualsLiteral("true")) {
5524 return false;
5526 break;
5527 default:
5528 break;
5531 AutoRunningExecCommandMarker markRunningExecCommand(*this,
5532 &aSubjectPrincipal);
5534 // If we're running an execCommand, we should just return false.
5535 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
5536 if (!markRunningExecCommand.IsSafeToRun()) {
5537 return false;
5540 // Do security check first.
5541 if (commandData.IsCutOrCopyCommand()) {
5542 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
5543 // We have rejected the event due to it not being performed in an
5544 // input-driven context therefore, we report the error to the console.
5545 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
5546 this, nsContentUtils::eDOM_PROPERTIES,
5547 "ExecCommandCutCopyDeniedNotInputDriven");
5548 return false;
5550 } else if (commandData.IsPasteCommand()) {
5551 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
5552 nsGkAtoms::clipboardRead)) {
5553 return false;
5557 // Next, consider context of command handling which is automatically resolved
5558 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5559 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5560 if (commandData.IsAvailableOnlyWhenEditable()) {
5561 if (!editCommandTarget.IsEditable(this)) {
5562 return false;
5564 // If currently the editor cannot dispatch `input` events, it means that the
5565 // editor value is being set and that caused unexpected composition events.
5566 // In this case, the value will be updated to the setting value soon and
5567 // Chromium does not dispatch any events during the sequence but we dispatch
5568 // `compositionupdate` and `compositionend` events to conform to the UI
5569 // Events spec. Therefore, this execCommand must be called accidentally.
5570 EditorBase* targetEditor = editCommandTarget.GetTargetEditor();
5571 if (targetEditor && targetEditor->IsSuppressingDispatchingInputEvent()) {
5572 return false;
5576 if (editCommandTarget.DoNothing()) {
5577 return false;
5580 // If we cannot use EditorCommand instance directly, we need to handle the
5581 // command with traditional path (i.e., with DocShell or nsCommandManager).
5582 if (!editCommandTarget.IsEditor()) {
5583 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
5585 // Special case clipboard write commands like Command::Cut and
5586 // Command::Copy. For such commands, we need the behaviour from
5587 // nsWindowRoot::GetControllers() which is to look at the focused element,
5588 // and defer to a focused textbox's controller. The code past taken by
5589 // other commands in ExecCommand() always uses the window directly, rather
5590 // than deferring to the textbox, which is desireable for most editor
5591 // commands, but not these commands (as those should allow copying out of
5592 // embedded editors). This behaviour is invoked if we call DoCommand()
5593 // directly on the docShell.
5594 // XXX This means that we allow web app to pick up selected content in
5595 // descendant document and write it into the clipboard when a
5596 // descendant document has focus. However, Chromium does not allow
5597 // this and this seems that it's not good behavior from point of view
5598 // of security. We should treat this issue in another bug.
5599 if (commandData.IsCutOrCopyCommand()) {
5600 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
5601 if (!docShell) {
5602 return false;
5604 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
5605 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
5606 return false;
5608 return NS_SUCCEEDED(rv);
5611 // Otherwise (currently, only clipboard read commands like Command::Paste),
5612 // we don't need to redirect the command to focused subdocument.
5613 // Therefore, we should handle it with nsCommandManager as used to be.
5614 // It may dispatch only preceding event of editing on non-editable element
5615 // to make web apps possible to handle standard shortcut key, etc in
5616 // their own editor.
5617 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5618 if (!commandManager) {
5619 return false;
5622 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5623 if (!window) {
5624 return false;
5627 // Return false for disabled commands (bug 760052)
5628 if (!commandManager->IsCommandEnabled(
5629 nsDependentCString(commandData.mXULCommandName), window)) {
5630 return false;
5633 MOZ_ASSERT(commandData.IsPasteCommand() ||
5634 commandData.mCommand == Command::SelectAll);
5635 nsresult rv =
5636 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
5637 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5640 // Now, our target is fixed to the editor. So, we can use EditorCommand
5641 // in EditorCommandTarget directly.
5643 EditorCommandParamType paramType =
5644 EditorCommand::GetParamType(commandData.mCommand);
5646 // If we don't have meaningful parameter or the EditorCommand does not
5647 // require additional parameter, we can use `DoCommand()`.
5648 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
5649 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
5650 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
5651 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5654 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
5655 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
5656 // a `bool` value.
5657 if (!!(paramType & EditorCommandParamType::Bool)) {
5658 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
5659 adjustedValue.EqualsLiteral("false"));
5660 nsresult rv = editCommandTarget.DoCommandParam(
5661 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
5662 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5665 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
5666 // in this case. However, `paramType` may contain both `String` and
5667 // `CString` but in such case, we should use `DoCommandParam()` which
5668 // takes `nsAString`. So, we should check whether `paramType` contains
5669 // `String` or not first.
5670 if (!!(paramType & EditorCommandParamType::String)) {
5671 MOZ_ASSERT(!adjustedValue.IsVoid());
5672 nsresult rv =
5673 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
5674 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5677 // Finally, `paramType` should have `CString`. We should use
5678 // `DoCommandParam()` which takes `nsACString`.
5679 if (!!(paramType & EditorCommandParamType::CString)) {
5680 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
5681 MOZ_ASSERT(!utf8Value.IsVoid());
5682 nsresult rv =
5683 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
5684 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5687 MOZ_ASSERT_UNREACHABLE(
5688 "Not yet implemented to handle new EditorCommandParamType");
5689 return false;
5692 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
5693 nsIPrincipal& aSubjectPrincipal,
5694 ErrorResult& aRv) {
5695 // Only allow on HTML documents.
5696 if (!IsHTMLOrXHTML()) {
5697 aRv.ThrowInvalidStateError(
5698 "queryCommandEnabled is only supported on HTML documents");
5699 return false;
5701 // Otherwise, don't throw exception for compatibility with Chrome.
5703 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5704 switch (commandData.mCommand) {
5705 case Command::DoNothing:
5706 return false;
5707 case Command::SetDocumentReadOnly:
5708 SetUseCounter(
5709 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5710 break;
5711 case Command::SetDocumentInsertBROnEnterKeyPress:
5712 SetUseCounter(
5713 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5714 break;
5715 default:
5716 break;
5719 // cut & copy are always allowed
5720 if (commandData.IsCutOrCopyCommand()) {
5721 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
5724 // Report false for restricted commands
5725 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
5726 return false;
5729 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5730 if (commandData.IsAvailableOnlyWhenEditable() &&
5731 !editCommandTarget.IsEditable(this)) {
5732 return false;
5735 if (editCommandTarget.IsEditor()) {
5736 return editCommandTarget.IsCommandEnabled();
5739 // get command manager and dispatch command to our window if it's acceptable
5740 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5741 if (!commandManager) {
5742 return false;
5745 nsPIDOMWindowOuter* window = GetWindow();
5746 if (!window) {
5747 return false;
5750 return commandManager->IsCommandEnabled(
5751 nsDependentCString(commandData.mXULCommandName), window);
5754 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5755 ErrorResult& aRv) {
5756 // Only allow on HTML documents.
5757 if (!IsHTMLOrXHTML()) {
5758 aRv.ThrowInvalidStateError(
5759 "queryCommandIndeterm is only supported on HTML documents");
5760 return false;
5762 // Otherwise, don't throw exception for compatibility with Chrome.
5764 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5765 if (commandData.mCommand == Command::DoNothing) {
5766 return false;
5769 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5770 if (commandData.IsAvailableOnlyWhenEditable() &&
5771 !editCommandTarget.IsEditable(this)) {
5772 return false;
5774 RefPtr<nsCommandParams> params = new nsCommandParams();
5775 if (editCommandTarget.IsEditor()) {
5776 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5777 return false;
5779 } else {
5780 // get command manager and dispatch command to our window if it's acceptable
5781 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5782 if (!commandManager) {
5783 return false;
5786 nsPIDOMWindowOuter* window = GetWindow();
5787 if (!window) {
5788 return false;
5791 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5792 window, params))) {
5793 return false;
5797 // If command does not have a state_mixed value, this call fails and sets
5798 // retval to false. This is fine -- we want to return false in that case
5799 // anyway (bug 738385), so we just don't throw regardless.
5800 return params->GetBool("state_mixed");
5803 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5804 ErrorResult& aRv) {
5805 // Only allow on HTML documents.
5806 if (!IsHTMLOrXHTML()) {
5807 aRv.ThrowInvalidStateError(
5808 "queryCommandState is only supported on HTML documents");
5809 return false;
5811 // Otherwise, don't throw exception for compatibility with Chrome.
5813 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5814 switch (commandData.mCommand) {
5815 case Command::DoNothing:
5816 return false;
5817 case Command::SetDocumentReadOnly:
5818 SetUseCounter(
5819 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5820 break;
5821 case Command::SetDocumentInsertBROnEnterKeyPress:
5822 SetUseCounter(
5823 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5824 break;
5825 default:
5826 break;
5829 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5830 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5831 // return false always.
5832 return false;
5835 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5836 if (commandData.IsAvailableOnlyWhenEditable() &&
5837 !editCommandTarget.IsEditable(this)) {
5838 return false;
5840 RefPtr<nsCommandParams> params = new nsCommandParams();
5841 if (editCommandTarget.IsEditor()) {
5842 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5843 return false;
5845 } else {
5846 // get command manager and dispatch command to our window if it's acceptable
5847 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5848 if (!commandManager) {
5849 return false;
5852 nsPIDOMWindowOuter* window = GetWindow();
5853 if (!window) {
5854 return false;
5857 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5858 window, params))) {
5859 return false;
5863 // handle alignment as a special case (possibly other commands too?)
5864 // Alignment is special because the external api is individual
5865 // commands but internally we use cmd_align with different
5866 // parameters. When getting the state of this command, we need to
5867 // return the boolean for this particular alignment rather than the
5868 // string of 'which alignment is this?'
5869 switch (commandData.mCommand) {
5870 case Command::FormatJustifyLeft: {
5871 nsAutoCString currentValue;
5872 nsresult rv = params->GetCString("state_attribute", currentValue);
5873 if (NS_FAILED(rv)) {
5874 return false;
5876 return currentValue.EqualsLiteral("left");
5878 case Command::FormatJustifyRight: {
5879 nsAutoCString currentValue;
5880 nsresult rv = params->GetCString("state_attribute", currentValue);
5881 if (NS_FAILED(rv)) {
5882 return false;
5884 return currentValue.EqualsLiteral("right");
5886 case Command::FormatJustifyCenter: {
5887 nsAutoCString currentValue;
5888 nsresult rv = params->GetCString("state_attribute", currentValue);
5889 if (NS_FAILED(rv)) {
5890 return false;
5892 return currentValue.EqualsLiteral("center");
5894 case Command::FormatJustifyFull: {
5895 nsAutoCString currentValue;
5896 nsresult rv = params->GetCString("state_attribute", currentValue);
5897 if (NS_FAILED(rv)) {
5898 return false;
5900 return currentValue.EqualsLiteral("justify");
5902 default:
5903 break;
5906 // If command does not have a state_all value, this call fails and sets
5907 // retval to false. This is fine -- we want to return false in that case
5908 // anyway (bug 738385), so we just succeed and return false regardless.
5909 return params->GetBool("state_all");
5912 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5913 CallerType aCallerType, ErrorResult& aRv) {
5914 // Only allow on HTML documents.
5915 if (!IsHTMLOrXHTML()) {
5916 aRv.ThrowInvalidStateError(
5917 "queryCommandSupported is only supported on HTML documents");
5918 return false;
5920 // Otherwise, don't throw exception for compatibility with Chrome.
5922 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5923 switch (commandData.mCommand) {
5924 case Command::DoNothing:
5925 return false;
5926 case Command::SetDocumentReadOnly:
5927 SetUseCounter(
5928 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5929 break;
5930 case Command::SetDocumentInsertBROnEnterKeyPress:
5931 SetUseCounter(
5932 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5933 break;
5934 default:
5935 break;
5938 // Gecko technically supports all the clipboard commands including
5939 // cut/copy/paste, but non-privileged content will be unable to call
5940 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5941 // may also be disallowed to be called from non-privileged content.
5942 // For that reason, we report the support status of corresponding
5943 // command accordingly.
5944 if (aCallerType != CallerType::System) {
5945 if (commandData.IsPasteCommand()) {
5946 return false;
5948 if (commandData.IsCutOrCopyCommand() &&
5949 !StaticPrefs::dom_allow_cut_copy()) {
5950 // XXXbz should we worry about correctly reporting "true" in the
5951 // "restricted, but we're an addon with clipboardWrite permissions" case?
5952 // See also nsContentUtils::IsCutCopyAllowed.
5953 return false;
5957 // aHTMLCommandName is supported if it can be converted to a Midas command
5958 return true;
5961 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5962 nsAString& aValue, ErrorResult& aRv) {
5963 aValue.Truncate();
5965 // Only allow on HTML documents.
5966 if (!IsHTMLOrXHTML()) {
5967 aRv.ThrowInvalidStateError(
5968 "queryCommandValue is only supported on HTML documents");
5969 return;
5971 // Otherwise, don't throw exception for compatibility with Chrome.
5973 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5974 switch (commandData.mCommand) {
5975 case Command::DoNothing:
5976 // Return empty string
5977 return;
5978 case Command::SetDocumentReadOnly:
5979 SetUseCounter(
5980 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5981 break;
5982 case Command::SetDocumentInsertBROnEnterKeyPress:
5983 SetUseCounter(
5984 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5985 break;
5986 default:
5987 break;
5990 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5991 if (commandData.IsAvailableOnlyWhenEditable() &&
5992 !editCommandTarget.IsEditable(this)) {
5993 return;
5995 RefPtr<nsCommandParams> params = new nsCommandParams();
5996 if (editCommandTarget.IsEditor()) {
5997 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5998 return;
6001 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
6002 return;
6004 } else {
6005 // get command manager and dispatch command to our window if it's acceptable
6006 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
6007 if (!commandManager) {
6008 return;
6011 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6012 if (!window) {
6013 return;
6016 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
6017 return;
6020 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
6021 window, params))) {
6022 return;
6026 // If command does not have a state_attribute value, this call fails, and
6027 // aValue will wind up being the empty string. This is fine -- we want to
6028 // return "" in that case anyway (bug 738385), so we just return NS_OK
6029 // regardless.
6030 nsAutoCString result;
6031 params->GetCString("state_attribute", result);
6032 CopyUTF8toUTF16(result, aValue);
6035 void Document::MaybeEditingStateChanged() {
6036 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
6037 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
6038 if (nsContentUtils::IsSafeToRunScript()) {
6039 EditingStateChanged();
6040 } else if (!mInDestructor) {
6041 nsContentUtils::AddScriptRunner(
6042 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
6043 &Document::MaybeEditingStateChanged));
6048 void Document::NotifyFetchOrXHRSuccess() {
6049 if (mShouldNotifyFetchSuccess) {
6050 nsContentUtils::DispatchEventOnlyToChrome(
6051 this, this, u"DOMDocFetchSuccess"_ns, CanBubble::eNo, Cancelable::eNo,
6052 /* DefaultAction */ nullptr);
6056 void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
6057 mShouldNotifyFetchSuccess = aShouldNotify;
6060 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
6061 mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
6064 void Document::TearingDownEditor() {
6065 if (IsEditingOn()) {
6066 mEditingState = EditingState::eTearingDown;
6067 if (IsHTMLOrXHTML()) {
6068 RemoveContentEditableStyleSheet();
6073 nsresult Document::TurnEditingOff() {
6074 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
6076 nsPIDOMWindowOuter* window = GetWindow();
6077 if (!window) {
6078 return NS_ERROR_FAILURE;
6081 nsIDocShell* docshell = window->GetDocShell();
6082 if (!docshell) {
6083 return NS_ERROR_FAILURE;
6086 bool isBeingDestroyed = false;
6087 docshell->IsBeingDestroyed(&isBeingDestroyed);
6088 if (isBeingDestroyed) {
6089 return NS_ERROR_FAILURE;
6092 nsCOMPtr<nsIEditingSession> editSession;
6093 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6094 NS_ENSURE_SUCCESS(rv, rv);
6096 // turn editing off
6097 rv = editSession->TearDownEditorOnWindow(window);
6098 NS_ENSURE_SUCCESS(rv, rv);
6100 mEditingState = EditingState::eOff;
6102 // Editor resets selection since it is being destroyed. But if focus is
6103 // still into editable control, we have to initialize selection again.
6104 if (RefPtr<TextControlElement> textControlElement =
6105 TextControlElement::FromNodeOrNull(
6106 nsFocusManager::GetFocusedElementStatic())) {
6107 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
6108 textEditor->ReinitializeSelection(*textControlElement);
6112 return NS_OK;
6115 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
6116 nsIDocShell* docShell = aWindow->GetDocShell();
6117 if (!docShell) {
6118 return false;
6120 return docShell->GetPresShell() != nullptr;
6123 HTMLEditor* Document::GetHTMLEditor() const {
6124 nsPIDOMWindowOuter* window = GetWindow();
6125 if (!window) {
6126 return nullptr;
6129 nsIDocShell* docshell = window->GetDocShell();
6130 if (!docshell) {
6131 return nullptr;
6134 return docshell->GetHTMLEditor();
6137 nsresult Document::EditingStateChanged() {
6138 if (mRemovedFromDocShell) {
6139 return NS_OK;
6142 if (mEditingState == EditingState::eSettingUp ||
6143 mEditingState == EditingState::eTearingDown) {
6144 // XXX We shouldn't recurse
6145 return NS_OK;
6148 const bool designMode = IsInDesignMode();
6149 const EditingState newState =
6150 designMode ? EditingState::eDesignMode
6151 : (mContentEditableCount > 0 ? EditingState::eContentEditable
6152 : EditingState::eOff);
6153 if (mEditingState == newState) {
6154 // No changes in editing mode.
6155 return NS_OK;
6158 const bool thisDocumentHasFocus = ThisDocumentHasFocus();
6159 if (newState == EditingState::eOff) {
6160 // Editing is being turned off.
6161 nsAutoScriptBlocker scriptBlocker;
6162 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6163 nsresult rv = TurnEditingOff();
6164 // If this document has focus and the editing state of this document
6165 // becomes "off", it means that HTMLEditor won't handle any inputs nor
6166 // modify the DOM tree. However, HTMLEditor may not receive `blur`
6167 // event for this state change since this may occur without focus change.
6168 // Therefore, let's notify HTMLEditor of this editing state change.
6169 // Note that even if focusedElement is an editable text control element,
6170 // it becomes not editable from HTMLEditor point of view since text
6171 // control elements are manged by TextEditor.
6172 RefPtr<Element> focusedElement = nsFocusManager::GetFocusedElementStatic();
6173 DebugOnly<nsresult> rvIgnored =
6174 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6175 htmlEditor, *this, focusedElement);
6176 NS_WARNING_ASSERTION(
6177 NS_SUCCEEDED(rvIgnored),
6178 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but "
6179 "ignored");
6180 return rv;
6183 const EditingState oldState = mEditingState;
6184 MOZ_ASSERT(newState == EditingState::eDesignMode ||
6185 newState == EditingState::eContentEditable);
6186 MOZ_ASSERT_IF(newState == EditingState::eDesignMode,
6187 oldState == EditingState::eContentEditable ||
6188 oldState == EditingState::eOff);
6189 MOZ_ASSERT_IF(
6190 newState == EditingState::eContentEditable,
6191 oldState == EditingState::eDesignMode || oldState == EditingState::eOff);
6193 // Flush out style changes on our _parent_ document, if any, so that
6194 // our check for a presshell won't get stale information.
6195 if (mParentDocument) {
6196 mParentDocument->FlushPendingNotifications(FlushType::Style);
6199 // get editing session, make sure this is a strong reference so the
6200 // window can't get deleted during the rest of this call.
6201 const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6202 if (!window) {
6203 return NS_ERROR_FAILURE;
6206 nsIDocShell* docshell = window->GetDocShell();
6207 if (!docshell) {
6208 return NS_ERROR_FAILURE;
6211 // FlushPendingNotifications might destroy our docshell.
6212 bool isBeingDestroyed = false;
6213 docshell->IsBeingDestroyed(&isBeingDestroyed);
6214 if (isBeingDestroyed) {
6215 return NS_ERROR_FAILURE;
6218 nsCOMPtr<nsIEditingSession> editSession;
6219 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6220 NS_ENSURE_SUCCESS(rv, rv);
6222 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
6223 if (htmlEditor) {
6224 // We might already have an editor if it was set up for mail, let's see
6225 // if this is actually the case.
6226 uint32_t flags = 0;
6227 htmlEditor->GetFlags(&flags);
6228 if (flags & nsIEditor::eEditorMailMask) {
6229 // We already have a mail editor, then we should not attempt to create
6230 // another one.
6231 return NS_OK;
6235 if (!HasPresShell(window)) {
6236 // We should not make the window editable or setup its editor.
6237 // It's probably style=display:none.
6238 return NS_OK;
6241 bool makeWindowEditable = mEditingState == EditingState::eOff;
6242 bool spellRecheckAll = false;
6243 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
6244 htmlEditor = nullptr;
6247 nsAutoEditingState push(this, EditingState::eSettingUp);
6249 RefPtr<PresShell> presShell = GetPresShell();
6250 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
6252 // If we're entering the design mode from non-editable state, put the
6253 // selection at the beginning of the document for compatibility reasons.
6254 bool collapseSelectionAtBeginningOfDocument =
6255 designMode && oldState == EditingState::eOff;
6256 // However, mEditingState may be eOff even if there is some
6257 // `contenteditable` area and selection has been initialized for it because
6258 // mEditingState for `contenteditable` may have been scheduled to modify
6259 // when safe. In such case, we should not reinitialize selection.
6260 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
6261 Selection* selection =
6262 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
6263 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
6264 if (selection && selection->RangeCount()) {
6265 // Perhaps, we don't need to check whether the selection is in
6266 // an editing host or not because all contents will be editable
6267 // in designMode. (And we don't want to make this code so complicated
6268 // because of legacy API.)
6269 collapseSelectionAtBeginningOfDocument = false;
6273 MOZ_ASSERT(mStyleSetFilled);
6275 // Before making this window editable, we need to modify UA style sheet
6276 // because new style may change whether focused element will be focusable
6277 // or not.
6278 if (IsHTMLOrXHTML()) {
6279 AddContentEditableStyleSheetToStyleSet();
6282 if (designMode) {
6283 // designMode is being turned on (overrides contentEditable).
6284 spellRecheckAll = oldState == EditingState::eContentEditable;
6287 // Adjust focused element with new style but blur event shouldn't be fired
6288 // until mEditingState is modified with newState.
6289 nsAutoScriptBlocker scriptBlocker;
6290 if (designMode) {
6291 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6292 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6293 window, nsFocusManager::eOnlyCurrentWindow,
6294 getter_AddRefs(focusedWindow));
6295 if (focusedContent) {
6296 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
6297 bool clearFocus = focusedFrame
6298 ? !focusedFrame->IsFocusable()
6299 : !focusedContent->IsFocusableWithoutStyle();
6300 if (clearFocus) {
6301 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
6302 fm->ClearFocus(window);
6303 // If we need to dispatch blur event, we should put off after
6304 // modifying mEditingState since blur event handler may change
6305 // designMode state again.
6306 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
6312 if (makeWindowEditable) {
6313 // Editing is being turned on (through designMode or contentEditable)
6314 // Turn on editor.
6315 // XXX This can cause flushing which can change the editing state, so make
6316 // sure to avoid recursing.
6317 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
6318 NS_ENSURE_SUCCESS(rv, rv);
6321 // XXX Need to call TearDownEditorOnWindow for all failures.
6322 htmlEditor = docshell->GetHTMLEditor();
6323 if (!htmlEditor) {
6324 // Return NS_OK even though we've failed to create an editor here. This
6325 // is so that the setter of designMode on non-HTML documents does not
6326 // fail.
6327 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
6328 // would detect that we can't support the mimetype if appropriate and
6329 // would fall onto the eEditorErrorCantEditMimeType path.
6330 return NS_OK;
6333 if (collapseSelectionAtBeginningOfDocument) {
6334 htmlEditor->BeginningOfDocument();
6337 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6338 nsContentUtils::AddScriptBlocker();
6342 mEditingState = newState;
6343 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6344 nsContentUtils::RemoveScriptBlocker();
6345 // If mEditingState is overwritten by another call and already disabled
6346 // the editing, we shouldn't keep making window editable.
6347 if (mEditingState == EditingState::eOff) {
6348 return NS_OK;
6352 if (makeWindowEditable) {
6353 // TODO: We should do this earlier in this method.
6354 // Previously, we called `ExecCommand` with `insertBrOnReturn` command
6355 // whose argument is false here. Then, if it returns error, we
6356 // stopped making it editable. However, after bug 1697078 fixed,
6357 // `ExecCommand` returns error only when the document is not XHTML's
6358 // nor HTML's. Therefore, we use same error handling for now.
6359 if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
6360 // Editor setup failed. Editing is not on after all.
6361 // XXX Should we reset the editable flag on nodes?
6362 editSession->TearDownEditorOnWindow(window);
6363 mEditingState = EditingState::eOff;
6364 return NS_ERROR_DOM_INVALID_STATE_ERR;
6366 // Set the editor to not insert <br> elements on return when in <p> elements
6367 // by default.
6368 htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
6371 // Resync the editor's spellcheck state.
6372 if (spellRecheckAll) {
6373 nsCOMPtr<nsISelectionController> selectionController =
6374 htmlEditor->GetSelectionController();
6375 if (NS_WARN_IF(!selectionController)) {
6376 return NS_ERROR_FAILURE;
6379 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
6380 nsISelectionController::SELECTION_SPELLCHECK);
6381 if (spellCheckSelection) {
6382 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
6385 htmlEditor->SyncRealTimeSpell();
6387 MaybeDispatchCheckKeyPressEventModelEvent();
6389 // If this document keeps having focus, the HTMLEditor may not receive `focus`
6390 // event for this editing state change since this may occur without a focus
6391 // change. Therefore, let's notify HTMLEditor of this editing state change.
6392 if (thisDocumentHasFocus && ThisDocumentHasFocus()) {
6393 RefPtr<Element> focusedElement = nsFocusManager::GetFocusedElementStatic();
6394 MOZ_ASSERT_IF(focusedElement, focusedElement->GetComposedDoc() == this);
6395 if ((focusedElement && focusedElement->IsEditable() &&
6396 (!focusedElement->IsTextControlElement() ||
6397 !TextControlElement::FromNode(focusedElement)
6398 ->IsSingleLineTextControlOrTextArea())) ||
6399 (!focusedElement && IsInDesignMode())) {
6400 DebugOnly<nsresult> rvIgnored =
6401 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this,
6402 focusedElement);
6403 NS_WARNING_ASSERTION(
6404 NS_SUCCEEDED(rvIgnored),
6405 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6406 "ignored");
6407 } else if (htmlEditor->HasFocus()) {
6408 DebugOnly<nsresult> rvIgnored =
6409 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6410 htmlEditor, *this, focusedElement);
6411 NS_WARNING_ASSERTION(
6412 NS_SUCCEEDED(rvIgnored),
6413 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, "
6414 "but ignored");
6418 return NS_OK;
6421 // Helper class, used below in ChangeContentEditableCount().
6422 class DeferredContentEditableCountChangeEvent : public Runnable {
6423 public:
6424 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
6425 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
6426 mDoc(aDoc),
6427 mElement(aElement) {}
6429 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
6430 if (mElement && mElement->OwnerDoc() == mDoc) {
6431 RefPtr<Document> doc = std::move(mDoc);
6432 RefPtr<Element> element = std::move(mElement);
6433 doc->DeferredContentEditableCountChange(element);
6435 return NS_OK;
6438 private:
6439 RefPtr<Document> mDoc;
6440 RefPtr<Element> mElement;
6443 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
6444 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
6445 "Trying to decrement too much.");
6447 mContentEditableCount += aChange;
6449 if (aElement) {
6450 nsContentUtils::AddScriptRunner(
6451 new DeferredContentEditableCountChangeEvent(this, aElement));
6455 void Document::DeferredContentEditableCountChange(Element* aElement) {
6456 const bool elementHasFocus =
6457 aElement && nsFocusManager::GetFocusedElementStatic() == aElement;
6458 if (elementHasFocus) {
6459 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
6460 // When contenteditable of aElement is changed and HTMLEditor works with it
6461 // or needs to start working with it, HTMLEditor may not receive `focus`
6462 // event nor `blur` event because this may occur without a focus change.
6463 // Therefore, we need to notify HTMLEditor of this contenteditable attribute
6464 // change.
6465 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6466 if (aElement->HasFlag(NODE_IS_EDITABLE)) {
6467 if (htmlEditor) {
6468 DebugOnly<nsresult> rvIgnored =
6469 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this,
6470 aElement);
6471 NS_WARNING_ASSERTION(
6472 NS_SUCCEEDED(rvIgnored),
6473 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6474 "ignored");
6476 } else {
6477 DebugOnly<nsresult> rvIgnored =
6478 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6479 htmlEditor, *this, aElement);
6480 NS_WARNING_ASSERTION(
6481 NS_SUCCEEDED(rvIgnored),
6482 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, "
6483 "but ignored");
6487 if (mParser ||
6488 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
6489 return;
6492 EditingState oldState = mEditingState;
6494 nsresult rv = EditingStateChanged();
6495 NS_ENSURE_SUCCESS_VOID(rv);
6497 if (oldState == mEditingState &&
6498 mEditingState == EditingState::eContentEditable) {
6499 // We just changed the contentEditable state of a node, we need to reset
6500 // the spellchecking state of that node.
6501 if (aElement) {
6502 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6503 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
6504 DebugOnly<nsresult> rvIgnored = htmlEditor->GetInlineSpellChecker(
6505 false, getter_AddRefs(spellChecker));
6506 NS_WARNING_ASSERTION(
6507 NS_SUCCEEDED(rvIgnored),
6508 "EditorBase::GetInlineSpellChecker() failed, but ignored");
6510 if (spellChecker &&
6511 aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) {
6512 RefPtr<nsRange> range = nsRange::Create(aElement);
6513 IgnoredErrorResult res;
6514 range->SelectNode(*aElement, res);
6515 if (res.Failed()) {
6516 // The node might be detached from the document at this point,
6517 // which would cause this call to fail. In this case, we can
6518 // safely ignore the contenteditable count change.
6519 return;
6522 rv = spellChecker->SpellCheckRange(range);
6523 NS_ENSURE_SUCCESS_VOID(rv);
6529 // aElement causes creating new HTMLEditor and the element had and keep
6530 // having focus, the HTMLEditor won't receive `focus` event. Therefore, we
6531 // need to notify HTMLEditor of it becomes editable.
6532 if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) &&
6533 nsFocusManager::GetFocusedElementStatic() == aElement) {
6534 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6535 DebugOnly<nsresult> rvIgnored =
6536 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement);
6537 NS_WARNING_ASSERTION(
6538 NS_SUCCEEDED(rvIgnored),
6539 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6540 "ignored");
6545 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
6546 // Currently, we need to check only when we're becoming editable for
6547 // contenteditable.
6548 if (mEditingState != EditingState::eContentEditable) {
6549 return;
6552 if (mHasBeenEditable) {
6553 return;
6555 mHasBeenEditable = true;
6557 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
6558 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
6559 // with proper keypress event for the active web app.
6560 WidgetEvent checkEvent(true, eUnidentifiedEvent);
6561 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
6562 checkEvent.mFlags.mCancelable = false;
6563 checkEvent.mFlags.mBubbles = false;
6564 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
6565 // Post the event rather than dispatching it synchronously because we need
6566 // a call of SetKeyPressEventModel() before first key input. Therefore, we
6567 // can avoid paying unnecessary runtime cost for most web apps.
6568 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
6571 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
6572 PresShell* presShell = GetPresShell();
6573 if (!presShell) {
6574 return;
6576 presShell->SetKeyPressEventModel(aKeyPressEventModel);
6579 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
6581 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
6582 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
6583 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
6584 aFocusTime >= mLastFocusTime);
6585 mLastFocusTime = aFocusTime;
6588 void Document::GetReferrer(nsACString& aReferrer) const {
6589 aReferrer.Truncate();
6590 if (!mReferrerInfo) {
6591 return;
6594 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
6595 if (!referrer) {
6596 return;
6599 URLDecorationStripper::StripTrackingIdentifiers(referrer, aReferrer);
6602 void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
6603 aCookie.Truncate(); // clear current cookie in case service fails;
6604 // no cookie isn't an error condition.
6606 nsCOMPtr<nsIPrincipal> cookiePrincipal;
6607 nsCOMPtr<nsIPrincipal> cookiePartitionedPrincipal;
6609 CookieCommons::SecurityChecksResult checkResult =
6610 CookieCommons::CheckGlobalAndRetrieveCookiePrincipals(
6611 this, getter_AddRefs(cookiePrincipal),
6612 getter_AddRefs(cookiePartitionedPrincipal));
6613 switch (checkResult) {
6614 case CookieCommons::SecurityChecksResult::eSandboxedError:
6615 aRv.ThrowSecurityError(
6616 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6617 "flag.");
6618 return;
6620 case CookieCommons::SecurityChecksResult::eSecurityError:
6621 [[fallthrough]];
6623 case CookieCommons::SecurityChecksResult::eDoNotContinue:
6624 return;
6626 case CookieCommons::SecurityChecksResult::eContinue:
6627 break;
6630 bool thirdParty = true;
6631 nsPIDOMWindowInner* innerWindow = GetInnerWindow();
6632 // in gtests we don't have a window, let's consider those requests as 3rd
6633 // party.
6634 if (innerWindow) {
6635 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
6637 if (thirdPartyUtil) {
6638 Unused << thirdPartyUtil->IsThirdPartyWindow(
6639 innerWindow->GetOuterWindow(), nullptr, &thirdParty);
6643 nsTArray<nsCOMPtr<nsIPrincipal>> principals;
6645 MOZ_ASSERT(cookiePrincipal);
6646 principals.AppendElement(cookiePrincipal);
6648 if (cookiePartitionedPrincipal) {
6649 principals.AppendElement(cookiePartitionedPrincipal);
6652 nsTArray<RefPtr<Cookie>> cookieList;
6653 bool stale = false;
6654 int64_t currentTimeInUsec = PR_Now();
6655 int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
6657 // not having a cookie service isn't an error
6658 nsCOMPtr<nsICookieService> service =
6659 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6660 if (!service) {
6661 return;
6664 for (auto& principal : principals) {
6665 nsAutoCString baseDomain;
6666 nsresult rv = CookieCommons::GetBaseDomain(principal, baseDomain);
6667 if (NS_WARN_IF(NS_FAILED(rv))) {
6668 return;
6671 nsAutoCString hostFromURI;
6672 rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI);
6673 if (NS_WARN_IF(NS_FAILED(rv))) {
6674 return;
6677 nsAutoCString pathFromURI;
6678 rv = principal->GetFilePath(pathFromURI);
6679 if (NS_WARN_IF(NS_FAILED(rv))) {
6680 return;
6683 nsTArray<RefPtr<Cookie>> cookies;
6684 service->GetCookiesFromHost(baseDomain, principal->OriginAttributesRef(),
6685 cookies);
6686 if (cookies.IsEmpty()) {
6687 continue;
6690 // check if the nsIPrincipal is using an https secure protocol.
6691 // if it isn't, then we can't send a secure cookie over the connection.
6692 bool potentiallyTrustworthy =
6693 principal->GetIsOriginPotentiallyTrustworthy();
6695 // iterate the cookies!
6696 for (Cookie* cookie : cookies) {
6697 // check the host, since the base domain lookup is conservative.
6698 if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
6699 continue;
6702 // if the cookie is httpOnly and it's not going directly to the HTTP
6703 // connection, don't send it
6704 if (cookie->IsHttpOnly()) {
6705 continue;
6708 if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookie(
6709 cookie, CookieJarSettings()->GetPartitionForeign(),
6710 IsInPrivateBrowsing(), UsingStorageAccess())) {
6711 continue;
6714 // if the cookie is secure and the host scheme isn't, we can't send it
6715 if (cookie->IsSecure() && !potentiallyTrustworthy) {
6716 continue;
6719 // if the nsIURI path doesn't match the cookie path, don't send it back
6720 if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
6721 continue;
6724 // check if the cookie has expired
6725 if (cookie->Expiry() <= currentTime) {
6726 continue;
6729 // all checks passed - add to list and check if lastAccessed stamp needs
6730 // updating
6731 cookieList.AppendElement(cookie);
6732 if (cookie->IsStale()) {
6733 stale = true;
6738 if (cookieList.IsEmpty()) {
6739 return;
6742 // update lastAccessed timestamps. we only do this if the timestamp is stale
6743 // by a certain amount, to avoid thrashing the db during pageload.
6744 if (stale) {
6745 service->StaleCookies(cookieList, currentTimeInUsec);
6748 // return cookies in order of path length; longest to shortest.
6749 // this is required per RFC2109. if cookies match in length,
6750 // then sort by creation time (see bug 236772).
6751 cookieList.Sort(CompareCookiesForSending());
6753 nsAutoCString cookieString;
6754 CookieCommons::ComposeCookieString(cookieList, cookieString);
6756 // CopyUTF8toUTF16 doesn't handle error
6757 // because it assumes that the input is valid.
6758 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookieString, aCookie);
6761 void Document::SetCookie(const nsAString& aCookieString, ErrorResult& aRv) {
6762 nsCOMPtr<nsIPrincipal> cookiePrincipal;
6764 CookieCommons::SecurityChecksResult checkResult =
6765 CookieCommons::CheckGlobalAndRetrieveCookiePrincipals(
6766 this, getter_AddRefs(cookiePrincipal), nullptr);
6767 switch (checkResult) {
6768 case CookieCommons::SecurityChecksResult::eSandboxedError:
6769 aRv.ThrowSecurityError(
6770 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6771 "flag.");
6772 return;
6774 case CookieCommons::SecurityChecksResult::eSecurityError:
6775 [[fallthrough]];
6777 case CookieCommons::SecurityChecksResult::eDoNotContinue:
6778 return;
6780 case CookieCommons::SecurityChecksResult::eContinue:
6781 break;
6784 if (!mDocumentURI) {
6785 return;
6788 // not having a cookie service isn't an error
6789 nsCOMPtr<nsICookieService> service =
6790 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6791 if (!service) {
6792 return;
6795 NS_ConvertUTF16toUTF8 cookieString(aCookieString);
6797 nsCOMPtr<nsIURI> documentURI;
6798 nsAutoCString baseDomain;
6799 OriginAttributes attrs;
6801 int64_t currentTimeInUsec = PR_Now();
6803 auto* basePrincipal = BasePrincipal::Cast(NodePrincipal());
6804 basePrincipal->GetURI(getter_AddRefs(documentURI));
6805 if (NS_WARN_IF(!documentURI)) {
6806 // Document's principal is not a content or null (may be system), so
6807 // can't set cookies
6808 return;
6811 // Console report takes care of the correct reporting at the exit of this
6812 // method.
6813 RefPtr<ConsoleReportCollector> crc = new ConsoleReportCollector();
6814 auto scopeExit = MakeScopeExit([&] { crc->FlushConsoleReports(this); });
6816 CookieParser cookieParser(crc, documentURI);
6818 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
6819 if (!thirdPartyUtil) {
6820 return;
6823 nsCOMPtr<nsIEffectiveTLDService> tldService =
6824 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
6825 if (!tldService) {
6826 return;
6829 RefPtr<Cookie> cookie = CookieCommons::CreateCookieFromDocument(
6830 cookieParser, this, cookieString, currentTimeInUsec, tldService,
6831 thirdPartyUtil, baseDomain, attrs);
6832 if (!cookie) {
6833 return;
6836 bool thirdParty = true;
6837 nsPIDOMWindowInner* innerWindow = GetInnerWindow();
6838 // in gtests we don't have a window, let's consider those requests as 3rd
6839 // party.
6840 if (innerWindow) {
6841 Unused << thirdPartyUtil->IsThirdPartyWindow(innerWindow->GetOuterWindow(),
6842 nullptr, &thirdParty);
6845 if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookie(
6846 cookie, CookieJarSettings()->GetPartitionForeign(),
6847 IsInPrivateBrowsing(), UsingStorageAccess())) {
6848 return;
6851 // add the cookie to the list. AddCookieFromDocument() takes care of logging.
6852 service->AddCookieFromDocument(cookieParser, baseDomain, attrs, *cookie,
6853 currentTimeInUsec, documentURI, thirdParty,
6854 this);
6856 nsCOMPtr<nsIObserverService> observerService =
6857 mozilla::services::GetObserverService();
6858 if (observerService) {
6859 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
6860 nsString(aCookieString).get());
6864 ReferrerPolicy Document::GetReferrerPolicy() const {
6865 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
6866 : ReferrerPolicy::_empty;
6869 void Document::GetAlinkColor(nsAString& aAlinkColor) {
6870 aAlinkColor.Truncate();
6872 HTMLBodyElement* body = GetBodyElement();
6873 if (body) {
6874 body->GetALink(aAlinkColor);
6878 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
6879 HTMLBodyElement* body = GetBodyElement();
6880 if (body) {
6881 body->SetALink(aAlinkColor);
6885 void Document::GetLinkColor(nsAString& aLinkColor) {
6886 aLinkColor.Truncate();
6888 HTMLBodyElement* body = GetBodyElement();
6889 if (body) {
6890 body->GetLink(aLinkColor);
6894 void Document::SetLinkColor(const nsAString& aLinkColor) {
6895 HTMLBodyElement* body = GetBodyElement();
6896 if (body) {
6897 body->SetLink(aLinkColor);
6901 void Document::GetVlinkColor(nsAString& aVlinkColor) {
6902 aVlinkColor.Truncate();
6904 HTMLBodyElement* body = GetBodyElement();
6905 if (body) {
6906 body->GetVLink(aVlinkColor);
6910 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
6911 HTMLBodyElement* body = GetBodyElement();
6912 if (body) {
6913 body->SetVLink(aVlinkColor);
6917 void Document::GetBgColor(nsAString& aBgColor) {
6918 aBgColor.Truncate();
6920 HTMLBodyElement* body = GetBodyElement();
6921 if (body) {
6922 body->GetBgColor(aBgColor);
6926 void Document::SetBgColor(const nsAString& aBgColor) {
6927 HTMLBodyElement* body = GetBodyElement();
6928 if (body) {
6929 body->SetBgColor(aBgColor);
6933 void Document::GetFgColor(nsAString& aFgColor) {
6934 aFgColor.Truncate();
6936 HTMLBodyElement* body = GetBodyElement();
6937 if (body) {
6938 body->GetText(aFgColor);
6942 void Document::SetFgColor(const nsAString& aFgColor) {
6943 HTMLBodyElement* body = GetBodyElement();
6944 if (body) {
6945 body->SetText(aFgColor);
6949 void Document::CaptureEvents() {
6950 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
6953 void Document::ReleaseEvents() {
6954 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
6957 HTMLAllCollection* Document::All() {
6958 if (!mAll) {
6959 mAll = new HTMLAllCollection(this);
6961 return mAll;
6964 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
6965 if (mIsSrcdocDocument) {
6966 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
6967 if (inStrmChan) {
6968 return inStrmChan->GetSrcdocData(aSrcdocData);
6971 aSrcdocData = VoidString();
6972 return NS_OK;
6975 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
6976 nsPIDOMWindowOuter* win = GetWindow();
6977 if (!win) {
6978 return nullptr;
6980 return WindowProxyHolder(win->GetBrowsingContext());
6983 nsIContent* Document::GetUnretargetedFocusedContent(
6984 IncludeChromeOnly aIncludeChromeOnly) const {
6985 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6986 if (!window) {
6987 return nullptr;
6989 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6990 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6991 window, nsFocusManager::eOnlyCurrentWindow,
6992 getter_AddRefs(focusedWindow));
6993 if (!focusedContent) {
6994 return nullptr;
6996 // be safe and make sure the element is from this document
6997 if (focusedContent->OwnerDoc() != this) {
6998 return nullptr;
7000 if (focusedContent->ChromeOnlyAccess() &&
7001 aIncludeChromeOnly == IncludeChromeOnly::No) {
7002 return focusedContent->FindFirstNonChromeOnlyAccessContent();
7004 return focusedContent;
7007 Element* Document::GetActiveElement() {
7008 // Get the focused element.
7009 Element* focusedElement = GetRetargetedFocusedElement();
7010 if (focusedElement) {
7011 return focusedElement;
7014 // No focused element anywhere in this document. Try to get the BODY.
7015 if (IsHTMLOrXHTML()) {
7016 Element* bodyElement = AsHTMLDocument()->GetBody();
7017 if (bodyElement) {
7018 return bodyElement;
7020 // Special case to handle the transition to XHTML from XUL documents
7021 // where there currently isn't a body element, but we need to match the
7022 // XUL behavior. This should be removed when bug 1540278 is resolved.
7023 if (nsContentUtils::IsChromeDoc(this)) {
7024 Element* docElement = GetDocumentElement();
7025 if (docElement && docElement->IsXULElement()) {
7026 return docElement;
7029 // Because of IE compatibility, return null when html document doesn't have
7030 // a body.
7031 return nullptr;
7034 // If we couldn't get a BODY, return the root element.
7035 return GetDocumentElement();
7038 Element* Document::GetCurrentScript() {
7039 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
7040 return el;
7043 void Document::ReleaseCapture() const {
7044 // only release the capture if the caller can access it. This prevents a
7045 // page from stopping a scrollbar grab for example.
7046 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
7047 if (node && nsContentUtils::CanCallerAccess(node)) {
7048 PresShell::ReleaseCapturingContent();
7052 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
7053 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
7054 return mChromeXHRDocBaseURI;
7057 return GetDocBaseURI();
7060 void Document::SetBaseURI(nsIURI* aURI) {
7061 if (!aURI && !mDocumentBaseURI) {
7062 return;
7065 // Don't do anything if the URI wasn't actually changed.
7066 if (aURI && mDocumentBaseURI) {
7067 bool equalBases = false;
7068 mDocumentBaseURI->Equals(aURI, &equalBases);
7069 if (equalBases) {
7070 return;
7074 mDocumentBaseURI = aURI;
7075 mCachedURLData = nullptr;
7076 RefreshLinkHrefs();
7079 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
7080 const nsAString& aURI) {
7081 RefPtr<nsIURI> resolvedURI;
7082 MOZ_TRY(
7083 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
7084 return OwningNonNull<nsIURI>(std::move(resolvedURI));
7087 nsIReferrerInfo* Document::ReferrerInfoForInternalCSSAndSVGResources() {
7088 if (!mCachedReferrerInfoForInternalCSSAndSVGResources) {
7089 mCachedReferrerInfoForInternalCSSAndSVGResources =
7090 ReferrerInfo::CreateForInternalCSSAndSVGResources(this);
7092 return mCachedReferrerInfoForInternalCSSAndSVGResources;
7095 URLExtraData* Document::DefaultStyleAttrURLData() {
7096 MOZ_ASSERT(NS_IsMainThread());
7097 if (!mCachedURLData) {
7098 mCachedURLData = new URLExtraData(
7099 GetDocBaseURI(), ReferrerInfoForInternalCSSAndSVGResources(),
7100 NodePrincipal());
7102 return mCachedURLData;
7105 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
7106 if (mCharacterSet != aEncoding) {
7107 mCharacterSet = aEncoding;
7108 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
7109 RecomputeLanguageFromCharset();
7111 if (nsPresContext* context = GetPresContext()) {
7112 context->DocumentCharSetChanged(aEncoding);
7117 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
7118 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
7121 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
7122 aData.Truncate();
7123 const HeaderData* data = mHeaderData.get();
7124 while (data) {
7125 if (data->mField == aHeaderField) {
7126 aData = data->mData;
7127 break;
7129 data = data->mNext.get();
7133 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
7134 if (!aHeaderField) {
7135 NS_ERROR("null headerField");
7136 return;
7139 if (!mHeaderData) {
7140 if (!aData.IsEmpty()) { // don't bother storing empty string
7141 mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
7143 } else {
7144 HeaderData* data = mHeaderData.get();
7145 UniquePtr<HeaderData>* lastPtr = &mHeaderData;
7146 bool found = false;
7147 do { // look for existing and replace
7148 if (data->mField == aHeaderField) {
7149 if (!aData.IsEmpty()) {
7150 data->mData.Assign(aData);
7151 } else { // don't store empty string
7152 // Note that data->mNext is moved to a temporary before the old value
7153 // of *lastPtr is deleted.
7154 *lastPtr = std::move(data->mNext);
7156 found = true;
7158 break;
7160 lastPtr = &data->mNext;
7161 data = lastPtr->get();
7162 } while (data);
7164 if (!aData.IsEmpty() && !found) {
7165 // didn't find, append
7166 *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
7170 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
7171 if (aData.IsEmpty()) {
7172 mContentLanguage = nullptr;
7173 } else {
7174 mContentLanguage = NS_AtomizeMainThread(aData);
7176 mMayNeedFontPrefsUpdate = true;
7177 if (auto* presContext = GetPresContext()) {
7178 presContext->ContentLanguageChanged();
7182 if (aHeaderField == nsGkAtoms::origin_trial) {
7183 mTrials.UpdateFromToken(aData, NodePrincipal());
7184 if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) {
7185 InitCOEP(mChannel);
7187 // If we still don't have a WindowContext, WindowContext::OnNewDocument
7188 // will take care of this.
7189 if (WindowContext* ctx = GetWindowContext()) {
7190 if (mEmbedderPolicy) {
7191 Unused << ctx->SetEmbedderPolicy(mEmbedderPolicy.value());
7197 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
7198 SetPreferredStyleSheetSet(aData);
7201 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
7202 // We get into this code before we have a script global yet, so get to our
7203 // container via mDocumentContainer.
7204 if (mDocumentContainer) {
7205 // Note: using mDocumentURI instead of mBaseURI here, for consistency
7206 // (used to just use the current URI of our webnavigation, but that
7207 // should really be the same thing). Note that this code can run
7208 // before the current URI of the webnavigation has been updated, so we
7209 // can't assert equality here.
7210 mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
7214 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
7215 mAllowDNSPrefetch) {
7216 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
7217 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
7220 if (aHeaderField == nsGkAtoms::handheldFriendly) {
7221 mViewportType = Unknown;
7225 void Document::SetEarlyHints(
7226 nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) {
7227 mEarlyHints = std::move(aEarlyHints);
7230 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
7231 NotNull<const Encoding*>& aEncoding,
7232 nsHtml5TreeOpExecutor* aExecutor) {
7233 if (aChannel) {
7234 nsAutoCString charsetVal;
7235 nsresult rv = aChannel->GetContentCharset(charsetVal);
7236 if (NS_SUCCEEDED(rv)) {
7237 const Encoding* preferred = Encoding::ForLabel(charsetVal);
7238 if (preferred) {
7239 if (aExecutor && preferred == REPLACEMENT_ENCODING) {
7240 aExecutor->ComplainAboutBogusProtocolCharset(this, false);
7242 aEncoding = WrapNotNull(preferred);
7243 aCharsetSource = kCharsetFromChannel;
7244 return;
7245 } else if (aExecutor && !charsetVal.IsEmpty()) {
7246 aExecutor->ComplainAboutBogusProtocolCharset(this, true);
7252 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
7253 #ifdef DEBUG
7254 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
7255 const Element* element = Element::FromNode(node);
7256 if (!element) {
7257 continue;
7259 MOZ_ASSERT(!element->HasServoData());
7261 #endif
7264 already_AddRefed<PresShell> Document::CreatePresShell(
7265 nsPresContext* aContext, nsViewManager* aViewManager) {
7266 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
7268 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
7270 AssertNoStaleServoDataIn(*this);
7272 RefPtr<PresShell> presShell = new PresShell(this);
7273 // Note: we don't hold a ref to the shell (it holds a ref to us)
7274 mPresShell = presShell;
7276 if (!mStyleSetFilled) {
7277 FillStyleSet();
7280 presShell->Init(aContext, aViewManager);
7281 if (RefPtr<class HighlightRegistry> highlightRegistry = mHighlightRegistry) {
7282 highlightRegistry->AddHighlightSelectionsToFrameSelection();
7284 // Gaining a shell causes changes in how media queries are evaluated, so
7285 // invalidate that.
7286 aContext->MediaFeatureValuesChanged(
7287 {MediaFeatureChange::kAllChanges},
7288 MediaFeatureChangePropagation::JustThisDocument);
7290 // Make sure to never paint if we belong to an invisible DocShell.
7291 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7292 if (docShell && docShell->IsInvisible()) {
7293 presShell->SetNeverPainting(true);
7296 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
7297 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
7298 presShell.get(), docShell.get()));
7300 mExternalResourceMap.ShowViewers();
7302 MaybeScheduleFrameRequestCallbacks();
7304 if (mDocumentL10n) {
7305 // In case we already accumulated mutations,
7306 // we'll trigger the refresh driver now.
7307 mDocumentL10n->OnCreatePresShell();
7310 if (HasAutoFocusCandidates()) {
7311 ScheduleFlushAutoFocusCandidates();
7313 // Now that we have a shell, we might have @font-face rules (the presence of a
7314 // shell may change which rules apply to us). We don't need to do anything
7315 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
7316 // is ready to update we'll flush the font set.
7317 MarkUserFontSetDirty();
7319 // Take the author style disabled state from the top browsing cvontext.
7320 // (PageStyleChild.sys.mjs ensures this is up to date.)
7321 if (BrowsingContext* bc = GetBrowsingContext()) {
7322 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
7325 return presShell.forget();
7328 void Document::MaybeScheduleFrameRequestCallbacks() {
7329 if (!HasFrameRequestCallbacks() || !ShouldFireFrameRequestCallbacks()) {
7330 return;
7332 MOZ_ASSERT(mPresShell);
7333 nsRefreshDriver* rd = mPresShell->GetPresContext()->RefreshDriver();
7334 rd->EnsureFrameRequestCallbacksHappen();
7337 void Document::TakeVideoFrameRequestCallbacks(
7338 nsTArray<RefPtr<HTMLVideoElement>>& aVideoCallbacks) {
7339 MOZ_ASSERT(aVideoCallbacks.IsEmpty());
7340 mFrameRequestManager.Take(aVideoCallbacks);
7343 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
7344 MOZ_ASSERT(aCallbacks.IsEmpty());
7345 mFrameRequestManager.Take(aCallbacks);
7348 bool Document::ShouldThrottleFrameRequests() const {
7349 if (mStaticCloneCount > 0) {
7350 // Even if we're not visible, a static clone may be, so run at full speed.
7351 return false;
7354 if (Hidden()) {
7355 // We're not visible (probably in a background tab or the bf cache).
7356 return true;
7359 if (!mPresShell) {
7360 // Can't do anything smarter. We don't run frame requests in documents
7361 // without a pres shell anyways.
7362 return false;
7365 if (!mPresShell->IsActive()) {
7366 // The pres shell is not active (we're an invisible OOP iframe or such), so
7367 // throttle.
7368 return true;
7371 if (mPresShell->IsPaintingSuppressed()) {
7372 // Historically we have throttled frame requests until we've painted at
7373 // least once, so keep doing that.
7374 return true;
7377 if (mPresShell->IsUnderHiddenEmbedderElement()) {
7378 // For display: none and visibility: hidden we always throttle, for
7379 // consistency with OOP iframes.
7380 return true;
7383 Element* el = GetEmbedderElement();
7384 if (!el) {
7385 // If we're not in-process, our refresh driver is throttled separately (via
7386 // PresShell::SetIsActive, so not much more we can do here.
7387 return false;
7390 if (!StaticPrefs::layout_throttle_in_process_iframes()) {
7391 return false;
7394 // Note that because we have to scroll this document into view at least once
7395 // to un-throttle it, we will drop one requestAnimationFrame frame when a
7396 // document that previously wasn't visible scrolls into view. This is
7397 // acceptable / unlikely to be human-perceivable, though we could improve on
7398 // it if needed by adding an intersection margin or something of that sort.
7399 auto margin = DOMIntersectionObserver::LazyLoadingRootMargin();
7400 const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
7401 *el->OwnerDoc(), /* aRoot = */ nullptr, &margin);
7402 const IntersectionOutput output = DOMIntersectionObserver::Intersect(
7403 input, *el, DOMIntersectionObserver::BoxToUse::Content);
7404 return !output.Intersects();
7407 void Document::DeletePresShell() {
7408 mExternalResourceMap.HideViewers();
7409 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
7410 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
7411 presContext->RefreshDriver()->CancelFlushAutoFocus(this);
7414 // When our shell goes away, request that all our images be immediately
7415 // discarded, so we don't carry around decoded image data for a document we
7416 // no longer intend to paint.
7417 ImageTracker()->RequestDiscardAll();
7419 // Now that we no longer have a shell, we need to forget about any FontFace
7420 // objects for @font-face rules that came from the style set. There's no need
7421 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
7422 // no point on it.
7423 mFontFaceSetDirty = true;
7425 if (IsEditingOn()) {
7426 TurnEditingOff();
7429 mPresShell = nullptr;
7431 ClearStaleServoData();
7432 AssertNoStaleServoDataIn(*this);
7434 mStyleSet->ShellDetachedFromDocument();
7435 mStyleSetFilled = false;
7436 mQuirkSheetAdded = false;
7437 mContentEditableSheetAdded = false;
7440 void Document::DisallowBFCaching(uint32_t aStatus) {
7441 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
7442 if (!mBFCacheDisallowed) {
7443 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
7444 wgc->SendUpdateBFCacheStatus(aStatus, 0);
7447 mBFCacheDisallowed = true;
7450 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
7451 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
7453 if (mPresShell) {
7454 if (aEntry) {
7455 mPresShell->StopObservingRefreshDriver();
7456 } else if (mBFCacheEntry) {
7457 mPresShell->StartObservingRefreshDriver();
7460 mBFCacheEntry = aEntry;
7463 bool Document::RemoveFromBFCacheSync() {
7464 bool removed = false;
7465 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
7466 entry->RemoveFromBFCacheSync();
7467 removed = true;
7468 } else if (!IsCurrentActiveDocument()) {
7469 // In the old bfcache implementation while the new page is loading, but
7470 // before nsIDocumentViewer.show() has been called, the previous page
7471 // doesn't yet have nsIBFCacheEntry. However, the previous page isn't the
7472 // current active document anymore.
7473 DisallowBFCaching();
7474 removed = true;
7477 if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
7478 if (BrowsingContext* bc = GetBrowsingContext()) {
7479 if (bc->IsInBFCache()) {
7480 ContentChild* cc = ContentChild::GetSingleton();
7481 // IPC is asynchronous but the caller is supposed to check the return
7482 // value. The reason for 'Sync' in the method name is that the old
7483 // implementation may run scripts. There is Async variant in
7484 // the old session history implementation for the cases where
7485 // synchronous operation isn't safe.
7486 cc->SendRemoveFromBFCache(bc->Top());
7487 removed = true;
7491 return removed;
7494 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
7495 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
7497 NS_RELEASE(e->mKey);
7498 if (e->mSubDocument) {
7499 e->mSubDocument->SetParentDocument(nullptr);
7500 NS_RELEASE(e->mSubDocument);
7504 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
7505 SubDocMapEntry* e =
7506 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
7508 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
7509 NS_ADDREF(e->mKey);
7511 e->mSubDocument = nullptr;
7514 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
7515 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
7517 if (!aSubDoc) {
7518 // aSubDoc is nullptr, remove the mapping
7520 if (mSubDocuments) {
7521 mSubDocuments->Remove(aElement);
7523 } else {
7524 if (!mSubDocuments) {
7525 // Create a new hashtable
7527 static const PLDHashTableOps hash_table_ops = {
7528 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
7529 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
7531 mSubDocuments =
7532 MakeUnique<PLDHashTable>(&hash_table_ops, sizeof(SubDocMapEntry));
7535 // Add a mapping to the hash table
7536 auto entry =
7537 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
7539 if (!entry) {
7540 return NS_ERROR_OUT_OF_MEMORY;
7543 if (entry->mSubDocument) {
7544 entry->mSubDocument->SetParentDocument(nullptr);
7546 // Release the old sub document
7547 NS_RELEASE(entry->mSubDocument);
7550 entry->mSubDocument = aSubDoc;
7551 NS_ADDREF(entry->mSubDocument);
7553 aSubDoc->SetParentDocument(this);
7556 return NS_OK;
7559 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
7560 if (mSubDocuments && aContent->IsElement()) {
7561 auto entry = static_cast<SubDocMapEntry*>(
7562 mSubDocuments->Search(aContent->AsElement()));
7564 if (entry) {
7565 return entry->mSubDocument;
7569 return nullptr;
7572 Element* Document::GetEmbedderElement() const {
7573 // We check if we're the active document in our BrowsingContext
7574 // by comparing against its document, rather than checking if the
7575 // WindowContext is cached, since mWindow may be null when we're
7576 // called (such as in nsPresContext::Init).
7577 if (BrowsingContext* bc = GetBrowsingContext()) {
7578 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
7581 return nullptr;
7584 Element* Document::GetRootElement() const {
7585 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
7586 ? mCachedRootElement
7587 : GetRootElementInternal();
7590 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
7592 Element* Document::GetRootElementInternal() const {
7593 // We invoke GetRootElement() immediately before the servo traversal, so we
7594 // should always have a cache hit from Servo.
7595 MOZ_ASSERT(NS_IsMainThread());
7597 // Loop backwards because any non-elements, such as doctypes and PIs
7598 // are likely to appear before the root element.
7599 for (nsIContent* child = GetLastChild(); child;
7600 child = child->GetPreviousSibling()) {
7601 if (Element* element = Element::FromNode(child)) {
7602 const_cast<Document*>(this)->mCachedRootElement = element;
7603 return element;
7607 const_cast<Document*>(this)->mCachedRootElement = nullptr;
7608 return nullptr;
7611 void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
7612 bool aNotify, ErrorResult& aRv) {
7613 if (aKid->IsElement() && GetRootElement()) {
7614 NS_WARNING("Inserting root element when we already have one");
7615 aRv.ThrowHierarchyRequestError("There is already a root element.");
7616 return;
7619 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
7622 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
7623 Maybe<mozAutoDocUpdate> updateBatch;
7624 const bool removingRoot = aKid->IsElement();
7625 if (removingRoot) {
7626 updateBatch.emplace(this, aNotify);
7627 // Destroy the link map up front before we mess with the child list.
7628 DestroyElementMaps();
7630 // Notify early so that we can clear the cached element after notifying,
7631 // without having to slow down nsINode::RemoveChildNode.
7632 if (aNotify) {
7633 MutationObservers::NotifyContentWillBeRemoved(this, aKid);
7634 aNotify = false;
7637 // Preemptively clear mCachedRootElement, since we are about to remove it
7638 // from our child list, and we don't want to return this maybe-obsolete
7639 // value from any GetRootElement() calls that happen inside of
7640 // RemoveChildNode().
7641 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
7642 // GetRootElement() calls until after it's removed the child from mChildren.
7643 // Any call before that point would restore this soon-to-be-obsolete cached
7644 // answer, and our clearing here would be fruitless.)
7645 mCachedRootElement = nullptr;
7648 nsINode::RemoveChildNode(aKid, aNotify);
7649 MOZ_ASSERT(mCachedRootElement != aKid,
7650 "Stale pointer in mCachedRootElement, after we tried to clear it "
7651 "(maybe somebody called GetRootElement() too early?)");
7654 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
7655 if (mStyleSetFilled) {
7656 EnsureStyleSet().AddDocStyleSheet(aSheet);
7657 ApplicableStylesChanged();
7661 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
7662 EnsureStyleSet().RecordShadowStyleChange(aShadowRoot);
7663 ApplicableStylesChanged(/* aKnownInShadowTree= */ true);
7666 void Document::ApplicableStylesChanged(bool aKnownInShadowTree) {
7667 // TODO(emilio): if we decide to resolve style in display: none iframes, then
7668 // we need to always track style changes and remove the mStyleSetFilled.
7669 if (!mStyleSetFilled) {
7670 return;
7672 if (!aKnownInShadowTree) {
7673 MarkUserFontSetDirty();
7675 PresShell* ps = GetPresShell();
7676 if (!ps) {
7677 return;
7680 ps->EnsureStyleFlush();
7681 nsPresContext* pc = ps->GetPresContext();
7682 if (!pc) {
7683 return;
7686 if (!aKnownInShadowTree) {
7687 pc->MarkCounterStylesDirty();
7688 pc->MarkFontFeatureValuesDirty();
7689 pc->MarkFontPaletteValuesDirty();
7691 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
7694 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
7695 if (mStyleSetFilled) {
7696 mStyleSet->RemoveStyleSheet(aSheet);
7697 ApplicableStylesChanged();
7701 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
7702 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
7704 if (aSheet.IsApplicable()) {
7705 AddStyleSheetToStyleSets(aSheet);
7709 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
7710 const bool applicable = aSheet.IsApplicable();
7711 // If we're actually in the document style sheet list
7712 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
7713 if (applicable) {
7714 AddStyleSheetToStyleSets(aSheet);
7715 } else {
7716 RemoveStyleSheetFromStyleSets(aSheet);
7721 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
7722 if (!StyleSheetChangeEventsEnabled()) {
7723 return;
7726 StyleSheetApplicableStateChangeEventInit init;
7727 init.mBubbles = true;
7728 init.mCancelable = true;
7729 init.mStylesheet = &aSheet;
7730 init.mApplicable = aSheet.IsApplicable();
7732 RefPtr<StyleSheetApplicableStateChangeEvent> event =
7733 StyleSheetApplicableStateChangeEvent::Constructor(
7734 this, u"StyleSheetApplicableStateChanged"_ns, init);
7735 event->SetTrusted(true);
7736 event->SetTarget(this);
7737 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7738 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7739 asyncDispatcher->PostDOMEvent();
7742 void Document::PostStyleSheetRemovedEvent(StyleSheet& aSheet) {
7743 if (!StyleSheetChangeEventsEnabled()) {
7744 return;
7747 StyleSheetRemovedEventInit init;
7748 init.mBubbles = true;
7749 init.mCancelable = false;
7750 init.mStylesheet = &aSheet;
7752 RefPtr<StyleSheetRemovedEvent> event =
7753 StyleSheetRemovedEvent::Constructor(this, u"StyleSheetRemoved"_ns, init);
7754 event->SetTrusted(true);
7755 event->SetTarget(this);
7756 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7757 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7758 asyncDispatcher->PostDOMEvent();
7761 void Document::PostCustomPropertyRegistered(
7762 const PropertyDefinition& aDefinition) {
7763 if (!StyleSheetChangeEventsEnabled()) {
7764 return;
7767 CSSCustomPropertyRegisteredEventInit init;
7768 init.mBubbles = true;
7769 init.mCancelable = false;
7771 InspectorCSSPropertyDefinition property;
7773 property.mName.Append(aDefinition.mName);
7774 property.mSyntax.Append(aDefinition.mSyntax);
7775 property.mInherits = aDefinition.mInherits;
7776 if (aDefinition.mInitialValue.WasPassed()) {
7777 property.mInitialValue.Append(aDefinition.mInitialValue.Value());
7778 } else {
7779 property.mInitialValue.SetIsVoid(true);
7781 property.mFromJS = true;
7782 init.mPropertyDefinition = property;
7784 RefPtr<CSSCustomPropertyRegisteredEvent> event =
7785 CSSCustomPropertyRegisteredEvent::Constructor(
7786 this, u"csscustompropertyregistered"_ns, init);
7787 event->SetTrusted(true);
7788 event->SetTarget(this);
7789 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7790 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7791 asyncDispatcher->PostDOMEvent();
7794 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
7795 nsIURI* aSheetURI) {
7796 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
7797 bool bEqual;
7798 nsIURI* uri = aSheets[i]->GetSheetURI();
7800 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
7801 return i;
7804 return -1;
7807 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
7808 nsIURI* aSheetURI) {
7809 MOZ_ASSERT(aSheetURI, "null arg");
7811 // Checking if we have loaded this one already.
7812 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
7813 return NS_ERROR_INVALID_ARG;
7815 // Loading the sheet sync.
7816 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
7818 css::SheetParsingMode parsingMode;
7819 switch (aType) {
7820 case Document::eAgentSheet:
7821 parsingMode = css::eAgentSheetFeatures;
7822 break;
7824 case Document::eUserSheet:
7825 parsingMode = css::eUserSheetFeatures;
7826 break;
7828 case Document::eAuthorSheet:
7829 parsingMode = css::eAuthorSheetFeatures;
7830 break;
7832 default:
7833 MOZ_CRASH("impossible value for aType");
7836 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
7837 css::Loader::UseSystemPrincipal::Yes);
7838 if (result.isErr()) {
7839 return result.unwrapErr();
7842 RefPtr<StyleSheet> sheet = result.unwrap();
7844 sheet->SetAssociatedDocumentOrShadowRoot(this);
7845 MOZ_ASSERT(sheet->IsApplicable());
7847 return AddAdditionalStyleSheet(aType, sheet);
7850 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
7851 StyleSheet* aSheet) {
7852 if (mAdditionalSheets[aType].Contains(aSheet)) {
7853 return NS_ERROR_INVALID_ARG;
7856 if (!aSheet->IsApplicable()) {
7857 return NS_ERROR_INVALID_ARG;
7860 mAdditionalSheets[aType].AppendElement(aSheet);
7862 if (mStyleSetFilled) {
7863 EnsureStyleSet().AppendStyleSheet(*aSheet);
7864 ApplicableStylesChanged();
7866 return NS_OK;
7869 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
7870 nsIURI* aSheetURI) {
7871 MOZ_ASSERT(aSheetURI);
7873 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
7875 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
7876 if (i >= 0) {
7877 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
7878 sheets.RemoveElementAt(i);
7880 if (!mIsGoingAway) {
7881 MOZ_ASSERT(sheetRef->IsApplicable());
7882 if (mStyleSetFilled) {
7883 EnsureStyleSet().RemoveStyleSheet(*sheetRef);
7884 ApplicableStylesChanged();
7887 sheetRef->ClearAssociatedDocumentOrShadowRoot();
7891 nsIGlobalObject* Document::GetScopeObject() const {
7892 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
7893 return scope;
7896 DocGroup* Document::GetDocGroupOrCreate() {
7897 if (!mDocGroup && GetBrowsingContext()) {
7898 BrowsingContextGroup* group = GetBrowsingContext()->Group();
7899 MOZ_ASSERT(group);
7901 nsAutoCString docGroupKey;
7902 nsresult rv = mozilla::dom::DocGroup::GetKey(
7903 NodePrincipal(), group->IsPotentiallyCrossOriginIsolated(),
7904 docGroupKey);
7905 if (NS_SUCCEEDED(rv)) {
7906 mDocGroup = group->AddDocument(docGroupKey, this);
7909 return mDocGroup;
7912 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
7913 mScopeObject = do_GetWeakReference(aGlobal);
7914 if (aGlobal) {
7915 mHasHadScriptHandlingObject = true;
7917 nsPIDOMWindowInner* window = aGlobal->GetAsInnerWindow();
7918 if (!window) {
7919 return;
7922 // Same origin data documents should have the same docGroup as their scope
7923 // window.
7924 if (mLoadedAsData && window->GetExtantDoc() &&
7925 window->GetExtantDoc() != this &&
7926 window->GetExtantDoc()->NodePrincipal() == NodePrincipal()) {
7927 DocGroup* docGroup = window->GetExtantDoc()->GetDocGroup();
7929 if (docGroup) {
7930 if (!mDocGroup) {
7931 mDocGroup = docGroup;
7932 mDocGroup->AddDocument(this);
7933 } else {
7934 MOZ_ASSERT(mDocGroup == docGroup,
7935 "Data document has a mismatched doc group?");
7937 #ifdef DEBUG
7938 AssertDocGroupMatchesKey();
7939 #endif
7941 // Update data document's mMutationEventsEnabled early on so that
7942 // we can avoid extra IsURIInPrefList calls.
7943 if (mMutationEventsEnabled.isNothing()) {
7944 mMutationEventsEnabled.emplace(
7945 window->GetExtantDoc()->MutationEventsEnabled());
7948 return;
7951 MOZ_ASSERT_UNREACHABLE(
7952 "Scope window doesn't have DocGroup when creating data document?");
7953 // ... but fall through to be safe.
7956 BrowsingContextGroup* browsingContextGroup =
7957 window->GetBrowsingContextGroup();
7959 // We should already have the principal, and now that we have been added
7960 // to a window, we should be able to join a DocGroup!
7961 nsAutoCString docGroupKey;
7962 nsresult rv = mozilla::dom::DocGroup::GetKey(
7963 NodePrincipal(),
7964 browsingContextGroup->IsPotentiallyCrossOriginIsolated(), docGroupKey);
7965 if (mDocGroup) {
7966 if (NS_SUCCEEDED(rv)) {
7967 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
7969 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
7970 browsingContextGroup);
7971 } else {
7972 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
7974 MOZ_ASSERT(mDocGroup);
7977 MOZ_ASSERT_IF(
7978 mNodeInfoManager->GetArenaAllocator(),
7979 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
7983 bool Document::ContainsEMEContent() {
7984 nsPIDOMWindowInner* win = GetInnerWindow();
7985 // Note this case is different from checking just media elements in that
7986 // it covers when we've created MediaKeys but not associated them with a
7987 // media element.
7988 return win && win->HasActiveMediaKeysInstance();
7991 bool Document::ContainsMSEContent() {
7992 bool containsMSE = false;
7993 EnumerateActivityObservers([&containsMSE](nsISupports* aSupports) {
7994 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7995 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7996 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
7997 if (ms) {
7998 containsMSE = true;
8002 return containsMSE;
8005 static void NotifyActivityChangedCallback(nsISupports* aSupports) {
8006 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
8007 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
8008 mediaElem->NotifyOwnerDocumentActivityChanged();
8010 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
8011 do_QueryInterface(aSupports));
8012 if (objectDocumentActivity) {
8013 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
8014 } else {
8015 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
8016 do_QueryInterface(aSupports));
8017 if (imageLoadingContent) {
8018 auto* ilc =
8019 static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
8020 ilc->NotifyOwnerDocumentActivityChanged();
8025 void Document::NotifyActivityChanged() {
8026 EnumerateActivityObservers(NotifyActivityChangedCallback);
8027 // https://w3c.github.io/screen-wake-lock/#handling-document-loss-of-full-activity
8028 if (!IsActive()) {
8029 UnlockAllWakeLocks(WakeLockType::Screen);
8033 void Document::SetContainer(nsDocShell* aContainer) {
8034 if (aContainer) {
8035 mDocumentContainer = aContainer;
8036 } else {
8037 mDocumentContainer = WeakPtr<nsDocShell>();
8040 mInChromeDocShell =
8041 aContainer && aContainer->GetBrowsingContext()->IsChrome();
8043 NotifyActivityChanged();
8045 // IsTopLevelWindowInactive depends on the docshell, so
8046 // update the cached value now that it's available.
8047 UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false);
8048 if (!aContainer) {
8049 return;
8052 BrowsingContext* context = aContainer->GetBrowsingContext();
8053 MOZ_ASSERT_IF(context && mDocGroup,
8054 context->Group() == mDocGroup->GetBrowsingContextGroup());
8055 if (context && context->IsContent()) {
8056 SetIsTopLevelContentDocument(context->IsTopContent());
8057 SetIsContentDocument(true);
8058 } else {
8059 SetIsTopLevelContentDocument(false);
8060 SetIsContentDocument(false);
8064 nsISupports* Document::GetContainer() const {
8065 return static_cast<nsIDocShell*>(mDocumentContainer);
8068 void Document::SetScriptGlobalObject(
8069 nsIScriptGlobalObject* aScriptGlobalObject) {
8070 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
8071 mAnimationController->IsPausedByType(
8072 SMILTimeContainer::PAUSE_PAGEHIDE |
8073 SMILTimeContainer::PAUSE_BEGIN),
8074 "Clearing window pointer while animations are unpaused");
8076 if (mScriptGlobalObject && !aScriptGlobalObject) {
8077 // We're detaching from the window. We need to grab a pointer to
8078 // our layout history state now.
8079 mLayoutHistoryState = GetLayoutHistoryState();
8081 // Also make sure to remove our onload blocker now if we haven't done it yet
8082 if (mOnloadBlockCount != 0) {
8083 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
8084 if (loadGroup) {
8085 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
8089 if (GetController().isSome()) {
8090 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
8091 loader->ClearCacheForControlledDocument(this);
8094 // We may become controlled again if this document comes back out
8095 // of bfcache. Clear our state to allow that to happen. Only
8096 // clear this flag if we are actually controlled, though, so pages
8097 // that were force reloaded don't become controlled when they
8098 // come out of bfcache.
8099 mMaybeServiceWorkerControlled = false;
8102 if (GetWindowContext()) {
8103 // The document is about to lose its window, so this is a good time to
8104 // send our page use counters, while we still have access to our
8105 // WindowContext.
8107 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
8108 // catches some cases of documents losing their window that don't
8109 // get in here.)
8110 SendPageUseCounters();
8114 // BlockOnload() might be called before mScriptGlobalObject is set.
8115 // We may need to add the blocker once mScriptGlobalObject is set.
8116 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
8118 mScriptGlobalObject = aScriptGlobalObject;
8120 if (needOnloadBlocker) {
8121 EnsureOnloadBlocker();
8124 // FIXME(emilio): is this really needed?
8125 MaybeScheduleFrameRequestCallbacks();
8127 if (aScriptGlobalObject) {
8128 // Go back to using the docshell for the layout history state
8129 mLayoutHistoryState = nullptr;
8130 SetScopeObject(aScriptGlobalObject);
8131 mHasHadDefaultView = true;
8133 if (mAllowDNSPrefetch) {
8134 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
8135 if (docShell) {
8136 #ifdef DEBUG
8137 nsCOMPtr<nsIWebNavigation> webNav =
8138 do_GetInterface(aScriptGlobalObject);
8139 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
8140 "Unexpected container or script global?");
8141 #endif
8142 bool allowDNSPrefetch;
8143 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
8144 mAllowDNSPrefetch = allowDNSPrefetch;
8148 // If we are set in a window that is already focused we should remember this
8149 // as the time the document gained focus.
8150 if (HasFocus(IgnoreErrors())) {
8151 SetLastFocusTime(TimeStamp::Now());
8155 // Remember the pointer to our window (or lack there of), to avoid
8156 // having to QI every time it's asked for.
8157 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
8158 mWindow = window;
8160 // Now that we know what our window is, we can flush the CSP errors to the
8161 // Web Console. We are flushing all messages that occurred and were stored in
8162 // the queue prior to this point.
8163 if (mCSP) {
8164 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
8167 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
8168 do_QueryInterface(GetChannel());
8169 if (internalChannel) {
8170 nsCOMArray<nsISecurityConsoleMessage> messages;
8171 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
8172 MOZ_ASSERT(NS_SUCCEEDED(rv));
8173 SendToConsole(messages);
8176 // Set our visibility state, but do not fire the event. This is correct
8177 // because either we're coming out of bfcache (in which case IsVisible() will
8178 // still test false at this point and no state change will happen) or we're
8179 // doing the initial document load and don't want to fire the event for this
8180 // change.
8182 // When the visibility is changed, notify it to observers.
8183 // Some observers need the notification, for example HTMLMediaElement uses
8184 // it to update internal media resource allocation.
8185 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
8186 // creation are already done before Document::SetScriptGlobalObject() call.
8187 // MediaDecoder decides whether starting decoding is decided based on
8188 // document's visibility. When the MediaDecoder is created,
8189 // Document::SetScriptGlobalObject() is not yet called and document is
8190 // hidden state. Therefore the MediaDecoder decides that decoding is
8191 // not yet necessary. But soon after Document::SetScriptGlobalObject()
8192 // call, the document becomes not hidden. At the time, MediaDecoder needs
8193 // to know it and needs to start updating decoding.
8194 UpdateVisibilityState(DispatchVisibilityChange::No);
8196 // The global in the template contents owner document should be the same.
8197 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
8198 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
8201 // Tell the script loader about the new global object.
8202 if (mScriptLoader && !IsTemplateContentsOwner()) {
8203 mScriptLoader->SetGlobalObject(mScriptGlobalObject);
8206 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
8207 mScriptGlobalObject && GetChannel()) {
8208 // If we are shift-reloaded, don't associate with a ServiceWorker.
8209 if (mDocumentContainer->IsForceReloading()) {
8210 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
8211 return;
8214 mMaybeServiceWorkerControlled = true;
8218 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
8219 MOZ_ASSERT(!mScriptGlobalObject,
8220 "Do not call this when mScriptGlobalObject is set!");
8221 if (mHasHadDefaultView) {
8222 return nullptr;
8225 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
8226 do_QueryReferent(mScopeObject);
8227 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
8228 if (win) {
8229 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
8230 if (!outer || outer->GetCurrentInnerWindow() != win) {
8231 NS_WARNING("Wrong inner/outer window combination!");
8232 return nullptr;
8235 return scriptHandlingObject;
8237 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
8238 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
8239 "Wrong script object!");
8240 if (aScriptObject) {
8241 SetScopeObject(aScriptObject);
8242 mHasHadDefaultView = false;
8246 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
8247 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
8248 // Let's use mScriptGlobalObject. Even if the document is already removed from
8249 // the docshell, the outer window might be still obtainable from the it.
8250 nsCOMPtr<nsPIDOMWindowOuter> win;
8251 if (mRemovedFromDocShell) {
8252 // The docshell returns the outer window we are done.
8253 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
8254 if (kungFuDeathGrip) {
8255 win = kungFuDeathGrip->GetWindow();
8257 } else {
8258 if (nsCOMPtr<nsPIDOMWindowInner> inner =
8259 do_QueryInterface(mScriptGlobalObject)) {
8260 // mScriptGlobalObject is always the inner window, let's get the outer.
8261 win = inner->GetOuterWindow();
8265 return win;
8268 bool Document::InternalAllowXULXBL() {
8269 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
8270 mAllowXULXBL = eTriTrue;
8271 return true;
8274 mAllowXULXBL = eTriFalse;
8275 return false;
8278 // Note: We don't hold a reference to the document observer; we assume
8279 // that it has a live reference to the document.
8280 void Document::AddObserver(nsIDocumentObserver* aObserver) {
8281 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
8282 "Observer already in the list");
8283 mObservers.AppendElement(aObserver);
8284 AddMutationObserver(aObserver);
8287 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
8288 // If we're in the process of destroying the document (and we're
8289 // informing the observers of the destruction), don't remove the
8290 // observers from the list. This is not a big deal, since we
8291 // don't hold a live reference to the observers.
8292 if (!mInDestructor) {
8293 RemoveMutationObserver(aObserver);
8294 return mObservers.RemoveElement(aObserver);
8297 return mObservers.Contains(aObserver);
8300 void Document::BeginUpdate() {
8301 ++mUpdateNestLevel;
8302 nsContentUtils::AddScriptBlocker();
8303 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
8306 void Document::EndUpdate() {
8307 const bool reset = !mPendingMaybeEditingStateChanged;
8308 mPendingMaybeEditingStateChanged = true;
8310 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
8312 --mUpdateNestLevel;
8314 nsContentUtils::RemoveScriptBlocker();
8316 if (mXULBroadcastManager) {
8317 mXULBroadcastManager->MaybeBroadcast();
8320 if (reset) {
8321 mPendingMaybeEditingStateChanged = false;
8323 MaybeEditingStateChanged();
8326 void Document::BeginLoad() {
8327 if (IsEditingOn()) {
8328 // Reset() blows away all event listeners in the document, and our
8329 // editor relies heavily on those. Midas is turned on, to make it
8330 // work, re-initialize it to give it a chance to add its event
8331 // listeners again.
8333 TurnEditingOff();
8334 EditingStateChanged();
8337 MOZ_ASSERT(!mDidCallBeginLoad);
8338 mDidCallBeginLoad = true;
8340 // Block onload here to prevent having to deal with blocking and
8341 // unblocking it while we know the document is loading.
8342 BlockOnload();
8343 mDidFireDOMContentLoaded = false;
8344 BlockDOMContentLoaded();
8346 if (mScriptLoader) {
8347 mScriptLoader->BeginDeferringScripts();
8350 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
8353 void Document::MozSetImageElement(const nsAString& aImageElementId,
8354 Element* aElement) {
8355 if (aImageElementId.IsEmpty()) return;
8357 // Hold a script blocker while calling SetImageElement since that can call
8358 // out to id-observers
8359 nsAutoScriptBlocker scriptBlocker;
8361 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
8362 if (entry) {
8363 entry->SetImageElement(aElement);
8364 if (entry->IsEmpty()) {
8365 mIdentifierMap.RemoveEntry(entry);
8370 void Document::DispatchContentLoadedEvents() {
8371 // If you add early returns from this method, make sure you're
8372 // calling UnblockOnload properly.
8374 // Unpin references to preloaded images
8375 mPreloadingImages.Clear();
8377 // DOM manipulation after content loaded should not care if the element
8378 // came from the preloader.
8379 mPreloadedPreconnects.Clear();
8381 if (mTiming) {
8382 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
8385 // Dispatch observer notification to notify observers document is interactive.
8386 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
8387 if (os) {
8388 nsIPrincipal* principal = NodePrincipal();
8389 os->NotifyObservers(ToSupports(this),
8390 principal->IsSystemPrincipal()
8391 ? "chrome-document-interactive"
8392 : "content-document-interactive",
8393 nullptr);
8396 // Fire a DOM event notifying listeners that this document has been
8397 // loaded (excluding images and other loads initiated by this
8398 // document).
8399 nsContentUtils::DispatchTrustedEvent(this, this, u"DOMContentLoaded"_ns,
8400 CanBubble::eYes, Cancelable::eNo);
8402 if (auto* const window = GetInnerWindow()) {
8403 const RefPtr<ServiceWorkerContainer> serviceWorker =
8404 window->Navigator()->ServiceWorker();
8406 // This could cause queued messages from a service worker to get
8407 // dispatched on serviceWorker.
8408 serviceWorker->StartMessages();
8411 if (MayStartLayout()) {
8412 MaybeResolveReadyForIdle();
8415 if (mTiming) {
8416 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
8419 // If this document is a [i]frame, fire a DOMFrameContentLoaded
8420 // event on all parent documents notifying that the HTML (excluding
8421 // other external files such as images and stylesheets) in a frame
8422 // has finished loading.
8424 // target_frame is the [i]frame element that will be used as the
8425 // target for the event. It's the [i]frame whose content is done
8426 // loading.
8427 nsCOMPtr<Element> target_frame = GetEmbedderElement();
8429 if (target_frame && target_frame->IsInComposedDoc()) {
8430 nsCOMPtr<Document> parent = target_frame->OwnerDoc();
8431 while (parent) {
8432 RefPtr<Event> event;
8433 if (parent) {
8434 IgnoredErrorResult ignored;
8435 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
8438 if (event) {
8439 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
8441 event->SetTarget(target_frame);
8442 event->SetTrusted(true);
8444 // To dispatch this event we must manually call
8445 // EventDispatcher::Dispatch() on the ancestor document since the
8446 // target is not in the same document, so the event would never reach
8447 // the ancestor document if we used the normal event
8448 // dispatching code.
8450 WidgetEvent* innerEvent = event->WidgetEventPtr();
8451 if (innerEvent) {
8452 nsEventStatus status = nsEventStatus_eIgnore;
8454 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
8455 EventDispatcher::Dispatch(parent, context, innerEvent, event,
8456 &status);
8461 parent = parent->GetInProcessParentDocument();
8465 nsPIDOMWindowInner* inner = GetInnerWindow();
8466 if (inner) {
8467 inner->NoteDOMContentLoaded();
8470 // TODO
8471 if (mMaybeServiceWorkerControlled) {
8472 using mozilla::dom::ServiceWorkerManager;
8473 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
8474 if (swm) {
8475 Maybe<ClientInfo> clientInfo = GetClientInfo();
8476 if (clientInfo.isSome()) {
8477 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
8482 if (mSetCompleteAfterDOMContentLoaded) {
8483 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
8484 mSetCompleteAfterDOMContentLoaded = false;
8487 UnblockOnload(true);
8490 void Document::EndLoad() {
8491 bool turnOnEditing =
8492 mParser && (IsInDesignMode() || mContentEditableCount > 0);
8494 #if defined(DEBUG)
8495 // only assert if nothing stopped the load on purpose
8496 if (!mParserAborted) {
8497 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
8499 #endif
8501 // EndLoad may have been called without a matching call to BeginLoad, in the
8502 // case of a failed parse (for example, due to timeout). In such a case, we
8503 // still want to execute part of this code to do appropriate cleanup, but we
8504 // gate part of it because it is intended to match 1-for-1 with calls to
8505 // BeginLoad. We have an explicit flag bit for this purpose, since it's
8506 // complicated and error prone to derive this condition from other related
8507 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
8509 // Part 1: Code that always executes to cleanup end of parsing, whether
8510 // that parsing was successful or not.
8512 // Drop the ref to our parser, if any, but keep hold of the sink so that we
8513 // can flush it from FlushPendingNotifications as needed. We might have to
8514 // do that to get a StartLayout() to happen.
8515 if (mParser) {
8516 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
8517 mParser = nullptr;
8520 // Update the attributes on the PerformanceNavigationTiming before notifying
8521 // the onload observers.
8522 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
8523 if (RefPtr<Performance> performance = window->GetPerformance()) {
8524 performance->UpdateNavigationTimingEntry();
8528 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
8530 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
8532 if (!mDidCallBeginLoad) {
8533 return;
8535 mDidCallBeginLoad = false;
8537 UnblockDOMContentLoaded();
8539 if (turnOnEditing) {
8540 EditingStateChanged();
8543 if (!GetWindow()) {
8544 // This is a document that's not in a window. For example, this could be an
8545 // XMLHttpRequest responseXML document, or a document created via DOMParser
8546 // or DOMImplementation. We don't reach this code normally for such
8547 // documents (which is not obviously correct), but can reach it via
8548 // document.open()/document.close().
8550 // Such documents don't fire load events, but per spec should set their
8551 // readyState to "complete" when parsing and all loading of subresources is
8552 // done. Parsing is done now, and documents not in a window don't load
8553 // subresources, so just go ahead and mark ourselves as complete.
8554 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
8555 /* updateTimingInformation = */ false);
8557 // Reset mSkipLoadEventAfterClose just in case.
8558 mSkipLoadEventAfterClose = false;
8562 void Document::UnblockDOMContentLoaded() {
8563 MOZ_ASSERT(mBlockDOMContentLoaded);
8564 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
8565 return;
8568 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
8569 ("DOCUMENT %p UnblockDOMContentLoaded", this));
8571 mDidFireDOMContentLoaded = true;
8573 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
8574 if (!mSynchronousDOMContentLoaded) {
8575 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8576 nsCOMPtr<nsIRunnable> ev =
8577 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
8578 &Document::DispatchContentLoadedEvents);
8579 Dispatch(ev.forget());
8580 } else {
8581 DispatchContentLoadedEvents();
8585 void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) {
8586 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
8587 "Someone forgot a scriptblocker");
8588 NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged,
8589 (this, aElement, aStateMask));
8592 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
8593 const StyleRuleChange&) {
8594 if (aSheet.IsApplicable()) {
8595 ApplicableStylesChanged();
8599 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
8600 if (aRule.IsIncompleteImportRule()) {
8601 return;
8604 if (aSheet.IsApplicable()) {
8605 ApplicableStylesChanged();
8609 void Document::ImportRuleLoaded(StyleSheet& aSheet) {
8610 if (aSheet.IsApplicable()) {
8611 ApplicableStylesChanged();
8615 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
8616 if (aSheet.IsApplicable()) {
8617 ApplicableStylesChanged();
8621 static Element* GetCustomContentContainer(PresShell* aPresShell) {
8622 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
8623 return nullptr;
8626 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
8629 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
8630 bool aForce, ErrorResult& aRv) {
8631 RefPtr<PresShell> shell = GetPresShell();
8632 if (aForce && !GetCustomContentContainer(shell)) {
8633 FlushPendingNotifications(FlushType::Layout);
8634 shell = GetPresShell();
8637 nsAutoScriptBlocker scriptBlocker;
8639 RefPtr<AnonymousContent> anonContent = AnonymousContent::Create(*this);
8640 if (!anonContent) {
8641 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
8642 return nullptr;
8645 mAnonymousContents.AppendElement(anonContent);
8647 if (RefPtr<Element> container = GetCustomContentContainer(shell)) {
8648 // If the container is empty and we have other anon content we should be
8649 // about to show all the other anonymous content nodes.
8650 if (container->HasChildren() || mAnonymousContents.Length() == 1) {
8651 container->AppendChildTo(anonContent->Host(), true, IgnoreErrors());
8652 if (auto* canvasFrame = shell->GetCanvasFrame()) {
8653 canvasFrame->ShowCustomContentContainer();
8658 return anonContent.forget();
8661 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
8662 PresShell* aPresShell) {
8663 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
8664 if (!container) {
8665 return;
8667 container->RemoveChild(*aAnonContent.Host(), IgnoreErrors());
8670 void Document::RemoveAnonymousContent(AnonymousContent& aContent) {
8671 nsAutoScriptBlocker scriptBlocker;
8673 auto index = mAnonymousContents.IndexOf(&aContent);
8674 if (index == mAnonymousContents.NoIndex) {
8675 return;
8678 mAnonymousContents.RemoveElementAt(index);
8679 RemoveAnonContentFromCanvas(aContent, GetPresShell());
8681 if (mAnonymousContents.IsEmpty() &&
8682 GetCustomContentContainer(GetPresShell())) {
8683 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
8687 Element* Document::GetAnonRootIfInAnonymousContentContainer(
8688 nsINode* aNode) const {
8689 if (!aNode->IsInNativeAnonymousSubtree()) {
8690 return nullptr;
8693 PresShell* presShell = GetPresShell();
8694 if (!presShell || !presShell->GetCanvasFrame()) {
8695 return nullptr;
8698 nsAutoScriptBlocker scriptBlocker;
8699 nsCOMPtr<Element> customContainer =
8700 presShell->GetCanvasFrame()->GetCustomContentContainer();
8701 if (!customContainer) {
8702 return nullptr;
8705 // An arbitrary number of elements can be inserted as children of the custom
8706 // container frame. We want the one that was added that contains aNode, so
8707 // we need to keep track of the last child separately using |child| here.
8708 nsINode* child = aNode;
8709 nsINode* parent = aNode->GetParentNode();
8710 while (parent && parent->IsInNativeAnonymousSubtree()) {
8711 if (parent == customContainer) {
8712 return Element::FromNode(child);
8714 child = parent;
8715 parent = child->GetParentNode();
8717 return nullptr;
8720 Maybe<ClientInfo> Document::GetClientInfo() const {
8721 if (const Document* orig = GetOriginalDocument()) {
8722 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
8723 return info;
8727 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8728 return inner->GetClientInfo();
8731 return Maybe<ClientInfo>();
8734 Maybe<ClientState> Document::GetClientState() const {
8735 if (const Document* orig = GetOriginalDocument()) {
8736 if (Maybe<ClientState> state = orig->GetClientState()) {
8737 return state;
8741 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8742 return inner->GetClientState();
8745 return Maybe<ClientState>();
8748 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
8749 if (const Document* orig = GetOriginalDocument()) {
8750 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
8751 return controller;
8755 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8756 return inner->GetController();
8759 return Maybe<ServiceWorkerDescriptor>();
8763 // Document interface
8765 DocumentType* Document::GetDoctype() const {
8766 for (nsIContent* child = GetFirstChild(); child;
8767 child = child->GetNextSibling()) {
8768 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
8769 return static_cast<DocumentType*>(child);
8772 return nullptr;
8775 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
8776 if (!mDOMImplementation) {
8777 nsCOMPtr<nsIURI> uri;
8778 NS_NewURI(getter_AddRefs(uri), "about:blank");
8779 if (!uri) {
8780 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
8781 return nullptr;
8783 bool hasHadScriptObject = true;
8784 nsIScriptGlobalObject* scriptObject =
8785 GetScriptHandlingObject(hasHadScriptObject);
8786 if (!scriptObject && hasHadScriptObject) {
8787 rv.Throw(NS_ERROR_UNEXPECTED);
8788 return nullptr;
8790 mDOMImplementation = new DOMImplementation(
8791 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
8794 return mDOMImplementation;
8797 bool IsLowercaseASCII(const nsAString& aValue) {
8798 int32_t len = aValue.Length();
8799 for (int32_t i = 0; i < len; ++i) {
8800 char16_t c = aValue[i];
8801 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
8802 return false;
8805 return true;
8808 already_AddRefed<Element> Document::CreateElement(
8809 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8810 ErrorResult& rv) {
8811 rv = nsContentUtils::CheckQName(aTagName, false);
8812 if (rv.Failed()) {
8813 return nullptr;
8816 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
8817 nsAutoString lcTagName;
8818 if (needsLowercase) {
8819 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
8822 const nsString* is = nullptr;
8823 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
8824 if (aOptions.IsElementCreationOptions()) {
8825 const ElementCreationOptions& options =
8826 aOptions.GetAsElementCreationOptions();
8828 if (options.mIs.WasPassed()) {
8829 is = &options.mIs.Value();
8832 // Check 'pseudo' and throw an exception if it's not one allowed
8833 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
8834 if (options.mPseudo.WasPassed()) {
8835 Maybe<PseudoStyleRequest> request =
8836 nsCSSPseudoElements::ParsePseudoElement(options.mPseudo.Value());
8837 if (!request || request->IsNotPseudo() ||
8838 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(request->mType)) {
8839 rv.ThrowNotSupportedError("Invalid pseudo-element");
8840 return nullptr;
8842 pseudoType = request->mType;
8846 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
8847 nullptr, mDefaultElementType, is);
8849 if (pseudoType != PseudoStyleType::NotPseudo) {
8850 elem->SetPseudoElementType(pseudoType);
8853 return elem.forget();
8856 already_AddRefed<Element> Document::CreateElementNS(
8857 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8858 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
8859 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8860 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8861 mNodeInfoManager, ELEMENT_NODE,
8862 getter_AddRefs(nodeInfo));
8863 if (rv.Failed()) {
8864 return nullptr;
8867 const nsString* is = nullptr;
8868 if (aOptions.IsElementCreationOptions()) {
8869 const ElementCreationOptions& options =
8870 aOptions.GetAsElementCreationOptions();
8871 if (options.mIs.WasPassed()) {
8872 is = &options.mIs.Value();
8876 nsCOMPtr<Element> element;
8877 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
8878 NOT_FROM_PARSER, is);
8879 if (rv.Failed()) {
8880 return nullptr;
8883 return element.forget();
8886 already_AddRefed<Element> Document::CreateXULElement(
8887 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8888 ErrorResult& aRv) {
8889 aRv = nsContentUtils::CheckQName(aTagName, false);
8890 if (aRv.Failed()) {
8891 return nullptr;
8894 const nsString* is = nullptr;
8895 if (aOptions.IsElementCreationOptions()) {
8896 const ElementCreationOptions& options =
8897 aOptions.GetAsElementCreationOptions();
8898 if (options.mIs.WasPassed()) {
8899 is = &options.mIs.Value();
8903 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
8904 if (!elem) {
8905 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
8906 return nullptr;
8908 return elem.forget();
8911 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
8912 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8913 return text.forget();
8916 already_AddRefed<nsTextNode> Document::CreateTextNode(
8917 const nsAString& aData) const {
8918 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8919 // Don't notify; this node is still being created.
8920 text->SetText(aData, false);
8921 return text.forget();
8924 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
8925 RefPtr<DocumentFragment> frag =
8926 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
8927 return frag.forget();
8930 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
8931 already_AddRefed<dom::Comment> Document::CreateComment(
8932 const nsAString& aData) const {
8933 RefPtr<dom::Comment> comment =
8934 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
8936 // Don't notify; this node is still being created.
8937 comment->SetText(aData, false);
8938 return comment.forget();
8941 already_AddRefed<CDATASection> Document::CreateCDATASection(
8942 const nsAString& aData, ErrorResult& rv) {
8943 if (IsHTMLDocument()) {
8944 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8945 return nullptr;
8948 if (FindInReadable(u"]]>"_ns, aData)) {
8949 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8950 return nullptr;
8953 RefPtr<CDATASection> cdata =
8954 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
8956 // Don't notify; this node is still being created.
8957 cdata->SetText(aData, false);
8959 return cdata.forget();
8962 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
8963 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
8964 nsresult res = nsContentUtils::CheckQName(aTarget, false);
8965 if (NS_FAILED(res)) {
8966 rv.Throw(res);
8967 return nullptr;
8970 if (FindInReadable(u"?>"_ns, aData)) {
8971 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8972 return nullptr;
8975 RefPtr<ProcessingInstruction> pi =
8976 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
8978 return pi.forget();
8981 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
8982 ErrorResult& rv) {
8983 if (!mNodeInfoManager) {
8984 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8985 return nullptr;
8988 nsresult res = nsContentUtils::CheckQName(aName, false);
8989 if (NS_FAILED(res)) {
8990 rv.Throw(res);
8991 return nullptr;
8994 nsAutoString name;
8995 if (IsHTMLDocument()) {
8996 nsContentUtils::ASCIIToLower(aName, name);
8997 } else {
8998 name = aName;
9001 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
9002 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
9003 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
9004 if (NS_FAILED(res)) {
9005 rv.Throw(res);
9006 return nullptr;
9009 RefPtr<Attr> attribute =
9010 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
9011 return attribute.forget();
9014 already_AddRefed<Attr> Document::CreateAttributeNS(
9015 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
9016 ErrorResult& rv) {
9017 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
9018 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
9019 mNodeInfoManager, ATTRIBUTE_NODE,
9020 getter_AddRefs(nodeInfo));
9021 if (rv.Failed()) {
9022 return nullptr;
9025 RefPtr<Attr> attribute =
9026 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
9027 return attribute.forget();
9030 void Document::ScheduleForPresAttrEvaluation(Element* aElement) {
9031 MOZ_ASSERT(aElement->IsInComposedDoc());
9032 DebugOnly<bool> inserted = mLazyPresElements.EnsureInserted(aElement);
9033 MOZ_ASSERT(inserted);
9034 if (aElement->HasServoData()) {
9035 // TODO(emilio): RESTYLE_SELF is too strong, there should be no need to
9036 // re-selector-match, but right now this is needed to pick up the new mapped
9037 // attributes. We need something like RESTYLE_STYLE_ATTRIBUTE but for mapped
9038 // attributes.
9039 nsLayoutUtils::PostRestyleEvent(aElement, RestyleHint::RESTYLE_SELF,
9040 nsChangeHint(0));
9041 } else if (auto* presContext = GetPresContext()) {
9042 presContext->RestyleManager()->IncrementUndisplayedRestyleGeneration();
9046 void Document::UnscheduleForPresAttrEvaluation(Element* aElement) {
9047 mLazyPresElements.Remove(aElement);
9050 void Document::DoResolveScheduledPresAttrs() {
9051 MOZ_ASSERT(!mLazyPresElements.IsEmpty());
9052 for (Element* el : mLazyPresElements) {
9053 MOZ_ASSERT(el->IsInComposedDoc(),
9054 "Un-schedule when removing from the document");
9055 MOZ_ASSERT(el->IsPendingMappedAttributeEvaluation());
9056 if (auto* svg = SVGElement::FromNode(el)) {
9057 // SVG does its own (very similar) thing, for now at least.
9058 svg->UpdateMappedDeclarationBlock();
9059 } else {
9060 MappedDeclarationsBuilder builder(*el, *this,
9061 el->GetMappedAttributeStyle());
9062 auto function = el->GetAttributeMappingFunction();
9063 function(builder);
9064 el->SetMappedDeclarationBlock(builder.TakeDeclarationBlock());
9066 MOZ_ASSERT(!el->IsPendingMappedAttributeEvaluation());
9068 mLazyPresElements.Clear();
9071 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
9072 const {
9073 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
9075 for (const nsWeakPtr& weakNode : mBlockedNodesByClassifier) {
9076 if (nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode)) {
9077 // Consider only nodes to which we have managed to get strong references.
9078 // Coping with nullptrs since it's expected for nodes to disappear when
9079 // nobody else is referring to them.
9080 list->AppendElement(node);
9084 return list.forget();
9087 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
9088 aSheetSet.Truncate();
9090 // Look through our sheets, find the selected set title
9091 size_t count = SheetCount();
9092 nsAutoString title;
9093 for (size_t index = 0; index < count; index++) {
9094 StyleSheet* sheet = SheetAt(index);
9095 NS_ASSERTION(sheet, "Null sheet in sheet list!");
9097 if (sheet->Disabled()) {
9098 // Disabled sheets don't affect the currently selected set
9099 continue;
9102 sheet->GetTitle(title);
9104 if (aSheetSet.IsEmpty()) {
9105 aSheetSet = title;
9106 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
9107 // Sheets from multiple sets enabled; return null string, per spec.
9108 SetDOMStringToNull(aSheetSet);
9109 return;
9114 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
9115 if (DOMStringIsNull(aSheetSet)) {
9116 return;
9119 // Must update mLastStyleSheetSet before doing anything else with stylesheets
9120 // or CSSLoaders.
9121 mLastStyleSheetSet = aSheetSet;
9122 EnableStyleSheetsForSetInternal(aSheetSet, true);
9125 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
9126 mPreferredStyleSheetSet = aSheetSet;
9127 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
9128 // spec.
9129 if (DOMStringIsNull(mLastStyleSheetSet)) {
9130 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
9131 // per spec. The idea here is that we're changing our preferred set and
9132 // that shouldn't change the value of lastStyleSheetSet. Also, we're
9133 // using the Internal version so we can update the CSSLoader and not have
9134 // to worry about null strings.
9135 EnableStyleSheetsForSetInternal(aSheetSet, true);
9139 DOMStringList* Document::StyleSheetSets() {
9140 if (!mStyleSheetSetList) {
9141 mStyleSheetSetList = new DOMStyleSheetSetList(this);
9143 return mStyleSheetSetList;
9146 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
9147 // Per spec, passing in null is a no-op.
9148 if (!DOMStringIsNull(aSheetSet)) {
9149 // Note: must make sure to not change the CSSLoader's preferred sheet --
9150 // that value should be equal to either our lastStyleSheetSet (if that's
9151 // non-null) or to our preferredStyleSheetSet. And this method doesn't
9152 // change either of those.
9153 EnableStyleSheetsForSetInternal(aSheetSet, false);
9157 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
9158 bool aUpdateCSSLoader) {
9159 size_t count = SheetCount();
9160 nsAutoString title;
9161 for (size_t index = 0; index < count; index++) {
9162 StyleSheet* sheet = SheetAt(index);
9163 NS_ASSERTION(sheet, "Null sheet in sheet list!");
9165 sheet->GetTitle(title);
9166 if (!title.IsEmpty()) {
9167 sheet->SetEnabled(title.Equals(aSheetSet));
9170 if (aUpdateCSSLoader) {
9171 CSSLoader()->DocumentStyleSheetSetChanged();
9173 if (EnsureStyleSet().StyleSheetsHaveChanged()) {
9174 ApplicableStylesChanged();
9178 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
9179 nsAutoCString charset;
9180 GetDocumentCharacterSet()->Name(charset);
9181 CopyASCIItoUTF16(charset, aCharacterSet);
9184 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
9185 ErrorResult& rv) const {
9186 nsINode* imported = &aNode;
9188 switch (imported->NodeType()) {
9189 case DOCUMENT_NODE: {
9190 break;
9192 case DOCUMENT_FRAGMENT_NODE:
9193 case ATTRIBUTE_NODE:
9194 case ELEMENT_NODE:
9195 case PROCESSING_INSTRUCTION_NODE:
9196 case TEXT_NODE:
9197 case CDATA_SECTION_NODE:
9198 case COMMENT_NODE:
9199 case DOCUMENT_TYPE_NODE: {
9200 return imported->Clone(aDeep, mNodeInfoManager, rv);
9202 default: {
9203 NS_WARNING("Don't know how to clone this nodetype for importNode.");
9207 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
9208 return nullptr;
9211 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
9212 return nsRange::Create(this, 0, this, 0, rv);
9215 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
9216 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
9217 ErrorResult& rv) const {
9218 RefPtr<NodeIterator> iterator =
9219 new NodeIterator(&aRoot, aWhatToShow, aFilter);
9220 return iterator.forget();
9223 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
9224 uint32_t aWhatToShow,
9225 NodeFilter* aFilter,
9226 ErrorResult& rv) const {
9227 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
9228 return walker.forget();
9231 already_AddRefed<Location> Document::GetLocation() const {
9232 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
9234 if (!w) {
9235 return nullptr;
9238 return do_AddRef(w->Location());
9241 already_AddRefed<nsIURI> Document::GetDomainURI() {
9242 nsIPrincipal* principal = NodePrincipal();
9244 nsCOMPtr<nsIURI> uri;
9245 principal->GetDomain(getter_AddRefs(uri));
9246 if (uri) {
9247 return uri.forget();
9249 auto* basePrin = BasePrincipal::Cast(principal);
9250 basePrin->GetURI(getter_AddRefs(uri));
9251 return uri.forget();
9254 void Document::GetDomain(nsAString& aDomain) {
9255 nsCOMPtr<nsIURI> uri = GetDomainURI();
9257 if (!uri) {
9258 aDomain.Truncate();
9259 return;
9262 nsAutoCString hostName;
9263 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
9264 if (NS_SUCCEEDED(rv)) {
9265 CopyUTF8toUTF16(hostName, aDomain);
9266 } else {
9267 // If we can't get the host from the URI (e.g. about:, javascript:,
9268 // etc), just return an empty string.
9269 aDomain.Truncate();
9273 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
9274 if (!GetBrowsingContext()) {
9275 // If our browsing context is null; disallow setting domain
9276 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
9277 return;
9280 if (mSandboxFlags & SANDBOXED_DOMAIN) {
9281 // We're sandboxed; disallow setting domain
9282 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
9283 return;
9286 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
9287 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
9288 return;
9291 if (aDomain.IsEmpty()) {
9292 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
9293 return;
9296 nsCOMPtr<nsIURI> uri = GetDomainURI();
9297 if (!uri) {
9298 rv.Throw(NS_ERROR_FAILURE);
9299 return;
9302 // Check new domain - must be a superdomain of the current host
9303 // For example, a page from foo.bar.com may set domain to bar.com,
9304 // but not to ar.com, baz.com, or fi.foo.bar.com.
9306 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
9307 if (!newURI) {
9308 // Error: illegal domain
9309 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
9310 return;
9313 if (GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated()) {
9314 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
9315 return;
9318 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
9319 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
9320 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
9321 wgc->SendSetDocumentDomain(WrapNotNull(newURI));
9325 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
9326 const nsACString& aHostString) {
9327 if (aHostString.IsEmpty()) {
9328 return nullptr;
9331 // Create new URI
9332 nsCOMPtr<nsIURI> uri = GetDomainURI();
9333 if (!uri) {
9334 return nullptr;
9337 nsresult rv;
9338 rv = NS_MutateURI(uri)
9339 .SetUserPass(""_ns)
9340 .SetPort(-1) // we want to reset the port number if needed.
9341 .SetHostPort(aHostString)
9342 .Finalize(uri);
9343 if (NS_FAILED(rv)) {
9344 return nullptr;
9347 return uri.forget();
9350 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
9351 const nsAString& aNewDomain, nsIURI* aOrigHost) {
9352 if (NS_WARN_IF(!aOrigHost)) {
9353 return nullptr;
9356 nsCOMPtr<nsIURI> newURI =
9357 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
9358 if (!newURI) {
9359 // Error: failed to parse input domain
9360 return nullptr;
9363 if (!IsValidDomain(aOrigHost, newURI)) {
9364 // Error: illegal domain
9365 return nullptr;
9368 nsAutoCString domain;
9369 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
9370 return nullptr;
9373 return CreateInheritingURIForHost(domain);
9376 /* static */
9377 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
9378 // Check new domain - must be a superdomain of the current host
9379 // For example, a page from foo.bar.com may set domain to bar.com,
9380 // but not to ar.com, baz.com, or fi.foo.bar.com.
9381 nsAutoCString current;
9382 nsAutoCString domain;
9383 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
9384 current.Truncate();
9386 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
9387 domain.Truncate();
9390 bool ok = current.Equals(domain);
9391 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
9392 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
9393 // We're golden if the new domain is the current page's base domain or a
9394 // subdomain of it.
9395 nsCOMPtr<nsIEffectiveTLDService> tldService =
9396 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
9397 if (!tldService) {
9398 return false;
9401 nsAutoCString currentBaseDomain;
9402 ok = NS_SUCCEEDED(
9403 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
9404 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
9405 (domain.Length() >= currentBaseDomain.Length()),
9406 "uh-oh! slight optimization wasn't valid somehow!");
9407 ok = ok && domain.Length() >= currentBaseDomain.Length();
9410 return ok;
9413 Element* Document::GetHtmlElement() const {
9414 Element* rootElement = GetRootElement();
9415 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html)) {
9416 return rootElement;
9418 return nullptr;
9421 Element* Document::GetHtmlChildElement(
9422 nsAtom* aTag, const nsIContent* aContentToIgnore) const {
9423 Element* html = GetHtmlElement();
9424 if (!html) {
9425 return nullptr;
9428 // Look for the element with aTag inside html. This needs to run
9429 // forwards to find the first such element.
9430 for (nsIContent* child = html->GetFirstChild(); child;
9431 child = child->GetNextSibling()) {
9432 if (child->IsHTMLElement(aTag) && MOZ_LIKELY(child != aContentToIgnore)) {
9433 return child->AsElement();
9436 return nullptr;
9439 nsGenericHTMLElement* Document::GetBody() const {
9440 Element* html = GetHtmlElement();
9441 if (!html) {
9442 return nullptr;
9445 for (nsIContent* child = html->GetFirstChild(); child;
9446 child = child->GetNextSibling()) {
9447 if (child->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
9448 return static_cast<nsGenericHTMLElement*>(child);
9452 return nullptr;
9455 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
9456 nsCOMPtr<Element> root = GetRootElement();
9458 // The body element must be either a body tag or a frameset tag. And we must
9459 // have a root element to be able to add kids to it.
9460 if (!newBody ||
9461 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
9462 rv.ThrowHierarchyRequestError(
9463 "The new body must be either a body tag or frameset tag.");
9464 return;
9467 if (!root) {
9468 rv.ThrowHierarchyRequestError("No root element.");
9469 return;
9472 // Use DOM methods so that we pass through the appropriate security checks.
9473 nsCOMPtr<Element> currentBody = GetBody();
9474 if (currentBody) {
9475 root->ReplaceChild(*newBody, *currentBody, rv);
9476 } else {
9477 root->AppendChild(*newBody, rv);
9481 HTMLSharedElement* Document::GetHead() const {
9482 return static_cast<HTMLSharedElement*>(GetHeadElement());
9485 Element* Document::GetTitleElement() {
9486 // mMayHaveTitleElement will have been set to true if any HTML or SVG
9487 // <title> element has been bound to this document. So if it's false,
9488 // we know there is nothing to do here. This avoids us having to search
9489 // the whole DOM if someone calls document.title on a large document
9490 // without a title.
9491 if (!mMayHaveTitleElement) {
9492 return nullptr;
9495 Element* root = GetRootElement();
9496 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
9497 // In SVG, the document's title must be a child
9498 for (nsIContent* child = root->GetFirstChild(); child;
9499 child = child->GetNextSibling()) {
9500 if (child->IsSVGElement(nsGkAtoms::title)) {
9501 return child->AsElement();
9504 return nullptr;
9507 // We check the HTML namespace even for non-HTML documents, except SVG. This
9508 // matches the spec and the behavior of all tested browsers.
9509 for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
9510 if (node->IsHTMLElement(nsGkAtoms::title)) {
9511 return node->AsElement();
9514 return nullptr;
9517 void Document::GetTitle(nsAString& aTitle) {
9518 aTitle.Truncate();
9520 Element* rootElement = GetRootElement();
9521 if (!rootElement) {
9522 return;
9525 if (rootElement->IsXULElement()) {
9526 rootElement->GetAttr(nsGkAtoms::title, aTitle);
9527 } else if (Element* title = GetTitleElement()) {
9528 nsContentUtils::GetNodeTextContent(title, false, aTitle);
9529 } else {
9530 return;
9533 aTitle.CompressWhitespace();
9536 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
9537 Element* rootElement = GetRootElement();
9538 if (!rootElement) {
9539 return;
9542 if (rootElement->IsXULElement()) {
9543 aRv =
9544 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
9545 return;
9548 Maybe<mozAutoDocUpdate> updateBatch;
9549 nsCOMPtr<Element> title = GetTitleElement();
9550 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
9551 if (!title) {
9552 // Batch updates so that mutation events don't change "the title
9553 // element" under us
9554 updateBatch.emplace(this, true);
9555 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
9556 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
9557 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
9558 NOT_FROM_PARSER);
9559 if (!title) {
9560 return;
9562 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
9563 IgnoreErrors());
9565 } else if (rootElement->IsHTMLElement()) {
9566 if (!title) {
9567 // Batch updates so that mutation events don't change "the title
9568 // element" under us
9569 updateBatch.emplace(this, true);
9570 Element* head = GetHeadElement();
9571 if (!head) {
9572 return;
9575 RefPtr<mozilla::dom::NodeInfo> titleInfo;
9576 titleInfo = mNodeInfoManager->GetNodeInfo(
9577 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
9578 title = NS_NewHTMLTitleElement(titleInfo.forget());
9579 if (!title) {
9580 return;
9583 head->AppendChildTo(title, true, IgnoreErrors());
9585 } else {
9586 return;
9589 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
9592 class Document::TitleChangeEvent final : public Runnable {
9593 public:
9594 explicit TitleChangeEvent(Document* aDoc)
9595 : Runnable("Document::TitleChangeEvent"),
9596 mDoc(aDoc),
9597 mBlockOnload(aDoc->IsInChromeDocShell()) {
9598 if (mBlockOnload) {
9599 mDoc->BlockOnload();
9603 NS_IMETHOD Run() final {
9604 if (!mDoc) {
9605 return NS_OK;
9607 const RefPtr<Document> doc = mDoc;
9608 const bool blockOnload = mBlockOnload;
9609 mDoc = nullptr;
9610 doc->DoNotifyPossibleTitleChange();
9611 if (blockOnload) {
9612 doc->UnblockOnload(/* aFireSync = */ true);
9614 return NS_OK;
9617 void Revoke() {
9618 if (mDoc) {
9619 if (mBlockOnload) {
9620 mDoc->UnblockOnload(/* aFireSync = */ false);
9622 mDoc = nullptr;
9626 private:
9627 // Weak, caller is responsible for calling Revoke() when needed.
9628 Document* mDoc;
9629 // title changes should block the load event on chrome docshells, so that the
9630 // window title is consistently set by the time the top window is displayed.
9631 // Otherwise, some window manager integrations don't work properly,
9632 // see bug 1874766.
9633 const bool mBlockOnload = false;
9636 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
9637 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
9638 "Setting a title while unlinking or destroying the element?");
9639 if (mInUnlinkOrDeletion) {
9640 return;
9643 if (aBoundTitleElement) {
9644 mMayHaveTitleElement = true;
9647 if (mPendingTitleChangeEvent.IsPending()) {
9648 return;
9651 MOZ_RELEASE_ASSERT(NS_IsMainThread());
9652 RefPtr<TitleChangeEvent> event = new TitleChangeEvent(this);
9653 if (NS_WARN_IF(NS_FAILED(Dispatch(do_AddRef(event))))) {
9654 event->Revoke();
9655 return;
9657 mPendingTitleChangeEvent = std::move(event);
9660 void Document::DoNotifyPossibleTitleChange() {
9661 if (!mPendingTitleChangeEvent.IsPending()) {
9662 return;
9664 // Make sure the pending runnable method is cleared.
9665 mPendingTitleChangeEvent.Revoke();
9666 mHaveFiredTitleChange = true;
9668 nsAutoString title;
9669 GetTitle(title);
9671 if (RefPtr<PresShell> presShell = GetPresShell()) {
9672 nsCOMPtr<nsISupports> container =
9673 presShell->GetPresContext()->GetContainerWeak();
9674 if (container) {
9675 if (nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container)) {
9676 docShellWin->SetTitle(title);
9681 if (WindowGlobalChild* child = GetWindowGlobalChild()) {
9682 child->SendUpdateDocumentTitle(title);
9685 // Fire a DOM event for the title change.
9686 nsContentUtils::DispatchChromeEvent(this, this, u"DOMTitleChanged"_ns,
9687 CanBubble::eYes, Cancelable::eYes);
9689 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
9690 if (obs) {
9691 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
9695 already_AddRefed<MediaQueryList> Document::MatchMedia(
9696 const nsACString& aMediaQueryList, CallerType aCallerType) {
9697 RefPtr<MediaQueryList> result =
9698 new MediaQueryList(this, aMediaQueryList, aCallerType);
9700 mDOMMediaQueryLists.insertBack(result);
9702 return result.forget();
9705 void Document::SetMayStartLayout(bool aMayStartLayout) {
9706 mMayStartLayout = aMayStartLayout;
9707 if (MayStartLayout()) {
9708 // Before starting layout, check whether we're a toplevel chrome
9709 // window. If we are, setup some state so that we don't have to restyle
9710 // the whole tree after StartLayout.
9711 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
9712 // We're the chrome document!
9713 win->BeforeStartLayout();
9715 ReadyState state = GetReadyStateEnum();
9716 if (state >= READYSTATE_INTERACTIVE) {
9717 // DOMContentLoaded has fired already.
9718 MaybeResolveReadyForIdle();
9722 MaybeEditingStateChanged();
9725 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
9726 mInitializableFrameLoaders.RemoveElement(aLoader);
9727 // Don't even try to initialize.
9728 if (mInDestructor) {
9729 NS_WARNING(
9730 "Trying to initialize a frame loader while"
9731 "document is being deleted");
9732 return NS_ERROR_FAILURE;
9735 mInitializableFrameLoaders.AppendElement(aLoader);
9736 if (!mFrameLoaderRunner) {
9737 mFrameLoaderRunner =
9738 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9739 &Document::MaybeInitializeFinalizeFrameLoaders);
9740 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9741 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9743 return NS_OK;
9746 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
9747 nsIRunnable* aFinalizer) {
9748 mInitializableFrameLoaders.RemoveElement(aLoader);
9749 if (mInDestructor) {
9750 return NS_ERROR_FAILURE;
9753 LogRunnable::LogDispatch(aFinalizer);
9754 mFrameLoaderFinalizers.AppendElement(aFinalizer);
9755 if (!mFrameLoaderRunner) {
9756 mFrameLoaderRunner =
9757 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9758 &Document::MaybeInitializeFinalizeFrameLoaders);
9759 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9760 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9762 return NS_OK;
9765 void Document::MaybeInitializeFinalizeFrameLoaders() {
9766 if (mDelayFrameLoaderInitialization) {
9767 // This method will be recalled when !mDelayFrameLoaderInitialization.
9768 mFrameLoaderRunner = nullptr;
9769 return;
9772 // We're not in an update, but it is not safe to run scripts, so
9773 // postpone frameloader initialization and finalization.
9774 if (!nsContentUtils::IsSafeToRunScript()) {
9775 if (!mInDestructor && !mFrameLoaderRunner &&
9776 (mInitializableFrameLoaders.Length() ||
9777 mFrameLoaderFinalizers.Length())) {
9778 mFrameLoaderRunner = NewRunnableMethod(
9779 "Document::MaybeInitializeFinalizeFrameLoaders", this,
9780 &Document::MaybeInitializeFinalizeFrameLoaders);
9781 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9783 return;
9785 mFrameLoaderRunner = nullptr;
9787 // Don't use a temporary array for mInitializableFrameLoaders, because
9788 // loading a frame may cause some other frameloader to be removed from the
9789 // array. But be careful to keep the loader alive when starting the load!
9790 while (mInitializableFrameLoaders.Length()) {
9791 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
9792 mInitializableFrameLoaders.RemoveElementAt(0);
9793 NS_ASSERTION(loader, "null frameloader in the array?");
9794 loader->ReallyStartLoading();
9797 uint32_t length = mFrameLoaderFinalizers.Length();
9798 if (length > 0) {
9799 nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
9800 std::move(mFrameLoaderFinalizers);
9801 for (uint32_t i = 0; i < length; ++i) {
9802 LogRunnable::Run run(finalizers[i]);
9803 finalizers[i]->Run();
9808 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
9809 uint32_t length = mInitializableFrameLoaders.Length();
9810 for (uint32_t i = 0; i < length; ++i) {
9811 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
9812 mInitializableFrameLoaders.RemoveElementAt(i);
9813 return;
9818 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
9819 mPrototypeDocument = aPrototype;
9820 mSynchronousDOMContentLoaded = true;
9823 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
9824 return GetPermissionDelegateHandler();
9827 Document* Document::RequestExternalResource(
9828 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
9829 ExternalResourceLoad** aPendingLoad) {
9830 MOZ_ASSERT(aURI, "Must have a URI");
9831 MOZ_ASSERT(aRequestingNode, "Must have a node");
9832 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
9833 if (mDisplayDocument) {
9834 return mDisplayDocument->RequestExternalResource(
9835 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
9838 return mExternalResourceMap.RequestResource(
9839 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
9842 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
9843 mExternalResourceMap.EnumerateResources(aCallback);
9846 SMILAnimationController* Document::GetAnimationController() {
9847 // We create the animation controller lazily because most documents won't want
9848 // one and only SVG documents and the like will call this
9849 if (mAnimationController) return mAnimationController;
9850 // Refuse to create an Animation Controller for data documents.
9851 if (mLoadedAsData) return nullptr;
9853 mAnimationController = new SMILAnimationController(this);
9855 // If there's a presContext then check the animation mode and pause if
9856 // necessary.
9857 nsPresContext* context = GetPresContext();
9858 if (mAnimationController && context &&
9859 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
9860 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
9863 // If we're hidden (or being hidden), notify the newly-created animation
9864 // controller. (Skip this check for SVG-as-an-image documents, though,
9865 // because they don't get OnPageShow / OnPageHide calls).
9866 if (!mIsShowing && !mIsBeingUsedAsImage) {
9867 mAnimationController->OnPageHide();
9870 return mAnimationController;
9873 ScrollTimelineAnimationTracker*
9874 Document::GetOrCreateScrollTimelineAnimationTracker() {
9875 if (!mScrollTimelineAnimationTracker) {
9876 mScrollTimelineAnimationTracker = new ScrollTimelineAnimationTracker(this);
9879 return mScrollTimelineAnimationTracker;
9883 * Retrieve the "direction" property of the document.
9885 * @lina 01/09/2001
9887 void Document::GetDir(nsAString& aDirection) const {
9888 aDirection.Truncate();
9889 Element* rootElement = GetHtmlElement();
9890 if (rootElement) {
9891 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
9896 * Set the "direction" property of the document.
9898 * @lina 01/09/2001
9900 void Document::SetDir(const nsAString& aDirection) {
9901 Element* rootElement = GetHtmlElement();
9902 if (rootElement) {
9903 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
9907 nsIHTMLCollection* Document::Images() {
9908 if (!mImages) {
9909 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
9910 nsGkAtoms::img);
9912 return mImages;
9915 nsIHTMLCollection* Document::Embeds() {
9916 if (!mEmbeds) {
9917 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
9918 nsGkAtoms::embed);
9920 return mEmbeds;
9923 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9924 void* aData) {
9925 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
9926 aElement->HasAttr(nsGkAtoms::href);
9929 nsIHTMLCollection* Document::Links() {
9930 if (!mLinks) {
9931 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
9933 return mLinks;
9936 nsIHTMLCollection* Document::Forms() {
9937 if (!mForms) {
9938 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
9939 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
9940 nsGkAtoms::form);
9943 return mForms;
9946 nsIHTMLCollection* Document::Scripts() {
9947 if (!mScripts) {
9948 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
9949 nsGkAtoms::script);
9951 return mScripts;
9954 nsIHTMLCollection* Document::Applets() {
9955 if (!mApplets) {
9956 mApplets = new nsEmptyContentList(this);
9958 return mApplets;
9961 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9962 void* aData) {
9963 return aElement->IsHTMLElement(nsGkAtoms::a) &&
9964 aElement->HasAttr(nsGkAtoms::name);
9967 nsIHTMLCollection* Document::Anchors() {
9968 if (!mAnchors) {
9969 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
9971 return mAnchors;
9974 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
9975 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
9976 ErrorResult& rv) {
9977 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9978 "XOW should have caught this!");
9980 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
9981 if (!window) {
9982 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
9983 return nullptr;
9985 nsCOMPtr<nsPIDOMWindowOuter> outer =
9986 nsPIDOMWindowOuter::GetFromCurrentInner(window);
9987 if (!outer) {
9988 rv.Throw(NS_ERROR_NOT_INITIALIZED);
9989 return nullptr;
9991 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
9992 RefPtr<BrowsingContext> newBC;
9993 rv = win->OpenJS(NS_ConvertUTF16toUTF8(aURL), aName, aFeatures,
9994 getter_AddRefs(newBC));
9995 if (!newBC) {
9996 return nullptr;
9998 return WindowProxyHolder(std::move(newBC));
10001 Document* Document::Open(const Optional<nsAString>& /* unused */,
10002 const Optional<nsAString>& /* unused */,
10003 ErrorResult& aError) {
10004 // Implements
10005 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
10007 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
10008 "XOW should have caught this!");
10010 // Step 1 -- throw if we're an XML document.
10011 if (!IsHTMLDocument() || mDisableDocWrite) {
10012 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
10013 return nullptr;
10016 // Step 2 -- throw if dynamic markup insertion should throw.
10017 if (ShouldThrowOnDynamicMarkupInsertion()) {
10018 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
10019 return nullptr;
10022 // Step 3 -- get the entry document, so we can use it for security checks.
10023 nsCOMPtr<Document> callerDoc = GetEntryDocument();
10024 if (!callerDoc) {
10025 if (nsIGlobalObject* callerGlobal = GetEntryGlobal()) {
10026 if (callerGlobal->IsXPCSandbox()) {
10027 if (nsIPrincipal* principal = callerGlobal->PrincipalOrNull()) {
10028 if (principal->Equals(NodePrincipal())) {
10029 // In case we're being called from some JS sandbox scope,
10030 // pretend that the caller is the document itself.
10031 callerDoc = this;
10037 if (!callerDoc) {
10038 // If we're called from C++ or in some other way without an originating
10039 // document we can't do a document.open w/o changing the principal of the
10040 // document to something like about:blank (as that's the only sane thing
10041 // to do when we don't know the origin of this call), and since we can't
10042 // change the principals of a document for security reasons we'll have to
10043 // refuse to go ahead with this call.
10045 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
10046 return nullptr;
10050 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
10051 // the entry document.
10052 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
10053 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
10054 return nullptr;
10057 // Step 5 -- if we have an active parser with a nonzero script nesting level,
10058 // just no-op.
10059 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
10060 return this;
10063 // Step 6 -- check for open() during unload. Per spec, this is just a check
10064 // of the ignore-opens-during-unload counter, but our unload event code
10065 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
10066 // do), so we check for unload directly.
10067 if (ShouldIgnoreOpens()) {
10068 return this;
10071 RefPtr<nsDocShell> shell(mDocumentContainer);
10072 if (shell) {
10073 bool inUnload;
10074 shell->GetIsInUnload(&inUnload);
10075 if (inUnload) {
10076 return this;
10080 // At this point we know this is a valid-enough document.open() call
10081 // and not a no-op. Increment our use counter.
10082 SetUseCounter(eUseCounter_custom_DocumentOpen);
10084 // XXX The spec has changed. There is a new step 7 and step 8 has changed
10085 // a bit.
10087 // Step 8 -- stop existing navigation of our browsing context (and all other
10088 // loads it's doing) if we're the active document of our browsing context.
10089 // Note that we do not want to stop anything if there is no existing
10090 // navigation.
10091 if (shell && IsCurrentActiveDocument() &&
10092 shell->GetIsAttemptingToNavigate()) {
10093 shell->Stop(nsIWebNavigation::STOP_NETWORK);
10095 // The Stop call may have cancelled the onload blocker request or
10096 // prevented it from getting added, so we need to make sure it gets added
10097 // to the document again otherwise the document could have a non-zero
10098 // onload block count without the onload blocker request being in the
10099 // loadgroup.
10100 EnsureOnloadBlocker();
10103 // Step 9 -- clear event listeners out of our DOM tree
10104 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
10105 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
10106 elm->RemoveAllListeners();
10110 // Step 10 -- clear event listeners from our window, if we have one.
10112 // Note that we explicitly want the inner window, and only if we're its
10113 // document. We want to do this (per spec) even when we're not the "active
10114 // document", so we can't go through GetWindow(), because it might forward to
10115 // the wrong inner.
10116 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
10117 if (win->GetExtantDoc() == this) {
10118 if (EventListenerManager* elm =
10119 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
10120 elm->RemoveAllListeners();
10125 // If we have a parser that has a zero script nesting level, we need to
10126 // properly terminate it. We do that after we've removed all the event
10127 // listeners (so termination won't trigger event listeners if it does
10128 // something to the DOM), but before we remove all elements from the document
10129 // (so if termination does modify the DOM in some way we will just blow it
10130 // away immediately. See the similar code in WriteCommon that handles the
10131 // !IsInsertionPointDefined() case and should stay in sync with this code.
10132 if (mParser) {
10133 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
10134 "Why didn't we take the early return?");
10135 // Make sure we don't re-enter.
10136 IgnoreOpensDuringUnload ignoreOpenGuard(this);
10137 mParser->Terminate();
10138 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
10141 // Steps 11, 12, 13, 14 --
10142 // remove all our DOM kids without firing any mutation events.
10144 bool oldFlag = FireMutationEvents();
10145 SetFireMutationEvents(false);
10147 // We want to ignore any recursive calls to Open() that happen while
10148 // disconnecting the node tree. The spec doesn't say to do this, but the
10149 // spec also doesn't envision unload events on subframes firing while we do
10150 // this, while all browsers fire them in practice. See
10151 // <https://github.com/whatwg/html/issues/4611>.
10152 IgnoreOpensDuringUnload ignoreOpenGuard(this);
10153 DisconnectNodeTree();
10154 SetFireMutationEvents(oldFlag);
10157 // Step 15 -- if we're the current document in our docshell, do the
10158 // equivalent of pushState() with the new URL we should have.
10159 if (shell && IsCurrentActiveDocument()) {
10160 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
10161 if (callerDoc != this) {
10162 nsCOMPtr<nsIURI> noFragmentURI;
10163 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
10164 if (NS_WARN_IF(NS_FAILED(rv))) {
10165 aError.Throw(rv);
10166 return nullptr;
10168 newURI = std::move(noFragmentURI);
10171 // UpdateURLAndHistory might do various member-setting, so make sure we're
10172 // holding strong refs to all the refcounted args on the stack. We can
10173 // assume that our caller is holding on to "this" already.
10174 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
10175 bool equalURIs;
10176 nsresult rv = currentURI->Equals(newURI, &equalURIs);
10177 if (NS_WARN_IF(NS_FAILED(rv))) {
10178 aError.Throw(rv);
10179 return nullptr;
10181 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
10182 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
10183 /* aReplace = */ true, currentURI,
10184 equalURIs);
10185 if (NS_WARN_IF(NS_FAILED(rv))) {
10186 aError.Throw(rv);
10187 return nullptr;
10190 // And use the security info of the caller document as well, since
10191 // it's the thing providing our data.
10192 mSecurityInfo = callerDoc->GetSecurityInfo();
10194 // Step 16
10195 // See <https://github.com/whatwg/html/issues/4299>. Since our
10196 // URL may be changing away from about:blank here, we really want to unset
10197 // this flag no matter what, since only about:blank can be an initial
10198 // document.
10199 SetIsInitialDocument(false);
10201 // And let our docloader know that it will need to track our load event.
10202 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
10205 // Per spec nothing happens with our URI in other cases, though note
10206 // <https://github.com/whatwg/html/issues/4286>.
10208 // Note that we don't need to do anything here with base URIs per spec.
10209 // That said, this might be assuming that we implement
10210 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
10211 // correctly, which we don't right now for the about:blank case.
10213 // Step 17, but note <https://github.com/whatwg/html/issues/4292>.
10214 mSkipLoadEventAfterClose = mLoadEventFiring;
10216 // Preliminary to steps 18-21. Set our ready state to uninitialized before
10217 // we do anything else, so we can then proceed to later ready state levels.
10218 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
10219 /* updateTimingInformation = */ false);
10220 // Reset a flag that affects readyState behavior.
10221 mSetCompleteAfterDOMContentLoaded = false;
10223 // Step 18 -- set our compat mode to standards.
10224 SetCompatibilityMode(eCompatibility_FullStandards);
10226 // Step 19 -- create a new parser associated with document. This also does
10227 // step 20 implicitly.
10228 mParserAborted = false;
10229 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
10230 mParser = parser;
10231 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
10232 nsresult rv = parser->StartExecutor();
10233 if (NS_WARN_IF(NS_FAILED(rv))) {
10234 aError.Throw(rv);
10235 return nullptr;
10238 // Clear out our form control state, because the state of controls
10239 // in the pre-open() document should not affect the state of
10240 // controls that are now going to be written.
10241 mLayoutHistoryState = nullptr;
10243 if (shell) {
10244 // Prepare the docshell and the document viewer for the impending
10245 // out-of-band document.write()
10246 shell->PrepareForNewContentModel();
10248 nsCOMPtr<nsIDocumentViewer> viewer;
10249 shell->GetDocViewer(getter_AddRefs(viewer));
10250 if (viewer) {
10251 viewer->LoadStart(this);
10255 // Step 21.
10256 SetReadyStateInternal(Document::READYSTATE_LOADING,
10257 /* updateTimingInformation = */ false);
10259 // Step 22.
10260 return this;
10263 void Document::Close(ErrorResult& rv) {
10264 if (!IsHTMLDocument()) {
10265 // No calling document.close() on XHTML!
10267 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
10268 return;
10271 if (ShouldThrowOnDynamicMarkupInsertion()) {
10272 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
10273 return;
10276 if (!mParser || !mParser->IsScriptCreated()) {
10277 return;
10280 ++mWriteLevel;
10281 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
10282 ->Parse(u""_ns, nullptr, true);
10283 --mWriteLevel;
10286 void Document::WriteCommon(const Sequence<OwningTrustedHTMLOrString>& aText,
10287 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
10288 bool isTrusted = true;
10289 auto getAsString =
10290 [&isTrusted](const OwningTrustedHTMLOrString& aTrustedHTMLOrString) {
10291 if (aTrustedHTMLOrString.IsString()) {
10292 isTrusted = false;
10293 return &aTrustedHTMLOrString.GetAsString();
10295 return &aTrustedHTMLOrString.GetAsTrustedHTML()->mData;
10298 // Fast path the common case
10299 if (aText.Length() == 1) {
10300 WriteCommon(*getAsString(aText[0]), aNewlineTerminate,
10301 aText[0].IsTrustedHTML(), rv);
10302 } else {
10303 // XXXbz it would be nice if we could pass all the strings to the parser
10304 // without having to do all this copying and then ask it to start
10305 // parsing....
10306 nsString text;
10307 for (size_t i = 0; i < aText.Length(); ++i) {
10308 text.Append(*getAsString(aText[i]));
10310 WriteCommon(text, aNewlineTerminate, isTrusted, rv);
10314 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
10315 bool aIsTrusted, ErrorResult& aRv) {
10316 #ifdef DEBUG
10318 // Assert that we do not use or accidentally introduce doc.write()
10319 // in system privileged context or in any of our about: pages.
10320 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
10321 bool isAboutOrPrivContext = principal->IsSystemPrincipal();
10322 if (!isAboutOrPrivContext) {
10323 if (principal->SchemeIs("about")) {
10324 // about:blank inherits the security contetext and this assertion
10325 // is only meant for actual about: pages.
10326 nsAutoCString host;
10327 principal->GetHost(host);
10328 isAboutOrPrivContext = !host.EqualsLiteral("blank");
10331 // Some automated tests use an empty string to kick off some parsing
10332 // mechansims, but they do not do any harm since they use an empty string.
10333 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
10334 "do not use doc.write in privileged context!");
10336 #endif
10338 mTooDeepWriteRecursion =
10339 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
10340 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
10341 aRv.Throw(NS_ERROR_UNEXPECTED);
10342 return;
10345 if (!IsHTMLDocument() || mDisableDocWrite) {
10346 // No calling document.write*() on XHTML!
10348 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
10349 return;
10352 if (ShouldThrowOnDynamicMarkupInsertion()) {
10353 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
10354 return;
10357 if (mParserAborted) {
10358 // Hixie says aborting the parser doesn't undefine the insertion point.
10359 // However, since we null out mParser in that case, we track the
10360 // theoretically defined insertion point using mParserAborted.
10361 return;
10364 // Implement Step 4.1 of:
10365 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
10366 if (ShouldIgnoreOpens()) {
10367 return;
10370 void* key = GenerateParserKey();
10371 if (mParser && !mParser->IsInsertionPointDefined()) {
10372 if (mIgnoreDestructiveWritesCounter) {
10373 // Instead of implying a call to document.open(), ignore the call.
10374 nsContentUtils::ReportToConsole(
10375 nsIScriptError::warningFlag, "DOM Events"_ns, this,
10376 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
10377 return;
10379 // The spec doesn't tell us to ignore opens from here, but we need to
10380 // ensure opens are ignored here. See similar code in Open() that handles
10381 // the case of an existing parser which is not currently running script and
10382 // should stay in sync with this code.
10383 IgnoreOpensDuringUnload ignoreOpenGuard(this);
10384 mParser->Terminate();
10385 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
10388 if (!mParser) {
10389 if (mIgnoreDestructiveWritesCounter) {
10390 // Instead of implying a call to document.open(), ignore the call.
10391 nsContentUtils::ReportToConsole(
10392 nsIScriptError::warningFlag, "DOM Events"_ns, this,
10393 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
10394 return;
10397 Open({}, {}, aRv);
10399 // If Open() fails, or if it didn't create a parser (as it won't
10400 // if the user chose to not discard the current document through
10401 // onbeforeunload), don't write anything.
10402 if (aRv.Failed() || !mParser) {
10403 return;
10407 static constexpr auto new_line = u"\n"_ns;
10409 ++mWriteLevel;
10411 auto parseString =
10412 [this, &aNewlineTerminate, &aRv, &key](const nsAString& aString)
10413 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> void {
10414 // This could be done with less code, but for performance reasons it
10415 // makes sense to have the code for two separate Parse() calls here
10416 // since the concatenation of strings costs more than we like. And
10417 // why pay that price when we don't need to?
10418 if (aNewlineTerminate) {
10419 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
10420 ->Parse(aString + new_line, key, false);
10421 } else {
10422 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
10423 ->Parse(aString, key, false);
10427 if (aIsTrusted) {
10428 parseString(aText);
10429 } else {
10430 constexpr nsLiteralString sinkWrite = u"Document write"_ns;
10431 constexpr nsLiteralString sinkWriteLn = u"Document writeln"_ns;
10432 Maybe<nsAutoString> compliantStringHolder;
10433 const nsAString* compliantString =
10434 TrustedTypeUtils::GetTrustedTypesCompliantStringForTrustedHTML(
10435 aText, aNewlineTerminate ? sinkWriteLn : sinkWrite,
10436 kTrustedTypesOnlySinkGroup, *this, compliantStringHolder, aRv);
10437 if (!aRv.Failed()) {
10438 parseString(*compliantString);
10442 --mWriteLevel;
10444 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
10447 void Document::Write(const Sequence<OwningTrustedHTMLOrString>& aText,
10448 ErrorResult& rv) {
10449 WriteCommon(aText, false, rv);
10452 void Document::Writeln(const Sequence<OwningTrustedHTMLOrString>& aText,
10453 ErrorResult& rv) {
10454 WriteCommon(aText, true, rv);
10457 void* Document::GenerateParserKey(void) {
10458 if (!mScriptLoader) {
10459 // If we don't have a script loader, then the parser probably isn't parsing
10460 // anything anyway, so just return null.
10461 return nullptr;
10464 // The script loader provides us with the currently executing script element,
10465 // which is guaranteed to be unique per script.
10466 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
10467 if (script && mParser && mParser->IsScriptCreated()) {
10468 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
10469 if (creatorParser != mParser) {
10470 // Make scripts that aren't inserted by the active parser of this document
10471 // participate in the context of the script that document.open()ed
10472 // this document.
10473 return nullptr;
10476 return script;
10479 /* static */
10480 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
10481 nsAtom* aAtom, void* aData) {
10482 MOZ_ASSERT(aElement, "Must have element to work with!");
10484 if (!aElement->HasName()) {
10485 return false;
10488 nsString* elementName = static_cast<nsString*>(aData);
10489 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
10490 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
10491 eCaseMatters);
10494 /* static */
10495 void* Document::UseExistingNameString(nsINode* aRootNode,
10496 const nsString* aName) {
10497 return const_cast<nsString*>(aName);
10500 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
10501 if (mDocumentURI) {
10502 nsAutoCString uri;
10503 nsresult rv = mDocumentURI->GetSpec(uri);
10504 NS_ENSURE_SUCCESS(rv, rv);
10506 CopyUTF8toUTF16(uri, aDocumentURI);
10507 } else {
10508 aDocumentURI.Truncate();
10511 return NS_OK;
10514 // Alias of above
10515 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
10517 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
10518 CallerType aCallerType,
10519 ErrorResult& aRv) const {
10520 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
10521 aRv = GetDocumentURI(aDocumentURI);
10522 return;
10525 nsAutoCString uri;
10526 nsresult res = mChromeXHRDocURI->GetSpec(uri);
10527 if (NS_FAILED(res)) {
10528 aRv.Throw(res);
10529 return;
10531 CopyUTF8toUTF16(uri, aDocumentURI);
10534 nsIURI* Document::GetDocumentURIObject() const {
10535 if (!mChromeXHRDocURI) {
10536 return GetDocumentURI();
10539 return mChromeXHRDocURI;
10542 void Document::GetCompatMode(nsString& aCompatMode) const {
10543 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
10544 mCompatMode == eCompatibility_AlmostStandards ||
10545 mCompatMode == eCompatibility_FullStandards,
10546 "mCompatMode is neither quirks nor strict for this document");
10548 if (mCompatMode == eCompatibility_NavQuirks) {
10549 aCompatMode.AssignLiteral("BackCompat");
10550 } else {
10551 aCompatMode.AssignLiteral("CSS1Compat");
10555 } // namespace dom
10556 } // namespace mozilla
10558 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
10559 if (Element* element = Element::FromNode(aNode)) {
10560 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
10561 while (true) {
10562 RefPtr<Attr> attr;
10564 // Use an iterator to get an arbitrary attribute from the
10565 // cache. The iterator must be destroyed before any other
10566 // operations on mAttributeCache, to avoid hash table
10567 // assertions.
10568 auto iter = map->mAttributeCache.ConstIter();
10569 if (iter.Done()) {
10570 break;
10572 attr = iter.UserData();
10575 BlastSubtreeToPieces(attr);
10577 mozilla::DebugOnly<nsresult> rv =
10578 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
10579 attr->NodeInfo()->NameAtom(), false);
10581 // XXX Should we abort here?
10582 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
10586 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
10587 BlastSubtreeToPieces(shadow);
10588 element->UnattachShadow();
10592 while (aNode->HasChildren()) {
10593 nsIContent* node = aNode->GetFirstChild();
10594 BlastSubtreeToPieces(node);
10595 aNode->RemoveChildNode(node, false);
10599 namespace mozilla::dom {
10601 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv,
10602 bool aAcceptShadowRoot) {
10603 OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
10604 if (adoptedNode->IsShadowRoot() && !aAcceptShadowRoot) {
10605 rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
10606 return nullptr;
10609 // Scope firing mutation events so that we don't carry any state that
10610 // might be stale
10612 if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
10613 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
10617 nsAutoScriptBlocker scriptBlocker;
10619 switch (adoptedNode->NodeType()) {
10620 case ATTRIBUTE_NODE: {
10621 // Remove from ownerElement.
10622 OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
10624 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement();
10625 if (rv.Failed()) {
10626 return nullptr;
10629 if (ownerElement) {
10630 OwningNonNull<Attr> newAttr =
10631 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
10632 if (rv.Failed()) {
10633 return nullptr;
10637 break;
10639 case DOCUMENT_FRAGMENT_NODE:
10640 case ELEMENT_NODE:
10641 case PROCESSING_INSTRUCTION_NODE:
10642 case TEXT_NODE:
10643 case CDATA_SECTION_NODE:
10644 case COMMENT_NODE:
10645 case DOCUMENT_TYPE_NODE: {
10646 // Don't allow adopting a node's anonymous subtree out from under it.
10647 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
10648 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10649 return nullptr;
10652 // We don't want to adopt an element into its own contentDocument or into
10653 // a descendant contentDocument, so we check if the frameElement of this
10654 // document or any of its parents is the adopted node or one of its
10655 // descendants.
10656 RefPtr<BrowsingContext> bc = GetBrowsingContext();
10657 while (bc) {
10658 nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
10659 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
10660 rv.ThrowHierarchyRequestError(
10661 "Trying to adopt a node into its own contentDocument or a "
10662 "descendant contentDocument.");
10663 return nullptr;
10666 if (XRE_IsParentProcess()) {
10667 bc = bc->Canonical()->GetParentCrossChromeBoundary();
10668 } else {
10669 bc = bc->GetParent();
10673 // Remove from parent.
10674 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
10675 if (parent) {
10676 parent->RemoveChildNode(adoptedNode->AsContent(), true);
10677 } else {
10678 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
10681 break;
10683 case DOCUMENT_NODE: {
10684 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10685 return nullptr;
10687 default: {
10688 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
10690 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10691 return nullptr;
10695 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
10696 bool sameDocument = oldDocument == this;
10698 AutoJSContext cx;
10699 JS::Rooted<JSObject*> newScope(cx, nullptr);
10700 if (!sameDocument) {
10701 newScope = GetWrapper();
10702 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
10703 // Make sure cx is in a semi-sane compartment before we call WrapNative.
10704 // It's kind of irrelevant, given that we're passing aAllowWrapping =
10705 // false, and documents should always insist on being wrapped in an
10706 // canonical scope. But we try to pass something sane anyway.
10707 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
10708 JSAutoRealm ar(cx, globalObject);
10709 JS::Rooted<JS::Value> v(cx);
10710 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
10711 /* aAllowWrapping = */ false);
10712 if (rv.Failed()) return nullptr;
10713 newScope = &v.toObject();
10717 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
10718 if (rv.Failed()) {
10719 // Disconnect all nodes from their parents, since some have the old document
10720 // as their ownerDocument and some have this as their ownerDocument.
10721 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
10722 return nullptr;
10725 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
10726 "Should still be in the document we just got adopted into");
10728 return adoptedNode;
10731 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
10733 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
10734 const nsString& aScaleString) {
10735 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
10736 if (aScaleString.EqualsLiteral("device-width") ||
10737 aScaleString.EqualsLiteral("device-height")) {
10738 return Some(LayoutDeviceToScreenScale(10.0f));
10739 } else if (aScaleString.EqualsLiteral("yes")) {
10740 return Some(LayoutDeviceToScreenScale(1.0f));
10741 } else if (aScaleString.EqualsLiteral("no")) {
10742 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10743 } else if (aScaleString.IsEmpty()) {
10744 return Nothing();
10747 nsresult scaleErrorCode;
10748 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
10749 if (NS_FAILED(scaleErrorCode)) {
10750 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10753 if (scale < 0) {
10754 return Nothing();
10756 return Some(std::clamp(LayoutDeviceToScreenScale(scale), ViewportMinScale(),
10757 ViewportMaxScale()));
10760 void Document::ParseScalesInViewportMetaData(
10761 const ViewportMetaData& aViewportMetaData) {
10762 Maybe<LayoutDeviceToScreenScale> scale;
10764 scale = ParseScaleString(aViewportMetaData.mInitialScale);
10765 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
10766 mValidScaleFloat = scale.isSome();
10768 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
10769 // Chrome uses '5' for the fallback value of maximum-scale, we might
10770 // consider matching it in future.
10771 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
10772 mScaleMaxFloat = scale.valueOr(ViewportMaxScale());
10773 mValidMaxScale = scale.isSome();
10775 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
10776 mScaleMinFloat = scale.valueOr(ViewportMinScale());
10777 mValidMinScale = scale.isSome();
10779 // Resolve min-zoom and max-zoom values.
10780 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
10781 if (mValidMaxScale && mValidMinScale) {
10782 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
10786 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
10787 const nsAString& aHeightString,
10788 bool aHasValidScale) {
10789 // The width and height properties
10790 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
10792 // The width and height viewport <META> properties are translated into width
10793 // and height descriptors, setting the min-width/min-height value to
10794 // extend-to-zoom and the max-width/max-height value to the length from the
10795 // viewport <META> property as follows:
10797 // 1. Non-negative number values are translated to pixel lengths, clamped to
10798 // the range: [1px, 10000px]
10799 // 2. Negative number values are dropped
10800 // 3. device-width and device-height translate to 100vw and 100vh respectively
10801 // 4. Other keywords and unknown values are also dropped
10802 mMinWidth = nsViewportInfo::kAuto;
10803 mMaxWidth = nsViewportInfo::kAuto;
10804 if (!aWidthString.IsEmpty()) {
10805 mMinWidth = nsViewportInfo::kExtendToZoom;
10806 if (aWidthString.EqualsLiteral("device-width")) {
10807 mMaxWidth = nsViewportInfo::kDeviceSize;
10808 } else {
10809 nsresult widthErrorCode;
10810 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
10811 if (NS_FAILED(widthErrorCode)) {
10812 mMaxWidth = nsViewportInfo::kAuto;
10813 } else if (mMaxWidth >= 0.0f) {
10814 mMaxWidth = std::clamp(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
10815 } else {
10816 mMaxWidth = nsViewportInfo::kAuto;
10819 } else if (aHasValidScale) {
10820 if (aHeightString.IsEmpty()) {
10821 mMinWidth = nsViewportInfo::kExtendToZoom;
10822 mMaxWidth = nsViewportInfo::kExtendToZoom;
10824 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
10825 mMinWidth = nsViewportInfo::kExtendToZoom;
10826 mMaxWidth = nsViewportInfo::kDeviceSize;
10829 mMinHeight = nsViewportInfo::kAuto;
10830 mMaxHeight = nsViewportInfo::kAuto;
10831 if (!aHeightString.IsEmpty()) {
10832 mMinHeight = nsViewportInfo::kExtendToZoom;
10833 if (aHeightString.EqualsLiteral("device-height")) {
10834 mMaxHeight = nsViewportInfo::kDeviceSize;
10835 } else {
10836 nsresult heightErrorCode;
10837 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
10838 if (NS_FAILED(heightErrorCode)) {
10839 mMaxHeight = nsViewportInfo::kAuto;
10840 } else if (mMaxHeight >= 0.0f) {
10841 mMaxHeight = std::clamp(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
10842 } else {
10843 mMaxHeight = nsViewportInfo::kAuto;
10849 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
10850 MOZ_ASSERT(mPresShell);
10852 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
10853 // widget scale and the full zoom.
10854 nsPresContext* context = mPresShell->GetPresContext();
10855 // When querying the full zoom, get it from the device context rather than
10856 // directly from the pres context, because the device context's value can
10857 // include an adjustment necessary to keep the number of app units per device
10858 // pixel an integer, and we want the adjusted value.
10859 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
10860 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
10861 CSSToLayoutDeviceScale layoutDeviceScale =
10862 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
10864 CSSToScreenScale defaultScale =
10865 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
10867 auto* bc = GetBrowsingContext();
10868 const bool inRDM = bc && bc->InRDMPane();
10869 const bool ignoreMetaTag = [&] {
10870 if (!nsLayoutUtils::ShouldHandleMetaViewport(this)) {
10871 return true;
10873 if (Fullscreen()) {
10874 // We ignore viewport meta tag etc when in fullscreen, see bug 1696717.
10875 return true;
10877 if (inRDM && bc->ForceDesktopViewport()) {
10878 // We ignore meta viewport when devtools tells us to force desktop
10879 // viewport on RDM.
10880 return true;
10882 return false;
10883 }();
10885 if (ignoreMetaTag) {
10886 return nsViewportInfo(aDisplaySize, defaultScale,
10887 nsLayoutUtils::AllowZoomingForDocument(this)
10888 ? nsViewportInfo::ZoomFlag::AllowZoom
10889 : nsViewportInfo::ZoomFlag::DisallowZoom,
10890 StaticPrefs::apz_allow_zooming_out()
10891 ? nsViewportInfo::ZoomBehaviour::Mobile
10892 : nsViewportInfo::ZoomBehaviour::Desktop);
10895 // Special behaviour for desktop mode, provided we are not on an about: page.
10896 if (bc && bc->ForceDesktopViewport() && !IsAboutPage()) {
10897 CSSCoord viewportWidth =
10898 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
10899 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
10900 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
10901 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
10902 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
10903 return nsViewportInfo(fakeDesktopSize, scaleToFit,
10904 nsViewportInfo::ZoomFlag::AllowZoom,
10905 nsViewportInfo::ZoomBehaviour::Mobile,
10906 nsViewportInfo::AutoScaleFlag::AutoScale);
10909 // In cases where the width of the CSS viewport is less than or equal to the
10910 // width of the display (i.e. width <= device-width) then we disable
10911 // double-tap-to-zoom behaviour. See bug 941995 for details.
10913 switch (mViewportType) {
10914 case DisplayWidthHeight:
10915 return nsViewportInfo(aDisplaySize, defaultScale,
10916 nsViewportInfo::ZoomFlag::AllowZoom,
10917 nsViewportInfo::ZoomBehaviour::Mobile);
10918 case Unknown: {
10919 // We might early exit if the viewport is empty. Even if we don't,
10920 // at the end of this case we'll note that it was empty. Later, when
10921 // we're using the cached values, this will trigger alternate code paths.
10922 if (!mLastModifiedViewportMetaData) {
10923 // If the docType specifies that we are on a site optimized for mobile,
10924 // then we want to return specially crafted defaults for the viewport
10925 // info.
10926 if (RefPtr<DocumentType> docType = GetDoctype()) {
10927 nsAutoString docId;
10928 docType->GetPublicId(docId);
10929 if ((docId.Find(u"WAP") != -1) || (docId.Find(u"Mobile") != -1) ||
10930 (docId.Find(u"WML") != -1)) {
10931 // We're making an assumption that the docType can't change here
10932 mViewportType = DisplayWidthHeight;
10933 return nsViewportInfo(aDisplaySize, defaultScale,
10934 nsViewportInfo::ZoomFlag::AllowZoom,
10935 nsViewportInfo::ZoomBehaviour::Mobile);
10939 nsAutoString handheldFriendly;
10940 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
10941 if (handheldFriendly.EqualsLiteral("true")) {
10942 mViewportType = DisplayWidthHeight;
10943 return nsViewportInfo(aDisplaySize, defaultScale,
10944 nsViewportInfo::ZoomFlag::AllowZoom,
10945 nsViewportInfo::ZoomBehaviour::Mobile);
10949 ViewportMetaData metaData = GetViewportMetaData();
10951 // Parse initial-scale, minimum-scale and maximum-scale.
10952 ParseScalesInViewportMetaData(metaData);
10954 // Parse width and height properties
10955 // This function sets m{Min,Max}{Width,Height}.
10956 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
10957 mValidScaleFloat);
10959 mAllowZoom = true;
10960 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
10961 (metaData.mUserScalable.EqualsLiteral("no")) ||
10962 (metaData.mUserScalable.EqualsLiteral("false"))) {
10963 mAllowZoom = false;
10966 // Resolve viewport-fit value.
10967 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
10968 mViewportFit = ViewportFitType::Auto;
10969 if (!metaData.mViewportFit.IsEmpty()) {
10970 if (metaData.mViewportFit.EqualsLiteral("contain")) {
10971 mViewportFit = ViewportFitType::Contain;
10972 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
10973 mViewportFit = ViewportFitType::Cover;
10977 mWidthStrEmpty = metaData.mWidth.IsEmpty();
10979 mViewportType = Specified;
10980 [[fallthrough]];
10982 case Specified:
10983 default:
10984 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
10985 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
10986 bool effectiveValidMaxScale = mValidMaxScale;
10988 nsViewportInfo::ZoomFlag effectiveZoomFlag =
10989 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
10990 : nsViewportInfo::ZoomFlag::DisallowZoom;
10991 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
10992 // If the pref to force user-scalable is enabled, we ignore the values
10993 // from the meta-viewport tag for these properties and just assume they
10994 // allow the page to be scalable. Note in particular that this code is
10995 // in the "Specified" branch of the enclosing switch statement, so that
10996 // calls to GetViewportInfo always use the latest value of the
10997 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
10998 // return nsViewportInfo instances are all consistent with
10999 // browser_ui_zoom_force_user_scalable() already.
11000 effectiveMinScale = ViewportMinScale();
11001 effectiveMaxScale = ViewportMaxScale();
11002 effectiveValidMaxScale = true;
11003 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
11006 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
11007 auto ComputeExtendZoom = [&]() -> float {
11008 if (mValidScaleFloat && effectiveValidMaxScale) {
11009 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
11011 if (mValidScaleFloat) {
11012 return mScaleFloat.scale;
11014 if (effectiveValidMaxScale) {
11015 return effectiveMaxScale.scale;
11017 return nsViewportInfo::kAuto;
11020 // Resolving 'extend-to-zoom'
11021 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
11022 float extendZoom = ComputeExtendZoom();
11024 CSSCoord minWidth = mMinWidth;
11025 CSSCoord maxWidth = mMaxWidth;
11026 CSSCoord minHeight = mMinHeight;
11027 CSSCoord maxHeight = mMaxHeight;
11029 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
11030 // viewport size. We need to use this scaled size for any clamping of
11031 // width or height.
11032 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
11034 // Our min and max width and height values are mostly as specified by
11035 // the viewport declaration, but we make an exception for max width.
11036 // Max width, if auto, and if there's no initial-scale, will be set
11037 // to a default size. This is to support legacy site design with no
11038 // viewport declaration, and to do that using the same scheme as
11039 // Chrome does, in order to maintain web compatibility. Since the
11040 // default size has a complicated calculation, we fixup the maxWidth
11041 // value after setting it, above.
11042 if (maxWidth == nsViewportInfo::kAuto && !mValidScaleFloat) {
11043 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
11044 if (inRDM &&
11045 bc->TouchEventsOverride() == TouchEventsOverride::Enabled) {
11046 // If RDM and touch simulation are active, then use the simulated
11047 // screen width to accommodate for cases where the screen width is
11048 // larger than the desktop viewport default.
11049 maxWidth = nsViewportInfo::Max(displaySize.width, maxWidth);
11051 // Divide by fullZoom to stretch CSS pixel size of viewport in order
11052 // to keep device pixel size unchanged after full zoom applied.
11053 // See bug 974242.
11054 maxWidth /= fullZoom;
11056 // We set minWidth to ExtendToZoom, which will cause our later width
11057 // calculation to expand to maxWidth, if scale restrictions allow it.
11058 minWidth = nsViewportInfo::kExtendToZoom;
11061 // Resolve device-width and device-height first.
11062 if (maxWidth == nsViewportInfo::kDeviceSize) {
11063 maxWidth = displaySize.width;
11065 if (maxHeight == nsViewportInfo::kDeviceSize) {
11066 maxHeight = displaySize.height;
11068 if (extendZoom == nsViewportInfo::kAuto) {
11069 if (maxWidth == nsViewportInfo::kExtendToZoom) {
11070 maxWidth = nsViewportInfo::kAuto;
11072 if (maxHeight == nsViewportInfo::kExtendToZoom) {
11073 maxHeight = nsViewportInfo::kAuto;
11075 if (minWidth == nsViewportInfo::kExtendToZoom) {
11076 minWidth = maxWidth;
11078 if (minHeight == nsViewportInfo::kExtendToZoom) {
11079 minHeight = maxHeight;
11081 } else {
11082 CSSSize extendSize = displaySize / extendZoom;
11083 if (maxWidth == nsViewportInfo::kExtendToZoom) {
11084 maxWidth = extendSize.width;
11086 if (maxHeight == nsViewportInfo::kExtendToZoom) {
11087 maxHeight = extendSize.height;
11089 if (minWidth == nsViewportInfo::kExtendToZoom) {
11090 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
11092 if (minHeight == nsViewportInfo::kExtendToZoom) {
11093 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
11097 // Resolve initial width and height from min/max descriptors
11098 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
11099 CSSCoord width = nsViewportInfo::kAuto;
11100 if (minWidth != nsViewportInfo::kAuto ||
11101 maxWidth != nsViewportInfo::kAuto) {
11102 width = nsViewportInfo::Max(
11103 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
11105 CSSCoord height = nsViewportInfo::kAuto;
11106 if (minHeight != nsViewportInfo::kAuto ||
11107 maxHeight != nsViewportInfo::kAuto) {
11108 height = nsViewportInfo::Max(
11109 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
11112 // Resolve width value
11113 // https://drafts.csswg.org/css-device-adapt/#resolve-width
11114 if (width == nsViewportInfo::kAuto) {
11115 if (height == nsViewportInfo::kAuto || aDisplaySize.height == 0) {
11116 width = displaySize.width;
11117 } else {
11118 width = height * aDisplaySize.width / aDisplaySize.height;
11122 // Resolve height value
11123 // https://drafts.csswg.org/css-device-adapt/#resolve-height
11124 if (height == nsViewportInfo::kAuto) {
11125 if (aDisplaySize.width == 0) {
11126 height = displaySize.height;
11127 } else {
11128 height = width * aDisplaySize.height / aDisplaySize.width;
11131 MOZ_ASSERT(width != nsViewportInfo::kAuto &&
11132 height != nsViewportInfo::kAuto);
11134 CSSSize size(width, height);
11136 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
11137 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
11138 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
11140 nsViewportInfo::AutoSizeFlag sizeFlag =
11141 nsViewportInfo::AutoSizeFlag::FixedSize;
11142 if (mMaxWidth == nsViewportInfo::kDeviceSize ||
11143 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::kDeviceSize ||
11144 mScaleFloat.scale == 1.0f)) ||
11145 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::kAuto &&
11146 mMaxHeight < 0)) {
11147 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
11150 // FIXME: Resolving width and height should be done above 'Resolve width
11151 // value' and 'Resolve height value'.
11152 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
11153 size = displaySize;
11156 // The purpose of clamping the viewport width to a minimum size is to
11157 // prevent page authors from setting it to a ridiculously small value.
11158 // If the page is actually being rendered in a very small area (as might
11159 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
11160 // prevent the viewport from taking on that size.
11161 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
11163 size.width = std::clamp(size.width, effectiveMinSize.width,
11164 float(kViewportMaxSize.width));
11166 // Also recalculate the default zoom, if it wasn't specified in the
11167 // metadata, and the width is specified.
11168 if (!mValidScaleFloat && !mWidthStrEmpty) {
11169 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
11170 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
11173 size.height = std::clamp(size.height, effectiveMinSize.height,
11174 float(kViewportMaxSize.height));
11176 // In cases of user-scalable=no, if we have a positive scale, clamp it to
11177 // min and max, and then use the clamped value for the scale, the min, and
11178 // the max. If we don't have a positive scale, assert that we are setting
11179 // the auto scale flag.
11180 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
11181 scaleFloat > CSSToScreenScale(0.0f)) {
11182 scaleFloat = scaleMinFloat = scaleMaxFloat =
11183 std::clamp(scaleFloat, scaleMinFloat, scaleMaxFloat);
11185 MOZ_ASSERT(
11186 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
11187 "If we don't have a positive scale, we should be using auto scale.");
11189 // We need to perform a conversion, but only if the initial or maximum
11190 // scale were set explicitly by the user.
11191 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
11192 scaleFloat <= scaleMaxFloat) {
11193 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
11194 size.width = std::max(size.width, displaySize.width);
11195 size.height = std::max(size.height, displaySize.height);
11196 } else if (effectiveValidMaxScale) {
11197 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
11198 size.width = std::max(size.width, displaySize.width);
11199 size.height = std::max(size.height, displaySize.height);
11202 return nsViewportInfo(
11203 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
11204 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
11205 : nsViewportInfo::AutoScaleFlag::AutoScale,
11206 effectiveZoomFlag, mViewportFit);
11210 ViewportMetaData Document::GetViewportMetaData() const {
11211 return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
11212 : ViewportMetaData();
11215 static InteractiveWidget ParseInteractiveWidget(
11216 const ViewportMetaData& aViewportMetaData) {
11217 if (aViewportMetaData.mInteractiveWidgetMode.IsEmpty()) {
11218 return InteractiveWidgetUtils::DefaultInteractiveWidgetMode();
11221 if (aViewportMetaData.mInteractiveWidgetMode.EqualsIgnoreCase(
11222 "resizes-visual")) {
11223 return InteractiveWidget::ResizesVisual;
11225 if (aViewportMetaData.mInteractiveWidgetMode.EqualsIgnoreCase(
11226 "resizes-content")) {
11227 return InteractiveWidget::ResizesContent;
11229 if (aViewportMetaData.mInteractiveWidgetMode.EqualsIgnoreCase(
11230 "overlays-content")) {
11231 return InteractiveWidget::OverlaysContent;
11233 return InteractiveWidgetUtils::DefaultInteractiveWidgetMode();
11236 void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
11237 mLastModifiedViewportMetaData = std::move(aData);
11238 // Trigger recomputation of the nsViewportInfo the next time it's queried.
11239 mViewportType = Unknown;
11241 // Parse interactive-widget here anyway. Normally we parse any data in the
11242 // meta viewport tag in GetViewportInfo(), but GetViewportInfo() depends on
11243 // the document state (e.g. display size, fullscreen, desktop-mode etc.)
11244 // whereas interactive-widget is independent from the document state, it's
11245 // necessary whatever the document state is.
11246 dom::InteractiveWidget interactiveWidget =
11247 ParseInteractiveWidget(*mLastModifiedViewportMetaData);
11248 if (mInteractiveWidgetMode != interactiveWidget) {
11249 mInteractiveWidgetMode = interactiveWidget;
11252 AsyncEventDispatcher::RunDOMEventWhenSafe(
11253 *this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes,
11254 ChromeOnlyDispatch::eYes);
11257 EventListenerManager* Document::GetOrCreateListenerManager() {
11258 if (!mListenerManager) {
11259 mListenerManager =
11260 new EventListenerManager(static_cast<EventTarget*>(this));
11261 SetFlags(NODE_HAS_LISTENERMANAGER);
11264 return mListenerManager;
11267 EventListenerManager* Document::GetExistingListenerManager() const {
11268 return mListenerManager;
11271 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
11272 aVisitor.mCanHandle = true;
11273 // FIXME! This is a hack to make middle mouse paste working also in Editor.
11274 // Bug 329119
11275 aVisitor.mForceContentDispatch = true;
11277 // Load events must not propagate to |window| object, see bug 335251.
11278 if (aVisitor.mEvent->mMessage != eLoad) {
11279 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
11280 aVisitor.SetParentTarget(
11281 window ? window->GetTargetForEventTargetChain() : nullptr, false);
11285 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
11286 CallerType aCallerType,
11287 ErrorResult& rv) const {
11288 nsPresContext* presContext = GetPresContext();
11290 // Create event even without presContext.
11291 RefPtr<Event> ev =
11292 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
11293 nullptr, aEventType, aCallerType);
11294 if (!ev) {
11295 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
11296 return nullptr;
11298 WidgetEvent* e = ev->WidgetEventPtr();
11299 e->mFlags.mBubbles = false;
11300 e->mFlags.mCancelable = false;
11301 return ev.forget();
11304 void Document::FlushPendingNotifications(FlushType aType) {
11305 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
11306 FlushPendingNotifications(flush);
11309 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
11310 FlushType flushType = aFlush.mFlushType;
11312 RefPtr<Document> documentOnStack = this;
11314 // We need to flush the sink for non-HTML documents (because the XML
11315 // parser still does insertion with deferred notifications). We
11316 // also need to flush the sink if this is a layout-related flush, to
11317 // make sure that layout is started as needed. But we can skip that
11318 // part if we have no presshell or if it's already done an initial
11319 // reflow.
11320 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
11321 mPresShell && !mPresShell->DidInitialize())) &&
11322 (mParser || mWeakSink)) {
11323 nsCOMPtr<nsIContentSink> sink;
11324 if (mParser) {
11325 sink = mParser->GetContentSink();
11326 } else {
11327 sink = do_QueryReferent(mWeakSink);
11328 if (!sink) {
11329 mWeakSink = nullptr;
11332 // Determine if it is safe to flush the sink notifications
11333 // by determining if it safe to flush all the presshells.
11334 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
11335 sink->FlushPendingNotifications(flushType);
11339 // Should we be flushing pending binding constructors in here?
11341 if (flushType <= FlushType::ContentAndNotify) {
11342 // Nothing to do here
11343 return;
11346 // If we have a parent we must flush the parent too to ensure that our
11347 // container is reflowed if its size was changed.
11349 // We do it only if the subdocument and the parent can observe each other
11350 // synchronously (that is, if we're not cross-origin), to avoid work that is
11351 // not observable, and if the parent document has finished loading all its
11352 // render-blocking stylesheets and may start laying out the document, to avoid
11353 // unnecessary flashes of unstyled content on the parent document. Note that
11354 // this last bit means that size-dependent media queries in this document may
11355 // produce incorrect results temporarily.
11357 // But if it's not safe to flush ourselves, then don't flush the parent, since
11358 // that can cause things like resizes of our frame's widget, which we can't
11359 // handle while flushing is unsafe.
11360 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
11361 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
11362 ChangesToFlush parentFlush = aFlush;
11363 if (flushType >= FlushType::Style) {
11364 // Since media queries mean that a size change of our container can affect
11365 // style, we need to promote a style flush on ourself to a layout flush on
11366 // our parent, since we need our container to be the correct size to
11367 // determine the correct style.
11368 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
11370 mParentDocument->FlushPendingNotifications(parentFlush);
11373 if (RefPtr<PresShell> presShell = GetPresShell()) {
11374 presShell->FlushPendingNotifications(aFlush);
11378 void Document::FlushExternalResources(FlushType aType) {
11379 NS_ASSERTION(
11380 aType >= FlushType::Style,
11381 "should only need to flush for style or higher in external resources");
11382 if (GetDisplayDocument()) {
11383 return;
11386 EnumerateExternalResources([aType](Document& aDoc) {
11387 aDoc.FlushPendingNotifications(aType);
11388 return CallState::Continue;
11392 void Document::SetXMLDeclaration(const char16_t* aVersion,
11393 const char16_t* aEncoding,
11394 const int32_t aStandalone) {
11395 if (!aVersion || *aVersion == '\0') {
11396 mXMLDeclarationBits = 0;
11397 return;
11400 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
11402 if (aEncoding && *aEncoding != '\0') {
11403 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
11406 if (aStandalone == 1) {
11407 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
11408 XML_DECLARATION_BITS_STANDALONE_YES;
11409 } else if (aStandalone == 0) {
11410 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
11414 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
11415 nsAString& aStandalone) {
11416 aVersion.Truncate();
11417 aEncoding.Truncate();
11418 aStandalone.Truncate();
11420 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
11421 return;
11424 // always until we start supporting 1.1 etc.
11425 aVersion.AssignLiteral("1.0");
11427 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
11428 // This is what we have stored, not necessarily what was written
11429 // in the original
11430 GetCharacterSet(aEncoding);
11433 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
11434 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
11435 aStandalone.AssignLiteral("yes");
11436 } else {
11437 aStandalone.AssignLiteral("no");
11442 void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
11443 mColorSchemeMetaTags.Insert(aMeta);
11444 RecomputeColorScheme();
11447 void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
11448 mColorSchemeMetaTags.RemoveElement(aMeta);
11449 RecomputeColorScheme();
11452 void Document::RecomputeColorScheme() {
11453 auto oldColorScheme = mColorSchemeBits;
11454 mColorSchemeBits = 0;
11455 const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
11456 for (const HTMLMetaElement* el : elements) {
11457 nsAutoString content;
11458 if (!el->GetAttr(nsGkAtoms::content, content)) {
11459 continue;
11462 NS_ConvertUTF16toUTF8 contentU8(content);
11463 if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
11464 break;
11468 if (mColorSchemeBits == oldColorScheme) {
11469 return;
11472 if (nsPresContext* pc = GetPresContext()) {
11473 // This affects system colors, which are inherited, so we need to recascade.
11474 pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
11478 bool Document::IsScriptEnabled() const {
11479 // If this document is sandboxed without 'allow-scripts'
11480 // script is not enabled
11481 if (HasScriptsBlockedBySandbox()) {
11482 return false;
11485 nsCOMPtr<nsIScriptGlobalObject> globalObject =
11486 do_QueryInterface(GetInnerWindow());
11487 if (!globalObject || !globalObject->HasJSGlobal()) {
11488 return false;
11491 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
11492 .Allowed();
11495 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
11496 PRTime modDate = 0;
11497 nsresult rv;
11499 nsCOMPtr<nsIHttpChannel> httpChannel;
11500 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
11501 if (NS_WARN_IF(NS_FAILED(rv))) {
11502 return;
11505 if (httpChannel) {
11506 nsAutoCString tmp;
11507 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
11509 if (NS_SUCCEEDED(rv)) {
11510 PRTime time;
11511 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
11512 if (st == PR_SUCCESS) {
11513 modDate = time;
11517 static const char* const headers[] = {
11518 "default-style", "content-style-type", "content-language",
11519 "content-disposition", "refresh", "x-dns-prefetch-control",
11520 "x-frame-options", "origin-trial",
11521 // add more http headers if you need
11522 // XXXbz don't add content-location support without reading bug
11523 // 238654 and its dependencies/dups first.
11526 nsAutoCString headerVal;
11527 const char* const* name = headers;
11528 while (*name) {
11529 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
11530 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
11531 RefPtr<nsAtom> key = NS_Atomize(*name);
11532 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
11534 ++name;
11536 } else {
11537 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
11538 if (fileChannel) {
11539 nsCOMPtr<nsIFile> file;
11540 fileChannel->GetFile(getter_AddRefs(file));
11541 if (file) {
11542 PRTime msecs;
11543 rv = file->GetLastModifiedTime(&msecs);
11545 if (NS_SUCCEEDED(rv)) {
11546 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
11549 } else {
11550 nsAutoCString contentDisp;
11551 rv = aChannel->GetContentDispositionHeader(contentDisp);
11552 if (NS_SUCCEEDED(rv)) {
11553 SetHeaderData(nsGkAtoms::headerContentDisposition,
11554 NS_ConvertASCIItoUTF16(contentDisp));
11559 mLastModified.Truncate();
11560 if (modDate != 0) {
11561 GetFormattedTimeString(modDate,
11562 ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
11563 mLastModified);
11567 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
11568 // set any HTTP-EQUIV data into document's header data as well as url
11569 nsAutoString header;
11570 aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
11571 if (!header.IsEmpty()) {
11572 // Ignore META REFRESH when document is sandboxed from automatic features.
11573 nsContentUtils::ASCIIToLower(header);
11574 if (nsGkAtoms::refresh->Equals(header) &&
11575 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
11576 return;
11579 nsAutoString result;
11580 aMetaElement->GetAttr(nsGkAtoms::content, result);
11581 if (!result.IsEmpty()) {
11582 RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
11583 SetHeaderData(fieldAtom, result);
11587 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
11588 nsGkAtoms::handheldFriendly, eIgnoreCase)) {
11589 nsAutoString result;
11590 aMetaElement->GetAttr(nsGkAtoms::content, result);
11591 if (!result.IsEmpty()) {
11592 nsContentUtils::ASCIIToLower(result);
11593 SetHeaderData(nsGkAtoms::handheldFriendly, result);
11598 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
11599 nsAtom* aPrefix,
11600 int32_t aNamespaceID,
11601 const nsAString* aIs) {
11602 #ifdef DEBUG
11603 nsAutoString qName;
11604 if (aPrefix) {
11605 aPrefix->ToString(qName);
11606 qName.Append(':');
11608 qName.Append(aName);
11610 // Note: "a:b:c" is a valid name in non-namespaces XML, and
11611 // Document::CreateElement can call us with such a name and no prefix,
11612 // which would cause an error if we just used true here.
11613 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
11614 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
11615 "Don't pass invalid prefixes to Document::CreateElem, "
11616 "check caller.");
11617 #endif
11619 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
11620 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
11621 getter_AddRefs(nodeInfo));
11622 NS_ENSURE_TRUE(nodeInfo, nullptr);
11624 nsCOMPtr<Element> element;
11625 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
11626 NOT_FROM_PARSER, aIs);
11627 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
11630 bool Document::IsSafeToFlush() const {
11631 PresShell* presShell = GetPresShell();
11632 if (!presShell) {
11633 return true;
11635 return presShell->IsSafeToFlush();
11638 void Document::Sanitize() {
11639 // Sanitize the document by resetting all (current and former) password fields
11640 // and any form fields with autocomplete=off to their default values. We do
11641 // this now, instead of when the presentation is restored, to offer some
11642 // protection in case there is ever an exploit that allows a cached document
11643 // to be accessed from a different document.
11645 // First locate all input elements, regardless of whether they are
11646 // in a form, and reset the password and autocomplete=off elements.
11648 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
11650 nsAutoString value;
11652 uint32_t length = nodes->Length(true);
11653 for (uint32_t i = 0; i < length; ++i) {
11654 NS_ASSERTION(nodes->Item(i), "null item in node list!");
11656 RefPtr<HTMLInputElement> input =
11657 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
11658 if (!input) continue;
11660 input->GetAttr(nsGkAtoms::autocomplete, value);
11661 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
11662 input->Reset();
11666 // Now locate all _form_ elements that have autocomplete=off and reset them
11667 nodes = GetElementsByTagName(u"form"_ns);
11669 length = nodes->Length(true);
11670 for (uint32_t i = 0; i < length; ++i) {
11671 // Reset() may change the list dynamically.
11672 RefPtr<HTMLFormElement> form =
11673 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
11674 if (!form) continue;
11676 form->GetAttr(nsGkAtoms::autocomplete, value);
11677 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
11681 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
11682 if (!mSubDocuments) {
11683 return;
11686 // PLDHashTable::Iterator can't handle modifications while iterating so we
11687 // copy all entries to an array first before calling any callbacks.
11688 AutoTArray<RefPtr<Document>, 8> subdocs;
11689 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11690 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11691 if (Document* subdoc = entry->mSubDocument) {
11692 subdocs.AppendElement(subdoc);
11695 for (auto& subdoc : subdocs) {
11696 if (aCallback(*subdoc) == CallState::Stop) {
11697 break;
11702 void Document::CollectDescendantDocuments(
11703 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
11704 if (!mSubDocuments) {
11705 return;
11708 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11709 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11710 const Document* subdoc = entry->mSubDocument;
11711 if (subdoc) {
11712 if (aCallback(subdoc)) {
11713 aDescendants.AppendElement(entry->mSubDocument);
11715 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
11720 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
11721 uint32_t& aBFCacheCombo,
11722 bool aIncludeSubdocuments,
11723 bool aAllowUnloadListeners) {
11724 bool ret = true;
11726 if (!IsBFCachingAllowed()) {
11727 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
11728 ret = false;
11731 nsAutoCString uri;
11732 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11733 if (mDocumentURI) {
11734 mDocumentURI->GetSpec(uri);
11738 if (EventHandlingSuppressed()) {
11739 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11740 ("Save of %s blocked on event handling suppression", uri.get()));
11741 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
11742 ret = false;
11745 // Do not allow suspended windows to be placed in the
11746 // bfcache. This method is also used to verify a document
11747 // coming out of the bfcache is ok to restore, though. So
11748 // we only want to block suspend windows that aren't also
11749 // frozen.
11750 nsPIDOMWindowInner* win = GetInnerWindow();
11751 if (win && win->IsSuspended() && !win->IsFrozen()) {
11752 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11753 ("Save of %s blocked on suspended Window", uri.get()));
11754 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
11755 ret = false;
11758 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
11759 bool thirdParty = false;
11760 // Currently some other mobile browsers seem to bfcache only cross-domain
11761 // pages, but bfcache those also when there are unload event listeners, so
11762 // this is trying to match that behavior as much as possible.
11763 bool allowUnloadListeners =
11764 aAllowUnloadListeners &&
11765 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
11766 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
11767 channel, &thirdParty)) &&
11768 thirdParty));
11770 // Check our event listener manager for unload/beforeunload listeners.
11771 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
11772 if (!allowUnloadListeners && piTarget) {
11773 EventListenerManager* manager = piTarget->GetExistingListenerManager();
11774 if (manager) {
11775 if (manager->HasUnloadListeners()) {
11776 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11777 ("Save of %s blocked due to unload handlers", uri.get()));
11778 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
11779 ret = false;
11781 if (manager->HasBeforeUnloadListeners()) {
11782 if (!mozilla::SessionHistoryInParent() ||
11783 !StaticPrefs::
11784 docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
11785 MOZ_LOG(
11786 gPageCacheLog, mozilla::LogLevel::Verbose,
11787 ("Save of %s blocked due to beforeUnload handlers", uri.get()));
11788 aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
11789 ret = false;
11795 // Check if we have pending network requests
11796 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11797 if (loadGroup) {
11798 nsCOMPtr<nsISimpleEnumerator> requests;
11799 loadGroup->GetRequests(getter_AddRefs(requests));
11801 bool hasMore = false;
11803 // We want to bail out if we have any requests other than aNewRequest (or
11804 // in the case when aNewRequest is a part of a multipart response the base
11805 // channel the multipart response is coming in on).
11806 nsCOMPtr<nsIChannel> baseChannel;
11807 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
11808 if (part) {
11809 part->GetBaseChannel(getter_AddRefs(baseChannel));
11812 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11813 nsCOMPtr<nsISupports> elem;
11814 requests->GetNext(getter_AddRefs(elem));
11816 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11817 if (request && request != aNewRequest && request != baseChannel) {
11818 // Favicon loads don't need to block caching.
11819 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
11820 if (channel) {
11821 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
11822 if (li->InternalContentPolicyType() ==
11823 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
11824 continue;
11828 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11829 nsAutoCString requestName;
11830 request->GetName(requestName);
11831 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
11832 ("Save of %s blocked because document has request %s",
11833 uri.get(), requestName.get()));
11835 aBFCacheCombo |= BFCacheStatus::REQUEST;
11836 ret = false;
11841 // Check if we have active GetUserMedia use
11842 if (MediaManager::Exists() && win &&
11843 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
11844 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11845 ("Save of %s blocked due to GetUserMedia", uri.get()));
11846 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
11847 ret = false;
11850 #ifdef MOZ_WEBRTC
11851 // Check if we have active PeerConnections
11852 if (win && win->HasActivePeerConnections()) {
11853 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11854 ("Save of %s blocked due to PeerConnection", uri.get()));
11855 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
11856 ret = false;
11858 #endif // MOZ_WEBRTC
11860 // Don't save presentations for documents containing EME content, so that
11861 // CDMs reliably shutdown upon user navigation.
11862 if (ContainsEMEContent()) {
11863 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
11864 ret = false;
11867 // Don't save presentations for documents containing MSE content, to
11868 // reduce memory usage.
11869 if (ContainsMSEContent()) {
11870 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11871 ("Save of %s blocked due to MSE use", uri.get()));
11872 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
11873 ret = false;
11876 if (aIncludeSubdocuments && mSubDocuments) {
11877 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11878 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11879 Document* subdoc = entry->mSubDocument;
11881 uint32_t subDocBFCacheCombo = 0;
11882 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
11883 bool canCache =
11884 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
11885 true, allowUnloadListeners)
11886 : false;
11887 if (!canCache) {
11888 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11889 ("Save of %s blocked due to subdocument blocked", uri.get()));
11890 aBFCacheCombo |= subDocBFCacheCombo;
11891 ret = false;
11896 if (!mozilla::BFCacheInParent()) {
11897 // BFCache is currently not compatible with remote subframes (bug 1609324)
11898 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
11899 for (auto& child : browsingContext->Children()) {
11900 if (!child->IsInProcess()) {
11901 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
11902 ret = false;
11903 break;
11909 if (win) {
11910 auto* globalWindow = nsGlobalWindowInner::Cast(win);
11911 #ifdef MOZ_WEBSPEECH
11912 if (globalWindow->HasActiveSpeechSynthesis()) {
11913 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11914 ("Save of %s blocked due to Speech use", uri.get()));
11915 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
11916 ret = false;
11918 #endif
11919 if (globalWindow->HasUsedVR()) {
11920 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11921 ("Save of %s blocked due to having used VR", uri.get()));
11922 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
11923 ret = false;
11926 if (win->HasActiveLocks()) {
11927 MOZ_LOG(
11928 gPageCacheLog, mozilla::LogLevel::Verbose,
11929 ("Save of %s blocked due to having active lock requests", uri.get()));
11930 aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
11931 ret = false;
11934 if (win->HasActiveWebTransports()) {
11935 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11936 ("Save of %s blocked due to WebTransport", uri.get()));
11937 aBFCacheCombo |= BFCacheStatus::ACTIVE_WEBTRANSPORT;
11938 ret = false;
11942 return ret;
11945 void Document::Destroy() {
11946 // The DocumentViewer wants to release the document now. So, tell our content
11947 // to drop any references to the document so that it can be destroyed.
11948 if (mIsGoingAway) {
11949 return;
11952 if (RefPtr transition = mActiveViewTransition) {
11953 transition->SkipTransition(SkipTransitionReason::DocumentHidden);
11956 ReportDocumentUseCounters();
11957 ReportLCP();
11958 SetDevToolsWatchingDOMMutations(false);
11960 mIsGoingAway = true;
11962 ScriptLoader()->Destroy();
11963 SetScriptGlobalObject(nullptr);
11964 RemovedFromDocShell();
11966 bool oldVal = mInUnlinkOrDeletion;
11967 mInUnlinkOrDeletion = true;
11969 #ifdef DEBUG
11970 uint32_t oldChildCount = GetChildCount();
11971 #endif
11973 for (nsIContent* child = GetFirstChild(); child;
11974 child = child->GetNextSibling()) {
11975 child->DestroyContent();
11976 MOZ_ASSERT(child->GetParentNode() == this);
11978 MOZ_ASSERT(oldChildCount == GetChildCount());
11979 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
11981 mInUnlinkOrDeletion = oldVal;
11983 mLayoutHistoryState = nullptr;
11985 if (mOriginalDocument) {
11986 mOriginalDocument->mLatestStaticClone = nullptr;
11989 if (IsStaticDocument()) {
11990 RemoveProperty(nsGkAtoms::printisfocuseddoc);
11991 RemoveProperty(nsGkAtoms::printselectionranges);
11994 // Shut down our external resource map. We might not need this for
11995 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
11996 // tearing down all those frame trees right now is the right thing to do.
11997 mExternalResourceMap.Shutdown();
11999 // Manually break cycles via promise's global object pointer.
12000 mReadyForIdle = nullptr;
12001 mOrientationPendingPromise = nullptr;
12003 // To break cycles.
12004 mPreloadService.ClearAllPreloads();
12006 if (mDocumentL10n) {
12007 mDocumentL10n->Destroy();
12010 if (!mPresShell) {
12011 DropStyleSet();
12015 void Document::RemovedFromDocShell() {
12016 mEditingState = EditingState::eOff;
12018 if (mRemovedFromDocShell) return;
12020 mRemovedFromDocShell = true;
12021 NotifyActivityChanged();
12023 for (nsIContent* child = GetFirstChild(); child;
12024 child = child->GetNextSibling()) {
12025 child->SaveSubtreeState();
12028 nsIDocShell* docShell = GetDocShell();
12029 if (docShell) {
12030 docShell->SynchronizeLayoutHistoryState();
12034 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
12035 const {
12036 nsCOMPtr<nsILayoutHistoryState> state;
12037 if (!mScriptGlobalObject) {
12038 state = mLayoutHistoryState;
12039 } else {
12040 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
12041 if (docShell) {
12042 docShell->GetLayoutHistoryState(getter_AddRefs(state));
12046 return state.forget();
12049 void Document::EnsureOnloadBlocker() {
12050 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
12051 // -- it's not ours.
12052 if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
12053 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
12054 if (loadGroup) {
12055 // Check first to see if mOnloadBlocker is in the loadgroup.
12056 nsCOMPtr<nsISimpleEnumerator> requests;
12057 loadGroup->GetRequests(getter_AddRefs(requests));
12059 bool hasMore = false;
12060 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
12061 nsCOMPtr<nsISupports> elem;
12062 requests->GetNext(getter_AddRefs(elem));
12063 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
12064 if (request && request == mOnloadBlocker) {
12065 return;
12069 // Not in the loadgroup, so add it.
12070 loadGroup->AddRequest(mOnloadBlocker, nullptr);
12075 void Document::BlockOnload() {
12076 if (mDisplayDocument) {
12077 mDisplayDocument->BlockOnload();
12078 return;
12081 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
12082 // -- it's not ours.
12083 if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
12084 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
12085 loadGroup->AddRequest(mOnloadBlocker, nullptr);
12088 ++mOnloadBlockCount;
12091 void Document::UnblockOnload(bool aFireSync) {
12092 if (mDisplayDocument) {
12093 mDisplayDocument->UnblockOnload(aFireSync);
12094 return;
12097 --mOnloadBlockCount;
12099 if (mOnloadBlockCount == 0) {
12100 if (mScriptGlobalObject) {
12101 // Only manipulate the loadgroup in this case, because if
12102 // mScriptGlobalObject is null, it's not ours.
12103 if (aFireSync) {
12104 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
12105 ++mOnloadBlockCount;
12106 DoUnblockOnload();
12107 } else {
12108 PostUnblockOnloadEvent();
12110 } else if (mIsBeingUsedAsImage) {
12111 // To correctly unblock onload for a document that contains an SVG
12112 // image, we need to know when all of the SVG document's resources are
12113 // done loading, in a way comparable to |window.onload|. We fire this
12114 // event to indicate that the SVG should be considered fully loaded.
12115 // Because scripting is disabled on SVG-as-image documents, this event
12116 // is not accessible to content authors. (See bug 837315.)
12117 RefPtr<AsyncEventDispatcher> asyncDispatcher =
12118 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
12119 CanBubble::eNo, ChromeOnlyDispatch::eNo);
12120 asyncDispatcher->PostDOMEvent();
12125 class nsUnblockOnloadEvent : public Runnable {
12126 public:
12127 explicit nsUnblockOnloadEvent(Document* aDoc)
12128 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
12129 NS_IMETHOD Run() override {
12130 mDoc->DoUnblockOnload();
12131 return NS_OK;
12134 private:
12135 RefPtr<Document> mDoc;
12138 void Document::PostUnblockOnloadEvent() {
12139 MOZ_RELEASE_ASSERT(NS_IsMainThread());
12140 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
12141 nsresult rv = Dispatch(evt.forget());
12142 if (NS_SUCCEEDED(rv)) {
12143 // Stabilize block count so we don't post more events while this one is up
12144 ++mOnloadBlockCount;
12145 } else {
12146 NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
12150 void Document::DoUnblockOnload() {
12151 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
12152 MOZ_ASSERT(mOnloadBlockCount != 0,
12153 "Shouldn't have a count of zero here, since we stabilized in "
12154 "PostUnblockOnloadEvent");
12156 --mOnloadBlockCount;
12158 if (mOnloadBlockCount != 0) {
12159 // We blocked again after the last unblock. Nothing to do here. We'll
12160 // post a new event when we unblock again.
12161 return;
12164 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
12165 // -- it's not ours.
12166 if (mScriptGlobalObject) {
12167 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
12168 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
12173 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
12174 for (nsIFrame* f = aFrame; f;
12175 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
12176 nsIContent* content = f->GetContent();
12177 if (!content) {
12178 continue;
12181 if (content->OwnerDoc() == this) {
12182 return content;
12184 // We must be in a subdocument so jump directly to the root frame.
12185 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
12186 // the containing document.
12187 f = f->PresContext()->GetPresShell()->GetRootFrame();
12190 return nullptr;
12193 void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
12194 const nsAString& aType, bool aInFrameSwap,
12195 bool aPersisted, bool aOnlySystemGroup) {
12196 if (!aDispatchTarget) {
12197 return;
12200 PageTransitionEventInit init;
12201 init.mBubbles = true;
12202 init.mCancelable = true;
12203 init.mPersisted = aPersisted;
12204 init.mInFrameSwap = aInFrameSwap;
12206 RefPtr<PageTransitionEvent> event =
12207 PageTransitionEvent::Constructor(this, aType, init);
12209 event->SetTrusted(true);
12210 event->SetTarget(this);
12211 if (aOnlySystemGroup) {
12212 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
12214 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
12215 nullptr);
12218 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
12219 bool aOnlySystemGroup) {
12220 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
12221 nsCString uri;
12222 if (GetDocumentURI()) {
12223 uri = GetDocumentURI()->GetSpecOrDefault();
12225 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
12226 ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
12229 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
12230 MOZ_DIAGNOSTIC_ASSERT(
12231 inFrameLoaderSwap ==
12232 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
12234 Element* root = GetRootElement();
12235 if (aPersisted && root) {
12236 // Send out notifications that our <link> elements are attached.
12237 RefPtr<nsContentList> links =
12238 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
12240 uint32_t linkCount = links->Length(true);
12241 for (uint32_t i = 0; i < linkCount; ++i) {
12242 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
12246 // See Document
12247 if (!inFrameLoaderSwap) {
12248 if (aPersisted) {
12249 ImageTracker()->SetAnimatingState(true);
12252 // Set mIsShowing before firing events, in case those event handlers
12253 // move us around.
12254 mIsShowing = true;
12255 mVisible = true;
12257 UpdateVisibilityState();
12260 NotifyActivityChanged();
12262 EnumerateExternalResources([aPersisted](Document& aExternalResource) {
12263 aExternalResource.OnPageShow(aPersisted, nullptr);
12264 return CallState::Continue;
12267 if (mAnimationController) {
12268 mAnimationController->OnPageShow();
12271 if (!mIsBeingUsedAsImage) {
12272 // Dispatch observer notification to notify observers page is shown.
12273 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
12274 if (os) {
12275 nsIPrincipal* principal = NodePrincipal();
12276 os->NotifyObservers(ToSupports(this),
12277 principal->IsSystemPrincipal() ? "chrome-page-shown"
12278 : "content-page-shown",
12279 nullptr);
12282 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
12283 if (!target) {
12284 target = do_QueryInterface(GetWindow());
12286 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
12287 aPersisted, aOnlySystemGroup);
12291 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
12292 if (nsPresContext* presContext = aDocument.GetPresContext()) {
12293 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
12294 FullscreenEventType::Change, &aDocument, aTarget);
12295 presContext->RefreshDriver()->ScheduleFullscreenEvent(
12296 std::move(pendingEvent));
12300 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
12301 bool aOnlySystemGroup) {
12302 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
12303 nsCString uri;
12304 if (GetDocumentURI()) {
12305 uri = GetDocumentURI()->GetSpecOrDefault();
12307 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
12308 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
12311 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
12312 MOZ_DIAGNOSTIC_ASSERT(
12313 inFrameLoaderSwap ==
12314 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
12316 if (mAnimationController) {
12317 mAnimationController->OnPageHide();
12320 if (!inFrameLoaderSwap) {
12321 if (aPersisted) {
12322 // We do not stop the animations (bug 1024343) when the page is refreshing
12323 // while being dragged out.
12324 ImageTracker()->SetAnimatingState(false);
12327 // Set mIsShowing before firing events, in case those event handlers
12328 // move us around.
12329 mIsShowing = false;
12330 mVisible = false;
12333 PointerLockManager::Unlock("Document::OnPageHide", this);
12335 if (!mIsBeingUsedAsImage) {
12336 // Dispatch observer notification to notify observers page is hidden.
12337 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
12338 if (os) {
12339 nsIPrincipal* principal = NodePrincipal();
12340 os->NotifyObservers(ToSupports(this),
12341 principal->IsSystemPrincipal()
12342 ? "chrome-page-hidden"
12343 : "content-page-hidden",
12344 nullptr);
12347 // Now send out a PageHide event.
12348 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
12349 if (!target) {
12350 target = do_QueryInterface(GetWindow());
12353 PageUnloadingEventTimeStamp timeStamp(this);
12354 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
12355 aPersisted, aOnlySystemGroup);
12359 if (!inFrameLoaderSwap) {
12360 UpdateVisibilityState();
12363 EnumerateExternalResources([aPersisted](Document& aExternalResource) {
12364 aExternalResource.OnPageHide(aPersisted, nullptr);
12365 return CallState::Continue;
12367 NotifyActivityChanged();
12369 ClearPendingFullscreenRequests(this);
12370 if (Fullscreen()) {
12371 // If this document was fullscreen, we should exit fullscreen in this
12372 // doctree branch. This ensures that if the user navigates while in
12373 // fullscreen mode we don't leave its still visible ancestor documents
12374 // in fullscreen mode. So exit fullscreen in the document's fullscreen
12375 // root document, as this will exit fullscreen in all the root's
12376 // descendant documents. Note that documents are removed from the
12377 // doctree by the time OnPageHide() is called, so we must store a
12378 // reference to the root (in Document::mFullscreenRoot) since we can't
12379 // just traverse the doctree to get the root.
12380 Document::ExitFullscreenInDocTree(this);
12382 // Since the document is removed from the doctree before OnPageHide() is
12383 // called, ExitFullscreen() can't traverse from the root down to *this*
12384 // document, so we must manually call CleanupFullscreenState() below too.
12385 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
12386 // so we *must* call it after ExitFullscreen(), not before.
12387 // OnPageHide() is called in every hidden (i.e. descendant) document,
12388 // so calling CleanupFullscreenState() here will ensure all hidden
12389 // documents have their fullscreen state reset.
12390 CleanupFullscreenState();
12392 // The fullscreenchange event is to be queued in the refresh driver,
12393 // however a hidden page wouldn't trigger that again, so it makes no
12394 // sense to dispatch such event here.
12398 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
12399 NS_ASSERTION(
12400 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
12401 "mSubtreeModifiedTargets not cleared after dispatching?");
12402 ++mSubtreeModifiedDepth;
12403 if (aTarget) {
12404 // MayDispatchMutationEvent is often called just before this method,
12405 // so it has already appended the node to mSubtreeModifiedTargets.
12406 int32_t count = mSubtreeModifiedTargets.Count();
12407 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
12408 mSubtreeModifiedTargets.AppendObject(aTarget);
12413 void Document::MutationEventDispatched(nsINode* aTarget) {
12414 if (--mSubtreeModifiedDepth) {
12415 return;
12418 int32_t count = mSubtreeModifiedTargets.Count();
12419 if (!count) {
12420 return;
12423 nsPIDOMWindowInner* window = GetInnerWindow();
12424 if (window &&
12425 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
12426 mSubtreeModifiedTargets.Clear();
12427 return;
12430 nsCOMArray<nsINode> realTargets;
12431 for (nsINode* possibleTarget : mSubtreeModifiedTargets) {
12432 if (possibleTarget->ChromeOnlyAccess()) {
12433 continue;
12436 nsINode* commonAncestor = nullptr;
12437 int32_t realTargetCount = realTargets.Count();
12438 for (int32_t j = 0; j < realTargetCount; ++j) {
12439 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
12440 possibleTarget, realTargets[j]);
12441 if (commonAncestor) {
12442 realTargets.ReplaceObjectAt(commonAncestor, j);
12443 break;
12446 if (!commonAncestor) {
12447 realTargets.AppendObject(possibleTarget);
12451 mSubtreeModifiedTargets.Clear();
12453 for (const nsCOMPtr<nsINode>& target : realTargets) {
12454 InternalMutationEvent mutation(true, eLegacySubtreeModified);
12455 // MOZ_KnownLive due to bug 1620312
12456 AsyncEventDispatcher::RunDOMEventWhenSafe(MOZ_KnownLive(*target), mutation);
12460 void Document::DestroyElementMaps() {
12461 #ifdef DEBUG
12462 mStyledLinksCleared = true;
12463 #endif
12464 mStyledLinks.Clear();
12465 // Notify ID change listeners before clearing the identifier map.
12466 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
12467 iter.Get()->ClearAndNotify();
12469 mIdentifierMap.Clear();
12470 mComposedShadowRoots.Clear();
12471 mResponsiveContent.Clear();
12472 IncrementExpandoGeneration(*this);
12475 void Document::RefreshLinkHrefs() {
12476 // Get a list of all links we know about. We will reset them, which will
12477 // remove them from the document, so we need a copy of what is in the
12478 // hashtable.
12479 const nsTArray<Link*> linksToNotify = ToArray(mStyledLinks);
12481 // Reset all of our styled links.
12482 nsAutoScriptBlocker scriptBlocker;
12483 for (Link* link : linksToNotify) {
12484 link->ResetLinkState(true);
12488 nsresult Document::CloneDocHelper(Document* clone) const {
12489 clone->mIsStaticDocument = mCreatingStaticClone;
12491 // Init document
12492 nsresult rv = clone->Init(NodePrincipal(), mPartitionedPrincipal);
12493 NS_ENSURE_SUCCESS(rv, rv);
12495 if (mCreatingStaticClone) {
12496 if (mOriginalDocument) {
12497 clone->mOriginalDocument = mOriginalDocument;
12498 } else {
12499 clone->mOriginalDocument = const_cast<Document*>(this);
12501 clone->mOriginalDocument->mLatestStaticClone = clone;
12502 clone->mOriginalDocument->mStaticCloneCount++;
12504 nsCOMPtr<nsILoadGroup> loadGroup;
12506 // |mDocumentContainer| is the container of the document that is being
12507 // created and not the original container. See CreateStaticClone function().
12508 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
12509 if (docLoader) {
12510 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
12512 nsCOMPtr<nsIChannel> channel = GetChannel();
12513 nsCOMPtr<nsIURI> uri;
12514 if (channel) {
12515 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
12516 } else {
12517 uri = Document::GetDocumentURI();
12519 clone->mChannel = channel;
12520 clone->mShouldResistFingerprinting = mShouldResistFingerprinting;
12521 if (uri) {
12522 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
12525 clone->mIsSrcdocDocument = mIsSrcdocDocument;
12526 clone->SetContainer(mDocumentContainer);
12528 // Setup the navigation time. This will be needed by any animations in the
12529 // document, even if they are only paused.
12530 MOZ_ASSERT(!clone->GetNavigationTiming(),
12531 "Navigation time was already set?");
12532 if (mTiming) {
12533 RefPtr<nsDOMNavigationTiming> timing =
12534 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
12535 clone->SetNavigationTiming(timing);
12537 clone->SetCsp(mCSP);
12540 // Now ensure that our clone has the same URI, base URI, and principal as us.
12541 // We do this after the mCreatingStaticClone block above, because that block
12542 // can set the base URI to an incorrect value in cases when base URI
12543 // information came from the channel. So we override explicitly, and do it
12544 // for all these properties, in case ResetToURI messes with any of the rest of
12545 // them.
12546 clone->SetDocumentURI(Document::GetDocumentURI());
12547 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
12548 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
12549 clone->mActiveCookiePrincipal = mActiveCookiePrincipal;
12550 // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
12551 // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
12552 // when printed standalone via window.print() (where there won't be a parent
12553 // document to grab the URI from).
12554 clone->mDocumentBaseURI = GetDocBaseURI();
12555 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
12556 clone->mReferrerInfo =
12557 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
12558 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
12560 bool hasHadScriptObject = true;
12561 nsIScriptGlobalObject* scriptObject =
12562 GetScriptHandlingObject(hasHadScriptObject);
12563 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
12564 if (mCreatingStaticClone) {
12565 // If we're doing a static clone (print, print preview), then we're going to
12566 // be setting a scope object after the clone. It's better to set it only
12567 // once, so we don't do that here. However, we do want to act as if there is
12568 // a script handling object. So we set mHasHadScriptHandlingObject.
12569 clone->mHasHadScriptHandlingObject = true;
12570 } else if (scriptObject) {
12571 clone->SetScriptHandlingObject(scriptObject);
12572 } else {
12573 clone->SetScopeObject(GetScopeObject());
12575 // Make the clone a data document
12576 clone->SetLoadedAsData(
12577 true,
12578 /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
12580 // Misc state
12582 // State from Document
12583 clone->mCharacterSet = mCharacterSet;
12584 clone->mCharacterSetSource = mCharacterSetSource;
12585 clone->SetCompatibilityMode(mCompatMode);
12586 clone->mBidiOptions = mBidiOptions;
12587 clone->mContentLanguage = mContentLanguage;
12588 clone->SetContentType(GetContentTypeInternal());
12589 clone->mSecurityInfo = mSecurityInfo;
12591 // State from Document
12592 clone->mType = mType;
12593 clone->mXMLDeclarationBits = mXMLDeclarationBits;
12594 clone->mBaseTarget = mBaseTarget;
12596 return NS_OK;
12599 void Document::NotifyLoading(bool aNewParentIsLoading,
12600 const ReadyState& aCurrentState,
12601 ReadyState aNewState) {
12602 // Mirror the top-level loading state down to all subdocuments
12603 bool was_loading = mAncestorIsLoading ||
12604 aCurrentState == READYSTATE_LOADING ||
12605 aCurrentState == READYSTATE_INTERACTIVE;
12606 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
12607 aNewState == READYSTATE_INTERACTIVE; // new value for state
12608 bool set_load_state = was_loading != is_loading;
12610 MOZ_LOG(
12611 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12612 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
12613 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
12614 "set_load_state: %d",
12615 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
12616 (int)aNewState, was_loading, is_loading, set_load_state));
12618 mAncestorIsLoading = aNewParentIsLoading;
12619 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
12620 // Tell our innerwindow (and thus TimeoutManager)
12621 nsPIDOMWindowInner* inner = GetInnerWindow();
12622 if (inner) {
12623 inner->SetActiveLoadingState(is_loading);
12625 BrowsingContext* context = GetBrowsingContext();
12626 if (context) {
12627 // Don't use PreOrderWalk to mirror this down; go down one level as a
12628 // time so we can set mAncestorIsLoading and take into account the
12629 // readystates of the subdocument. In the child process it will call
12630 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
12631 // iterate it's children
12632 for (auto& child : context->Children()) {
12633 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12634 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
12635 // Setting ancestor loading on a discarded browsing context has no
12636 // effect.
12637 Unused << child->SetAncestorLoading(is_loading);
12643 void Document::SetReadyStateInternal(ReadyState aReadyState,
12644 bool aUpdateTimingInformation) {
12645 if (aReadyState == READYSTATE_UNINITIALIZED) {
12646 // Transition back to uninitialized happens only to keep assertions happy
12647 // right before readyState transitions to something else. Make this
12648 // transition undetectable by Web content.
12649 mReadyState = aReadyState;
12650 return;
12653 if (IsTopLevelContentDocument()) {
12654 if (aReadyState == READYSTATE_LOADING) {
12655 AddToplevelLoadingDocument(this);
12656 } else if (aReadyState == READYSTATE_COMPLETE) {
12657 RemoveToplevelLoadingDocument(this);
12661 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
12662 SetLoadingOrRestoredFromBFCacheTimeStampToNow();
12664 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
12665 mReadyState = aReadyState;
12666 if (aUpdateTimingInformation && mTiming) {
12667 switch (aReadyState) {
12668 case READYSTATE_LOADING:
12669 mTiming->NotifyDOMLoading(GetDocumentURI());
12670 break;
12671 case READYSTATE_INTERACTIVE:
12672 mTiming->NotifyDOMInteractive(GetDocumentURI());
12673 break;
12674 case READYSTATE_COMPLETE:
12675 mTiming->NotifyDOMComplete(GetDocumentURI());
12676 break;
12677 default:
12678 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
12679 break;
12682 // At the time of loading start, we don't have timing object, record time.
12684 if (READYSTATE_INTERACTIVE == aReadyState &&
12685 NodePrincipal()->IsSystemPrincipal()) {
12686 if (!mXULPersist && XRE_IsParentProcess()) {
12687 mXULPersist = new XULPersist(this);
12688 mXULPersist->Init();
12690 if (!mChromeObserver) {
12691 mChromeObserver = new ChromeObserver(this);
12692 mChromeObserver->Init();
12696 if (aUpdateTimingInformation) {
12697 RecordNavigationTiming(aReadyState);
12700 AsyncEventDispatcher::RunDOMEventWhenSafe(
12701 *this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
12704 void Document::GetReadyState(nsAString& aReadyState) const {
12705 switch (mReadyState) {
12706 case READYSTATE_LOADING:
12707 aReadyState.AssignLiteral(u"loading");
12708 break;
12709 case READYSTATE_INTERACTIVE:
12710 aReadyState.AssignLiteral(u"interactive");
12711 break;
12712 case READYSTATE_COMPLETE:
12713 aReadyState.AssignLiteral(u"complete");
12714 break;
12715 default:
12716 aReadyState.AssignLiteral(u"uninitialized");
12720 void Document::SuppressEventHandling(uint32_t aIncrease) {
12721 mEventsSuppressed += aIncrease;
12722 if (mEventsSuppressed == aIncrease) {
12723 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
12724 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12727 for (uint32_t i = 0; i < aIncrease; ++i) {
12728 ScriptLoader()->AddExecuteBlocker();
12731 EnumerateSubDocuments([aIncrease](Document& aSubDoc) {
12732 aSubDoc.SuppressEventHandling(aIncrease);
12733 return CallState::Continue;
12737 void Document::NotifyAbortedLoad() {
12738 // If we still have outstanding work blocking DOMContentLoaded,
12739 // then don't try to change the readystate now, but wait until
12740 // they finish and then do so.
12741 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
12742 mSetCompleteAfterDOMContentLoaded = true;
12743 return;
12746 // Otherwise we're fully done at this point, so set the
12747 // readystate to complete.
12748 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
12749 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
12753 MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents(
12754 nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) {
12755 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
12756 if (MOZ_UNLIKELY(!fm)) {
12757 return;
12760 nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments);
12761 for (uint32_t i = 0; i < documents.Length(); ++i) {
12762 nsCOMPtr<Document> document = std::move(documents[i]);
12763 // NB: Don't bother trying to fire delayed events on documents that were
12764 // closed before this event ran.
12765 if (!document->EventHandlingSuppressed()) {
12766 fm->FireDelayedEvents(document);
12767 RefPtr<PresShell> presShell = document->GetPresShell();
12768 if (presShell) {
12769 // Only fire events for active documents.
12770 bool fire = aFireEvents && document->GetInnerWindow() &&
12771 document->GetInnerWindow()->IsCurrentInnerWindow();
12772 presShell->FireOrClearDelayedEvents(fire);
12774 document->FireOrClearPostMessageEvents(aFireEvents);
12779 void Document::PreloadPictureClosed() {
12780 MOZ_ASSERT(mPreloadPictureDepth > 0);
12781 mPreloadPictureDepth--;
12782 if (mPreloadPictureDepth == 0) {
12783 mPreloadPictureFoundSource.SetIsVoid(true);
12787 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
12788 const nsAString& aSizesAttr,
12789 const nsAString& aTypeAttr,
12790 const nsAString& aMediaAttr) {
12791 // Nested pictures are not valid syntax, so while we'll eventually load them,
12792 // it's not worth tracking sources mixed between nesting levels to preload
12793 // them effectively.
12794 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
12795 // <picture> selects the first matching source, so if this returns a URI we
12796 // needn't consider new sources until a new <picture> is encountered.
12797 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
12798 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
12799 aMediaAttr, mPreloadPictureFoundSource);
12800 if (found && mPreloadPictureFoundSource.IsVoid()) {
12801 // Found an empty source, which counts
12802 mPreloadPictureFoundSource.SetIsVoid(false);
12807 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
12808 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
12809 const nsAString& aSizesAttr, bool* aIsImgSet) {
12810 nsString sourceURL;
12811 bool isImgSet;
12812 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
12813 // We're in a <picture> element and found a URI from a source previous to
12814 // this image, use it.
12815 sourceURL = mPreloadPictureFoundSource;
12816 isImgSet = true;
12817 } else {
12818 // Otherwise try to use this <img> as a source
12819 HTMLImageElement::SelectSourceForTagWithAttrs(
12820 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
12821 VoidString(), sourceURL);
12822 isImgSet = !aSrcsetAttr.IsEmpty();
12825 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
12826 if (sourceURL.IsEmpty()) {
12827 return nullptr;
12830 // Construct into URI using passed baseURI (the parser may know of base URI
12831 // changes that have not reached us)
12832 nsresult rv;
12833 nsCOMPtr<nsIURI> uri;
12834 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
12835 this, aBaseURI);
12836 if (NS_FAILED(rv)) {
12837 return nullptr;
12840 *aIsImgSet = isImgSet;
12842 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
12843 // this this <picture> share the same <sources> (though this is not valid per
12844 // spec)
12845 return uri.forget();
12848 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
12849 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
12850 bool aLinkPreload, uint64_t aEarlyHintPreloaderId,
12851 const nsAString& aFetchPriority) {
12852 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
12853 nsContentUtils::CORSModeToLoadImageFlags(
12854 Element::StringToCORSMode(aCrossOriginAttr));
12856 nsContentPolicyType policyType =
12857 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
12858 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
12860 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12861 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12863 RefPtr<imgRequestProxy> request;
12865 nsLiteralString initiator = aEarlyHintPreloaderId
12866 ? u"early-hints"_ns
12867 : (aLinkPreload ? u"link"_ns : u"img"_ns);
12869 nsresult rv = nsContentUtils::LoadImage(
12870 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
12871 nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request),
12872 policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId,
12873 nsGenericHTMLElement::ToFetchPriority(aFetchPriority));
12875 // Pin image-reference to avoid evicting it from the img-cache before
12876 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
12877 // unlink
12878 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
12879 mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
12883 void Document::MaybePreLoadImage(nsIURI* aUri,
12884 const nsAString& aCrossOriginAttr,
12885 ReferrerPolicyEnum aReferrerPolicy,
12886 bool aIsImgSet, bool aLinkPreload,
12887 const nsAString& aFetchPriority) {
12888 const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr);
12889 if (aLinkPreload) {
12890 // Check if the image was already preloaded in this document to avoid
12891 // duplicate preloading.
12892 PreloadHashKey key =
12893 PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode);
12894 if (!mPreloadService.PreloadExists(key)) {
12895 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12896 aLinkPreload, 0, aFetchPriority);
12898 return;
12901 // Early exit if the img is already present in the img-cache
12902 // which indicates that the "real" load has already started and
12903 // that we shouldn't preload it.
12904 if (nsContentUtils::IsImageAvailable(aUri, NodePrincipal(), corsMode, this)) {
12905 return;
12908 // Image not in cache - trigger preload
12909 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload,
12910 0, aFetchPriority);
12913 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
12914 if (!StaticPrefs::network_preconnect()) {
12915 return;
12918 NS_MutateURI mutator(aOrigURI);
12919 if (NS_FAILED(mutator.GetStatus())) {
12920 return;
12923 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
12924 // which ignores the path and uses only the origin. The other is for the
12925 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
12926 // non-Anonymous preconnects create different connections on the wire and
12927 // therefore should not be considred duplicates of each other and we
12928 // normalize the path before putting it in the hash to accomplish that.
12930 if (aCORSMode == CORS_ANONYMOUS) {
12931 mutator.SetPathQueryRef("/anonymous"_ns);
12932 } else {
12933 mutator.SetPathQueryRef("/"_ns);
12936 nsCOMPtr<nsIURI> uri;
12937 nsresult rv = mutator.Finalize(uri);
12938 if (NS_FAILED(rv)) {
12939 return;
12942 const bool existingEntryFound =
12943 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
12944 if (entry) {
12945 return true;
12947 entry.Insert(true);
12948 return false;
12950 if (existingEntryFound) {
12951 return;
12954 nsCOMPtr<nsISpeculativeConnect> speculator =
12955 mozilla::components::IO::Service();
12956 if (!speculator) {
12957 return;
12960 OriginAttributes oa;
12961 StoragePrincipalHelper::GetOriginAttributesForNetworkState(this, oa);
12962 speculator->SpeculativeConnectWithOriginAttributesNative(
12963 uri, std::move(oa), nullptr, aCORSMode == CORS_ANONYMOUS);
12966 void Document::ForgetImagePreload(nsIURI* aURI) {
12967 // Checking count is faster than hashing the URI in the common
12968 // case of empty table.
12969 if (mPreloadingImages.Count() != 0) {
12970 nsCOMPtr<imgIRequest> req;
12971 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
12972 if (req) {
12973 // Make sure to cancel the request so imagelib knows it's gone.
12974 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
12979 void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates,
12980 bool aNotify) {
12981 const DocumentState oldStates = mState;
12982 if (aMaybeChangedStates.HasAtLeastOneOfStates(
12983 DocumentState::ALL_LOCALEDIR_BITS)) {
12984 mState &= ~DocumentState::ALL_LOCALEDIR_BITS;
12985 if (IsDocumentRightToLeft()) {
12986 mState |= DocumentState::RTL_LOCALE;
12987 } else {
12988 mState |= DocumentState::LTR_LOCALE;
12992 if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) {
12993 BrowsingContext* bc = GetBrowsingContext();
12994 if (!bc || !bc->GetIsActiveBrowserWindow()) {
12995 mState |= DocumentState::WINDOW_INACTIVE;
12996 } else {
12997 mState &= ~DocumentState::WINDOW_INACTIVE;
13001 const DocumentState changedStates = oldStates ^ mState;
13002 if (aNotify && !changedStates.IsEmpty()) {
13003 if (PresShell* ps = GetObservingPresShell()) {
13004 ps->DocumentStatesChanged(changedStates);
13009 namespace {
13012 * Stub for LoadSheet(), since all we want is to get the sheet into
13013 * the CSSLoader's style cache
13015 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
13016 ~StubCSSLoaderObserver() = default;
13018 public:
13019 NS_IMETHOD
13020 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
13021 NS_DECL_ISUPPORTS
13023 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
13025 } // namespace
13027 SheetPreloadStatus Document::PreloadStyle(
13028 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
13029 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aNonce,
13030 const nsAString& aIntegrity, css::StylePreloadKind aKind,
13031 uint64_t aEarlyHintPreloaderId, const nsAString& aFetchPriority) {
13032 MOZ_ASSERT(aKind != css::StylePreloadKind::None);
13034 // The CSSLoader will retain this object after we return.
13035 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
13037 nsCOMPtr<nsIReferrerInfo> referrerInfo =
13038 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
13040 // Charset names are always ASCII.
13041 auto result = CSSLoader()->LoadSheet(
13042 uri, aKind, aEncoding, referrerInfo, obs, aEarlyHintPreloaderId,
13043 Element::StringToCORSMode(aCrossOriginAttr), aNonce, aIntegrity,
13044 nsGenericHTMLElement::ToFetchPriority(aFetchPriority));
13045 if (result.isErr()) {
13046 return SheetPreloadStatus::Errored;
13048 RefPtr<StyleSheet> sheet = result.unwrap();
13049 if (sheet->IsComplete()) {
13050 return SheetPreloadStatus::AlreadyComplete;
13052 return SheetPreloadStatus::InProgress;
13055 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
13056 return CSSLoader()
13057 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
13058 .unwrapOr(nullptr);
13061 void Document::ResetDocumentDirection() {
13062 if (!nsContentUtils::IsChromeDoc(this)) {
13063 return;
13065 UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true);
13068 bool Document::IsDocumentRightToLeft() {
13069 if (!nsContentUtils::IsChromeDoc(this)) {
13070 return false;
13072 // setting the localedir attribute on the root element forces a
13073 // specific direction for the document.
13074 Element* element = GetRootElement();
13075 if (element) {
13076 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
13077 nullptr};
13078 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
13079 strings, eCaseMatters)) {
13080 case 0:
13081 return false;
13082 case 1:
13083 return true;
13084 default:
13085 break; // otherwise, not a valid value, so fall through
13089 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
13090 !mDocumentURI->SchemeIs("resource")) {
13091 return false;
13094 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
13097 class nsDelayedEventDispatcher : public Runnable {
13098 public:
13099 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
13100 : mozilla::Runnable("nsDelayedEventDispatcher"),
13101 mDocuments(std::move(aDocuments)) {}
13102 virtual ~nsDelayedEventDispatcher() = default;
13104 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
13105 // bug 1535398.
13106 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
13107 FireOrClearDelayedEvents(std::move(mDocuments), true);
13108 return NS_OK;
13111 private:
13112 nsTArray<nsCOMPtr<Document>> mDocuments;
13115 static void GetAndUnsuppressSubDocuments(
13116 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
13117 if (aDocument.EventHandlingSuppressed() > 0) {
13118 aDocument.DecreaseEventSuppression();
13119 aDocument.ScriptLoader()->RemoveExecuteBlocker();
13121 aDocuments.AppendElement(&aDocument);
13122 aDocument.EnumerateSubDocuments([&aDocuments](Document& aSubDoc) {
13123 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
13124 return CallState::Continue;
13128 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
13129 nsTArray<nsCOMPtr<Document>> documents;
13130 GetAndUnsuppressSubDocuments(*this, documents);
13132 for (nsCOMPtr<Document>& doc : documents) {
13133 if (!doc->EventHandlingSuppressed()) {
13134 if (WindowGlobalChild* wgc = doc->GetWindowGlobalChild()) {
13135 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
13138 MOZ_ASSERT(NS_IsMainThread());
13139 nsTArray<RefPtr<net::ChannelEventQueue>> queues =
13140 std::move(doc->mSuspendedQueues);
13141 for (net::ChannelEventQueue* queue : queues) {
13142 queue->Resume();
13145 // If there have been any events driven by the refresh driver which were
13146 // delayed due to events being suppressed in this document, make sure
13147 // there is a refresh scheduled soon so the events will run.
13148 if (doc->mHasDelayedRefreshEvent) {
13149 doc->mHasDelayedRefreshEvent = false;
13151 if (doc->mPresShell) {
13152 nsRefreshDriver* rd =
13153 doc->mPresShell->GetPresContext()->RefreshDriver();
13154 rd->RunDelayedEventsSoon();
13160 if (aFireEvents) {
13161 MOZ_RELEASE_ASSERT(NS_IsMainThread());
13162 nsCOMPtr<nsIRunnable> ded =
13163 new nsDelayedEventDispatcher(std::move(documents));
13164 Dispatch(ded.forget());
13165 } else {
13166 FireOrClearDelayedEvents(std::move(documents), false);
13170 bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
13171 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
13174 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
13175 MOZ_ASSERT(NS_IsMainThread());
13176 MOZ_ASSERT(EventHandlingSuppressed());
13177 mSuspendedQueues.AppendElement(aQueue);
13180 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
13181 MOZ_ASSERT(NS_IsMainThread());
13183 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
13184 mSuspendedPostMessageEvents.AppendElement(aEvent);
13185 return true;
13187 return false;
13190 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
13191 nsTArray<RefPtr<PostMessageEvent>> events =
13192 std::move(mSuspendedPostMessageEvents);
13194 if (aFireEvents) {
13195 for (PostMessageEvent* event : events) {
13196 event->Run();
13201 void Document::SetSuppressedEventListener(EventListener* aListener) {
13202 mSuppressedEventListener = aListener;
13203 EnumerateSubDocuments([&](Document& aDocument) {
13204 aDocument.SetSuppressedEventListener(aListener);
13205 return CallState::Continue;
13209 bool Document::IsActive() const {
13210 return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
13211 !GetBrowsingContext()->IsInBFCache();
13214 nsISupports* Document::GetCurrentContentSink() {
13215 return mParser ? mParser->GetContentSink() : nullptr;
13218 Document* Document::GetTemplateContentsOwner() {
13219 if (!mTemplateContentsOwner) {
13220 bool hasHadScriptObject = true;
13221 nsIScriptGlobalObject* scriptObject =
13222 GetScriptHandlingObject(hasHadScriptObject);
13224 nsCOMPtr<Document> document;
13225 nsresult rv = NS_NewDOMDocument(
13226 getter_AddRefs(document),
13227 u""_ns, // aNamespaceURI
13228 u""_ns, // aQualifiedName
13229 nullptr, // aDoctype
13230 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
13231 true, // aLoadedAsData
13232 scriptObject, // aEventObject
13233 IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
13234 NS_ENSURE_SUCCESS(rv, nullptr);
13236 mTemplateContentsOwner = document;
13237 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
13239 if (!scriptObject) {
13240 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
13243 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
13245 // Set |mTemplateContentsOwner| as the template contents owner of itself so
13246 // that it is the template contents owner of nested template elements.
13247 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
13250 MOZ_ASSERT(mTemplateContentsOwner->IsTemplateContentsOwner());
13251 return mTemplateContentsOwner;
13254 // https://html.spec.whatwg.org/#the-autofocus-attribute
13255 void Document::ElementWithAutoFocusInserted(Element* aAutoFocusCandidate) {
13256 BrowsingContext* bc = GetBrowsingContext();
13257 if (!bc) {
13258 return;
13261 // If target is not fully active, then return.
13262 if (!IsCurrentActiveDocument()) {
13263 return;
13266 // If target's active sandboxing flag set has the sandboxed automatic features
13267 // browsing context flag, then return.
13268 if (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES) {
13269 return;
13272 // For each ancestorBC of target's browsing context's ancestor browsing
13273 // contexts: if ancestorBC's active document's origin is not same origin with
13274 // target's origin, then return.
13275 while (bc) {
13276 BrowsingContext* parent = bc->GetParent();
13277 if (!parent) {
13278 break;
13280 // AncestorBC is not the same site
13281 if (!parent->IsInProcess()) {
13282 return;
13285 Document* currentDocument = bc->GetDocument();
13286 if (!currentDocument) {
13287 return;
13290 Document* parentDocument = parent->GetDocument();
13291 if (!parentDocument) {
13292 return;
13295 // Not same origin
13296 if (!currentDocument->NodePrincipal()->Equals(
13297 parentDocument->NodePrincipal())) {
13298 return;
13301 bc = parent;
13303 MOZ_ASSERT(bc->IsTop());
13305 Document* topDocument = bc->GetDocument();
13306 MOZ_ASSERT(topDocument);
13307 topDocument->AppendAutoFocusCandidateToTopDocument(aAutoFocusCandidate);
13310 void Document::ScheduleFlushAutoFocusCandidates() {
13311 MOZ_ASSERT(mPresShell && mPresShell->DidInitialize());
13312 MOZ_ASSERT(GetBrowsingContext()->IsTop());
13313 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
13314 rd->ScheduleAutoFocusFlush(this);
13318 void Document::AppendAutoFocusCandidateToTopDocument(
13319 Element* aAutoFocusCandidate) {
13320 MOZ_ASSERT(GetBrowsingContext()->IsTop());
13321 if (mAutoFocusFired) {
13322 return;
13325 if (!HasAutoFocusCandidates()) {
13326 // PresShell may be initialized later
13327 if (mPresShell && mPresShell->DidInitialize()) {
13328 ScheduleFlushAutoFocusCandidates();
13332 nsWeakPtr element = do_GetWeakReference(aAutoFocusCandidate);
13333 mAutoFocusCandidates.RemoveElement(element);
13334 mAutoFocusCandidates.AppendElement(element);
13337 void Document::SetAutoFocusFired() {
13338 mAutoFocusCandidates.Clear();
13339 mAutoFocusFired = true;
13342 // https://html.spec.whatwg.org/#flush-autofocus-candidates
13343 void Document::FlushAutoFocusCandidates() {
13344 MOZ_ASSERT(GetBrowsingContext()->IsTop());
13345 if (mAutoFocusFired) {
13346 return;
13349 if (!mPresShell) {
13350 return;
13353 MOZ_ASSERT(HasAutoFocusCandidates());
13354 MOZ_ASSERT(mPresShell->DidInitialize());
13356 nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetWindow();
13357 // We should be the top document
13358 if (!topWindow) {
13359 return;
13362 #ifdef DEBUG
13364 // Trying to find the top window (equivalent to window.top).
13365 nsCOMPtr<nsPIDOMWindowOuter> top = topWindow->GetInProcessTop();
13366 MOZ_ASSERT(topWindow == top);
13368 #endif
13370 // Don't steal the focus from the user
13371 if (topWindow->GetFocusedElement()) {
13372 SetAutoFocusFired();
13373 return;
13376 MOZ_ASSERT(mDocumentURI);
13377 nsAutoCString ref;
13378 // GetRef never fails
13379 nsresult rv = mDocumentURI->GetRef(ref);
13380 if (NS_SUCCEEDED(rv) &&
13381 nsContentUtils::GetTargetElement(this, NS_ConvertUTF8toUTF16(ref))) {
13382 SetAutoFocusFired();
13383 return;
13386 nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mAutoFocusCandidates);
13387 while (iter.HasMore()) {
13388 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(iter.GetNext());
13389 if (!autoFocusElement) {
13390 continue;
13392 RefPtr<Document> autoFocusElementDoc = autoFocusElement->OwnerDoc();
13393 // Get the latest info about the frame and allow scripts
13394 // to run which might affect the focusability of this element.
13395 autoFocusElementDoc->FlushPendingNotifications(FlushType::Frames);
13397 // Above layout flush may cause the PresShell to disappear.
13398 if (!mPresShell) {
13399 return;
13402 // Re-get the element because the ownerDoc() might have changed
13403 autoFocusElementDoc = autoFocusElement->OwnerDoc();
13404 BrowsingContext* bc = autoFocusElementDoc->GetBrowsingContext();
13405 if (!bc) {
13406 continue;
13409 // If doc is not fully active, then remove element from candidates, and
13410 // continue.
13411 if (!autoFocusElementDoc->IsCurrentActiveDocument()) {
13412 iter.Remove();
13413 continue;
13416 nsCOMPtr<nsIContentSink> sink =
13417 do_QueryInterface(autoFocusElementDoc->GetCurrentContentSink());
13418 if (sink) {
13419 nsHtml5TreeOpExecutor* executor =
13420 static_cast<nsHtml5TreeOpExecutor*>(sink->AsExecutor());
13421 if (executor) {
13422 // This is a HTML5 document
13423 MOZ_ASSERT(autoFocusElementDoc->IsHTMLDocument());
13424 // If doc's script-blocking style sheet counter is greater than 0, th
13425 // return.
13426 if (executor->WaitForPendingSheets()) {
13427 // In this case, element is the currently-best candidate, but doc is
13428 // not ready for autofocusing. We'll try again next time flush
13429 // autofocus candidates is called.
13430 ScheduleFlushAutoFocusCandidates();
13431 return;
13436 // The autofocus element could be moved to a different
13437 // top level BC.
13438 if (bc->Top()->GetDocument() != this) {
13439 continue;
13442 iter.Remove();
13444 // Let inclusiveAncestorDocuments be a list consisting of doc, plus the
13445 // active documents of each of doc's browsing context's ancestor browsing
13446 // contexts.
13447 // If any Document in inclusiveAncestorDocuments has non-null target
13448 // element, then continue.
13449 bool shouldFocus = true;
13450 while (bc) {
13451 Document* doc = bc->GetDocument();
13452 if (!doc) {
13453 shouldFocus = false;
13454 break;
13457 nsIURI* uri = doc->GetDocumentURI();
13458 if (!uri) {
13459 shouldFocus = false;
13460 break;
13463 nsAutoCString ref;
13464 nsresult rv = uri->GetRef(ref);
13465 // If there is an element in the document tree that has an ID equal to
13466 // fragment
13467 if (NS_SUCCEEDED(rv) &&
13468 nsContentUtils::GetTargetElement(doc, NS_ConvertUTF8toUTF16(ref))) {
13469 shouldFocus = false;
13470 break;
13472 bc = bc->GetParent();
13475 if (!shouldFocus) {
13476 continue;
13479 MOZ_ASSERT(topWindow);
13480 if (TryAutoFocusCandidate(*autoFocusElement)) {
13481 // We've successfully autofocused an element, don't
13482 // need to try to focus the rest.
13483 SetAutoFocusFired();
13484 break;
13488 if (HasAutoFocusCandidates()) {
13489 ScheduleFlushAutoFocusCandidates();
13493 bool Document::TryAutoFocusCandidate(Element& aElement) {
13494 const FocusOptions options;
13495 if (RefPtr<Element> target = nsFocusManager::GetTheFocusableArea(
13496 &aElement, nsFocusManager::ProgrammaticFocusFlags(options))) {
13497 target->Focus(options, CallerType::NonSystem, IgnoreErrors());
13498 return true;
13501 return false;
13504 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
13505 if (!aDocumentURI) {
13506 return;
13509 nsAutoCString ref;
13511 // Since all URI's that pass through here aren't URL's we can't
13512 // rely on the nsIURI implementation for providing a way for
13513 // finding the 'ref' part of the URI, we'll haveto revert to
13514 // string routines for finding the data past '#'
13516 nsresult rv = aDocumentURI->GetSpec(ref);
13517 if (NS_FAILED(rv)) {
13518 Unused << aDocumentURI->GetRef(mScrollToRef);
13519 return;
13522 nsReadingIterator<char> start, end;
13524 ref.BeginReading(start);
13525 ref.EndReading(end);
13527 if (FindCharInReadable('#', start, end)) {
13528 ++start; // Skip over the '#'
13530 mScrollToRef = Substring(start, end);
13534 // https://html.spec.whatwg.org/#scrolling-to-a-fragment
13535 void Document::ScrollToRef() {
13536 RefPtr<PresShell> presShell = GetPresShell();
13537 if (!presShell) {
13538 return;
13541 // https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives
13542 // Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment:
13543 // 1. Let text directives be the document's pending text directives.
13544 const RefPtr fragmentDirective = FragmentDirective();
13545 const nsTArray<RefPtr<nsRange>> textDirectives =
13546 fragmentDirective->FindTextFragmentsInDocument();
13547 // 2. If ranges is non-empty, then:
13548 // 2.1 Let firstRange be the first item of ranges
13549 const RefPtr<nsRange> textDirectiveToScroll =
13550 !textDirectives.IsEmpty() ? textDirectives[0] : nullptr;
13551 // 2.2 Visually indicate each range in ranges in an implementation-defined
13552 // way. The indication must not be observable from author script. See § 3.7
13553 // Indicating The Text Match.
13554 fragmentDirective->HighlightTextDirectives(textDirectives);
13556 // In a subsequent call to `ScrollToRef()` during page load, `textDirectives`
13557 // would only contain text directives that were not found in the previous
13558 // runs. If an earlier call during the same page load already found a text
13559 // directive to scroll to, only highlighting of the text directives needs to
13560 // be done.
13561 // This is indicated by `mScrolledToRefAlready`.
13562 if (mScrolledToRefAlready) {
13563 presShell->ScrollToAnchor();
13564 return;
13566 // 2. If fragment is the empty string and no text directives have been
13567 // scrolled to, then return the special value top of the document.
13568 if (!textDirectiveToScroll && mScrollToRef.IsEmpty()) {
13569 return;
13572 // TODO(mccr8): This will incorrectly block scrolling from a same-document
13573 // navigation triggered before the document is full loaded. See bug 1898630.
13574 if (ForceLoadAtTop()) {
13575 return;
13578 // 3. Let potentialIndicatedElement be the result of finding a potential
13579 // indicated element given document and fragment.
13580 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
13581 // This also covers 2.3 of the Monkeypatch for text fragments mentioned above:
13582 // 2.3 Set firstRange as document's indicated part, return.
13584 const bool scrollToTextDirective =
13585 textDirectiveToScroll
13586 ? fragmentDirective->IsTextDirectiveAllowedToBeScrolledTo()
13587 : mChangeScrollPosWhenScrollingToRef;
13589 auto rv =
13590 presShell->GoToAnchor(ref, textDirectiveToScroll, scrollToTextDirective);
13592 // 4. If potentialIndicatedElement is not null, then return
13593 // potentialIndicatedElement.
13594 if (NS_SUCCEEDED(rv)) {
13595 mScrolledToRefAlready = true;
13596 return;
13599 // 5. Let fragmentBytes be the result of percent-decoding fragment.
13600 nsAutoCString fragmentBytes;
13601 const bool unescaped =
13602 NS_UnescapeURL(mScrollToRef.Data(), mScrollToRef.Length(),
13603 /* aFlags = */ 0, fragmentBytes);
13605 if (!unescaped || fragmentBytes.IsEmpty()) {
13606 // Another attempt is only necessary if characters were unescaped.
13607 return;
13610 // 6. Let decodedFragment be the result of running UTF-8 decode without BOM on
13611 // fragmentBytes.
13612 nsAutoString decodedFragment;
13613 rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(fragmentBytes, decodedFragment);
13614 NS_ENSURE_SUCCESS_VOID(rv);
13616 // 7. Set potentialIndicatedElement to the result of finding a potential
13617 // indicated element given document and decodedFragment.
13618 rv = presShell->GoToAnchor(decodedFragment, nullptr,
13619 mChangeScrollPosWhenScrollingToRef);
13620 if (NS_SUCCEEDED(rv)) {
13621 mScrolledToRefAlready = true;
13625 void Document::RegisterActivityObserver(nsISupports* aSupports) {
13626 if (!mActivityObservers) {
13627 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
13629 mActivityObservers->Insert(aSupports);
13632 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
13633 if (!mActivityObservers) {
13634 return false;
13636 return mActivityObservers->EnsureRemoved(aSupports);
13639 void Document::EnumerateActivityObservers(
13640 ActivityObserverEnumerator aEnumerator) {
13641 if (!mActivityObservers) {
13642 return;
13645 const auto keyArray =
13646 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
13647 for (auto& observer : keyArray) {
13648 aEnumerator(observer.get());
13652 void Document::RegisterPendingLinkUpdate(Link* aLink) {
13653 if (aLink->HasPendingLinkUpdate()) {
13654 return;
13657 aLink->SetHasPendingLinkUpdate();
13659 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
13660 nsCOMPtr<nsIRunnable> event =
13661 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
13662 &Document::FlushPendingLinkUpdates);
13663 // Do this work in a second in the worst case.
13664 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
13665 EventQueuePriority::Idle);
13666 if (NS_FAILED(rv)) {
13667 // If during shutdown posting a runnable doesn't succeed, we probably
13668 // don't need to update link states.
13669 return;
13671 mHasLinksToUpdateRunnable = true;
13674 mLinksToUpdate.InfallibleAppend(aLink);
13677 void Document::FlushPendingLinkUpdates() {
13678 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
13679 MOZ_ASSERT(mHasLinksToUpdateRunnable);
13680 mHasLinksToUpdateRunnable = false;
13682 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
13683 mFlushingPendingLinkUpdates = true;
13685 while (!mLinksToUpdate.IsEmpty()) {
13686 LinksToUpdateList links(std::move(mLinksToUpdate));
13687 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
13688 Link* link = iter.Get();
13689 Element* element = link->GetElement();
13690 if (element->OwnerDoc() == this) {
13691 link->ClearHasPendingLinkUpdate();
13692 if (element->IsInComposedDoc()) {
13693 link->TriggerLinkUpdate(/* aNotify = */ true);
13701 * Retrieves the node in a static-clone document that corresponds to aOrigNode,
13702 * which is a node in the original document from which aStaticClone was cloned.
13704 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
13705 Document& aStaticClone) {
13706 MOZ_ASSERT(aOrigNode);
13708 // Selections in anonymous subtrees aren't supported.
13709 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
13710 return nullptr;
13713 // If the node is disconnected, this is a bug in the selection code, but it
13714 // can happen with shadow DOM so handle it.
13715 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
13716 return nullptr;
13719 AutoTArray<Maybe<uint32_t>, 32> indexArray;
13720 const nsINode* current = aOrigNode;
13721 while (const nsINode* parent = current->GetParentNode()) {
13722 Maybe<uint32_t> index = parent->ComputeIndexOf(current);
13723 NS_ENSURE_TRUE(index.isSome(), nullptr);
13724 indexArray.AppendElement(std::move(index));
13725 current = parent;
13727 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
13728 nsINode* correspondingNode = [&]() -> nsINode* {
13729 if (current->IsDocument()) {
13730 return &aStaticClone;
13732 const auto* shadow = ShadowRoot::FromNode(*current);
13733 if (!shadow) {
13734 return nullptr;
13736 nsINode* correspondingHost =
13737 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
13738 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
13739 return nullptr;
13741 return correspondingHost->AsElement()->GetShadowRoot();
13742 }();
13744 if (NS_WARN_IF(!correspondingNode)) {
13745 return nullptr;
13747 for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
13748 correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
13749 NS_ENSURE_TRUE(correspondingNode, nullptr);
13751 return correspondingNode;
13755 * Caches the selection ranges from the source document onto the static clone in
13756 * case the "Print Selection Only" functionality is invoked.
13758 * Note that we cannot use the selection obtained from GetOriginalDocument()
13759 * since that selection may have mutated after the print was invoked.
13761 * Note also that because nsRange objects point into a specific document's
13762 * nodes, we cannot reuse an array of nsRange objects across multiple static
13763 * clone documents. For that reason we cache a new array of ranges on each
13764 * static clone that we create.
13766 * TODO(emilio): This can be simplified once we don't re-clone from static
13767 * documents.
13769 * @param aSourceDoc the document from which we are caching selection ranges
13770 * @param aStaticClone the document that will hold the cache
13771 * @return true if a selection range was cached
13773 static void CachePrintSelectionRanges(const Document& aSourceDoc,
13774 Document& aStaticClone) {
13775 MOZ_ASSERT(aStaticClone.IsStaticDocument());
13776 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc));
13777 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
13779 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
13781 // When the user opts to "Print Selection Only", the print code prefers any
13782 // selection in the static clone corresponding to the focused frame. If this
13783 // is that static clone, flag it for the printing code:
13784 const bool isFocusedDoc = [&] {
13785 if (sourceDocIsStatic) {
13786 return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc));
13788 nsPIDOMWindowOuter* window = aSourceDoc.GetWindow();
13789 if (!window) {
13790 return false;
13792 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
13793 if (!rootWindow) {
13794 return false;
13796 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
13797 nsFocusManager::GetFocusedDescendant(rootWindow,
13798 nsFocusManager::eIncludeAllDescendants,
13799 getter_AddRefs(focusedWindow));
13800 return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc;
13801 }();
13802 if (isFocusedDoc) {
13803 aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc,
13804 reinterpret_cast<void*>(true));
13807 const Selection* origSelection = nullptr;
13808 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
13810 if (sourceDocIsStatic) {
13811 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
13812 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
13813 } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
13814 origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
13817 if (!origSelection && !origRanges) {
13818 return;
13821 const uint32_t rangeCount =
13822 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
13823 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
13825 for (const uint32_t i : IntegerRange(rangeCount)) {
13826 MOZ_ASSERT_IF(!sourceDocIsStatic,
13827 origSelection->RangeCount() == rangeCount);
13828 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
13829 : origSelection->GetRangeAt(i);
13830 MOZ_ASSERT(range);
13831 nsINode* startContainer = range->GetMayCrossShadowBoundaryStartContainer();
13832 nsINode* endContainer = range->GetMayCrossShadowBoundaryEndContainer();
13834 if (!startContainer || !endContainer) {
13835 continue;
13838 nsINode* startNode =
13839 GetCorrespondingNodeInDocument(startContainer, aStaticClone);
13840 nsINode* endNode =
13841 GetCorrespondingNodeInDocument(endContainer, aStaticClone);
13843 if (NS_WARN_IF(!startNode || !endNode)) {
13844 continue;
13847 RefPtr<nsRange> clonedRange = nsRange::Create(
13848 startNode, range->MayCrossShadowBoundaryStartOffset(), endNode,
13849 range->MayCrossShadowBoundaryEndOffset(), IgnoreErrors());
13850 if (clonedRange &&
13851 !clonedRange->AreNormalRangeAndCrossShadowBoundaryRangeCollapsed()) {
13852 printRanges->AppendElement(std::move(clonedRange));
13856 if (printRanges->IsEmpty()) {
13857 return;
13860 aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
13861 printRanges.release(),
13862 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
13865 already_AddRefed<Document> Document::CreateStaticClone(
13866 nsIDocShell* aCloneContainer, nsIDocumentViewer* aViewer,
13867 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
13868 MOZ_ASSERT(!mCreatingStaticClone);
13869 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
13870 MOZ_DIAGNOSTIC_ASSERT(aViewer);
13872 mCreatingStaticClone = true;
13873 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
13874 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
13876 auto raii = MakeScopeExit([&] {
13877 RemoveProperty(nsGkAtoms::adoptedsheetclones);
13878 mCreatingStaticClone = false;
13881 // Make document use different container during cloning.
13883 // FIXME(emilio): Why is this needed?
13884 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
13885 SetContainer(nsDocShell::Cast(aCloneContainer));
13886 IgnoredErrorResult rv;
13887 nsCOMPtr<nsINode> clonedNode = CloneNode(true, rv);
13888 SetContainer(originalShell);
13889 if (rv.Failed()) {
13890 return nullptr;
13893 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
13894 if (!clonedDoc) {
13895 return nullptr;
13898 size_t sheetsCount = SheetCount();
13899 for (size_t i = 0; i < sheetsCount; ++i) {
13900 RefPtr<StyleSheet> sheet = SheetAt(i);
13901 if (sheet) {
13902 if (sheet->IsApplicable()) {
13903 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13904 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13905 if (clonedSheet) {
13906 clonedDoc->AddStyleSheet(clonedSheet);
13911 clonedDoc->CloneAdoptedSheetsFrom(*this);
13913 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
13914 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
13915 for (StyleSheet* sheet : sheets) {
13916 if (sheet->IsApplicable()) {
13917 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13918 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13919 if (clonedSheet) {
13920 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
13921 clonedSheet);
13927 // Font faces created with the JS API will not be reflected in the
13928 // stylesheets and need to be copied over to the cloned document.
13929 if (const FontFaceSet* set = GetFonts()) {
13930 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
13933 clonedDoc->mReferrerInfo =
13934 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
13935 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
13936 CachePrintSelectionRanges(*this, *clonedDoc);
13938 // We're done with the clone, embed ourselves into the document viewer and
13939 // clone our children. The order here is pretty important, because our
13940 // document our document needs to have an owner global before we can create
13941 // the frame loaders for subdocuments.
13942 aViewer->SetDocument(clonedDoc);
13944 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
13946 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
13947 for (const auto& clone : pendingClones) {
13948 RefPtr<Element> element = do_QueryObject(clone.mElement);
13949 RefPtr<nsFrameLoader> frameLoader =
13950 nsFrameLoader::Create(element, /* aNetworkCreated */ false);
13952 if (NS_WARN_IF(!frameLoader)) {
13953 continue;
13956 clone.mElement->SetFrameLoader(frameLoader);
13958 nsresult rv = frameLoader->FinishStaticClone(
13959 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
13960 Unused << NS_WARN_IF(NS_FAILED(rv));
13963 return clonedDoc.forget();
13966 void Document::UnlinkOriginalDocumentIfStatic() {
13967 if (IsStaticDocument() && mOriginalDocument) {
13968 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
13969 mOriginalDocument->mStaticCloneCount--;
13970 mOriginalDocument = nullptr;
13972 MOZ_ASSERT(!mOriginalDocument);
13975 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
13976 uint32_t* aHandle) {
13977 const bool wasEmpty = mFrameRequestManager.IsEmpty();
13978 MOZ_TRY(mFrameRequestManager.Schedule(aCallback, aHandle));
13979 if (wasEmpty) {
13980 MaybeScheduleFrameRequestCallbacks();
13982 return NS_OK;
13985 void Document::CancelFrameRequestCallback(uint32_t aHandle) {
13986 mFrameRequestManager.Cancel(aHandle);
13989 bool Document::IsCanceledFrameRequestCallback(uint32_t aHandle) const {
13990 return mFrameRequestManager.IsCanceled(aHandle);
13993 void Document::ScheduleVideoFrameCallbacks(HTMLVideoElement* aElement) {
13994 const bool wasEmpty = mFrameRequestManager.IsEmpty();
13995 mFrameRequestManager.Schedule(aElement);
13996 if (wasEmpty) {
13997 MaybeScheduleFrameRequestCallbacks();
14001 void Document::CancelVideoFrameCallbacks(HTMLVideoElement* aElement) {
14002 mFrameRequestManager.Cancel(aElement);
14005 nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
14006 // Get the document's current state object. This is the object backing both
14007 // history.state and popStateEvent.state.
14009 // mStateObjectContainer may be null; this just means that there's no
14010 // current state object.
14012 if (!mCachedStateObjectValid) {
14013 if (mStateObjectContainer) {
14014 AutoJSAPI jsapi;
14015 // Init with null is "OK" in the sense that it will just fail.
14016 if (!jsapi.Init(GetScopeObject())) {
14017 return NS_ERROR_UNEXPECTED;
14019 JS::Rooted<JS::Value> value(jsapi.cx());
14020 nsresult rv =
14021 mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value);
14022 NS_ENSURE_SUCCESS(rv, rv);
14024 mCachedStateObject = value;
14025 if (!value.isNullOrUndefined()) {
14026 mozilla::HoldJSObjects(this);
14028 } else {
14029 mCachedStateObject = JS::NullValue();
14031 mCachedStateObjectValid = true;
14034 aState.set(mCachedStateObject);
14035 return NS_OK;
14038 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
14039 mTiming = aTiming;
14040 if (!mLoadingOrRestoredFromBFCacheTimeStamp.IsNull() && mTiming) {
14041 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(),
14042 mLoadingOrRestoredFromBFCacheTimeStamp);
14045 // If there's already the DocumentTimeline instance, tell it since the
14046 // DocumentTimeline is based on both the navigation start time stamp and the
14047 // refresh driver timestamp.
14048 if (mDocumentTimeline) {
14049 mDocumentTimeline->UpdateLastRefreshDriverTime();
14053 nsContentList* Document::ImageMapList() {
14054 if (!mImageMaps) {
14055 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
14056 nsGkAtoms::map);
14059 return mImageMaps;
14062 #define DEPRECATED_OPERATION(_op) #_op "Warning",
14063 static const char* kDeprecationWarnings[] = {
14064 #include "nsDeprecatedOperationList.h"
14065 nullptr};
14066 #undef DEPRECATED_OPERATION
14068 #define DOCUMENT_WARNING(_op) #_op "Warning",
14069 static const char* kDocumentWarnings[] = {
14070 #include "nsDocumentWarningList.h"
14071 nullptr};
14072 #undef DOCUMENT_WARNING
14074 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
14075 switch (aOperation) {
14076 #define DEPRECATED_OPERATION(_op) \
14077 case DeprecatedOperations::e##_op: \
14078 return eUseCounter_##_op;
14079 #include "nsDeprecatedOperationList.h"
14080 #undef DEPRECATED_OPERATION
14081 default:
14082 MOZ_CRASH();
14086 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
14087 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
14090 void Document::WarnOnceAbout(
14091 DeprecatedOperations aOperation, bool asError /* = false */,
14092 const nsTArray<nsString>& aParams /* = empty array */) const {
14093 MOZ_ASSERT(NS_IsMainThread());
14094 if (HasWarnedAbout(aOperation)) {
14095 return;
14097 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
14098 // Don't count deprecated operations for about pages since those pages
14099 // are almost in our control, and we always need to remove uses there
14100 // before we remove the operation itself anyway.
14101 if (!IsAboutPage()) {
14102 const_cast<Document*>(this)->SetUseCounter(
14103 OperationToUseCounter(aOperation));
14105 uint32_t flags =
14106 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
14107 nsContentUtils::ReportToConsole(
14108 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
14109 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
14112 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
14113 return mDocWarningWarnedAbout[aWarning];
14116 void Document::WarnOnceAbout(
14117 DocumentWarnings aWarning, bool asError /* = false */,
14118 const nsTArray<nsString>& aParams /* = empty array */) const {
14119 MOZ_ASSERT(NS_IsMainThread());
14120 if (HasWarnedAbout(aWarning)) {
14121 return;
14123 mDocWarningWarnedAbout[aWarning] = true;
14124 uint32_t flags =
14125 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
14126 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
14127 nsContentUtils::eDOM_PROPERTIES,
14128 kDocumentWarnings[aWarning], aParams);
14131 mozilla::dom::ImageTracker* Document::ImageTracker() {
14132 if (!mImageTracker) {
14133 mImageTracker = new mozilla::dom::ImageTracker;
14135 return mImageTracker;
14138 void Document::ScheduleSVGUseElementShadowTreeUpdate(
14139 SVGUseElement& aUseElement) {
14140 MOZ_ASSERT(aUseElement.IsInComposedDoc());
14142 if (MOZ_UNLIKELY(mIsStaticDocument)) {
14143 // Printing doesn't deal well with dynamic DOM mutations.
14144 return;
14147 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
14149 if (PresShell* presShell = GetPresShell()) {
14150 presShell->EnsureStyleFlush();
14154 void Document::DoUpdateSVGUseElementShadowTrees() {
14155 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
14157 MOZ_ASSERT(!mCloningForSVGUse);
14158 nsAutoScriptBlockerSuppressNodeRemoved blocker;
14159 mCloningForSVGUse = true;
14161 do {
14162 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
14163 mSVGUseElementsNeedingShadowTreeUpdate);
14164 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
14166 for (const auto& useElement : useElementsToUpdate) {
14167 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
14168 // The element was in another <use> shadow tree which we processed
14169 // already and also needed an update, and is removed from the document
14170 // now, so nothing to do here.
14171 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
14172 continue;
14174 useElement->UpdateShadowTree();
14176 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
14178 mCloningForSVGUse = false;
14181 void Document::NotifyMediaFeatureValuesChanged() {
14182 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
14183 imageElement->MediaFeatureValuesChanged();
14187 already_AddRefed<Touch> Document::CreateTouch(
14188 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
14189 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
14190 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
14191 float aRotationAngle, float aForce) {
14192 RefPtr<Touch> touch =
14193 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
14194 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
14195 return touch.forget();
14198 already_AddRefed<TouchList> Document::CreateTouchList() {
14199 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
14200 return retval.forget();
14203 already_AddRefed<TouchList> Document::CreateTouchList(
14204 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
14205 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
14206 retval->Append(&aTouch);
14207 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
14208 retval->Append(aTouches[i].get());
14210 return retval.forget();
14213 already_AddRefed<TouchList> Document::CreateTouchList(
14214 const Sequence<OwningNonNull<Touch>>& aTouches) {
14215 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
14216 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
14217 retval->Append(aTouches[i].get());
14219 return retval.forget();
14222 // https://drafts.csswg.org/cssom-view/Overview#dom-document-caretpositionfrompoint
14223 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
14224 float aX, float aY, const CaretPositionFromPointOptions& aOptions) {
14225 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
14227 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
14228 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
14229 nsPoint pt(x, y);
14231 FlushPendingNotifications(FlushType::Layout);
14233 PresShell* presShell = GetPresShell();
14234 if (!presShell) {
14235 return nullptr;
14238 nsIFrame* rootFrame = presShell->GetRootFrame();
14240 // XUL docs, unlike HTML, have no frame tree until everything's done loading
14241 if (!rootFrame) {
14242 return nullptr;
14245 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
14246 RelativeTo{rootFrame}, pt,
14247 {{FrameForPointOption::IgnorePaintSuppression,
14248 FrameForPointOption::IgnoreCrossDoc}});
14249 if (!ptFrame) {
14250 return nullptr;
14253 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
14254 nsPoint adjustedPoint = pt;
14255 if (nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
14256 adjustedPoint) !=
14257 nsLayoutUtils::TRANSFORM_SUCCEEDED) {
14258 return nullptr;
14261 nsIFrame::ContentOffsets offsets =
14262 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
14264 nsCOMPtr<nsINode> node = offsets.content;
14265 uint32_t offset = offsets.offset;
14266 nsCOMPtr<nsINode> anonNode = node;
14267 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
14268 if (nodeIsAnonymous) {
14269 node = ptFrame->GetContent();
14270 nsINode* nonChrome =
14271 node->AsContent()->FindFirstNonChromeOnlyAccessContent();
14272 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonChrome);
14273 nsTextControlFrame* textFrame =
14274 do_QueryFrame(nonChrome->AsContent()->GetPrimaryFrame());
14275 if (!textFrame) {
14276 return nullptr;
14279 // If the anonymous content node has a child, then we need to make sure
14280 // that we get the appropriate child, as otherwise the offset may not be
14281 // correct when we construct a range for it.
14282 nsCOMPtr<nsINode> firstChild = anonNode->GetFirstChild();
14283 if (firstChild) {
14284 anonNode = firstChild;
14287 if (textArea) {
14288 offset = nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
14291 node = nonChrome;
14294 bool offsetAndNodeNeedsAdjustment = false;
14296 if (StaticPrefs::
14297 dom_shadowdom_new_caretPositionFromPoint_behavior_enabled()) {
14298 while (node->IsInShadowTree() &&
14299 !aOptions.mShadowRoots.Contains(node->GetContainingShadow())) {
14300 node = node->GetContainingShadowHost();
14301 offsetAndNodeNeedsAdjustment = true;
14305 if (offsetAndNodeNeedsAdjustment) {
14306 const Maybe<uint32_t> maybeIndex = node->ComputeIndexInParentContent();
14307 if (MOZ_UNLIKELY(maybeIndex.isNothing())) {
14308 // Unlikely to happen, but still return nullptr to avoid leaking
14309 // information about the shadow tree.
14310 return nullptr;
14312 // 5.3.1: Set startOffset to index of startNode’s root's host.
14313 offset = maybeIndex.value();
14314 // 5.3.2: Set startNode to startNode’s root's host's parent.
14315 node = node->GetParentNode();
14318 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
14319 if (nodeIsAnonymous) {
14320 aCaretPos->SetAnonymousContentNode(anonNode);
14322 return aCaretPos.forget();
14325 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
14326 // We rely on correct frame information here, so need to flush frames.
14327 FlushPendingNotifications(FlushType::Frames);
14329 // An element that is the HTML body element is potentially scrollable if all
14330 // of the following conditions are true:
14332 // The element has an associated CSS layout box.
14333 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
14334 if (!bodyFrame) {
14335 return false;
14338 // The element's parent element's computed value of the overflow-x and
14339 // overflow-y properties are visible.
14340 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
14341 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
14342 if (parentFrame &&
14343 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
14344 return false;
14347 // The element's computed value of the overflow-x or overflow-y properties is
14348 // not visible.
14349 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
14352 Element* Document::GetScrollingElement() {
14353 // Keep this in sync with IsScrollingElement.
14354 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
14355 RefPtr<HTMLBodyElement> body = GetBodyElement();
14356 if (body && !IsPotentiallyScrollable(body)) {
14357 return body;
14360 return nullptr;
14363 return GetRootElement();
14366 bool Document::IsScrollingElement(Element* aElement) {
14367 // Keep this in sync with GetScrollingElement.
14368 MOZ_ASSERT(aElement);
14370 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
14371 return aElement == GetRootElement();
14374 // In the common case when aElement != body, avoid refcounting.
14375 HTMLBodyElement* body = GetBodyElement();
14376 if (aElement != body) {
14377 return false;
14380 // Now we know body is non-null, since aElement is not null. It's the
14381 // scrolling element for the document if it itself is not potentially
14382 // scrollable.
14383 RefPtr<HTMLBodyElement> strongBody(body);
14384 return !IsPotentiallyScrollable(strongBody);
14387 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
14388 public:
14389 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
14390 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
14392 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
14393 const BlockParsingOptions& aOptions)
14394 : mPromise(aPromise) {
14395 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
14396 if (parser &&
14397 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
14398 parser->BlockParser();
14399 mParser = do_GetWeakReference(parser);
14400 mDocument = aDocument;
14401 mDocument->BlockOnload();
14402 mDocument->BlockDOMContentLoaded();
14406 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
14407 ErrorResult& aRv) override {
14408 MaybeUnblockParser();
14410 mPromise->MaybeResolve(aValue);
14413 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
14414 ErrorResult& aRv) override {
14415 MaybeUnblockParser();
14417 mPromise->MaybeReject(aValue);
14420 protected:
14421 virtual ~UnblockParsingPromiseHandler() {
14422 // If we're being cleaned up by the cycle collector, our mDocument reference
14423 // may have been unlinked while our mParser weak reference is still alive.
14424 if (mDocument) {
14425 MaybeUnblockParser();
14429 private:
14430 void MaybeUnblockParser() {
14431 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
14432 if (parser) {
14433 MOZ_DIAGNOSTIC_ASSERT(mDocument);
14434 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
14435 if (parser == docParser) {
14436 parser->UnblockParser();
14437 parser->ContinueInterruptedParsingAsync();
14440 if (mDocument) {
14441 // We blocked DOMContentLoaded and load events on this document. Unblock
14442 // them. Note that we want to do that no matter what's going on with the
14443 // parser state for this document. Maybe someone caused it to stop being
14444 // parsed, so CreatorParserOrNull() is returning null, but we still want
14445 // to unblock these.
14446 mDocument->UnblockDOMContentLoaded();
14447 mDocument->UnblockOnload(false);
14449 mParser = nullptr;
14450 mDocument = nullptr;
14453 nsWeakPtr mParser;
14454 RefPtr<Promise> mPromise;
14455 RefPtr<Document> mDocument;
14458 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
14460 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
14461 NS_INTERFACE_MAP_ENTRY(nsISupports)
14462 NS_INTERFACE_MAP_END
14464 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
14465 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
14467 already_AddRefed<Promise> Document::BlockParsing(
14468 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
14469 RefPtr<Promise> resultPromise =
14470 Promise::Create(aPromise.GetParentObject(), aRv);
14471 if (aRv.Failed()) {
14472 return nullptr;
14475 RefPtr<PromiseNativeHandler> promiseHandler =
14476 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
14477 aPromise.AppendNativeHandler(promiseHandler);
14479 return resultPromise.forget();
14482 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
14483 if (mFailedChannel) {
14484 nsCOMPtr<nsIURI> failedURI;
14485 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
14486 return failedURI.forget();
14490 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
14491 if (!uri) {
14492 return nullptr;
14495 return uri.forget();
14498 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
14499 if (mIsGoingAway) {
14500 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
14501 return nullptr;
14504 if (!mReadyForIdle) {
14505 nsIGlobalObject* global = GetScopeObject();
14506 if (!global) {
14507 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
14508 return nullptr;
14511 mReadyForIdle = Promise::Create(global, aRv);
14512 if (aRv.Failed()) {
14513 return nullptr;
14517 return mReadyForIdle;
14520 void Document::MaybeResolveReadyForIdle() {
14521 IgnoredErrorResult rv;
14522 Promise* readyPromise = GetDocumentReadyForIdle(rv);
14523 if (readyPromise) {
14524 readyPromise->MaybeResolveWithUndefined();
14528 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
14529 // The policy is created when the document is initialized. We _must_ have a
14530 // policy here even if the featurePolicy pref is off. If this assertion fails,
14531 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
14532 MOZ_ASSERT(mFeaturePolicy);
14533 return mFeaturePolicy;
14536 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
14537 // Only chrome documents are allowed to use command dispatcher.
14538 if (!nsContentUtils::IsChromeDoc(this)) {
14539 return nullptr;
14541 if (!mCommandDispatcher) {
14542 // Create our command dispatcher and hook it up.
14543 mCommandDispatcher = new nsXULCommandDispatcher(this);
14545 return mCommandDispatcher;
14548 void Document::InitializeXULBroadcastManager() {
14549 if (mXULBroadcastManager) {
14550 return;
14552 mXULBroadcastManager = new XULBroadcastManager(this);
14555 namespace {
14557 class DevToolsMutationObserver final : public nsStubMutationObserver {
14558 NS_DECL_ISUPPORTS
14559 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
14560 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
14561 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
14563 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
14564 // relies on the event firing _before_ the removal happens.
14565 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
14567 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
14568 // data changes right now (maybe intentionally?).
14569 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
14571 DevToolsMutationObserver() = default;
14573 private:
14574 void FireEvent(nsINode* aTarget, const nsAString& aType);
14576 ~DevToolsMutationObserver() = default;
14579 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
14581 void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
14582 const nsAString& aType) {
14583 AsyncEventDispatcher::RunDOMEventWhenSafe(*aTarget, aType, CanBubble::eNo,
14584 ChromeOnlyDispatch::eYes,
14585 Composed::eYes);
14588 void DevToolsMutationObserver::AttributeChanged(Element* aElement,
14589 int32_t aNamespaceID,
14590 nsAtom* aAttribute,
14591 int32_t aModType,
14592 const nsAttrValue* aOldValue) {
14593 FireEvent(aElement, u"devtoolsattrmodified"_ns);
14596 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
14597 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
14598 ContentInserted(c);
14602 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
14603 FireEvent(aChild, u"devtoolschildinserted"_ns);
14606 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
14608 } // namespace
14610 void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
14611 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
14612 return;
14614 mDevToolsWatchingDOMMutations = aValue;
14615 if (aValue) {
14616 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
14617 sDevToolsMutationObserver = new DevToolsMutationObserver();
14618 ClearOnShutdown(&sDevToolsMutationObserver);
14620 AddMutationObserver(sDevToolsMutationObserver);
14621 } else if (sDevToolsMutationObserver) {
14622 RemoveMutationObserver(sDevToolsMutationObserver);
14626 void EvaluateMediaQueryLists(nsTArray<RefPtr<MediaQueryList>>& aListsToNotify,
14627 Document& aDocument, bool aRecurse) {
14628 if (nsPresContext* pc = aDocument.GetPresContext()) {
14629 pc->FlushPendingMediaFeatureValuesChanged();
14632 for (MediaQueryList* mql : aDocument.MediaQueryLists()) {
14633 if (mql->EvaluateOnRenderingUpdate()) {
14634 aListsToNotify.AppendElement(mql);
14637 if (!aRecurse) {
14638 return;
14640 aDocument.EnumerateSubDocuments([&](Document& aSubDoc) {
14641 EvaluateMediaQueryLists(aListsToNotify, aSubDoc, true);
14642 return CallState::Continue;
14646 void Document::EvaluateMediaQueriesAndReportChanges(bool aRecurse) {
14647 AutoTArray<RefPtr<MediaQueryList>, 32> mqls;
14648 EvaluateMediaQueryLists(mqls, *this, aRecurse);
14649 for (auto& mql : mqls) {
14650 mql->FireChangeEvent();
14654 nsIHTMLCollection* Document::Children() {
14655 if (!mChildrenCollection) {
14656 mChildrenCollection =
14657 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
14658 nsGkAtoms::_asterisk, false);
14661 return mChildrenCollection;
14664 uint32_t Document::ChildElementCount() { return Children()->Length(); }
14666 // Singleton class to manage the list of fullscreen documents which are the
14667 // root of a branch which contains fullscreen documents. We maintain this list
14668 // so that we can easily exit all windows from fullscreen when the user
14669 // presses the escape key.
14670 class FullscreenRoots {
14671 public:
14672 // Adds the root of given document to the manager. Calling this method
14673 // with a document whose root is already contained has no effect.
14674 static void Add(Document* aDoc);
14676 // Iterates over every root in the root list, and calls aFunction, passing
14677 // each root once to aFunction. It is safe to call Add() and Remove() while
14678 // iterating over the list (i.e. in aFunction). Documents that are removed
14679 // from the manager during traversal are not traversed, and documents that
14680 // are added to the manager during traversal are also not traversed.
14681 static void ForEach(void (*aFunction)(Document* aDoc));
14683 // Removes the root of a specific document from the manager.
14684 static void Remove(Document* aDoc);
14686 // Returns true if all roots added to the list have been removed.
14687 static bool IsEmpty();
14689 private:
14690 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
14691 MOZ_COUNTED_DTOR(FullscreenRoots)
14693 using RootsArray = nsTArray<WeakPtr<Document>>;
14695 // Returns true if aRoot is in the list of fullscreen roots.
14696 static bool Contains(Document* aRoot);
14698 // Singleton instance of the FullscreenRoots. This is instantiated when a
14699 // root is added, and it is deleted when the last root is removed.
14700 static FullscreenRoots* sInstance;
14702 // List of weak pointers to roots.
14703 RootsArray mRoots;
14706 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
14708 /* static */
14709 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
14710 if (!sInstance) {
14711 return;
14713 // Create a copy of the roots array, and iterate over the copy. This is so
14714 // that if an element is removed from mRoots we don't mess up our iteration.
14715 RootsArray roots(sInstance->mRoots.Clone());
14716 // Call aFunction on all entries.
14717 for (uint32_t i = 0; i < roots.Length(); i++) {
14718 nsCOMPtr<Document> root(roots[i]);
14719 // Check that the root isn't in the manager. This is so that new additions
14720 // while we were running don't get traversed.
14721 if (root && FullscreenRoots::Contains(root)) {
14722 aFunction(root);
14727 /* static */
14728 bool FullscreenRoots::Contains(Document* aRoot) {
14729 return sInstance && sInstance->mRoots.Contains(aRoot);
14732 /* static */
14733 void FullscreenRoots::Add(Document* aDoc) {
14734 nsCOMPtr<Document> root =
14735 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14736 if (!FullscreenRoots::Contains(root)) {
14737 if (!sInstance) {
14738 sInstance = new FullscreenRoots();
14740 sInstance->mRoots.AppendElement(root);
14744 /* static */
14745 void FullscreenRoots::Remove(Document* aDoc) {
14746 nsCOMPtr<Document> root =
14747 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14748 if (!sInstance || !sInstance->mRoots.RemoveElement(root)) {
14749 NS_ERROR("Should only try to remove roots which are still added!");
14750 return;
14752 if (sInstance->mRoots.IsEmpty()) {
14753 delete sInstance;
14754 sInstance = nullptr;
14758 /* static */
14759 bool FullscreenRoots::IsEmpty() { return !sInstance; }
14761 // Any fullscreen change waiting for the widget to finish transition
14762 // is queued here. This is declared static instead of a member of
14763 // Document because in the majority of time, there would be at most
14764 // one document requesting or exiting fullscreen. We shouldn't waste
14765 // the space to hold for it in every document.
14766 class PendingFullscreenChangeList {
14767 public:
14768 PendingFullscreenChangeList() = delete;
14770 template <typename T>
14771 static void Add(UniquePtr<T> aChange) {
14772 sList.insertBack(aChange.release());
14775 static const FullscreenChange* GetLast() { return sList.getLast(); }
14777 enum IteratorOption {
14778 // When we are committing fullscreen changes or preparing for
14779 // that, we generally want to iterate all requests in the same
14780 // window with eDocumentsWithSameRoot option.
14781 eDocumentsWithSameRoot,
14782 // If we are removing a document from the tree, we would only
14783 // want to remove the requests from the given document and its
14784 // descendants. For that case, use eInclusiveDescendants.
14785 eInclusiveDescendants
14788 template <typename T>
14789 class Iterator {
14790 public:
14791 explicit Iterator(Document* aDoc, IteratorOption aOption)
14792 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
14793 if (mCurrent) {
14794 if (aDoc->GetBrowsingContext()) {
14795 mRootBCForIteration = aDoc->GetBrowsingContext();
14796 if (aOption == eDocumentsWithSameRoot) {
14797 BrowsingContext* bc =
14798 GetParentIgnoreChromeBoundary(mRootBCForIteration);
14799 while (bc) {
14800 mRootBCForIteration = bc;
14801 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
14805 SkipToNextMatch();
14809 UniquePtr<T> TakeAndNext() {
14810 auto thisChange = TakeAndNextInternal();
14811 SkipToNextMatch();
14812 return thisChange;
14814 bool AtEnd() const { return mCurrent == nullptr; }
14816 private:
14817 static BrowsingContext* GetParentIgnoreChromeBoundary(
14818 BrowsingContext* aBC) {
14819 // Chrome BrowsingContexts are only available in the parent process, so if
14820 // we're in a content process, we only worry about the context tree.
14821 if (XRE_IsParentProcess()) {
14822 return aBC->Canonical()->GetParentCrossChromeBoundary();
14824 return aBC->GetParent();
14827 UniquePtr<T> TakeAndNextInternal() {
14828 FullscreenChange* thisChange = mCurrent;
14829 MOZ_ASSERT(thisChange->Type() == T::kType);
14830 mCurrent = mCurrent->removeAndGetNext();
14831 return WrapUnique(static_cast<T*>(thisChange));
14833 void SkipToNextMatch() {
14834 while (mCurrent) {
14835 if (mCurrent->Type() == T::kType) {
14836 BrowsingContext* bc = mCurrent->Document()->GetBrowsingContext();
14837 if (!bc) {
14838 // Always automatically drop fullscreen changes which are
14839 // from a document detached from the doc shell.
14840 UniquePtr<T> change = TakeAndNextInternal();
14841 change->MayRejectPromise("Document is not active");
14842 continue;
14844 while (bc && bc != mRootBCForIteration) {
14845 bc = GetParentIgnoreChromeBoundary(bc);
14847 if (bc) {
14848 break;
14851 // The current one either don't have matched type, or isn't
14852 // inside the given subtree, so skip this item.
14853 mCurrent = mCurrent->getNext();
14857 FullscreenChange* mCurrent;
14858 RefPtr<BrowsingContext> mRootBCForIteration;
14861 private:
14862 static LinkedList<FullscreenChange> sList;
14865 /* static */
14866 MOZ_RUNINIT LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
14868 size_t Document::CountFullscreenElements() const {
14869 size_t count = 0;
14870 for (const nsWeakPtr& ptr : mTopLayer) {
14871 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14872 if (elem->State().HasState(ElementState::FULLSCREEN)) {
14873 count++;
14877 return count;
14880 // https://github.com/whatwg/html/issues/9143
14881 // We need to consider the precedence between active modal dialog, topmost auto
14882 // popover and fullscreen element once it's specified.
14883 void Document::HandleEscKey() {
14884 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14885 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14886 if (RefPtr popoverHTMLEl = nsGenericHTMLElement::FromNodeOrNull(element)) {
14887 if (element->IsAutoPopover() && element->IsPopoverOpen()) {
14888 popoverHTMLEl->HidePopover(IgnoreErrors());
14889 break;
14892 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14893 dialog->QueueCancelDialog();
14894 break;
14899 MOZ_CAN_RUN_SCRIPT void Document::ProcessCloseRequest() {
14900 if (RefPtr win = GetInnerWindow()) {
14901 if (win->IsFullyActive()) {
14902 RefPtr manager = win->EnsureCloseWatcherManager();
14903 manager->ProcessCloseRequest();
14908 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
14909 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
14910 RefPtr<Promise> promise = exit->GetPromise();
14911 RestorePreviousFullscreenState(std::move(exit));
14912 return promise.forget();
14915 static void AskWindowToExitFullscreen(Document* aDoc) {
14916 if (XRE_GetProcessType() == GeckoProcessType_Content) {
14917 nsContentUtils::DispatchEventOnlyToChrome(
14918 aDoc, aDoc, u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
14919 Cancelable::eNo, /* DefaultAction */ nullptr);
14920 } else {
14921 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
14922 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
14927 class nsCallExitFullscreen : public Runnable {
14928 public:
14929 explicit nsCallExitFullscreen(Document* aDoc)
14930 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
14932 NS_IMETHOD Run() final {
14933 if (!mDoc) {
14934 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
14935 } else {
14936 AskWindowToExitFullscreen(mDoc);
14938 return NS_OK;
14941 private:
14942 nsCOMPtr<Document> mDoc;
14945 /* static */
14946 void Document::AsyncExitFullscreen(Document* aDoc) {
14947 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14948 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
14949 NS_DispatchToCurrentThread(exit.forget());
14952 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
14953 uint32_t count = 0;
14954 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
14955 aDoc.EnumerateSubDocuments([&count](Document& aSubDoc) {
14956 if (aSubDoc.Fullscreen()) {
14957 count++;
14959 return CallState::Continue;
14961 return count;
14964 bool Document::IsFullscreenLeaf() {
14965 // A fullscreen leaf document is fullscreen, and has no fullscreen
14966 // subdocuments.
14968 // FIXME(emilio): This doesn't seem to account for fission iframes, is that
14969 // ok?
14970 return Fullscreen() && CountFullscreenSubDocuments(*this) == 0;
14973 static Document* GetFullscreenLeaf(Document& aDoc) {
14974 if (aDoc.IsFullscreenLeaf()) {
14975 return &aDoc;
14977 if (!aDoc.Fullscreen()) {
14978 return nullptr;
14980 Document* leaf = nullptr;
14981 aDoc.EnumerateSubDocuments([&leaf](Document& aSubDoc) {
14982 leaf = GetFullscreenLeaf(aSubDoc);
14983 return leaf ? CallState::Stop : CallState::Continue;
14985 return leaf;
14988 static Document* GetFullscreenLeaf(Document* aDoc) {
14989 if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
14990 return leaf;
14992 // Otherwise we could be either in a non-fullscreen doc tree, or we're
14993 // below the fullscreen doc. Start the search from the root.
14994 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14995 return GetFullscreenLeaf(*root);
14998 static CallState ResetFullscreen(Document& aDocument) {
14999 if (Element* fsElement = aDocument.GetUnretargetedFullscreenElement()) {
15000 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
15001 "Should have at most 1 fullscreen subdocument.");
15002 aDocument.CleanupFullscreenState();
15003 NS_ASSERTION(!aDocument.Fullscreen(), "Should reset fullscreen");
15004 DispatchFullscreenChange(aDocument, fsElement);
15005 aDocument.EnumerateSubDocuments(ResetFullscreen);
15007 return CallState::Continue;
15010 // Since Document::ExitFullscreenInDocTree() could be called from
15011 // Element::UnbindFromTree() where it is not safe to synchronously run
15012 // script. This runnable is the script part of that function.
15013 class ExitFullscreenScriptRunnable : public Runnable {
15014 public:
15015 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
15016 : mozilla::Runnable("ExitFullscreenScriptRunnable"),
15017 mRoot(aRoot),
15018 mLeaf(aLeaf) {}
15020 NS_IMETHOD Run() override {
15021 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
15022 // document since we want this event to follow the same path that
15023 // MozDOMFullscreen:Entered was dispatched.
15024 nsContentUtils::DispatchEventOnlyToChrome(
15025 mLeaf, mLeaf, u"MozDOMFullscreen:Exited"_ns, CanBubble::eYes,
15026 Cancelable::eNo, /* DefaultAction */ nullptr);
15027 // Ensure the window exits fullscreen, as long as we don't have
15028 // pending fullscreen requests.
15029 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
15030 if (!mRoot->HasPendingFullscreenRequests()) {
15031 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
15032 false);
15035 return NS_OK;
15038 private:
15039 nsCOMPtr<Document> mRoot;
15040 nsCOMPtr<Document> mLeaf;
15043 /* static */
15044 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
15045 MOZ_ASSERT(aMaybeNotARootDoc);
15047 // Unlock the pointer
15048 PointerLockManager::Unlock("Document::ExitFullscreenInDocTree");
15050 // Resolve all promises which waiting for exit fullscreen.
15051 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
15052 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15053 while (!iter.AtEnd()) {
15054 UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
15055 exit->MayResolvePromise();
15058 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
15059 if (!root || !root->Fullscreen()) {
15060 // If a document was detached before exiting from fullscreen, it is
15061 // possible that the root had left fullscreen state. In this case,
15062 // we would not get anything from the ResetFullscreen() call. Root's
15063 // not being a fullscreen doc also means the widget should have
15064 // exited fullscreen state. It means even if we do not return here,
15065 // we would actually do nothing below except crashing ourselves via
15066 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
15067 // document.
15068 return;
15071 // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
15072 // See ExitFullscreenScriptRunnable::Run for details. We have to
15073 // record it here because we don't have such information after we
15074 // reset the fullscreen state below.
15075 Document* fullscreenLeaf = GetFullscreenLeaf(root);
15077 // Walk the tree of fullscreen documents, and reset their fullscreen state.
15078 ResetFullscreen(*root);
15080 NS_ASSERTION(!root->Fullscreen(),
15081 "Fullscreen root should no longer be a fullscreen doc...");
15083 // Move the top-level window out of fullscreen mode.
15084 FullscreenRoots::Remove(root);
15086 nsContentUtils::AddScriptRunner(
15087 new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
15090 static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
15091 RefPtr<AsyncEventDispatcher> asyncDispatcher =
15092 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
15093 CanBubble::eYes, ChromeOnlyDispatch::eYes);
15094 asyncDispatcher->PostDOMEvent();
15097 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
15098 NS_ASSERTION(!Fullscreen() || !FullscreenRoots::IsEmpty(),
15099 "Should have at least 1 fullscreen root when fullscreen!");
15101 if (!GetWindow()) {
15102 aExit->MayRejectPromise("No active window");
15103 return;
15105 if (!Fullscreen() || FullscreenRoots::IsEmpty()) {
15106 aExit->MayRejectPromise("Not in fullscreen mode");
15107 return;
15110 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
15111 AutoTArray<Element*, 8> exitElements;
15113 Document* doc = fullScreenDoc;
15114 // Collect all subdocuments.
15115 for (; doc != this; doc = doc->GetInProcessParentDocument()) {
15116 Element* fsElement = doc->GetUnretargetedFullscreenElement();
15117 MOZ_ASSERT(fsElement,
15118 "Parent document of "
15119 "a fullscreen document without fullscreen element?");
15120 exitElements.AppendElement(fsElement);
15122 MOZ_ASSERT(doc == this, "Must have reached this doc");
15123 // Collect all ancestor documents which we are going to change.
15124 for (; doc; doc = doc->GetInProcessParentDocument()) {
15125 Element* fsElement = doc->GetUnretargetedFullscreenElement();
15126 MOZ_ASSERT(fsElement,
15127 "Ancestor of fullscreen document must also be in fullscreen");
15128 if (doc != this) {
15129 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
15130 if (iframe->FullscreenFlag()) {
15131 // If this is an iframe, and it explicitly requested
15132 // fullscreen, don't rollback it automatically.
15133 break;
15137 exitElements.AppendElement(fsElement);
15138 if (doc->CountFullscreenElements() > 1) {
15139 break;
15143 Document* lastDoc = exitElements.LastElement()->OwnerDoc();
15144 size_t fullscreenCount = lastDoc->CountFullscreenElements();
15145 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
15146 // If we are fully exiting fullscreen, don't touch anything here,
15147 // just wait for the window to get out from fullscreen first.
15148 PendingFullscreenChangeList::Add(std::move(aExit));
15149 AskWindowToExitFullscreen(this);
15150 return;
15153 // If fullscreen mode is updated the pointer should be unlocked
15154 PointerLockManager::Unlock("Document::RestorePreviousFullscreenState");
15155 // All documents listed in the array except the last one are going to
15156 // completely exit from the fullscreen state.
15157 for (auto i : IntegerRange(exitElements.Length() - 1)) {
15158 exitElements[i]->OwnerDoc()->CleanupFullscreenState();
15160 // The last document will either rollback one fullscreen element, or
15161 // completely exit from the fullscreen state as well.
15162 Document* newFullscreenDoc;
15163 if (fullscreenCount > 1) {
15164 DebugOnly<bool> removedFullscreenElement = lastDoc->PopFullscreenElement();
15165 MOZ_ASSERT(removedFullscreenElement);
15166 newFullscreenDoc = lastDoc;
15167 } else {
15168 lastDoc->CleanupFullscreenState();
15169 newFullscreenDoc = lastDoc->GetInProcessParentDocument();
15171 // Dispatch the fullscreenchange event to all document listed. Note
15172 // that the loop order is reversed so that events are dispatched in
15173 // the tree order as indicated in the spec.
15174 for (Element* e : Reversed(exitElements)) {
15175 DispatchFullscreenChange(*e->OwnerDoc(), e);
15177 aExit->MayResolvePromise();
15179 MOZ_ASSERT(newFullscreenDoc,
15180 "If we were going to exit from fullscreen on "
15181 "all documents in this doctree, we should've asked the window to "
15182 "exit first instead of reaching here.");
15183 if (fullScreenDoc != newFullscreenDoc &&
15184 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
15185 // We've popped so enough off the stack that we've rolled back to
15186 // a fullscreen element in a parent document. If this document is
15187 // cross origin, dispatch an event to chrome so it knows to show
15188 // the warning UI.
15189 DispatchFullscreenNewOriginEvent(newFullscreenDoc);
15193 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
15194 if (nsPresContext* presContext = aDoc->GetPresContext()) {
15195 presContext->UpdateViewportScrollStylesOverride();
15199 static void NotifyFullScreenChangedForMediaElement(Element& aElement) {
15200 // When a media element enters the fullscreen, we would like to notify that
15201 // to the media controller in order to update its status.
15202 if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) {
15203 mediaElem->NotifyFullScreenChanged();
15207 void Document::CleanupFullscreenState() {
15208 while (PopFullscreenElement(UpdateViewport::No)) {
15209 // Remove the next one if appropriate
15212 UpdateViewportScrollbarOverrideForFullscreen(this);
15213 mFullscreenRoot = nullptr;
15215 // Restore the zoom level that was in place prior to entering fullscreen.
15216 if (PresShell* presShell = GetPresShell()) {
15217 if (presShell->GetMobileViewportManager()) {
15218 presShell->SetResolutionAndScaleTo(
15219 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
15224 bool Document::PopFullscreenElement(UpdateViewport aUpdateViewport) {
15225 Element* removedElement = TopLayerPop([](Element* element) -> bool {
15226 return element->State().HasState(ElementState::FULLSCREEN);
15229 if (!removedElement) {
15230 return false;
15233 MOZ_ASSERT(removedElement->State().HasState(ElementState::FULLSCREEN));
15234 removedElement->RemoveStates(ElementState::FULLSCREEN | ElementState::MODAL);
15235 NotifyFullScreenChangedForMediaElement(*removedElement);
15236 // Reset iframe fullscreen flag.
15237 if (auto* iframe = HTMLIFrameElement::FromNode(removedElement)) {
15238 iframe->SetFullscreenFlag(false);
15240 if (aUpdateViewport == UpdateViewport::Yes) {
15241 UpdateViewportScrollbarOverrideForFullscreen(this);
15243 return true;
15246 void Document::SetFullscreenElement(Element& aElement) {
15247 ElementState statesToAdd = ElementState::FULLSCREEN;
15248 if (!IsInChromeDocShell()) {
15249 // Don't make the document modal in chrome documents, since we don't want
15250 // the browser UI like the context menu / etc to be inert.
15251 statesToAdd |= ElementState::MODAL;
15253 aElement.AddStates(statesToAdd);
15254 TopLayerPush(aElement);
15255 NotifyFullScreenChangedForMediaElement(aElement);
15256 UpdateViewportScrollbarOverrideForFullscreen(this);
15259 void Document::TopLayerPush(Element& aElement) {
15260 const bool modal = aElement.State().HasState(ElementState::MODAL);
15262 TopLayerPop(aElement);
15263 if (nsIFrame* f = aElement.GetPrimaryFrame()) {
15264 f->MarkNeedsDisplayItemRebuild();
15267 mTopLayer.AppendElement(do_GetWeakReference(&aElement));
15268 NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
15270 if (modal) {
15271 aElement.AddStates(ElementState::TOPMOST_MODAL);
15273 bool foundExistingModalElement = false;
15274 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15275 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15276 if (element && element != &aElement &&
15277 element->State().HasState(ElementState::TOPMOST_MODAL)) {
15278 element->RemoveStates(ElementState::TOPMOST_MODAL);
15279 foundExistingModalElement = true;
15280 break;
15284 if (!foundExistingModalElement) {
15285 Element* root = GetRootElement();
15286 MOZ_RELEASE_ASSERT(root, "top layer element outside of document?");
15287 if (&aElement != root) {
15288 // Add inert to the root element so that the inertness is applied to the
15289 // entire document.
15290 root->AddStates(ElementState::INERT);
15296 void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
15297 aDialogElement.AddStates(ElementState::MODAL);
15298 TopLayerPush(aDialogElement);
15301 void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
15302 DebugOnly<Element*> removedElement = TopLayerPop(aDialogElement);
15303 MOZ_ASSERT(removedElement == &aDialogElement);
15304 aDialogElement.RemoveStates(ElementState::MODAL);
15307 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
15308 if (mTopLayer.IsEmpty()) {
15309 return nullptr;
15312 // Remove the topmost element that qualifies aPredicate; This
15313 // is required is because the top layer contains not only
15314 // fullscreen elements, but also dialog elements.
15315 Element* removedElement = nullptr;
15316 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
15317 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
15318 if (element && aPredicate(element)) {
15319 removedElement = element;
15320 if (nsIFrame* f = element->GetPrimaryFrame()) {
15321 f->MarkNeedsDisplayItemRebuild();
15323 mTopLayer.RemoveElementAt(i);
15324 break;
15328 // Pop from the stack null elements (references to elements which have
15329 // been GC'd since they were added to the stack) and elements which are
15330 // no longer in this document.
15332 // FIXME(emilio): If this loop does something, it'd violate the assertions
15333 // from GetTopLayerTop()... What gives?
15334 while (!mTopLayer.IsEmpty()) {
15335 Element* element = GetTopLayerTop();
15336 if (!element || element->GetComposedDoc() != this) {
15337 if (element) {
15338 if (nsIFrame* f = element->GetPrimaryFrame()) {
15339 f->MarkNeedsDisplayItemRebuild();
15343 mTopLayer.RemoveLastElement();
15344 } else {
15345 // The top element of the stack is now an in-doc element. Return here.
15346 break;
15350 if (!removedElement) {
15351 return nullptr;
15354 const bool modal = removedElement->State().HasState(ElementState::MODAL);
15356 if (modal) {
15357 removedElement->RemoveStates(ElementState::TOPMOST_MODAL);
15358 bool foundExistingModalElement = false;
15359 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15360 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15361 if (element && element->State().HasState(ElementState::MODAL)) {
15362 element->AddStates(ElementState::TOPMOST_MODAL);
15363 foundExistingModalElement = true;
15364 break;
15367 // No more modal elements, make the document not inert anymore.
15368 if (!foundExistingModalElement) {
15369 Element* root = GetRootElement();
15370 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
15371 root->RemoveStates(ElementState::INERT);
15376 return removedElement;
15379 Element* Document::TopLayerPop(Element& aElement) {
15380 auto predictFunc = [&aElement](Element* element) {
15381 return element == &aElement;
15383 return TopLayerPop(predictFunc);
15386 void Document::GetWireframe(bool aIncludeNodes,
15387 Nullable<Wireframe>& aWireframe) {
15388 FlushPendingNotifications(FlushType::Layout);
15389 GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
15392 void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
15393 Nullable<Wireframe>& aWireframe) {
15394 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
15395 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
15397 PresShell* shell = GetPresShell();
15398 if (!shell) {
15399 return;
15402 nsPresContext* pc = shell->GetPresContext();
15403 if (!pc) {
15404 return;
15407 nsIFrame* rootFrame = shell->GetRootFrame();
15408 if (!rootFrame) {
15409 return;
15412 auto& wireframe = aWireframe.SetValue();
15413 wireframe.mCanvasBackground = shell->ComputeCanvasBackground().mViewportColor;
15415 FrameForPointOptions options;
15416 options.mBits += FrameForPointOption::IgnoreCrossDoc;
15417 options.mBits += FrameForPointOption::IgnorePaintSuppression;
15418 options.mBits += FrameForPointOption::OnlyVisible;
15420 AutoTArray<nsIFrame*, 32> frames;
15421 const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
15422 nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
15423 options);
15425 // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
15426 // something perhaps, but seems hard / like it'd involve at least some extra
15427 // copying around, since they don't outlive GetFramesForArea.
15428 auto& rects = wireframe.mRects.Construct();
15429 if (!rects.SetCapacity(frames.Length(), fallible)) {
15430 return;
15432 for (nsIFrame* frame : Reversed(frames)) {
15433 auto [rectColor,
15434 rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> {
15435 if (frame->IsTextFrame()) {
15436 return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
15437 WireframeRectType::Text};
15439 if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
15440 return {0, WireframeRectType::Image};
15442 if (frame->IsThemed()) {
15443 return {0, WireframeRectType::Background};
15445 bool drawImage = false;
15446 bool drawColor = false;
15447 if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) {
15448 const nscolor color = nsCSSRendering::DetermineBackgroundColor(
15449 pc, bgStyle, frame, drawImage, drawColor);
15450 if (drawImage &&
15451 !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
15452 return {color, WireframeRectType::Image};
15454 if (drawColor && !frame->IsCanvasFrame()) {
15455 // Canvas frame background already accounted for in mCanvasBackground.
15456 return {color, WireframeRectType::Background};
15459 return {0, WireframeRectType::Unknown};
15460 }();
15462 if (rectType == WireframeRectType::Unknown) {
15463 continue;
15466 const auto r =
15467 CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
15468 frame, frame->GetRectRelativeToSelf(), relativeTo));
15469 if ((uint32_t)r.Area() <
15470 StaticPrefs::browser_history_wireframeAreaThreshold()) {
15471 continue;
15474 // Can't really fail because SetCapacity succeeded.
15475 auto& taggedRect = *rects.AppendElement(fallible);
15477 if (aIncludeNodes) {
15478 if (nsIContent* c = frame->GetContent()) {
15479 taggedRect.mNode.Construct(c);
15482 taggedRect.mX = r.x;
15483 taggedRect.mY = r.y;
15484 taggedRect.mWidth = r.width;
15485 taggedRect.mHeight = r.height;
15486 taggedRect.mColor = rectColor;
15487 taggedRect.mType.Construct(rectType);
15491 Element* Document::GetTopLayerTop() {
15492 if (mTopLayer.IsEmpty()) {
15493 return nullptr;
15495 uint32_t last = mTopLayer.Length() - 1;
15496 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
15497 NS_ASSERTION(element, "Should have a top layer element!");
15498 NS_ASSERTION(element->IsInComposedDoc(),
15499 "Top layer element should be in doc");
15500 NS_ASSERTION(element->OwnerDoc() == this,
15501 "Top layer element should be in this doc");
15502 return element;
15505 Element* Document::GetUnretargetedFullscreenElement() const {
15506 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15507 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15508 // Per spec, the fullscreen element is the topmost element in the document’s
15509 // top layer whose fullscreen flag is set, if any, and null otherwise.
15510 if (element && element->State().HasState(ElementState::FULLSCREEN)) {
15511 return element;
15514 return nullptr;
15517 nsTArray<Element*> Document::GetTopLayer() const {
15518 nsTArray<Element*> elements;
15519 for (const nsWeakPtr& ptr : mTopLayer) {
15520 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
15521 elements.AppendElement(elem);
15524 return elements;
15527 bool Document::TopLayerContains(Element& aElement) const {
15528 if (mTopLayer.IsEmpty()) {
15529 return false;
15531 nsWeakPtr weakElement = do_GetWeakReference(&aElement);
15532 return mTopLayer.Contains(weakElement);
15535 void Document::HideAllPopoversUntil(nsINode& aEndpoint,
15536 bool aFocusPreviousElement,
15537 bool aFireEvents) {
15538 auto closeAllOpenPopovers = [&aFocusPreviousElement, &aFireEvents,
15539 this]() MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
15540 while (RefPtr<Element> topmost = GetTopmostAutoPopover()) {
15541 HidePopover(*topmost, aFocusPreviousElement, aFireEvents, IgnoreErrors());
15545 if (aEndpoint.IsElement() && !aEndpoint.AsElement()->IsPopoverOpen()) {
15546 return;
15549 if (&aEndpoint == this) {
15550 closeAllOpenPopovers();
15551 return;
15554 // https://github.com/whatwg/html/pull/9198
15555 auto needRepeatingHide = [&]() {
15556 auto autoList = AutoPopoverList();
15557 return autoList.Contains(&aEndpoint) &&
15558 &aEndpoint != autoList.LastElement();
15561 MOZ_ASSERT((&aEndpoint)->IsElement() &&
15562 (&aEndpoint)->AsElement()->IsAutoPopover());
15563 bool repeatingHide = false;
15564 bool fireEvents = aFireEvents;
15565 do {
15566 RefPtr<const Element> lastToHide = nullptr;
15567 bool foundEndpoint = false;
15568 for (const Element* popover : AutoPopoverList()) {
15569 if (popover == &aEndpoint) {
15570 foundEndpoint = true;
15571 } else if (foundEndpoint) {
15572 lastToHide = popover;
15573 break;
15577 if (!foundEndpoint) {
15578 closeAllOpenPopovers();
15579 return;
15582 while (lastToHide && lastToHide->IsPopoverOpen()) {
15583 RefPtr<Element> topmost = GetTopmostAutoPopover();
15584 if (!topmost) {
15585 break;
15587 HidePopover(*topmost, aFocusPreviousElement, fireEvents, IgnoreErrors());
15590 repeatingHide = needRepeatingHide();
15591 if (repeatingHide) {
15592 fireEvents = false;
15594 } while (repeatingHide);
15597 void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement,
15598 bool aFireEvents, ErrorResult& aRv) {
15599 RefPtr<nsGenericHTMLElement> popoverHTMLEl =
15600 nsGenericHTMLElement::FromNode(aPopover);
15601 NS_ASSERTION(popoverHTMLEl, "Not a HTML element");
15603 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15604 nullptr, aRv)) {
15605 return;
15608 bool wasShowingOrHiding =
15609 popoverHTMLEl->GetPopoverData()->IsShowingOrHiding();
15610 popoverHTMLEl->GetPopoverData()->SetIsShowingOrHiding(true);
15611 const bool fireEvents = aFireEvents && !wasShowingOrHiding;
15612 auto cleanupHidingFlag = MakeScopeExit([&]() {
15613 if (auto* popoverData = popoverHTMLEl->GetPopoverData()) {
15614 popoverData->SetIsShowingOrHiding(wasShowingOrHiding);
15615 if (auto* closeWatcher = popoverData->GetCloseWatcher()) {
15616 closeWatcher->Destroy();
15621 if (popoverHTMLEl->IsAutoPopover()) {
15622 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, fireEvents);
15623 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15624 nullptr, aRv)) {
15625 return;
15627 // TODO: we can't always guarantee:
15628 // The last item in document's auto popover list is popoverHTMLEl.
15629 // See, https://github.com/whatwg/html/issues/9197
15630 // If popoverHTMLEl is not on top, hide popovers again without firing
15631 // events.
15632 if (NS_WARN_IF(GetTopmostAutoPopover() != popoverHTMLEl)) {
15633 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
15634 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15635 nullptr, aRv)) {
15636 return;
15638 MOZ_ASSERT(GetTopmostAutoPopover() == popoverHTMLEl,
15639 "popoverHTMLEl should be on top of auto popover list");
15643 auto* data = popoverHTMLEl->GetPopoverData();
15644 MOZ_ASSERT(data, "Should have popover data");
15645 data->SetInvoker(nullptr);
15647 // Fire beforetoggle event and re-check popover validity.
15648 if (fireEvents) {
15649 // Intentionally ignore the return value here as only on open event for
15650 // beforetoggle the cancelable attribute is initialized to true.
15651 popoverHTMLEl->FireToggleEvent(u"open"_ns, u"closed"_ns,
15652 u"beforetoggle"_ns);
15654 // https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm
15655 // step 10.2.
15656 // Hide all popovers when beforetoggle shows a popover.
15657 if (popoverHTMLEl->IsAutoPopover() &&
15658 GetTopmostAutoPopover() != popoverHTMLEl &&
15659 popoverHTMLEl->PopoverOpen()) {
15660 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
15663 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15664 nullptr, aRv)) {
15665 return;
15669 RemovePopoverFromTopLayer(aPopover);
15671 popoverHTMLEl->PopoverPseudoStateUpdate(false, true);
15672 popoverHTMLEl->GetPopoverData()->SetPopoverVisibilityState(
15673 PopoverVisibilityState::Hidden);
15675 // Queue popover toggle event task.
15676 if (fireEvents) {
15677 popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing);
15680 if (aFocusPreviousElement) {
15681 popoverHTMLEl->FocusPreviousElementAfterHidingPopover();
15682 } else {
15683 popoverHTMLEl->ForgetPreviouslyFocusedElementAfterHidingPopover();
15687 nsTArray<Element*> Document::AutoPopoverList() const {
15688 nsTArray<Element*> elements;
15689 for (const nsWeakPtr& ptr : mTopLayer) {
15690 if (nsCOMPtr<Element> element = do_QueryReferent(ptr)) {
15691 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15692 elements.AppendElement(element);
15696 return elements;
15699 Element* Document::GetTopmostAutoPopover() const {
15700 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15701 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15702 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15703 return element;
15706 return nullptr;
15709 void Document::AddToAutoPopoverList(Element& aElement) {
15710 MOZ_ASSERT(aElement.IsAutoPopover());
15711 TopLayerPush(aElement);
15714 void Document::RemoveFromAutoPopoverList(Element& aElement) {
15715 MOZ_ASSERT(aElement.IsAutoPopover());
15716 TopLayerPop(aElement);
15719 void Document::AddPopoverToTopLayer(Element& aElement) {
15720 MOZ_ASSERT(aElement.GetPopoverData());
15721 TopLayerPush(aElement);
15724 void Document::RemovePopoverFromTopLayer(Element& aElement) {
15725 MOZ_ASSERT(aElement.GetPopoverData());
15726 TopLayerPop(aElement);
15729 // Returns true if aDoc browsing context is focused.
15730 bool IsInFocusedTab(Document* aDoc) {
15731 BrowsingContext* bc = aDoc->GetBrowsingContext();
15732 if (!bc) {
15733 return false;
15736 nsFocusManager* fm = nsFocusManager::GetFocusManager();
15737 if (!fm) {
15738 return false;
15741 if (XRE_IsParentProcess()) {
15742 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
15743 // by retaining the old code path for the parent process.
15744 nsIDocShell* docshell = aDoc->GetDocShell();
15745 if (!docshell) {
15746 return false;
15748 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15749 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15750 if (!rootItem) {
15751 return false;
15753 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
15754 if (!rootWin) {
15755 return false;
15758 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
15759 activeWindow = fm->GetActiveWindow();
15760 if (!activeWindow) {
15761 return false;
15764 return activeWindow == rootWin;
15767 return fm->GetActiveBrowsingContext() == bc->Top();
15770 // Returns true if aDoc browsing context is focused and is also active.
15771 bool IsInActiveTab(Document* aDoc) {
15772 if (!IsInFocusedTab(aDoc)) {
15773 return false;
15776 BrowsingContext* bc = aDoc->GetBrowsingContext();
15777 MOZ_ASSERT(bc, "With no BrowsingContext, we should have failed earlier.");
15778 return bc->IsActive();
15781 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
15782 // Ensure the frame element is the fullscreen element in this document.
15783 // If the frame element is already the fullscreen element in this document,
15784 // this has no effect.
15785 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
15786 RequestFullscreen(std::move(request), XRE_IsContentProcess());
15789 void Document::RemoteFrameFullscreenReverted() {
15790 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
15791 RestorePreviousFullscreenState(std::move(exit));
15794 static bool HasFullscreenSubDocument(Document& aDoc) {
15795 uint32_t count = CountFullscreenSubDocuments(aDoc);
15796 NS_ASSERTION(count <= 1,
15797 "Fullscreen docs should have at most 1 fullscreen child!");
15798 return count >= 1;
15801 // Returns nullptr if a request for Fullscreen API is currently enabled
15802 // in the given document. Returns a static string indicates the reason
15803 // why it is not enabled otherwise.
15804 const char* Document::GetFullscreenError(CallerType aCallerType) {
15805 if (!StaticPrefs::full_screen_api_enabled()) {
15806 return "FullscreenDeniedDisabled";
15809 if (aCallerType == CallerType::System) {
15810 // Chrome code can always use the fullscreen API, provided it's not
15811 // explicitly disabled.
15812 return nullptr;
15815 if (!IsVisible()) {
15816 return "FullscreenDeniedHidden";
15819 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
15820 return "FullscreenDeniedFeaturePolicy";
15823 // Ensure that all containing elements are <iframe> and have allowfullscreen
15824 // attribute set.
15825 BrowsingContext* bc = GetBrowsingContext();
15826 if (!bc || !bc->FullscreenAllowed()) {
15827 return "FullscreenDeniedContainerNotAllowed";
15830 return nullptr;
15833 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
15834 Element* elem = aRequest.Element();
15835 // Strictly speaking, this isn't part of the fullscreen element ready
15836 // check in the spec, but per steps in the spec, when an element which
15837 // is already the fullscreen element requests fullscreen, nothing
15838 // should change and no event should be dispatched, but we still need
15839 // to resolve the returned promise.
15840 Element* fullscreenElement = GetUnretargetedFullscreenElement();
15841 if (elem == fullscreenElement) {
15842 aRequest.MayResolvePromise();
15843 return false;
15845 if (!elem->IsInComposedDoc()) {
15846 aRequest.Reject("FullscreenDeniedNotInDocument");
15847 return false;
15849 if (elem->IsPopoverOpen()) {
15850 aRequest.Reject("FullscreenDeniedPopoverOpen");
15851 return false;
15853 if (elem->OwnerDoc() != this) {
15854 aRequest.Reject("FullscreenDeniedMovedDocument");
15855 return false;
15857 if (!GetWindow()) {
15858 aRequest.Reject("FullscreenDeniedLostWindow");
15859 return false;
15861 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
15862 aRequest.Reject(msg);
15863 return false;
15865 if (HasFullscreenSubDocument(*this)) {
15866 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
15867 return false;
15869 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
15870 aRequest.Reject("FullscreenDeniedHTMLDialog");
15871 return false;
15873 if (!nsContentUtils::IsChromeDoc(this) && !IsInFocusedTab(this)) {
15874 aRequest.Reject("FullscreenDeniedNotFocusedTab");
15875 return false;
15877 return true;
15880 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
15881 MOZ_ASSERT(XRE_IsParentProcess());
15882 nsIDocShell* docShell = aDoc->GetDocShell();
15883 if (!docShell) {
15884 return nullptr;
15886 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15887 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15888 return rootItem ? rootItem->GetWindow() : nullptr;
15891 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
15892 nsPIDOMWindowOuter* aRootWin) {
15893 MOZ_ASSERT(XRE_IsParentProcess());
15894 // If we are in the chrome process, and the window has not been in
15895 // fullscreen, we certainly need to make that fullscreen first.
15896 if (!aRootWin->GetFullScreen()) {
15897 return false;
15899 // The iterator not being at end indicates there is still some
15900 // pending fullscreen request relates to this document. We have to
15901 // push the request to the pending queue so requests are handled
15902 // in the correct order.
15903 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15904 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15905 if (!iter.AtEnd()) {
15906 return false;
15909 // Same thing for exits. If we have any pending, we have to push
15910 // to the pending queue.
15911 PendingFullscreenChangeList::Iterator<FullscreenExit> iterExit(
15912 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15913 if (!iterExit.AtEnd()) {
15914 return false;
15917 // We have to apply the fullscreen state directly in this case,
15918 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
15919 // if it is already in fullscreen. If we do not apply the state but
15920 // instead add it to the queue and wait for the window as normal,
15921 // we would get stuck.
15922 return true;
15925 static bool CheckFullscreenAllowedElementType(const Element* elem) {
15926 // Per spec only HTML, <svg>, and <math> should be allowed, but
15927 // we also need to allow XUL elements right now.
15928 return elem->IsHTMLElement() || elem->IsXULElement() ||
15929 elem->IsSVGElement(nsGkAtoms::svg) ||
15930 elem->IsMathMLElement(nsGkAtoms::math);
15933 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
15934 bool aApplyFullscreenDirectly) {
15935 if (XRE_IsContentProcess()) {
15936 RequestFullscreenInContentProcess(std::move(aRequest),
15937 aApplyFullscreenDirectly);
15938 } else {
15939 RequestFullscreenInParentProcess(std::move(aRequest),
15940 aApplyFullscreenDirectly);
15944 void Document::RequestFullscreenInContentProcess(
15945 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15946 MOZ_ASSERT(XRE_IsContentProcess());
15948 // If we are in the content process, we can apply the fullscreen
15949 // state directly only if we have been in DOM fullscreen, because
15950 // otherwise we always need to notify the chrome.
15951 if (aApplyFullscreenDirectly ||
15952 nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) {
15953 ApplyFullscreen(std::move(aRequest));
15954 return;
15957 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15958 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15959 return;
15962 // We don't need to check element ready before this point, because
15963 // if we called ApplyFullscreen, it would check that for us.
15964 if (!FullscreenElementReadyCheck(*aRequest)) {
15965 return;
15968 PendingFullscreenChangeList::Add(std::move(aRequest));
15969 // If we are not the top level process, dispatch an event to make
15970 // our parent process go fullscreen first.
15971 Dispatch(NS_NewRunnableFunction(
15972 "Document::RequestFullscreenInContentProcess", [self = RefPtr{this}] {
15973 if (!self->HasPendingFullscreenRequests()) {
15974 return;
15976 nsContentUtils::DispatchEventOnlyToChrome(
15977 self, self, u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
15978 Cancelable::eNo, /* DefaultAction */ nullptr);
15979 }));
15982 void Document::RequestFullscreenInParentProcess(
15983 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15984 MOZ_ASSERT(XRE_IsParentProcess());
15985 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
15986 if (!rootWin) {
15987 aRequest->MayRejectPromise("No active window");
15988 return;
15991 if (aApplyFullscreenDirectly ||
15992 ShouldApplyFullscreenDirectly(this, rootWin)) {
15993 ApplyFullscreen(std::move(aRequest));
15994 return;
15997 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15998 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15999 return;
16002 // See if we're waiting on an exit. If so, just make this one pending.
16003 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
16004 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
16005 if (!iter.AtEnd()) {
16006 PendingFullscreenChangeList::Add(std::move(aRequest));
16007 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
16008 return;
16011 // We don't need to check element ready before this point, because
16012 // if we called ApplyFullscreen, it would check that for us.
16013 if (!FullscreenElementReadyCheck(*aRequest)) {
16014 return;
16017 PendingFullscreenChangeList::Add(std::move(aRequest));
16018 // Make the window fullscreen.
16019 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
16022 /* static */
16023 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
16024 bool handled = false;
16025 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
16026 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
16027 while (!iter.AtEnd()) {
16028 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
16029 Document* doc = request->Document();
16030 if (doc->ApplyFullscreen(std::move(request))) {
16031 handled = true;
16034 return handled;
16037 /* static */
16038 void Document::ClearPendingFullscreenRequests(Document* aDoc) {
16039 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
16040 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
16041 while (!iter.AtEnd()) {
16042 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
16043 request->MayRejectPromise("Fullscreen request aborted");
16047 bool Document::HasPendingFullscreenRequests() {
16048 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
16049 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
16050 return !iter.AtEnd();
16053 MOZ_CAN_RUN_SCRIPT_BOUNDARY
16054 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
16055 if (!FullscreenElementReadyCheck(*aRequest)) {
16056 return false;
16059 Element* elem = aRequest->Element();
16061 RefPtr<nsINode> hideUntil = elem->GetTopmostPopoverAncestor(nullptr, false);
16062 if (!hideUntil) {
16063 hideUntil = OwnerDoc();
16066 RefPtr<Document> doc = aRequest->Document();
16067 doc->HideAllPopoversUntil(*hideUntil, false, true);
16069 // Stash a reference to any existing fullscreen doc, we'll use this later
16070 // to detect if the origin which is fullscreen has changed.
16071 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
16073 // Stores a list of documents which we must dispatch "fullscreenchange"
16074 // too. We're required by the spec to dispatch the events in root-to-leaf
16075 // order, but we traverse the doctree in a leaf-to-root order, so we save
16076 // references to the documents we must dispatch to so that we get the order
16077 // as specified.
16078 AutoTArray<Document*, 8> changed;
16080 // Remember the root document, so that if a fullscreen document is hidden
16081 // we can reset fullscreen state in the remaining visible fullscreen
16082 // documents.
16083 Document* fullScreenRootDoc =
16084 nsContentUtils::GetInProcessSubtreeRootDocument(this);
16086 // If a document is already in fullscreen, then unlock the mouse pointer
16087 // before setting a new document to fullscreen
16088 PointerLockManager::Unlock("Document::ApplyFullscreen");
16090 // Set the fullscreen element. This sets the fullscreen style on the
16091 // element, and the fullscreen-ancestor styles on ancestors of the element
16092 // in this document.
16093 SetFullscreenElement(*elem);
16094 // Set the iframe fullscreen flag.
16095 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
16096 iframe->SetFullscreenFlag(true);
16098 changed.AppendElement(this);
16100 // Propagate up the document hierarchy, setting the fullscreen element as
16101 // the element's container in ancestor documents. This also sets the
16102 // appropriate css styles as well. Note we don't propagate down the
16103 // document hierarchy, the fullscreen element (or its container) is not
16104 // visible there. Stop when we reach the root document.
16105 Document* child = this;
16106 while (true) {
16107 child->SetFullscreenRoot(fullScreenRootDoc);
16109 // When entering fullscreen, reset the RCD's resolution to the intrinsic
16110 // resolution, otherwise the fullscreen content could be sized larger than
16111 // the screen (since fullscreen is implemented using position:fixed and
16112 // fixed elements are sized to the layout viewport).
16113 // This also ensures that things like video controls aren't zoomed in
16114 // when in fullscreen mode.
16115 if (PresShell* presShell = child->GetPresShell()) {
16116 if (RefPtr<MobileViewportManager> manager =
16117 presShell->GetMobileViewportManager()) {
16118 // Save the previous resolution so it can be restored.
16119 child->mSavedResolution = presShell->GetResolution();
16120 presShell->SetResolutionAndScaleTo(
16121 manager->ComputeIntrinsicResolution(),
16122 ResolutionChangeOrigin::MainThreadRestore);
16126 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
16127 "Fullscreen root should be set!");
16128 if (child == fullScreenRootDoc) {
16129 break;
16132 Element* element = child->GetEmbedderElement();
16133 if (!element) {
16134 // We've reached the root.No more changes need to be made
16135 // to the top layer stacks of documents further up the tree.
16136 break;
16139 Document* parent = child->GetInProcessParentDocument();
16140 parent->SetFullscreenElement(*element);
16141 changed.AppendElement(parent);
16142 child = parent;
16145 FullscreenRoots::Add(this);
16147 // If it is the first entry of the fullscreen, trigger an event so
16148 // that the UI can response to this change, e.g. hide chrome, or
16149 // notifying parent process to enter fullscreen. Note that chrome
16150 // code may also want to listen to MozDOMFullscreen:NewOrigin event
16151 // to pop up warning UI.
16152 if (!previousFullscreenDoc) {
16153 nsContentUtils::DispatchEventOnlyToChrome(
16154 this, elem, u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
16155 Cancelable::eNo, /* DefaultAction */ nullptr);
16158 // The origin which is fullscreen gets changed. Trigger an event so
16159 // that the chrome knows to pop up a warning UI. Note that
16160 // previousFullscreenDoc == nullptr upon first entry, we show the warning UI
16161 // directly as soon as chrome document goes into fullscreen state. Also note
16162 // that, in a multi-process browser, the code in content process is
16163 // responsible for sending message with the origin to its parent, and the
16164 // parent shouldn't rely on this event itself.
16165 if (aRequest->mShouldNotifyNewOrigin && previousFullscreenDoc &&
16166 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
16167 DispatchFullscreenNewOriginEvent(this);
16170 // Dispatch "fullscreenchange" events. Note that the loop order is
16171 // reversed so that events are dispatched in the tree order as
16172 // indicated in the spec.
16173 for (Document* d : Reversed(changed)) {
16174 DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement());
16176 aRequest->MayResolvePromise();
16177 return true;
16180 void Document::ClearOrientationPendingPromise() {
16181 mOrientationPendingPromise = nullptr;
16184 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
16185 if (mIsGoingAway) {
16186 return false;
16189 mOrientationPendingPromise = aPromise;
16190 return true;
16193 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
16194 dom::VisibilityState oldState = mVisibilityState;
16195 mVisibilityState = ComputeVisibilityState();
16196 if (oldState != mVisibilityState) {
16197 if (aDispatchEvent == DispatchVisibilityChange::Yes) {
16198 nsContentUtils::DispatchTrustedEvent(this, this, u"visibilitychange"_ns,
16199 CanBubble::eYes, Cancelable::eNo);
16201 NotifyActivityChanged();
16202 if (mVisibilityState == dom::VisibilityState::Visible) {
16203 MaybeActiveMediaComponents();
16206 bool visible = !Hidden();
16207 for (auto* listener : mWorkerListeners) {
16208 listener->OnVisible(visible);
16211 // https://w3c.github.io/screen-wake-lock/#handling-document-loss-of-visibility
16212 if (!visible) {
16213 UnlockAllWakeLocks(WakeLockType::Screen);
16218 void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
16219 mWorkerListeners.Insert(aListener);
16220 aListener->OnVisible(!Hidden());
16223 void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
16224 mWorkerListeners.Remove(aListener);
16227 VisibilityState Document::ComputeVisibilityState() const {
16228 // We have to check a few pieces of information here:
16229 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
16230 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
16231 // want to use GetWindow here because it does weird groveling for windows
16232 // in some cases.
16233 // 3) Is our outer window background? If so, we're hidden.
16234 // Otherwise, we're visible.
16235 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
16236 mWindow->GetOuterWindow()->IsBackground()) {
16237 return dom::VisibilityState::Hidden;
16240 return dom::VisibilityState::Visible;
16243 void Document::PostVisibilityUpdateEvent() {
16244 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
16245 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
16246 DispatchVisibilityChange::Yes);
16247 Dispatch(event.forget());
16250 void Document::MaybeActiveMediaComponents() {
16251 auto* window = GetWindow();
16252 if (!window || !window->ShouldDelayMediaFromStart()) {
16253 return;
16255 window->ActivateMediaComponents();
16258 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
16259 nsINode::AddSizeOfExcludingThis(aWindowSizes,
16260 &aWindowSizes.mDOMSizes.mDOMOtherSize);
16262 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
16263 AddSizeOfNodeTree(*kid, aWindowSizes);
16266 // IMPORTANT: for our ComputedValues measurements, we want to measure
16267 // ComputedValues accessible from DOM elements before ComputedValues not
16268 // accessible from DOM elements (i.e. accessible only from the frame tree).
16270 // Therefore, the measurement of the Document superclass must happen after
16271 // the measurement of DOM nodes (above), because Document contains the
16272 // PresShell, which contains the frame tree.
16273 if (mPresShell) {
16274 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
16277 if (mStyleSet) {
16278 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
16281 aWindowSizes.mPropertyTablesSize +=
16282 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
16284 if (EventListenerManager* elm = GetExistingListenerManager()) {
16285 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
16288 if (mNodeInfoManager) {
16289 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
16292 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
16293 mDOMMediaQueryLists.sizeOfExcludingThis(
16294 aWindowSizes.mState.mMallocSizeOf);
16296 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
16297 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
16298 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
16301 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
16303 for (auto& sheetArray : mAdditionalSheets) {
16304 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
16306 // Lumping in the loader with the style-sheets size is not ideal,
16307 // but most of the things in there are in fact stylesheets, so it
16308 // doesn't seem worthwhile to separate it out.
16309 // This can be null if we've already been unlinked.
16310 if (mCSSLoader) {
16311 aWindowSizes.mLayoutStyleSheetsSize +=
16312 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
16315 aWindowSizes.mDOMSizes.mDOMResizeObserverControllerSize +=
16316 mResizeObservers.ShallowSizeOfExcludingThis(
16317 aWindowSizes.mState.mMallocSizeOf);
16319 if (mAttributeStyles) {
16320 aWindowSizes.mDOMSizes.mDOMOtherSize +=
16321 mAttributeStyles->DOMSizeOfIncludingThis(
16322 aWindowSizes.mState.mMallocSizeOf);
16325 if (mRadioGroupContainer) {
16326 aWindowSizes.mDOMSizes.mDOMOtherSize +=
16327 mRadioGroupContainer->SizeOfIncludingThis(
16328 aWindowSizes.mState.mMallocSizeOf);
16331 aWindowSizes.mDOMSizes.mDOMOtherSize +=
16332 mStyledLinks.ShallowSizeOfExcludingThis(
16333 aWindowSizes.mState.mMallocSizeOf);
16335 // Measurement of the following members may be added later if DMD finds it
16336 // is worthwhile:
16337 // - mMidasCommandManager
16338 // - many!
16341 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
16342 aWindowSizes.mDOMSizes.mDOMOtherSize +=
16343 aWindowSizes.mState.mMallocSizeOf(this);
16344 DocAddSizeOfExcludingThis(aWindowSizes);
16347 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
16348 size_t* aNodeSize) const {
16349 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
16350 // nsDocuments can only appear at the top of the DOM tree, and we use the
16351 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
16352 // be called.
16353 MOZ_CRASH();
16356 /* static */
16357 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
16358 size_t nodeSize = 0;
16359 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
16361 // This is where we transfer the nodeSize obtained from
16362 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
16363 switch (aNode.NodeType()) {
16364 case nsINode::ELEMENT_NODE:
16365 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
16366 break;
16367 case nsINode::TEXT_NODE:
16368 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
16369 break;
16370 case nsINode::CDATA_SECTION_NODE:
16371 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
16372 break;
16373 case nsINode::COMMENT_NODE:
16374 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
16375 break;
16376 default:
16377 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
16378 break;
16381 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
16382 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
16385 if (aNode.IsContent()) {
16386 nsTArray<nsIContent*> anonKids;
16387 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
16388 nsIContent::eAllChildren);
16389 for (nsIContent* anonKid : anonKids) {
16390 AddSizeOfNodeTree(*anonKid, aWindowSizes);
16393 if (auto* element = Element::FromNode(aNode)) {
16394 if (ShadowRoot* shadow = element->GetShadowRoot()) {
16395 AddSizeOfNodeTree(*shadow, aWindowSizes);
16400 // NOTE(emilio): If you feel smart and want to change this function to use
16401 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
16402 // sane way, and kids of <content> won't point to the parent, so we'd never
16403 // find the root node where we should stop at.
16404 for (nsIContent* kid = aNode.GetFirstChild(); kid;
16405 kid = kid->GetNextSibling()) {
16406 AddSizeOfNodeTree(*kid, aWindowSizes);
16410 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
16411 ErrorResult& rv) {
16412 nsCOMPtr<nsIScriptGlobalObject> global =
16413 do_QueryInterface(aGlobal.GetAsSupports());
16414 if (!global) {
16415 rv.Throw(NS_ERROR_UNEXPECTED);
16416 return nullptr;
16419 nsCOMPtr<nsIScriptObjectPrincipal> prin =
16420 do_QueryInterface(aGlobal.GetAsSupports());
16421 if (!prin) {
16422 rv.Throw(NS_ERROR_UNEXPECTED);
16423 return nullptr;
16426 nsCOMPtr<nsIURI> uri;
16427 NS_NewURI(getter_AddRefs(uri), "about:blank");
16428 if (!uri) {
16429 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
16430 return nullptr;
16433 nsCOMPtr<Document> doc;
16434 nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
16435 nullptr, uri, uri, prin->GetPrincipal(),
16436 true, global, DocumentFlavorPlain);
16437 if (NS_FAILED(res)) {
16438 rv.Throw(res);
16439 return nullptr;
16442 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
16444 return doc.forget();
16447 UniquePtr<XPathExpression> Document::CreateExpression(
16448 const nsAString& aExpression, XPathNSResolver* aResolver, ErrorResult& rv) {
16449 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
16452 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
16453 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
16456 already_AddRefed<XPathResult> Document::Evaluate(
16457 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
16458 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
16459 ErrorResult& rv) {
16460 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
16461 aType, aResult, rv);
16464 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
16465 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
16466 if (!item) {
16467 return nullptr;
16469 nsCOMPtr<nsIDocShellTreeOwner> owner;
16470 item->GetTreeOwner(getter_AddRefs(owner));
16471 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
16472 if (!appWin) {
16473 return nullptr;
16475 nsCOMPtr<nsIDocShell> appWinShell;
16476 appWin->GetDocShell(getter_AddRefs(appWinShell));
16477 if (!SameCOMIdentity(appWinShell, item)) {
16478 return nullptr;
16480 return appWin.forget();
16483 WindowContext* Document::GetTopLevelWindowContext() const {
16484 WindowContext* windowContext = GetWindowContext();
16485 return windowContext ? windowContext->TopWindowContext() : nullptr;
16488 Document* Document::GetTopLevelContentDocumentIfSameProcess() {
16489 Document* parent;
16491 if (!mLoadedAsData) {
16492 parent = this;
16493 } else {
16494 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
16495 if (!window) {
16496 return nullptr;
16499 parent = window->GetExtantDoc();
16500 if (!parent) {
16501 return nullptr;
16505 do {
16506 if (parent->IsTopLevelContentDocument()) {
16507 break;
16510 // If we ever have a non-content parent before we hit a toplevel content
16511 // parent, then we're never going to find one. Just bail.
16512 if (!parent->IsContentDocument()) {
16513 return nullptr;
16516 parent = parent->GetInProcessParentDocument();
16517 } while (parent);
16519 return parent;
16522 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
16523 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
16526 void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
16527 MOZ_ASSERT(IsBeingUsedAsImage());
16528 MOZ_ASSERT(aReferencingDocument);
16530 if (!aReferencingDocument->mShouldReportUseCounters) {
16531 // No need to propagate use counters to a document that itself won't report
16532 // use counters.
16533 return;
16536 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16537 ("PropagateImageUseCounters from %s to %s",
16538 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
16539 nsContentUtils::TruncatedURLForDisplay(
16540 aReferencingDocument->mDocumentURI)
16541 .get()));
16543 if (aReferencingDocument->IsBeingUsedAsImage()) {
16544 NS_WARNING(
16545 "Page use counters from nested image documents may not "
16546 "propagate to the top-level document (bug 1657805)");
16549 SetCssUseCounterBits();
16550 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
16551 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
16554 bool Document::HasScriptsBlockedBySandbox() const {
16555 return mSandboxFlags & SANDBOXED_SCRIPTS;
16558 void Document::SetCssUseCounterBits() {
16559 if (StaticPrefs::layout_css_use_counters_enabled()) {
16560 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
16561 auto id = nsCSSPropertyID(i);
16562 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
16563 SetUseCounter(nsCSSProps::UseCounterFor(id));
16568 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
16569 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
16570 auto id = CountedUnknownProperty(i);
16571 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
16572 id)) {
16573 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
16579 void Document::InitUseCounters() {
16580 // We can be called more than once, e.g. when session history navigation shows
16581 // us a second time.
16582 if (mUseCountersInitialized) {
16583 return;
16585 mUseCountersInitialized = true;
16587 if (!ShouldIncludeInTelemetry()) {
16588 return;
16591 // Now we know for sure that we should report use counters from this document.
16592 mShouldReportUseCounters = true;
16594 WindowContext* top = GetWindowContextForPageUseCounters();
16595 if (!top) {
16596 // This is the case for SVG image documents. They are not displayed in a
16597 // window, but we still do want to record document use counters for them.
16599 // Page use counter propagation is handled in PropagateImageUseCounters,
16600 // so there is no need to use the cross-process machinery to send them.
16601 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16602 ("InitUseCounters for a non-displayed document [%s]",
16603 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16604 return;
16607 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16608 if (!wgc) {
16609 return;
16612 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16613 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
16614 " [from %s]",
16615 wgc->InnerWindowId(), top->Id(),
16616 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16618 // Inform the parent process that we will send it page use counters later on.
16619 wgc->SendExpectPageUseCounters(top);
16620 mShouldSendPageUseCounters = true;
16623 // We keep separate counts for individual documents and top-level
16624 // pages to more accurately track how many web pages might break if
16625 // certain features were removed. Consider the case of a single
16626 // HTML document with several SVG images and/or iframes with
16627 // sub-documents of their own. If we maintained a single set of use
16628 // counters and all the sub-documents use a particular feature, then
16629 // telemetry would indicate that we would be breaking N documents if
16630 // that feature were removed. Whereas with a document/top-level
16631 // page split, we can see that N documents would be affected, but
16632 // only a single web page would be affected.
16634 // The difference between the values of these two counts and the
16635 // related use counters below tell us how many pages did *not* use
16636 // the feature in question. For instance, if we see that a given
16637 // session has destroyed 30 content documents, but a particular use
16638 // counter shows only a count of 5, we can infer that the use
16639 // counter was *not* used in 25 of those 30 documents.
16641 // We do things this way, rather than accumulating a boolean flag
16642 // for each use counter, to avoid sending data for features
16643 // that don't get widely used. Doing things in this fashion means
16644 // smaller telemetry payloads and faster processing on the server
16645 // side.
16646 void Document::ReportDocumentUseCounters() {
16647 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
16648 return;
16651 mReportedDocumentUseCounters = true;
16653 // Note that a document is being destroyed. See the comment above for how
16654 // use counter data are interpreted relative to this measurement.
16655 glean::use_counter::content_documents_destroyed.Add();
16657 // Ask all of our resource documents to report their own document use
16658 // counters.
16659 EnumerateExternalResources([](Document& aDoc) {
16660 aDoc.ReportDocumentUseCounters();
16661 return CallState::Continue;
16664 // Copy StyleUseCounters into our document use counters.
16665 SetCssUseCounterBits();
16667 Maybe<nsCString> urlForLogging;
16668 const bool dumpCounters = StaticPrefs::dom_use_counters_dump_document();
16669 if (dumpCounters) {
16670 urlForLogging.emplace(
16671 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()));
16674 // Report our per-document use counters.
16675 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
16676 auto uc = static_cast<UseCounter>(c);
16677 if (!mUseCounters[uc]) {
16678 continue;
16681 const char* metricName = IncrementUseCounter(uc, /* aIsPage = */ false);
16682 if (dumpCounters) {
16683 printf_stderr("USE_COUNTER_DOCUMENT: %s - %s\n", metricName,
16684 urlForLogging->get());
16689 void Document::ReportLCP() {
16690 const nsDOMNavigationTiming* timing = GetNavigationTiming();
16692 if (!ShouldIncludeInTelemetry() || !IsTopLevelContentDocument() || !timing ||
16693 !timing->DocShellHasBeenActiveSinceNavigationStart()) {
16694 return;
16697 TimeStamp lcpTime = timing->GetLargestContentfulRenderTimeStamp();
16699 if (!lcpTime) {
16700 return;
16703 mozilla::glean::perf::largest_contentful_paint.AccumulateRawDuration(
16704 lcpTime - timing->GetNavigationStartTimeStamp());
16706 if (!GetChannel()) {
16707 return;
16710 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
16711 if (!timedChannel) {
16712 return;
16715 TimeStamp responseStart;
16716 timedChannel->GetResponseStart(&responseStart);
16718 if (!responseStart) {
16719 return;
16722 mozilla::glean::perf::largest_contentful_paint_from_response_start
16723 .AccumulateRawDuration(lcpTime - responseStart);
16725 if (profiler_thread_is_being_profiled_for_markers()) {
16726 MarkerInnerWindowId innerWindowID =
16727 MarkerInnerWindowIdFromDocShell(GetDocShell());
16728 GetNavigationTiming()->MaybeAddLCPProfilerMarker(innerWindowID);
16732 void Document::SendPageUseCounters() {
16733 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
16734 return;
16737 // Ask all of our resource documents to send their own document use
16738 // counters to the parent process to be counted as page use counters.
16739 EnumerateExternalResources([](Document& aDoc) {
16740 aDoc.SendPageUseCounters();
16741 return CallState::Continue;
16744 // Send our use counters to the parent process to accumulate them towards the
16745 // page use counters for the top-level document.
16747 // We take our own document use counters (those in mUseCounters) and any child
16748 // document use counters (those in mChildDocumentUseCounters) that have been
16749 // explicitly propagated up to us, which includes resource documents, static
16750 // clones, and SVG images.
16751 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16752 if (!wgc) {
16753 MOZ_ASSERT_UNREACHABLE(
16754 "SendPageUseCounters should be called while we still have access "
16755 "to our WindowContext");
16756 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16757 (" > too late to send page use counters"));
16758 return;
16761 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16762 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
16763 wgc->WindowContext()->Id(),
16764 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
16766 // Copy StyleUseCounters into our document use counters.
16767 SetCssUseCounterBits();
16769 UseCounters counters = mUseCounters | mChildDocumentUseCounters;
16770 wgc->SendAccumulatePageUseCounters(counters);
16773 bool Document::RecomputeResistFingerprinting() {
16774 mOverriddenFingerprintingSettings.reset();
16775 const bool previous = mShouldResistFingerprinting;
16777 RefPtr<BrowsingContext> opener =
16778 GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
16779 // If we have a parent or opener document, defer to it only when we have a
16780 // null principal (e.g. a sandboxed iframe or a data: uri) or when the
16781 // document's principal matches. This means we will defer about:blank,
16782 // about:srcdoc, blob and same-origin iframes/popups to the parent/opener,
16783 // but not cross-origin ones. Cross-origin iframes/popups may inherit a
16784 // CookieJarSettings.mShouldRFP = false bit however, which will be respected.
16785 auto shouldInheritFrom = [this](Document* aDoc) {
16786 return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
16787 this->NodePrincipal()->GetIsNullPrincipal());
16790 if (shouldInheritFrom(mParentDocument)) {
16791 MOZ_LOG(
16792 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16793 ("Inside RecomputeResistFingerprinting with URI %s and deferring "
16794 "to parent document %s",
16795 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16796 mParentDocument->GetDocumentURI()->GetSpecOrDefault().get()));
16797 mShouldResistFingerprinting = mParentDocument->ShouldResistFingerprinting(
16798 RFPTarget::IsAlwaysEnabledForPrecompute);
16799 mOverriddenFingerprintingSettings =
16800 mParentDocument->mOverriddenFingerprintingSettings;
16801 } else if (opener && shouldInheritFrom(opener->GetDocument())) {
16802 MOZ_LOG(
16803 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16804 ("Inside RecomputeResistFingerprinting with URI %s and deferring to "
16805 "opener document %s",
16806 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16807 opener->GetDocument()->GetDocumentURI()->GetSpecOrDefault().get()));
16808 mShouldResistFingerprinting =
16809 opener->GetDocument()->ShouldResistFingerprinting(
16810 RFPTarget::IsAlwaysEnabledForPrecompute);
16811 mOverriddenFingerprintingSettings =
16812 opener->GetDocument()->mOverriddenFingerprintingSettings;
16813 } else if (nsContentUtils::IsChromeDoc(this)) {
16814 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16815 ("Inside RecomputeResistFingerprinting with a ChromeDoc"));
16817 mShouldResistFingerprinting = false;
16818 mOverriddenFingerprintingSettings.reset();
16819 } else if (mChannel) {
16820 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16821 ("Inside RecomputeResistFingerprinting with URI %s",
16822 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get()
16823 : "null"));
16824 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
16825 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16827 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16828 mOverriddenFingerprintingSettings =
16829 loadInfo->GetOverriddenFingerprintingSettings();
16830 } else {
16831 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16832 ("Inside RecomputeResistFingerprinting fallback case."));
16833 // We still preserve the behavior in the fallback case. But, it means there
16834 // might be some cases we haven't considered yet and we need to investigate
16835 // them.
16836 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
16837 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16838 mOverriddenFingerprintingSettings.reset();
16841 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16842 ("Finished RecomputeResistFingerprinting with result %x",
16843 mShouldResistFingerprinting));
16845 bool changed = previous != mShouldResistFingerprinting;
16846 if (changed) {
16847 if (auto win = nsGlobalWindowInner::Cast(GetInnerWindow())) {
16848 win->RefreshReduceTimerPrecisionCallerType();
16851 return changed;
16854 bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const {
16855 return mShouldResistFingerprinting &&
16856 nsRFPService::IsRFPEnabledFor(this->IsInPrivateBrowsing(), aTarget,
16857 mOverriddenFingerprintingSettings);
16860 void Document::RecordCanvasUsage(CanvasUsage& aUsage) {
16861 // Limit the number of recent canvas extraction uses that are tracked.
16862 const size_t kTrackedCanvasLimit = 8;
16863 // Timeout between different canvas extractions.
16864 const uint64_t kTimeoutUsec = 3000 * 1000;
16866 uint64_t now = PR_Now();
16867 if ((mCanvasUsage.Length() > kTrackedCanvasLimit) ||
16868 ((now - mLastCanvasUsage) > kTimeoutUsec)) {
16869 mCanvasUsage.ClearAndRetainStorage();
16872 mCanvasUsage.AppendElement(aUsage);
16873 mLastCanvasUsage = now;
16875 nsCString originNoSuffix;
16876 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
16877 return;
16880 nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsage, GetChannel(),
16881 originNoSuffix);
16884 void Document::RecordFontFingerprinting() {
16885 nsCString originNoSuffix;
16886 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
16887 return;
16890 nsRFPService::MaybeReportFontFingerprinter(GetChannel(), originNoSuffix);
16893 bool Document::IsInPrivateBrowsing() const { return mIsInPrivateBrowsing; }
16895 WindowContext* Document::GetWindowContextForPageUseCounters() const {
16896 if (mDisplayDocument) {
16897 // If we are a resource document, then go through it to find the
16898 // top-level document.
16899 return mDisplayDocument->GetWindowContextForPageUseCounters();
16902 if (mOriginalDocument) {
16903 // For static clones (print preview documents), contribute page use counters
16904 // towards the original document.
16905 return mOriginalDocument->GetWindowContextForPageUseCounters();
16908 WindowContext* wc = GetTopLevelWindowContext();
16909 if (!wc || !wc->GetBrowsingContext()->IsContent()) {
16910 return nullptr;
16913 return wc;
16916 void Document::UpdateIntersections(TimeStamp aNowTime) {
16917 if (!mIntersectionObservers.IsEmpty()) {
16918 DOMHighResTimeStamp time = 0;
16919 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
16920 if (Performance* perf = win->GetPerformance()) {
16921 time = perf->TimeStampToDOMHighResForRendering(aNowTime);
16924 for (DOMIntersectionObserver* observer : mIntersectionObservers) {
16925 observer->Update(*this, time);
16927 Dispatch(NewRunnableMethod("Document::NotifyIntersectionObservers", this,
16928 &Document::NotifyIntersectionObservers));
16930 EnumerateSubDocuments([aNowTime](Document& aDoc) {
16931 aDoc.UpdateIntersections(aNowTime);
16932 return CallState::Continue;
16936 static void UpdateEffectsOnBrowsingContext(BrowsingContext* aBc,
16937 const IntersectionInput& aInput,
16938 bool aIncludeInactive) {
16939 Element* el = aBc->GetEmbedderElement();
16940 if (!el) {
16941 return;
16943 auto* rb = RemoteBrowser::GetFrom(el);
16944 if (!rb) {
16945 return;
16947 const bool isInactiveTop = aBc->IsTop() && !aBc->IsActive();
16948 nsSubDocumentFrame* subDocFrame = do_QueryFrame(el->GetPrimaryFrame());
16949 rb->UpdateEffects([&] {
16950 if (isInactiveTop) {
16951 // We don't use the visible rect of top browsers, so if they're inactive
16952 // don't waste time computing it. Note that for OOP iframes, it might seem
16953 // a bit wasteful to bother computing this in background tabs, but:
16954 // * We need it so that child frames know if they're visible or not, in
16955 // case they're preserving layers for example.
16956 // * If we're hidden, our refresh driver is throttled and we don't run
16957 // this code very often anyways.
16958 return EffectsInfo::FullyHidden();
16960 const IntersectionOutput output = DOMIntersectionObserver::Intersect(
16961 aInput, *el, DOMIntersectionObserver::BoxToUse::Content);
16962 if (!output.Intersects()) {
16963 // XXX do we want to pass the scale and such down even if out of the
16964 // viewport?
16965 return EffectsInfo::FullyHidden();
16967 MOZ_ASSERT(el->GetPrimaryFrame(), "How do we intersect without a frame?");
16968 if (MOZ_UNLIKELY(NS_WARN_IF(!subDocFrame))) {
16969 // <frame> not inside a <frameset> might not create a subdoc frame,
16970 // for example.
16971 return EffectsInfo::FullyHidden();
16973 Maybe<nsRect> visibleRect = subDocFrame->GetVisibleRect();
16974 // If we're paginated, we the display list rect might not be reasonable,
16975 // because it is the one from the last display item painted. We assume the
16976 // frame is fully visible, lacking something better.
16977 if (subDocFrame->PresContext()->IsPaginated()) {
16978 visibleRect = Some(subDocFrame->GetDestRect());
16980 if (!visibleRect) {
16981 // If we have no visible rect (e.g., because we are zero-sized) we
16982 // still want to provide the intersection rect in order to get the
16983 // right throttling behavior.
16984 visibleRect.emplace(*output.mIntersectionRect -
16985 output.mTargetRect.TopLeft());
16987 gfx::MatrixScales rasterScale = subDocFrame->GetRasterScale();
16988 ParentLayerToScreenScale2D transformToAncestorScale =
16989 ParentLayerToParentLayerScale(
16990 subDocFrame->PresShell()->GetCumulativeResolution()) *
16991 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
16992 subDocFrame);
16993 return EffectsInfo::VisibleWithinRect(visibleRect, rasterScale,
16994 transformToAncestorScale);
16995 }());
16996 if (subDocFrame && (!isInactiveTop || aIncludeInactive)) {
16997 if (nsFrameLoader* fl = subDocFrame->FrameLoader()) {
16998 fl->UpdatePositionAndSize(subDocFrame);
17003 void Document::UpdateRemoteFrameEffects(bool aIncludeInactive) {
17004 auto margin = DOMIntersectionObserver::LazyLoadingRootMargin();
17005 const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
17006 *this, /* aRoot = */ nullptr, &margin);
17007 if (auto* wc = GetWindowContext()) {
17008 for (const RefPtr<BrowsingContext>& child : wc->Children()) {
17009 UpdateEffectsOnBrowsingContext(child, input, aIncludeInactive);
17012 if (XRE_IsParentProcess()) {
17013 if (auto* bc = GetBrowsingContext(); bc && bc->IsTop()) {
17014 bc->Canonical()->CallOnAllTopDescendants(
17015 [&](CanonicalBrowsingContext* aDescendant) {
17016 UpdateEffectsOnBrowsingContext(aDescendant, input,
17017 aIncludeInactive);
17018 return CallState::Continue;
17020 /* aIncludeNestedBrowsers = */ false);
17023 EnumerateSubDocuments([aIncludeInactive](Document& aDoc) {
17024 aDoc.UpdateRemoteFrameEffects(aIncludeInactive);
17025 return CallState::Continue;
17029 void Document::SynchronouslyUpdateRemoteBrowserDimensions(
17030 bool aIncludeInactive) {
17031 FlushPendingNotifications(FlushType::Layout);
17032 UpdateRemoteFrameEffects(aIncludeInactive);
17035 void Document::NotifyIntersectionObservers() {
17036 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
17037 mIntersectionObservers);
17038 for (const auto& observer : observers) {
17039 // MOZ_KnownLive because the 'observers' array guarantees to keep it
17040 // alive.
17041 MOZ_KnownLive(observer)->Notify();
17045 DOMIntersectionObserver& Document::EnsureLazyLoadObserver() {
17046 if (!mLazyLoadObserver) {
17047 mLazyLoadObserver = DOMIntersectionObserver::CreateLazyLoadObserver(*this);
17049 return *mLazyLoadObserver;
17052 void Document::ObserveForLastRememberedSize(Element& aElement) {
17053 if (NS_WARN_IF(!IsActive())) {
17054 return;
17056 mElementsObservedForLastRememberedSize.Insert(&aElement);
17059 void Document::UnobserveForLastRememberedSize(Element& aElement) {
17060 mElementsObservedForLastRememberedSize.Remove(&aElement);
17063 void Document::UpdateLastRememberedSizes() {
17064 auto shouldRemoveElement = [&](auto* element) {
17065 if (element->GetComposedDoc() != this) {
17066 element->RemoveLastRememberedBSize();
17067 element->RemoveLastRememberedISize();
17068 return true;
17070 return !element->GetPrimaryFrame();
17073 for (auto it = mElementsObservedForLastRememberedSize.begin(),
17074 end = mElementsObservedForLastRememberedSize.end();
17075 it != end; ++it) {
17076 if (shouldRemoveElement(*it)) {
17077 mElementsObservedForLastRememberedSize.Remove(it);
17078 continue;
17080 const auto element = *it;
17081 MOZ_ASSERT(element->GetComposedDoc() == this);
17082 nsIFrame* frame = element->GetPrimaryFrame();
17083 MOZ_ASSERT(frame);
17085 // As for ResizeObserver, skip nodes hidden `content-visibility`.
17086 if (frame->IsHiddenByContentVisibilityOnAnyAncestor()) {
17087 continue;
17090 MOZ_ASSERT(!frame->IsLineParticipant() || frame->IsReplaced(),
17091 "Should have unobserved non-replaced inline.");
17092 MOZ_ASSERT(!frame->HidesContent(),
17093 "Should have unobserved element skipping its contents.");
17094 const nsStylePosition* stylePos = frame->StylePosition();
17095 const WritingMode wm = frame->GetWritingMode();
17096 bool canUpdateBSize = stylePos->ContainIntrinsicBSize(wm).HasAuto();
17097 bool canUpdateISize = stylePos->ContainIntrinsicISize(wm).HasAuto();
17098 MOZ_ASSERT(canUpdateBSize || !element->HasLastRememberedBSize(),
17099 "Should have removed the last remembered block size.");
17100 MOZ_ASSERT(canUpdateISize || !element->HasLastRememberedISize(),
17101 "Should have removed the last remembered inline size.");
17102 MOZ_ASSERT(canUpdateBSize || canUpdateISize,
17103 "Should have unobserved if we can't update any size.");
17105 AutoTArray<LogicalPixelSize, 1> contentSizeList =
17106 ResizeObserver::CalculateBoxSize(element,
17107 ResizeObserverBoxOptions::Content_box,
17108 /* aForceFragmentHandling */ true);
17109 MOZ_ASSERT(!contentSizeList.IsEmpty());
17111 if (canUpdateBSize) {
17112 float bSize = 0;
17113 for (const auto& current : contentSizeList) {
17114 bSize += current.BSize();
17116 element->SetLastRememberedBSize(bSize);
17118 if (canUpdateISize) {
17119 float iSize = 0;
17120 for (const auto& current : contentSizeList) {
17121 iSize = std::max(iSize, current.ISize());
17123 element->SetLastRememberedISize(iSize);
17128 void Document::NotifyLayerManagerRecreated() {
17129 NotifyActivityChanged();
17130 EnumerateSubDocuments([](Document& aSubDoc) {
17131 aSubDoc.NotifyLayerManagerRecreated();
17132 return CallState::Continue;
17136 XPathEvaluator* Document::XPathEvaluator() {
17137 if (!mXPathEvaluator) {
17138 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
17140 return mXPathEvaluator.get();
17143 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
17144 return mCachedEncoder.forget();
17147 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
17148 mCachedEncoder = aEncoder;
17151 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
17153 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
17155 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
17156 mStateObjectContainer = scContainer;
17157 mCachedStateObject = JS::UndefinedValue();
17158 mCachedStateObjectValid = false;
17161 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
17162 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
17163 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
17164 ELEMENT_NODE);
17165 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
17167 nsCOMPtr<Element> element;
17168 DebugOnly<nsresult> rv =
17169 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
17170 mozilla::dom::NOT_FROM_PARSER);
17172 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
17173 return element.forget();
17176 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
17177 BrowsingContext* aContext) {
17178 aContext->PreOrderWalk([&](BrowsingContext* aBC) {
17179 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
17180 if (RefPtr<Document> doc = win->GetExtantDoc()) {
17181 SuppressDocument(doc);
17182 mDocuments.AppendElement(doc);
17188 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
17189 BrowsingContextGroup* aGroup) {
17190 for (const auto& bc : aGroup->Toplevels()) {
17191 SuppressBrowsingContext(bc);
17195 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
17196 SyncOperationBehavior aSyncBehavior)
17197 : mSyncBehavior(aSyncBehavior) {
17198 mMicroTaskLevel = 0;
17199 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
17200 mMicroTaskLevel = ccjs->MicroTaskLevel();
17201 ccjs->SetMicroTaskLevel(0);
17203 if (aDoc) {
17204 mBrowsingContext = aDoc->GetBrowsingContext();
17205 if (InputTaskManager::CanSuspendInputEvent()) {
17206 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
17207 SuppressBrowsingContextGroup(bcg);
17209 } else if (mBrowsingContext) {
17210 SuppressBrowsingContext(mBrowsingContext->Top());
17212 if (mBrowsingContext &&
17213 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
17214 InputTaskManager::CanSuspendInputEvent()) {
17215 mBrowsingContext->Group()->IncInputEventSuspensionLevel();
17220 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
17221 if (RefPtr<nsGlobalWindowInner> win =
17222 nsGlobalWindowInner::Cast(aDoc->GetInnerWindow())) {
17223 win->GetTimeoutManager()->BeginSyncOperation();
17225 aDoc->SetIsInSyncOperation(true);
17228 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
17229 if (RefPtr<nsGlobalWindowInner> win =
17230 nsGlobalWindowInner::Cast(aDoc->GetInnerWindow())) {
17231 win->GetTimeoutManager()->EndSyncOperation();
17233 aDoc->SetIsInSyncOperation(false);
17236 nsAutoSyncOperation::~nsAutoSyncOperation() {
17237 UnsuppressDocuments();
17238 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
17239 if (ccjs) {
17240 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
17242 if (mBrowsingContext &&
17243 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
17244 InputTaskManager::CanSuspendInputEvent()) {
17245 mBrowsingContext->Group()->DecInputEventSuspensionLevel();
17249 void Document::SetIsInSyncOperation(bool aSync) {
17250 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
17251 ccjs->UpdateMicroTaskSuppressionGeneration();
17254 if (aSync) {
17255 ++mInSyncOperationCount;
17256 } else {
17257 --mInSyncOperationCount;
17261 gfxUserFontSet* Document::GetUserFontSet() {
17262 if (!mFontFaceSet) {
17263 return nullptr;
17266 return mFontFaceSet->GetImpl();
17269 void Document::FlushUserFontSet() {
17270 if (!mFontFaceSetDirty) {
17271 return;
17274 mFontFaceSetDirty = false;
17276 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
17277 nsTArray<nsFontFaceRuleContainer> rules;
17278 RefPtr<PresShell> presShell = GetPresShell();
17279 if (presShell) {
17280 MOZ_ASSERT(mStyleSetFilled);
17281 EnsureStyleSet().AppendFontFaceRules(rules);
17284 if (!mFontFaceSet && !rules.IsEmpty()) {
17285 mFontFaceSet = FontFaceSet::CreateForDocument(this);
17288 bool changed = false;
17289 if (mFontFaceSet) {
17290 changed = mFontFaceSet->UpdateRules(rules);
17293 // We need to enqueue a style change reflow (for later) to
17294 // reflect that we're modifying @font-face rules. (However,
17295 // without a reflow, nothing will happen to start any downloads
17296 // that are needed.)
17297 if (changed && presShell) {
17298 if (nsPresContext* presContext = presShell->GetPresContext()) {
17299 presContext->UserFontSetUpdated();
17305 void Document::MarkUserFontSetDirty() {
17306 if (mFontFaceSetDirty) {
17307 return;
17309 mFontFaceSetDirty = true;
17310 if (PresShell* presShell = GetPresShell()) {
17311 presShell->EnsureStyleFlush();
17315 FontFaceSet* Document::Fonts() {
17316 if (!mFontFaceSet) {
17317 mFontFaceSet = FontFaceSet::CreateForDocument(this);
17318 FlushUserFontSet();
17320 return mFontFaceSet;
17323 void Document::ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp) {
17324 MOZ_ASSERT(!aTimeStamp.IsNull());
17326 if (!mLastScrollLinkedEffectDetectionTime.IsNull() &&
17327 mLastScrollLinkedEffectDetectionTime >= aTimeStamp) {
17328 return;
17331 if (mLastScrollLinkedEffectDetectionTime.IsNull()) {
17332 // Report to console just once.
17333 nsContentUtils::ReportToConsole(
17334 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
17335 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
17338 mLastScrollLinkedEffectDetectionTime = aTimeStamp;
17341 bool Document::HasScrollLinkedEffect() const {
17342 if (nsPresContext* pc = GetPresContext()) {
17343 return mLastScrollLinkedEffectDetectionTime ==
17344 pc->RefreshDriver()->MostRecentRefresh();
17347 return false;
17350 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
17351 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
17352 // Setting has user interction on a discarded browsing context has
17353 // no effect.
17354 Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
17358 bool Document::GetSHEntryHasUserInteraction() {
17359 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
17360 return topWc->GetSHEntryHasUserInteraction();
17362 return false;
17365 void Document::SetUserHasInteracted() {
17366 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
17367 ("Document %p has been interacted by user.", this));
17369 // We maybe need to update the user-interaction permission.
17370 MaybeStoreUserInteractionAsPermission();
17372 // For purposes of reducing irrelevant session history entries on
17373 // the back button, we annotate entries with whether they had user
17374 // interaction. This is gated on its own flag on the WindowContext
17375 // (instead of mUserHasInteracted) to account for the fact that multiple
17376 // top-level SH entries can be associated with the same document.
17377 // Thus, whenever we create a new SH entry for this document,
17378 // this flag is reset.
17379 if (!GetSHEntryHasUserInteraction()) {
17380 nsIDocShell* docShell = GetDocShell();
17381 if (docShell) {
17382 nsCOMPtr<nsISHEntry> currentEntry;
17383 bool oshe;
17384 nsresult rv =
17385 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
17386 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
17387 currentEntry->SetHasUserInteraction(true);
17390 SetSHEntryHasUserInteraction(true);
17393 if (mUserHasInteracted) {
17394 return;
17397 mUserHasInteracted = true;
17399 if (mChannel) {
17400 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17401 loadInfo->SetDocumentHasUserInteracted(true);
17403 // Tell the parent process about user interaction
17404 if (auto* wgc = GetWindowGlobalChild()) {
17405 wgc->SendUpdateDocumentHasUserInteracted(true);
17408 MaybeAllowStorageForOpenerAfterUserInteraction();
17411 BrowsingContext* Document::GetBrowsingContext() const {
17412 return mDocumentContainer ? mDocumentContainer->GetBrowsingContext()
17413 : nullptr;
17416 void Document::NotifyUserGestureActivation(
17417 UserActivation::Modifiers
17418 aModifiers /* = UserActivation::Modifiers::None() */) {
17419 // https://html.spec.whatwg.org/multipage/interaction.html#activation-notification
17420 // 1. "Assert: document is fully active."
17421 RefPtr<BrowsingContext> currentBC = GetBrowsingContext();
17422 if (!currentBC) {
17423 return;
17426 RefPtr<WindowContext> currentWC = GetWindowContext();
17427 if (!currentWC) {
17428 return;
17431 // 2. "Let windows be « document's relevant global object"
17432 // Instead of assembling a list, we just call notify for wanted windows as we
17433 // find them
17434 currentWC->NotifyUserGestureActivation(aModifiers);
17436 // 3. "...windows with the active window of each of document's ancestor
17437 // navigables."
17438 for (WindowContext* wc = currentWC; wc; wc = wc->GetParentWindowContext()) {
17439 wc->NotifyUserGestureActivation(aModifiers);
17442 // 4. "windows with the active window of each of document's descendant
17443 // navigables, filtered to include only those navigables whose active
17444 // document's origin is same origin with document's origin"
17445 currentBC->PreOrderWalk([&](BrowsingContext* bc) {
17446 WindowContext* wc = bc->GetCurrentWindowContext();
17447 if (!wc) {
17448 return;
17451 // Check same-origin as current document
17452 WindowGlobalChild* wgc = wc->GetWindowGlobalChild();
17453 if (!wgc || !wgc->IsSameOriginWith(currentWC)) {
17454 return;
17457 wc->NotifyUserGestureActivation(aModifiers);
17460 // If there has been a user activation, mark the current session history
17461 // entry as having been interacted with.
17462 SetSHEntryHasUserInteraction(true);
17465 bool Document::HasBeenUserGestureActivated() {
17466 RefPtr<WindowContext> wc = GetWindowContext();
17467 return wc && wc->HasBeenUserGestureActivated();
17470 bool Document::ConsumeTextDirectiveUserActivation() {
17471 if (!mChannel) {
17472 return false;
17474 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17475 if (!loadInfo) {
17476 return false;
17478 const bool textDirectiveUserActivation =
17479 loadInfo->GetTextDirectiveUserActivation();
17480 loadInfo->SetTextDirectiveUserActivation(false);
17481 return textDirectiveUserActivation;
17484 DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
17485 if (RefPtr<WindowContext> wc = GetWindowContext()) {
17486 if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) {
17487 if (Performance* perf = innerWindow->GetPerformance()) {
17488 return perf->GetDOMTiming()->TimeStampToDOMHighRes(
17489 wc->GetUserGestureStart());
17494 NS_WARNING(
17495 "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
17496 return 0;
17499 void Document::ClearUserGestureActivation() {
17500 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
17501 bc = bc->Top();
17502 bc->PreOrderWalk([&](BrowsingContext* aBC) {
17503 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
17504 windowContext->NotifyResetUserGestureActivation();
17510 bool Document::HasValidTransientUserGestureActivation() const {
17511 RefPtr<WindowContext> wc = GetWindowContext();
17512 return wc && wc->HasValidTransientUserGestureActivation();
17515 bool Document::ConsumeTransientUserGestureActivation() {
17516 RefPtr<WindowContext> wc = GetWindowContext();
17517 return wc && wc->ConsumeTransientUserGestureActivation();
17520 bool Document::GetTransientUserGestureActivationModifiers(
17521 UserActivation::Modifiers* aModifiers) {
17522 RefPtr<WindowContext> wc = GetWindowContext();
17523 return wc && wc->GetTransientUserGestureActivationModifiers(aModifiers);
17526 void Document::SetDocTreeHadMedia() {
17527 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
17528 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
17529 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
17533 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
17534 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
17535 return;
17538 // This will probably change for project fission, but currently this document
17539 // and the opener are on the same process. In the future, we should make this
17540 // part async.
17541 nsPIDOMWindowInner* inner = GetInnerWindow();
17542 if (NS_WARN_IF(!inner)) {
17543 return;
17546 // We care about first-party tracking resources only.
17547 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
17548 return;
17551 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17552 if (NS_WARN_IF(!outer)) {
17553 return;
17556 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
17557 if (!openerBC) {
17558 // No opener.
17559 return;
17562 // We want to ensure the following check works for both fission mode and
17563 // non-fission mode:
17564 // "If the opener is not a 3rd party and if this window is not a 3rd party
17565 // with respect to the opener, we should not continue."
17567 // In non-fission mode, the opener and the opened window are in the same
17568 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
17569 // In fission mode, if this window is not a 3rd party with respect to the
17570 // opener, they must be in the same process, so we can still use
17571 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
17572 // party.
17573 if (openerBC->IsInProcess()) {
17574 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
17575 if (NS_WARN_IF(!outerOpener)) {
17576 return;
17579 nsCOMPtr<nsPIDOMWindowInner> openerInner =
17580 outerOpener->GetCurrentInnerWindow();
17581 if (NS_WARN_IF(!openerInner)) {
17582 return;
17585 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
17586 if (NS_WARN_IF(!openerDocument)) {
17587 return;
17590 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
17591 if (NS_WARN_IF(!openerURI)) {
17592 return;
17595 // If the opener is not a 3rd party and if this window is not
17596 // a 3rd party with respect to the opener, we should not continue.
17597 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
17598 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
17599 return;
17603 // We don't care when the asynchronous work finishes here.
17604 Unused << StorageAccessAPIHelper::AllowAccessForOnChildProcess(
17605 NodePrincipal(), openerBC,
17606 ContentBlockingNotifier::eOpenerAfterUserInteraction);
17609 namespace {
17611 // Documents can stay alive for days. We don't want to update the permission
17612 // value at any user-interaction, and, using a timer triggered any X seconds
17613 // should be good enough. 'X' is taken from
17614 // privacy.userInteraction.document.interval pref.
17615 // We also want to store the user-interaction before shutting down, and, for
17616 // this reason, this class implements nsIAsyncShutdownBlocker interface.
17617 class UserInteractionTimer final : public Runnable,
17618 public nsITimerCallback,
17619 public nsIAsyncShutdownBlocker {
17620 public:
17621 NS_DECL_ISUPPORTS_INHERITED
17623 explicit UserInteractionTimer(Document* aDocument)
17624 : Runnable("UserInteractionTimer"),
17625 mPrincipal(aDocument->NodePrincipal()),
17626 mDocument(aDocument) {
17627 static int32_t userInteractionTimerId = 0;
17628 // Blocker names must be unique. Let's create it now because when needed,
17629 // the document could be already gone.
17630 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
17631 ++userInteractionTimerId, aDocument);
17633 // For ContentBlockingUserInteraction we care about user-interaction stored
17634 // only for top-level documents and documents with access to the Storage
17635 // Access API
17636 if (aDocument->IsTopLevelContentDocument()) {
17637 mShouldRecordContentBlockingUserInteraction = true;
17638 } else {
17639 bool hasSA;
17640 nsresult rv = aDocument->HasStorageAccessSync(hasSA);
17641 mShouldRecordContentBlockingUserInteraction = NS_SUCCEEDED(rv) && hasSA;
17645 // Runnable interface
17647 NS_IMETHOD
17648 Run() override {
17649 uint32_t interval =
17650 StaticPrefs::privacy_userInteraction_document_interval();
17651 if (!interval) {
17652 return NS_OK;
17655 RefPtr<UserInteractionTimer> self = this;
17656 auto raii =
17657 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
17659 nsresult rv = NS_NewTimerWithCallback(
17660 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
17661 NS_ENSURE_SUCCESS(rv, NS_OK);
17663 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
17664 NS_ENSURE_TRUE(!!phase, NS_OK);
17666 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
17667 __LINE__, u"UserInteractionTimer shutdown"_ns);
17668 NS_ENSURE_SUCCESS(rv, NS_OK);
17670 raii.release();
17671 return NS_OK;
17674 // nsITimerCallback interface
17676 NS_IMETHOD
17677 Notify(nsITimer* aTimer) override {
17678 StoreUserInteraction();
17679 return NS_OK;
17682 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
17683 using nsINamed::GetName;
17684 #endif
17686 // nsIAsyncShutdownBlocker interface
17688 NS_IMETHOD
17689 GetName(nsAString& aName) override {
17690 aName = mBlockerName;
17691 return NS_OK;
17694 NS_IMETHOD
17695 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
17696 CancelTimerAndStoreUserInteraction();
17697 return NS_OK;
17700 NS_IMETHOD
17701 GetState(nsIPropertyBag**) override { return NS_OK; }
17703 private:
17704 ~UserInteractionTimer() = default;
17706 void StoreUserInteraction() {
17707 // Remove the shutting down blocker
17708 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
17709 if (phase) {
17710 phase->RemoveBlocker(this);
17713 // If the document is not gone, let's reset its timer flag.
17714 nsCOMPtr<Document> document(mDocument);
17715 if (document) {
17716 if (mShouldRecordContentBlockingUserInteraction) {
17717 ContentBlockingUserInteraction::Observe(mPrincipal);
17719 Unused << BounceTrackingProtection::RecordUserActivation(
17720 mDocument->GetWindowContext());
17721 document->ResetUserInteractionTimer();
17725 void CancelTimerAndStoreUserInteraction() {
17726 if (mTimer) {
17727 mTimer->Cancel();
17728 mTimer = nullptr;
17731 StoreUserInteraction();
17734 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
17735 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
17736 NS_ENSURE_TRUE(!!svc, nullptr);
17738 nsCOMPtr<nsIAsyncShutdownClient> phase;
17739 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
17740 NS_ENSURE_SUCCESS(rv, nullptr);
17742 return phase.forget();
17745 nsCOMPtr<nsIPrincipal> mPrincipal;
17746 WeakPtr<Document> mDocument;
17747 bool mShouldRecordContentBlockingUserInteraction = false;
17749 nsCOMPtr<nsITimer> mTimer;
17751 nsString mBlockerName;
17754 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
17755 nsIAsyncShutdownBlocker)
17757 } // namespace
17759 void Document::MaybeStoreUserInteractionAsPermission() {
17760 if (!mUserHasInteracted) {
17761 // First interaction, let's store this info now.
17762 Unused << BounceTrackingProtection::RecordUserActivation(
17763 GetWindowContext());
17765 // For ContentBlockingUserInteraction we care about user-interaction stored
17766 // only for top-level documents and documents with access to the Storage
17767 // Access API
17768 if (!IsTopLevelContentDocument()) {
17769 bool hasSA;
17770 nsresult rv = HasStorageAccessSync(hasSA);
17771 if (NS_FAILED(rv) || !hasSA) {
17772 return;
17775 ContentBlockingUserInteraction::Observe(NodePrincipal());
17776 return;
17779 if (mHasUserInteractionTimerScheduled) {
17780 return;
17783 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
17784 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
17785 EventQueuePriority::Idle);
17786 if (NS_WARN_IF(NS_FAILED(rv))) {
17787 return;
17790 // This value will be reset by the timer.
17791 mHasUserInteractionTimerScheduled = true;
17794 void Document::ResetUserInteractionTimer() {
17795 mHasUserInteractionTimerScheduled = false;
17798 bool Document::IsExtensionPage() const {
17799 return BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
17802 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
17803 if (!mPermissionDelegateHandler) {
17804 mPermissionDelegateHandler = MakeAndAddRef<PermissionDelegateHandler>(this);
17807 if (!mPermissionDelegateHandler->Initialize()) {
17808 mPermissionDelegateHandler = nullptr;
17811 return mPermissionDelegateHandler;
17814 void Document::ScheduleResizeObserversNotification() const {
17815 if (!mPresShell) {
17816 return;
17818 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
17819 rd->EnsureResizeObserverUpdateHappens();
17823 static void FlushLayoutForWholeBrowsingContextTree(Document& aDoc) {
17824 const ChangesToFlush ctf(FlushType::Layout, /* aFlushAnimations = */ false);
17825 BrowsingContext* bc = aDoc.GetBrowsingContext();
17826 if (bc && bc->GetExtantDocument() == &aDoc) {
17827 RefPtr<BrowsingContext> top = bc->Top();
17828 top->PreOrderWalk([ctf](BrowsingContext* aCur) {
17829 if (Document* doc = aCur->GetExtantDocument()) {
17830 doc->FlushPendingNotifications(ctf);
17833 } else {
17834 // If there is no browsing context, or we're not the current document of the
17835 // browsing context, then we just flush this document itself.
17836 aDoc.FlushPendingNotifications(ctf);
17840 void Document::DetermineProximityToViewportAndNotifyResizeObservers() {
17841 uint32_t shallowestTargetDepth = 0;
17842 bool initialResetOfScrolledIntoViewFlagsDone = false;
17843 while (true) {
17844 // Flush layout, so that any callback functions' style changes / resizes
17845 // get a chance to take effect. The callback functions may do changes in its
17846 // sub-documents or ancestors, so flushing layout for the whole browsing
17847 // context tree makes sure we don't miss anyone.
17848 FlushLayoutForWholeBrowsingContextTree(*this);
17850 // Last remembered sizes are recorded "at the time that ResizeObserver
17851 // events are determined and delivered".
17852 // https://drafts.csswg.org/css-sizing-4/#last-remembered
17854 // We do it right after layout to make sure sizes are up-to-date. If we do
17855 // it after determining the proximities to viewport of
17856 // 'content-visibility: auto' nodes, and if one of such node ever becomes
17857 // relevant to the user, then we would be incorrectly recording the size
17858 // of its rendering when it was skipping its content.
17859 UpdateLastRememberedSizes();
17861 if (PresShell* presShell = GetPresShell()) {
17862 auto result = presShell->DetermineProximityToViewport();
17863 if (result.mHadInitialDetermination) {
17864 continue;
17866 if (result.mAnyScrollIntoViewFlag) {
17867 // Not defined in the spec: It's possible that some elements with
17868 // content-visibility: auto were forced to be visible in order to
17869 // perform scrollIntoView() so clear their flags now and restart the
17870 // loop.
17871 // See https://github.com/w3c/csswg-drafts/issues/9337
17872 presShell->ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags();
17873 presShell->ScheduleContentRelevancyUpdate(
17874 ContentRelevancyReason::Visible);
17875 if (!initialResetOfScrolledIntoViewFlagsDone) {
17876 initialResetOfScrolledIntoViewFlagsDone = true;
17877 continue;
17882 // To avoid infinite resize loop, we only gather all active observations
17883 // that have the depth of observed target element more than current
17884 // shallowestTargetDepth.
17885 GatherAllActiveResizeObservations(shallowestTargetDepth);
17887 if (!HasAnyActiveResizeObservations()) {
17888 break;
17891 DebugOnly<uint32_t> oldShallowestTargetDepth = shallowestTargetDepth;
17892 shallowestTargetDepth = BroadcastAllActiveResizeObservations();
17893 NS_ASSERTION(oldShallowestTargetDepth < shallowestTargetDepth,
17894 "shallowestTargetDepth should be getting strictly deeper");
17897 if (HasAnySkippedResizeObservations()) {
17898 if (nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow()) {
17899 // Per spec, we deliver an error if the document has any skipped
17900 // observations. Also, we re-register via ScheduleNotification().
17901 RootedDictionary<ErrorEventInit> init(RootingCx());
17902 init.mMessage.AssignLiteral(
17903 "ResizeObserver loop completed with undelivered notifications.");
17904 init.mBubbles = false;
17905 init.mCancelable = false;
17907 nsEventStatus status = nsEventStatus_eIgnore;
17908 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
17909 MOZ_ASSERT(sgo);
17910 if (NS_WARN_IF(sgo->HandleScriptError(init, &status))) {
17911 status = nsEventStatus_eIgnore;
17913 } else {
17914 // We don't fire error events at any global for non-window JS on the main
17915 // thread.
17918 // We need to deliver pending notifications in next cycle.
17919 ScheduleResizeObserversNotification();
17923 void Document::GatherAllActiveResizeObservations(uint32_t aDepth) {
17924 for (ResizeObserver* observer : mResizeObservers) {
17925 observer->GatherActiveObservations(aDepth);
17929 uint32_t Document::BroadcastAllActiveResizeObservations() {
17930 uint32_t shallowestTargetDepth = std::numeric_limits<uint32_t>::max();
17932 // Copy the observers as this invokes the callbacks and could register and
17933 // unregister observers at will.
17934 const auto observers =
17935 ToTArray<nsTArray<RefPtr<ResizeObserver>>>(mResizeObservers);
17936 for (const auto& observer : observers) {
17937 // MOZ_KnownLive because 'observers' is guaranteed to keep it
17938 // alive.
17940 // This can go away once
17941 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
17942 uint32_t targetDepth =
17943 MOZ_KnownLive(observer)->BroadcastActiveObservations();
17944 if (targetDepth < shallowestTargetDepth) {
17945 shallowestTargetDepth = targetDepth;
17949 return shallowestTargetDepth;
17952 bool Document::HasAnySkippedResizeObservations() const {
17953 for (const auto& observer : mResizeObservers) {
17954 if (observer->HasSkippedObservations()) {
17955 return true;
17958 return false;
17961 bool Document::HasAnyActiveResizeObservations() const {
17962 for (const auto& observer : mResizeObservers) {
17963 if (observer->HasActiveObservations()) {
17964 return true;
17967 return false;
17970 void Document::ClearStaleServoData() {
17971 DocumentStyleRootIterator iter(this);
17972 while (Element* root = iter.GetNextStyleRoot()) {
17973 RestyleManager::ClearServoDataFromSubtree(root);
17977 // https://drafts.csswg.org/css-view-transitions-1/#dom-document-startviewtransition
17978 already_AddRefed<ViewTransition> Document::StartViewTransition(
17979 const Optional<OwningNonNull<ViewTransitionUpdateCallback>>& aCallback) {
17980 // Steps 1-3
17981 RefPtr transition = new ViewTransition(
17982 *this, aCallback.WasPassed() ? &aCallback.Value() : nullptr);
17983 if (Hidden()) {
17984 // Step 4:
17986 // If document's visibility state is "hidden", then skip transition with an
17987 // "InvalidStateError" DOMException, and return transition.
17988 transition->SkipTransition(SkipTransitionReason::DocumentHidden);
17989 return transition.forget();
17991 if (mActiveViewTransition) {
17992 // Step 5:
17993 // If document's active view transition is not null, then skip that view
17994 // transition with an "AbortError" DOMException in this's relevant Realm.
17995 mActiveViewTransition->SkipTransition(
17996 SkipTransitionReason::ClobberedActiveTransition);
17998 // Step 6: Set document's active view transition to transition.
17999 mActiveViewTransition = transition;
18001 if (mPresShell) {
18002 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
18003 rd->EnsureViewTransitionOperationsHappen();
18006 // Step 7: return transition
18007 return transition.forget();
18010 void Document::ClearActiveViewTransition() { mActiveViewTransition = nullptr; }
18012 void Document::PerformPendingViewTransitionOperations() {
18013 if (mActiveViewTransition) {
18014 mActiveViewTransition->PerformPendingOperations();
18016 EnumerateSubDocuments([](Document& aDoc) {
18017 aDoc.PerformPendingViewTransitionOperations();
18018 return CallState::Continue;
18022 Selection* Document::GetSelection(ErrorResult& aRv) {
18023 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
18024 if (!window) {
18025 return nullptr;
18028 if (!window->IsCurrentInnerWindow()) {
18029 return nullptr;
18032 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
18035 void Document::MakeBrowsingContextNonSynthetic() {
18036 if (BrowsingContext* bc = GetBrowsingContext()) {
18037 if (bc->GetSyntheticDocumentContainer()) {
18038 Unused << bc->SetSyntheticDocumentContainer(false);
18043 nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
18044 // Step 1: check if cookie permissions are available or denied to this
18045 // document's principal
18046 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
18047 if (!inner) {
18048 aHasStorageAccess = false;
18049 return NS_OK;
18051 Maybe<bool> resultBecauseCookiesApproved =
18052 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
18053 CookieJarSettings(), NodePrincipal());
18054 if (resultBecauseCookiesApproved.isSome()) {
18055 if (resultBecauseCookiesApproved.value()) {
18056 aHasStorageAccess = true;
18057 return NS_OK;
18058 } else {
18059 aHasStorageAccess = false;
18060 return NS_OK;
18064 // Step 2: Check if the browser settings determine whether or not this
18065 // document has access to its unpartitioned cookies.
18066 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
18067 bool isOnThirdPartySkipList = false;
18068 if (mChannel) {
18069 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18070 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
18071 nsILoadInfo::StoragePermissionAllowListed;
18073 bool isThirdPartyTracker =
18074 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
18075 Maybe<bool> resultBecauseBrowserSettings =
18076 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
18077 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
18078 isThirdPartyTracker);
18079 if (resultBecauseBrowserSettings.isSome()) {
18080 if (resultBecauseBrowserSettings.value()) {
18081 aHasStorageAccess = true;
18082 return NS_OK;
18083 } else {
18084 aHasStorageAccess = false;
18085 return NS_OK;
18089 // Step 3: Check if the location of this call (embedded, top level, same-site)
18090 // determines if cookies are permitted or not.
18091 Maybe<bool> resultBecauseCallContext =
18092 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
18093 false);
18094 if (resultBecauseCallContext.isSome()) {
18095 if (resultBecauseCallContext.value()) {
18096 aHasStorageAccess = true;
18097 return NS_OK;
18098 } else {
18099 aHasStorageAccess = false;
18100 return NS_OK;
18104 // Step 4: Check if the permissions for this document determine if if has
18105 // access or is denied cookies.
18106 Maybe<bool> resultBecausePreviousPermission =
18107 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
18108 this, false);
18109 if (resultBecausePreviousPermission.isSome()) {
18110 if (resultBecausePreviousPermission.value()) {
18111 aHasStorageAccess = true;
18112 return NS_OK;
18113 } else {
18114 aHasStorageAccess = false;
18115 return NS_OK;
18118 // If you get here, we default to not giving you permission.
18119 aHasStorageAccess = false;
18120 return NS_OK;
18123 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
18124 mozilla::ErrorResult& aRv) {
18125 nsIGlobalObject* global = GetScopeObject();
18126 if (!global) {
18127 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
18128 return nullptr;
18131 RefPtr<Promise> promise =
18132 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
18133 if (aRv.Failed()) {
18134 return nullptr;
18137 if (!IsCurrentActiveDocument()) {
18138 promise->MaybeRejectWithInvalidStateError(
18139 "hasStorageAccess requires an active document");
18140 return promise.forget();
18143 bool hasStorageAccess;
18144 nsresult rv = HasStorageAccessSync(hasStorageAccess);
18145 if (NS_FAILED(rv)) {
18146 promise->MaybeRejectWithUndefined();
18147 } else {
18148 promise->MaybeResolve(hasStorageAccess);
18151 return promise.forget();
18154 RefPtr<Document::GetContentBlockingEventsPromise>
18155 Document::GetContentBlockingEvents() {
18156 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
18157 if (!wgc) {
18158 return nullptr;
18161 return wgc->SendGetContentBlockingEvents()->Then(
18162 GetCurrentSerialEventTarget(), __func__,
18163 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
18164 ResolveOrRejectValue& aValue) {
18165 if (aValue.IsResolve()) {
18166 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
18167 aValue.ResolveValue(), __func__);
18170 return Document::GetContentBlockingEventsPromise::CreateAndReject(
18171 false, __func__);
18175 StorageAccessAPIHelper::PerformPermissionGrant
18176 Document::CreatePermissionGrantPromise(
18177 nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal,
18178 bool aHasUserInteraction, bool aRequireUserInteraction,
18179 const Maybe<nsCString>& aTopLevelBaseDomain, bool aFrameOnly) {
18180 MOZ_ASSERT(aInnerWindow);
18181 MOZ_ASSERT(aPrincipal);
18182 RefPtr<Document> self(this);
18183 RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
18184 RefPtr<nsIPrincipal> principal(aPrincipal);
18186 return [inner, self, principal, aHasUserInteraction, aRequireUserInteraction,
18187 aTopLevelBaseDomain, aFrameOnly]() {
18188 RefPtr<StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::Private>
18189 p = new StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
18190 Private(__func__);
18192 // Before we prompt, see if we are same-site
18193 if (aFrameOnly) {
18194 nsIChannel* channel = self->GetChannel();
18195 if (channel) {
18196 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
18197 if (!loadInfo->GetIsThirdPartyContextToTopWindow()) {
18198 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
18199 return p;
18204 RefPtr<PWindowGlobalChild::GetStorageAccessPermissionPromise> promise;
18205 // Test the permission
18206 MOZ_ASSERT(XRE_IsContentProcess());
18208 WindowGlobalChild* wgc = inner->GetWindowGlobalChild();
18209 MOZ_ASSERT(wgc);
18211 promise = wgc->SendGetStorageAccessPermission(true);
18212 MOZ_ASSERT(promise);
18213 promise->Then(
18214 GetCurrentSerialEventTarget(), __func__,
18215 [self, p, inner, principal, aHasUserInteraction,
18216 aRequireUserInteraction, aTopLevelBaseDomain,
18217 aFrameOnly](uint32_t aAction) {
18218 if (aAction == nsIPermissionManager::ALLOW_ACTION) {
18219 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
18220 return;
18222 if (aAction == nsIPermissionManager::DENY_ACTION) {
18223 p->Reject(false, __func__);
18224 return;
18227 // We require user activation before conducting a permission request
18228 // See
18229 // https://privacycg.github.io/storage-access/#dom-document-requeststorageaccess
18230 // where we "If has transient activation is false: ..." immediately
18231 // before we "Let permissionState be the result of requesting
18232 // permission to use "storage-access"" from in parallel.
18233 if (!aHasUserInteraction && aRequireUserInteraction) {
18234 // Report an error to the console for this case
18235 nsContentUtils::ReportToConsole(
18236 nsIScriptError::errorFlag,
18237 nsLiteralCString("requestStorageAccess"), self,
18238 nsContentUtils::eDOM_PROPERTIES,
18239 "RequestStorageAccessUserGesture");
18240 p->Reject(false, __func__);
18241 return;
18244 // Create the user prompt
18245 RefPtr<StorageAccessPermissionRequest> sapr =
18246 StorageAccessPermissionRequest::Create(
18247 inner, principal, aTopLevelBaseDomain, aFrameOnly,
18248 // Allow
18249 [p] {
18250 Telemetry::AccumulateCategorical(
18251 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
18252 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
18254 // Block
18255 [p] {
18256 Telemetry::AccumulateCategorical(
18257 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
18258 p->Reject(false, __func__);
18261 using PromptResult = ContentPermissionRequestBase::PromptResult;
18262 PromptResult pr = sapr->CheckPromptPrefs();
18264 if (pr == PromptResult::Pending) {
18265 // We're about to show a prompt, record the request attempt
18266 Telemetry::AccumulateCategorical(
18267 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
18270 // Try to auto-grant the storage access so the user doesn't see the
18271 // prompt.
18272 self->AutomaticStorageAccessPermissionCanBeGranted(
18273 aHasUserInteraction)
18274 ->Then(
18275 GetCurrentSerialEventTarget(), __func__,
18276 // If the autogrant check didn't fail, call this function
18277 [p, pr, sapr,
18278 inner](const Document::
18279 AutomaticStorageAccessPermissionGrantPromise::
18280 ResolveOrRejectValue& aValue) -> void {
18281 // Make a copy because we can't modified copy-captured
18282 // lambda variables.
18283 PromptResult pr2 = pr;
18285 // If the user didn't already click "allow" and we can
18286 // autogrant, do that!
18287 bool storageAccessCanBeGrantedAutomatically =
18288 aValue.IsResolve() && aValue.ResolveValue();
18289 bool autoGrant = false;
18290 if (pr2 == PromptResult::Pending &&
18291 storageAccessCanBeGrantedAutomatically) {
18292 pr2 = PromptResult::Granted;
18293 autoGrant = true;
18295 Telemetry::AccumulateCategorical(
18296 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
18297 AllowAutomatically);
18300 // If we can complete the permission request, do so.
18301 if (pr2 != PromptResult::Pending) {
18302 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
18303 pr2 == PromptResult::Denied);
18304 if (pr2 == PromptResult::Granted) {
18305 StorageAccessAPIHelper::StorageAccessPromptChoices
18306 choice = StorageAccessAPIHelper::eAllow;
18307 if (autoGrant) {
18308 choice = StorageAccessAPIHelper::eAllowAutoGrant;
18310 if (!autoGrant) {
18311 p->Resolve(choice, __func__);
18312 } else {
18313 // We capture sapr here to prevent it from destructing
18314 // before the callbacks complete.
18315 sapr->MaybeDelayAutomaticGrants()->Then(
18316 GetCurrentSerialEventTarget(), __func__,
18317 [p, sapr, choice] {
18318 p->Resolve(choice, __func__);
18320 [p, sapr] { p->Reject(false, __func__); });
18322 return;
18324 p->Reject(false, __func__);
18325 return;
18328 // If we get here, the auto-decision failed and we need to
18329 // wait for the user prompt to complete.
18330 sapr->RequestDelayedTask(
18331 GetMainThreadSerialEventTarget(),
18332 ContentPermissionRequestBase::DelayedTaskType::Request);
18335 [p](mozilla::ipc::ResponseRejectReason aError) {
18336 p->Reject(false, __func__);
18337 return p;
18340 return p;
18344 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
18345 mozilla::ErrorResult& aRv) {
18346 nsIGlobalObject* global = GetScopeObject();
18347 if (!global) {
18348 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
18349 return nullptr;
18352 RefPtr<Promise> promise = Promise::Create(global, aRv);
18353 if (aRv.Failed()) {
18354 return nullptr;
18357 if (!IsCurrentActiveDocument()) {
18358 promise->MaybeRejectWithInvalidStateError(
18359 "requestStorageAccess requires an active document");
18360 return promise.forget();
18363 // Get a pointer to the inner window- We need this for convenience sake
18364 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
18365 if (!inner) {
18366 ConsumeTransientUserGestureActivation();
18367 promise->MaybeRejectWithNotAllowedError(
18368 "requestStorageAccess not allowed"_ns);
18369 return promise.forget();
18372 // Step 1: Check if the principal calling this has a permission that lets
18373 // them use cookies or forbids them from using cookies.
18374 // This is outside of the spec of the StorageAccess API, but makes the return
18375 // values to have proper semantics.
18376 Maybe<bool> resultBecauseCookiesApproved =
18377 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
18378 CookieJarSettings(), NodePrincipal());
18379 if (resultBecauseCookiesApproved.isSome()) {
18380 if (resultBecauseCookiesApproved.value()) {
18381 promise->MaybeResolveWithUndefined();
18382 return promise.forget();
18383 } else {
18384 ConsumeTransientUserGestureActivation();
18385 promise->MaybeRejectWithNotAllowedError(
18386 "requestStorageAccess not allowed"_ns);
18387 return promise.forget();
18391 // Step 2: Check if the browser settings always allow or deny cookies.
18392 // We should always return a resolved promise if the cookieBehavior is ACCEPT.
18393 // This is outside of the spec of the StorageAccess API, but makes the return
18394 // values to have proper semantics.
18395 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
18396 bool isOnThirdPartySkipList = false;
18397 if (mChannel) {
18398 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18399 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
18400 nsILoadInfo::StoragePermissionAllowListed;
18402 bool isThirdPartyTracker =
18403 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
18404 Maybe<bool> resultBecauseBrowserSettings =
18405 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
18406 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
18407 isThirdPartyTracker);
18408 if (resultBecauseBrowserSettings.isSome()) {
18409 if (resultBecauseBrowserSettings.value()) {
18410 promise->MaybeResolveWithUndefined();
18411 return promise.forget();
18412 } else {
18413 ConsumeTransientUserGestureActivation();
18414 promise->MaybeRejectWithNotAllowedError(
18415 "requestStorageAccess not allowed"_ns);
18416 return promise.forget();
18420 // Step 3: Check if the Document calling requestStorageAccess has anything to
18421 // gain from storage access. It should be embedded, non-null, etc.
18422 Maybe<bool> resultBecauseCallContext =
18423 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
18424 true);
18425 if (resultBecauseCallContext.isSome()) {
18426 if (resultBecauseCallContext.value()) {
18427 promise->MaybeResolveWithUndefined();
18428 return promise.forget();
18429 } else {
18430 ConsumeTransientUserGestureActivation();
18431 promise->MaybeRejectWithNotAllowedError(
18432 "requestStorageAccess not allowed"_ns);
18433 return promise.forget();
18437 // Step 4: Check if we already allowed or denied storage access for this
18438 // document's storage key.
18439 Maybe<bool> resultBecausePreviousPermission =
18440 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
18441 this, true);
18442 if (resultBecausePreviousPermission.isSome()) {
18443 if (resultBecausePreviousPermission.value()) {
18444 promise->MaybeResolveWithUndefined();
18445 return promise.forget();
18446 } else {
18447 ConsumeTransientUserGestureActivation();
18448 promise->MaybeRejectWithNotAllowedError(
18449 "requestStorageAccess not allowed"_ns);
18450 return promise.forget();
18454 // Get pointers to some objects that will be used in the async portion
18455 RefPtr<BrowsingContext> bc = GetBrowsingContext();
18456 RefPtr<nsGlobalWindowOuter> outer =
18457 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
18458 if (!outer) {
18459 ConsumeTransientUserGestureActivation();
18460 promise->MaybeRejectWithNotAllowedError(
18461 "requestStorageAccess not allowed"_ns);
18462 return promise.forget();
18464 RefPtr<Document> self(this);
18466 // Step 5. Start an async call to request storage access. This will either
18467 // perform an automatic decision or notify the user, then perform some follow
18468 // on work changing state to reflect the result of the API. If it resolves,
18469 // the request was granted. If it rejects it was denied.
18470 StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
18471 this, inner, bc, NodePrincipal(),
18472 self->HasValidTransientUserGestureActivation(), true, true,
18473 ContentBlockingNotifier::eStorageAccessAPI, true)
18474 ->Then(
18475 GetCurrentSerialEventTarget(), __func__,
18476 [inner] { return inner->SaveStorageAccessPermissionGranted(); },
18477 [] {
18478 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
18480 ->Then(
18481 GetCurrentSerialEventTarget(), __func__,
18482 [promise] { promise->MaybeResolveWithUndefined(); },
18483 [promise, self] {
18484 self->ConsumeTransientUserGestureActivation();
18485 promise->MaybeRejectWithNotAllowedError(
18486 "requestStorageAccess not allowed"_ns);
18489 return promise.forget();
18492 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
18493 const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
18494 mozilla::ErrorResult& aRv) {
18495 nsIGlobalObject* global = GetScopeObject();
18496 if (!global) {
18497 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
18498 return nullptr;
18500 RefPtr<Promise> promise = Promise::Create(global, aRv);
18501 if (aRv.Failed()) {
18502 return nullptr;
18505 // Step 0: Check that we have user activation before proceeding to prevent
18506 // rapid calls to the API to leak information.
18507 if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) {
18508 // Report an error to the console for this case
18509 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
18510 nsLiteralCString("requestStorageAccess"),
18511 this, nsContentUtils::eDOM_PROPERTIES,
18512 "RequestStorageAccessUserGesture");
18513 ConsumeTransientUserGestureActivation();
18514 promise->MaybeRejectWithNotAllowedError(
18515 "requestStorageAccess not allowed"_ns);
18516 return promise.forget();
18519 // Step 1: Check if the provided URI is different-site to this Document
18520 nsCOMPtr<nsIURI> thirdPartyURI;
18521 nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
18522 if (NS_WARN_IF(NS_FAILED(rv))) {
18523 aRv.Throw(rv);
18524 return nullptr;
18526 bool isThirdPartyDocument;
18527 rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument);
18528 if (NS_WARN_IF(NS_FAILED(rv))) {
18529 aRv.Throw(rv);
18530 return nullptr;
18532 Maybe<bool> resultBecauseBrowserSettings =
18533 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
18534 CookieJarSettings(), isThirdPartyDocument, false, true);
18535 if (resultBecauseBrowserSettings.isSome()) {
18536 if (resultBecauseBrowserSettings.value()) {
18537 promise->MaybeResolveWithUndefined();
18538 return promise.forget();
18540 ConsumeTransientUserGestureActivation();
18541 promise->MaybeRejectWithNotAllowedError(
18542 "requestStorageAccess not allowed"_ns);
18543 return promise.forget();
18546 // Step 2: Check that this Document is same-site to the top, and check that
18547 // we have user activation if we require it.
18548 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
18549 CheckSameSiteCallingContextDecidesStorageAccessAPI(
18550 this, aRequireUserActivation);
18551 if (resultBecauseCallContext.isSome()) {
18552 if (resultBecauseCallContext.value()) {
18553 promise->MaybeResolveWithUndefined();
18554 return promise.forget();
18556 ConsumeTransientUserGestureActivation();
18557 promise->MaybeRejectWithNotAllowedError(
18558 "requestStorageAccess not allowed"_ns);
18559 return promise.forget();
18562 // Step 3: Get some useful variables that can be captured by the lambda for
18563 // the asynchronous portion
18564 RefPtr<BrowsingContext> bc = GetBrowsingContext();
18565 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
18566 if (!inner) {
18567 ConsumeTransientUserGestureActivation();
18568 promise->MaybeRejectWithNotAllowedError(
18569 "requestStorageAccess not allowed"_ns);
18570 return promise.forget();
18572 RefPtr<nsGlobalWindowOuter> outer =
18573 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
18574 if (!outer) {
18575 ConsumeTransientUserGestureActivation();
18576 promise->MaybeRejectWithNotAllowedError(
18577 "requestStorageAccess not allowed"_ns);
18578 return promise.forget();
18580 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
18581 thirdPartyURI, NodePrincipal()->OriginAttributesRef());
18582 if (!principal) {
18583 ConsumeTransientUserGestureActivation();
18584 promise->MaybeRejectWithNotAllowedError(
18585 "requestStorageAccess not allowed"_ns);
18586 return promise.forget();
18589 RefPtr<Document> self(this);
18590 bool hasUserActivation = HasValidTransientUserGestureActivation();
18592 // Consume user activation before entering the async part of this method.
18593 // This prevents usage of other transient activation-gated APIs.
18594 ConsumeTransientUserGestureActivation();
18596 ContentChild* cc = ContentChild::GetSingleton();
18597 if (!cc) {
18598 // TODO(bug 1778561): Make this work in non-content processes.
18599 promise->MaybeRejectWithUndefined();
18600 return promise.forget();
18603 // Step 4a: Start the async part of this function. Check the cookie
18604 // permission, but this can't be done in this process. We needs the cookie
18605 // permission of the URL as if it were embedded on this page, so we need to
18606 // make this check in the ContentParent.
18607 StorageAccessAPIHelper::
18608 AsyncCheckCookiesPermittedDecidesStorageAccessAPIOnChildProcess(
18609 GetBrowsingContext(), principal)
18610 ->Then(
18611 GetCurrentSerialEventTarget(), __func__,
18612 [inner, thirdPartyURI, bc, principal, hasUserActivation,
18613 aRequireUserActivation, self,
18614 promise](Maybe<bool> cookieResult) {
18615 // Handle the result of the cookie permission check that took
18616 // place in the ContentParent.
18617 if (cookieResult.isSome()) {
18618 if (cookieResult.value()) {
18619 return MozPromise<int, bool, true>::CreateAndResolve(
18620 true, __func__);
18622 return MozPromise<int, bool, true>::CreateAndReject(false,
18623 __func__);
18626 // Step 4b: Check for the existing storage access permission
18627 nsAutoCString type;
18628 bool ok = AntiTrackingUtils::CreateStoragePermissionKey(
18629 principal, type);
18630 if (!ok) {
18631 return MozPromise<int, bool, true>::CreateAndReject(false,
18632 __func__);
18634 if (AntiTrackingUtils::CheckStoragePermission(
18635 self->NodePrincipal(), type,
18636 self->IsInPrivateBrowsing(), nullptr, 0)) {
18637 return MozPromise<int, bool, true>::CreateAndResolve(
18638 true, __func__);
18641 // Step 4c: Try to request storage access, either automatically
18642 // or with a user-prompt. This is the part that is async in the
18643 // typical requestStorageAccess function.
18644 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
18645 self, inner, bc, principal, hasUserActivation,
18646 aRequireUserActivation, false,
18647 ContentBlockingNotifier::
18648 ePrivilegeStorageAccessForOriginAPI,
18649 true);
18651 // If the IPC rejects, we should reject our promise here which
18652 // will cause a rejection of the promise we already returned
18653 [promise]() {
18654 return MozPromise<int, bool, true>::CreateAndReject(false,
18655 __func__);
18657 ->Then(
18658 GetCurrentSerialEventTarget(), __func__,
18659 // If the previous handlers resolved, we should reinstate user
18660 // activation and resolve the promise we returned in Step 5.
18661 [self, inner, promise] {
18662 inner->SaveStorageAccessPermissionGranted();
18663 self->NotifyUserGestureActivation();
18664 promise->MaybeResolveWithUndefined();
18666 // If the previous handler rejected, we should reject the promise
18667 // returned by this function.
18668 [promise] {
18669 promise->MaybeRejectWithNotAllowedError(
18670 "requestStorageAccess not allowed"_ns);
18673 // Step 5: While the async stuff is happening, we should return the promise so
18674 // our caller can continue executing.
18675 return promise.forget();
18678 already_AddRefed<Promise> Document::RequestStorageAccessUnderSite(
18679 const nsAString& aSerializedSite, ErrorResult& aRv) {
18680 nsIGlobalObject* global = GetScopeObject();
18681 if (!global) {
18682 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
18683 return nullptr;
18685 RefPtr<Promise> promise = Promise::Create(global, aRv);
18686 if (aRv.Failed()) {
18687 return nullptr;
18690 // Check that we have user activation before proceeding to prevent
18691 // rapid calls to the API to leak information.
18692 if (!ConsumeTransientUserGestureActivation()) {
18693 // Report an error to the console for this case
18694 nsContentUtils::ReportToConsole(
18695 nsIScriptError::errorFlag, "requestStorageAccess"_ns, this,
18696 nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture");
18697 promise->MaybeRejectWithUndefined();
18698 return promise.forget();
18701 // Check if the provided URI is different-site to this Document
18702 nsCOMPtr<nsIURI> siteURI;
18703 nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite);
18704 if (NS_WARN_IF(NS_FAILED(rv))) {
18705 promise->MaybeRejectWithUndefined();
18706 return promise.forget();
18708 bool isCrossSiteArgument;
18709 rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument);
18710 if (NS_WARN_IF(NS_FAILED(rv))) {
18711 aRv.Throw(rv);
18712 return nullptr;
18714 if (!isCrossSiteArgument) {
18715 promise->MaybeRejectWithUndefined();
18716 return promise.forget();
18719 // Check if this party has broad cookie permissions.
18720 Maybe<bool> resultBecauseCookiesApproved =
18721 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
18722 CookieJarSettings(), NodePrincipal());
18723 if (resultBecauseCookiesApproved.isSome()) {
18724 if (resultBecauseCookiesApproved.value()) {
18725 promise->MaybeResolveWithUndefined();
18726 return promise.forget();
18728 promise->MaybeRejectWithUndefined();
18729 return promise.forget();
18732 // Check if browser settings preclude this document getting storage
18733 // access under the provided site
18734 Maybe<bool> resultBecauseBrowserSettings =
18735 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
18736 CookieJarSettings(), true, false, true);
18737 if (resultBecauseBrowserSettings.isSome()) {
18738 if (resultBecauseBrowserSettings.value()) {
18739 promise->MaybeResolveWithUndefined();
18740 return promise.forget();
18742 promise->MaybeRejectWithUndefined();
18743 return promise.forget();
18746 // Check that this Document is same-site to the top
18747 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
18748 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
18749 if (resultBecauseCallContext.isSome()) {
18750 if (resultBecauseCallContext.value()) {
18751 promise->MaybeResolveWithUndefined();
18752 return promise.forget();
18754 promise->MaybeRejectWithUndefined();
18755 return promise.forget();
18758 nsCOMPtr<nsIPrincipal> principal(NodePrincipal());
18760 // Test if the permission this is requesting is already set
18761 nsCOMPtr<nsIPrincipal> argumentPrincipal =
18762 BasePrincipal::CreateContentPrincipal(
18763 siteURI, NodePrincipal()->OriginAttributesRef());
18764 if (!argumentPrincipal) {
18765 ConsumeTransientUserGestureActivation();
18766 promise->MaybeRejectWithUndefined();
18767 return promise.forget();
18769 nsCString originNoSuffix;
18770 rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix);
18771 if (NS_WARN_IF(NS_FAILED(rv))) {
18772 promise->MaybeRejectWithUndefined();
18773 return promise.forget();
18776 ContentChild* cc = ContentChild::GetSingleton();
18777 MOZ_ASSERT(cc);
18778 RefPtr<Document> self(this);
18779 cc->SendTestStorageAccessPermission(argumentPrincipal, originNoSuffix)
18780 ->Then(
18781 GetCurrentSerialEventTarget(), __func__,
18782 [promise, siteURI,
18783 self](const ContentChild::TestStorageAccessPermissionPromise::
18784 ResolveValueType& aResult) {
18785 if (aResult) {
18786 return StorageAccessAPIHelper::
18787 StorageAccessPermissionGrantPromise::CreateAndResolve(
18788 StorageAccessAPIHelper::eAllow, __func__);
18790 // Get a grant for the storage access permission that will be set
18791 // when this is completed in the embedding context
18792 nsCString serializedSite;
18793 RefPtr<nsEffectiveTLDService> etld =
18794 nsEffectiveTLDService::GetInstance();
18795 if (!etld) {
18796 return StorageAccessAPIHelper::
18797 StorageAccessPermissionGrantPromise::CreateAndReject(
18798 false, __func__);
18800 nsresult rv = etld->GetSite(siteURI, serializedSite);
18801 if (NS_FAILED(rv)) {
18802 return StorageAccessAPIHelper::
18803 StorageAccessPermissionGrantPromise::CreateAndReject(
18804 false, __func__);
18806 return self->CreatePermissionGrantPromise(
18807 self->GetInnerWindow(), self->NodePrincipal(), true, true,
18808 Some(serializedSite), false)();
18810 [](const ContentChild::TestStorageAccessPermissionPromise::
18811 RejectValueType& aResult) {
18812 return StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
18813 CreateAndReject(false, __func__);
18815 ->Then(
18816 GetCurrentSerialEventTarget(), __func__,
18817 [promise, principal, siteURI](int result) {
18818 ContentChild* cc = ContentChild::GetSingleton();
18819 if (!cc) {
18820 // TODO(bug 1778561): Make this work in non-content processes.
18821 promise->MaybeRejectWithUndefined();
18822 return;
18824 // Set a permission in the parent process that this document wants
18825 // storage access under the argument's site, resolving our returned
18826 // promise on success
18827 cc->SendSetAllowStorageAccessRequestFlag(principal, siteURI)
18828 ->Then(
18829 GetCurrentSerialEventTarget(), __func__,
18830 [promise](bool success) {
18831 if (success) {
18832 promise->MaybeResolveWithUndefined();
18833 } else {
18834 promise->MaybeRejectWithUndefined();
18837 [promise](mozilla::ipc::ResponseRejectReason reason) {
18838 promise->MaybeRejectWithUndefined();
18841 [promise](bool result) { promise->MaybeRejectWithUndefined(); });
18843 // Return the promise that is resolved in the async handler above
18844 return promise.forget();
18847 already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite(
18848 const nsAString& aSerializedOrigin, ErrorResult& aRv) {
18849 nsIGlobalObject* global = GetScopeObject();
18850 if (!global) {
18851 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
18852 return nullptr;
18854 RefPtr<Promise> promise = Promise::Create(global, aRv);
18855 if (aRv.Failed()) {
18856 return nullptr;
18859 // Check that the provided URI is different-site to this Document
18860 nsCOMPtr<nsIURI> argumentURI;
18861 nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin);
18862 if (NS_WARN_IF(NS_FAILED(rv))) {
18863 promise->MaybeRejectWithUndefined();
18864 return promise.forget();
18866 bool isCrossSiteArgument;
18867 rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument);
18868 if (NS_WARN_IF(NS_FAILED(rv))) {
18869 aRv.Throw(rv);
18870 return nullptr;
18872 if (!isCrossSiteArgument) {
18873 promise->MaybeRejectWithUndefined();
18874 return promise.forget();
18877 // Check if browser settings preclude this document getting storage
18878 // access under the provided site
18879 Maybe<bool> resultBecauseBrowserSettings =
18880 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
18881 CookieJarSettings(), true, false, true);
18882 if (resultBecauseBrowserSettings.isSome()) {
18883 if (resultBecauseBrowserSettings.value()) {
18884 promise->MaybeResolveWithUndefined();
18885 return promise.forget();
18887 promise->MaybeRejectWithUndefined();
18888 return promise.forget();
18891 // Check that this Document is same-site to the top
18892 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
18893 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
18894 if (resultBecauseCallContext.isSome()) {
18895 if (resultBecauseCallContext.value()) {
18896 promise->MaybeResolveWithUndefined();
18897 return promise.forget();
18899 promise->MaybeRejectWithUndefined();
18900 return promise.forget();
18903 // Create principal of the embedded site requesting storage access
18904 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
18905 argumentURI, NodePrincipal()->OriginAttributesRef());
18906 if (!principal) {
18907 promise->MaybeRejectWithUndefined();
18908 return promise.forget();
18911 // Get versions of these objects that we can use in lambdas for callbacks
18912 RefPtr<Document> self(this);
18913 RefPtr<BrowsingContext> bc = GetBrowsingContext();
18914 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
18916 // Test that the permission was set by a call to RequestStorageAccessUnderSite
18917 // from a top level document that is same-site with the argument
18918 ContentChild* cc = ContentChild::GetSingleton();
18919 if (!cc) {
18920 // TODO(bug 1778561): Make this work in non-content processes.
18921 promise->MaybeRejectWithUndefined();
18922 return promise.forget();
18924 cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI)
18925 ->Then(
18926 GetCurrentSerialEventTarget(), __func__,
18927 [inner, bc, self, principal](bool success) {
18928 if (success) {
18929 // If that resolved with true, check that we don't already have a
18930 // permission that gives cookie access.
18931 return StorageAccessAPIHelper::
18932 AsyncCheckCookiesPermittedDecidesStorageAccessAPIOnChildProcess(
18933 bc, principal);
18935 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18936 NS_ERROR_FAILURE, __func__);
18938 [](mozilla::ipc::ResponseRejectReason reason) {
18939 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18940 NS_ERROR_FAILURE, __func__);
18942 ->Then(
18943 GetCurrentSerialEventTarget(), __func__,
18944 [inner, bc, principal, self, promise](Maybe<bool> cookieResult) {
18945 // Handle the result of the cookie permission check that took place
18946 // in the ContentParent.
18947 if (cookieResult.isSome()) {
18948 if (cookieResult.value()) {
18949 return StorageAccessAPIHelper::
18950 StorageAccessPermissionGrantPromise::CreateAndResolve(
18951 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18953 return StorageAccessAPIHelper::
18954 StorageAccessPermissionGrantPromise::CreateAndReject(
18955 false, __func__);
18958 // Check for the existing storage access permission
18959 nsAutoCString type;
18960 bool ok =
18961 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
18962 if (!ok) {
18963 return StorageAccessAPIHelper::
18964 StorageAccessPermissionGrantPromise::CreateAndReject(
18965 false, __func__);
18967 if (AntiTrackingUtils::CheckStoragePermission(
18968 self->NodePrincipal(), type, self->IsInPrivateBrowsing(),
18969 nullptr, 0)) {
18970 return StorageAccessAPIHelper::
18971 StorageAccessPermissionGrantPromise::CreateAndResolve(
18972 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18975 // Try to request storage access, ignoring the final checks.
18976 // We ignore the final checks because this is where the "grant"
18977 // either by prompt doorhanger or autogrant takes place. We already
18978 // gathered an equivalent grant in requestStorageAccessUnderSite.
18979 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
18980 self, inner, bc, principal, true, true, false,
18981 ContentBlockingNotifier::eStorageAccessAPI, false);
18983 // If the IPC rejects, we should reject our promise here which will
18984 // cause a rejection of the promise we already returned
18985 [promise]() {
18986 return MozPromise<int, bool, true>::CreateAndReject(false,
18987 __func__);
18989 ->Then(
18990 GetCurrentSerialEventTarget(), __func__,
18991 // If the previous handlers resolved, we should reinstate user
18992 // activation and resolve the promise we returned in Step 5.
18993 [self, inner, promise] {
18994 inner->SaveStorageAccessPermissionGranted();
18995 promise->MaybeResolveWithUndefined();
18997 // If the previous handler rejected, we should reject the promise
18998 // returned by this function.
18999 [promise] { promise->MaybeRejectWithUndefined(); });
19001 return promise.forget();
19004 nsTHashSet<RefPtr<WakeLockSentinel>>& Document::ActiveWakeLocks(
19005 WakeLockType aType) {
19006 return mActiveLocks.LookupOrInsert(aType);
19009 class UnlockAllWakeLockRunnable final : public Runnable {
19010 public:
19011 UnlockAllWakeLockRunnable(WakeLockType aType, Document* aDoc)
19012 : Runnable("UnlockAllWakeLocks"), mType(aType), mDoc(aDoc) {}
19014 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
19015 // bug 1535398.
19016 MOZ_CAN_RUN_SCRIPT_BOUNDARY
19017 NS_IMETHOD Run() override {
19018 // Move, as ReleaseWakeLock will try to remove from and possibly allow
19019 // scripts via onrelease to add to document.[[ActiveLocks]]["screen"]
19020 nsCOMPtr<Document> doc = mDoc;
19021 nsTHashSet<RefPtr<WakeLockSentinel>> locks =
19022 std::move(doc->ActiveWakeLocks(mType));
19023 for (const auto& lock : locks) {
19024 // ReleaseWakeLock runs script, which could release other locks
19025 if (!lock->Released()) {
19026 ReleaseWakeLock(doc, MOZ_KnownLive(lock), mType);
19029 return NS_OK;
19032 protected:
19033 ~UnlockAllWakeLockRunnable() = default;
19035 private:
19036 WakeLockType mType;
19037 nsCOMPtr<Document> mDoc;
19040 void Document::UnlockAllWakeLocks(WakeLockType aType) {
19041 // Perform unlock in a runnable to prevent UnlockAll being MOZ_CAN_RUN_SCRIPT
19042 if (!ActiveWakeLocks(aType).IsEmpty()) {
19043 RefPtr<UnlockAllWakeLockRunnable> runnable =
19044 MakeRefPtr<UnlockAllWakeLockRunnable>(aType, this);
19045 nsresult rv = NS_DispatchToMainThread(runnable);
19046 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
19047 Unused << rv;
19051 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
19052 Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
19053 // requestStorageAccessForOrigin may not require user activation. If we don't
19054 // have user activation at this point we should always show the prompt.
19055 if (!hasUserActivation ||
19056 !StaticPrefs::privacy_antitracking_enableWebcompat()) {
19057 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
19058 false, __func__);
19060 if (XRE_IsContentProcess()) {
19061 // In the content process, we need to ask the parent process to compute
19062 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
19063 // isn't accessible in the content process.
19064 ContentChild* cc = ContentChild::GetSingleton();
19065 MOZ_ASSERT(cc);
19067 return cc->SendAutomaticStorageAccessPermissionCanBeGranted(NodePrincipal())
19068 ->Then(GetCurrentSerialEventTarget(), __func__,
19069 [](const ContentChild::
19070 AutomaticStorageAccessPermissionCanBeGrantedPromise::
19071 ResolveOrRejectValue& aValue) {
19072 if (aValue.IsResolve()) {
19073 return AutomaticStorageAccessPermissionGrantPromise::
19074 CreateAndResolve(aValue.ResolveValue(), __func__);
19077 return AutomaticStorageAccessPermissionGrantPromise::
19078 CreateAndReject(false, __func__);
19082 if (XRE_IsParentProcess()) {
19083 // In the parent process, we can directly compute this.
19084 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
19085 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
19086 __func__);
19089 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
19090 false, __func__);
19093 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
19094 nsIPrincipal* aPrincipal) {
19095 if (!StaticPrefs::dom_storage_access_auto_grants()) {
19096 return false;
19099 if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
19100 return false;
19103 nsCOMPtr<nsIBrowserUsage> bu = do_ImportESModule(
19104 "resource:///modules/BrowserUsageTelemetry.sys.mjs", fallible);
19105 if (NS_WARN_IF(!bu)) {
19106 return false;
19109 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
19110 nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours(
19111 &uniqueDomainsVisitedInPast24Hours);
19112 if (NS_WARN_IF(NS_FAILED(rv))) {
19113 return false;
19116 Maybe<size_t> maybeOriginsThirdPartyHasAccessTo =
19117 AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal);
19118 if (maybeOriginsThirdPartyHasAccessTo.isNothing()) {
19119 return false;
19121 size_t originsThirdPartyHasAccessTo =
19122 maybeOriginsThirdPartyHasAccessTo.value();
19124 // one percent of the number of top-levels origins visited in the current
19125 // session (but not to exceed 24 hours), or the value of the
19126 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
19127 // higher.
19128 size_t maxConcurrentAutomaticGrants = std::max(
19129 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
19130 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
19133 return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
19136 void Document::RecordNavigationTiming(ReadyState aReadyState) {
19137 if (!XRE_IsContentProcess()) {
19138 return;
19140 if (!IsTopLevelContentDocument()) {
19141 return;
19143 // If we dont have the timing yet (mostly because the doc is still loading),
19144 // get it from docshell.
19145 RefPtr<nsDOMNavigationTiming> timing = mTiming;
19146 if (!timing) {
19147 if (!mDocumentContainer) {
19148 return;
19150 timing = mDocumentContainer->GetNavigationTiming();
19151 if (!timing) {
19152 return;
19155 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
19156 switch (aReadyState) {
19157 case READYSTATE_LOADING:
19158 if (!mDOMLoadingSet) {
19159 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
19160 startTime);
19161 mDOMLoadingSet = true;
19163 break;
19164 case READYSTATE_INTERACTIVE:
19165 if (!mDOMInteractiveSet) {
19166 glean::performance_time::dom_interactive.AccumulateRawDuration(
19167 TimeStamp::Now() - startTime);
19168 mDOMInteractiveSet = true;
19170 break;
19171 case READYSTATE_COMPLETE:
19172 if (!mDOMCompleteSet) {
19173 glean::performance_time::dom_complete.AccumulateRawDuration(
19174 TimeStamp::Now() - startTime);
19175 mDOMCompleteSet = true;
19177 break;
19178 default:
19179 NS_WARNING("Unexpected ReadyState value");
19180 break;
19184 void Document::ReportShadowDOMUsage() {
19185 nsPIDOMWindowInner* inner = GetInnerWindow();
19186 if (NS_WARN_IF(!inner)) {
19187 return;
19190 WindowContext* wc = inner->GetWindowContext();
19191 if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
19192 return;
19195 WindowContext* topWc = wc->TopWindowContext();
19196 if (topWc->GetHasReportedShadowDOMUsage()) {
19197 return;
19200 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
19203 // static
19204 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
19205 return StaticPrefs::dom_storage_access_enabled() &&
19206 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
19209 bool Document::StorageAccessSandboxed() const {
19210 return Document::StorageAccessSandboxed(GetSandboxFlags());
19213 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
19214 if (mCachedTabSizeGeneration == 0 ||
19215 GetGeneration() != mCachedTabSizeGeneration) {
19216 return false;
19218 aSizes->mDom += mCachedTabSizes.mDom;
19219 aSizes->mStyle += mCachedTabSizes.mStyle;
19220 aSizes->mOther += mCachedTabSizes.mOther;
19221 return true;
19224 void Document::SetCachedSizes(nsTabSizes* aSizes) {
19225 mCachedTabSizes.mDom = aSizes->mDom;
19226 mCachedTabSizes.mStyle = aSizes->mStyle;
19227 mCachedTabSizes.mOther = aSizes->mOther;
19228 mCachedTabSizeGeneration = GetGeneration();
19231 nsAtom* Document::GetContentLanguageAsAtomForStyle() const {
19232 // Content-Language may be a comma-separated list of language codes,
19233 // in which case the HTML5 spec says to treat it as unknown
19234 if (mContentLanguage &&
19235 !nsDependentAtomString(mContentLanguage).Contains(char16_t(','))) {
19236 return GetContentLanguage();
19239 return nullptr;
19242 nsAtom* Document::GetLanguageForStyle() const {
19243 if (nsAtom* lang = GetContentLanguageAsAtomForStyle()) {
19244 return lang;
19246 return mLanguageFromCharset.get();
19249 void Document::GetContentLanguageForBindings(DOMString& aString) const {
19250 aString.SetKnownLiveAtom(mContentLanguage, DOMString::eTreatNullAsEmpty);
19253 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
19254 nsAtom* aLanguage, bool* aNeedsToCache) const {
19255 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
19256 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
19259 void Document::DoCacheAllKnownLangPrefs() {
19260 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
19261 RefPtr<nsAtom> lang = GetLanguageForStyle();
19262 StaticPresData* data = StaticPresData::Get();
19263 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
19264 data->GetFontPrefsForLang(nsGkAtoms::x_math);
19265 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
19266 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
19267 for (const auto& key : mLanguagesUsed) {
19268 data->GetFontPrefsForLang(key);
19270 mMayNeedFontPrefsUpdate = false;
19273 void Document::RecomputeLanguageFromCharset() {
19274 RefPtr<nsAtom> language;
19275 // Optimize the default character sets.
19276 if (mCharacterSet == WINDOWS_1252_ENCODING) {
19277 language = nsGkAtoms::x_western;
19278 } else {
19279 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
19280 if (mCharacterSet == UTF_8_ENCODING) {
19281 language = nsGkAtoms::Unicode;
19282 } else {
19283 language = service->LookupCharSet(mCharacterSet);
19286 if (language == nsGkAtoms::Unicode) {
19287 language = service->GetLocaleLanguage();
19291 if (language == mLanguageFromCharset) {
19292 return;
19295 mMayNeedFontPrefsUpdate = true;
19296 mLanguageFromCharset = std::move(language);
19299 nsICookieJarSettings* Document::CookieJarSettings() {
19300 // If we are here, this is probably a javascript: URL document. In any case,
19301 // we must have a nsCookieJarSettings. Let's create it.
19302 if (!mCookieJarSettings) {
19303 Document* inProcessParent = GetInProcessParentDocument();
19305 auto shouldInheritFrom = [this](Document* aDoc) {
19306 return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
19307 this->NodePrincipal()->GetIsNullPrincipal());
19309 RefPtr<BrowsingContext> opener =
19310 GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
19312 if (inProcessParent) {
19313 mCookieJarSettings = net::CookieJarSettings::Create(
19314 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
19315 mozilla::net::CookieJarSettings::Cast(
19316 inProcessParent->CookieJarSettings())
19317 ->GetPartitionKey(),
19318 inProcessParent->CookieJarSettings()->GetIsFirstPartyIsolated(),
19319 inProcessParent->CookieJarSettings()
19320 ->GetIsOnContentBlockingAllowList(),
19321 inProcessParent->CookieJarSettings()
19322 ->GetShouldResistFingerprinting());
19324 // Inherit the fingerprinting random key from the parent.
19325 nsTArray<uint8_t> randomKey;
19326 nsresult rv = inProcessParent->CookieJarSettings()
19327 ->GetFingerprintingRandomizationKey(randomKey);
19329 if (NS_SUCCEEDED(rv)) {
19330 net::CookieJarSettings::Cast(mCookieJarSettings)
19331 ->SetFingerprintingRandomizationKey(randomKey);
19334 // Inerit the top level windowContext id from the parent.
19335 net::CookieJarSettings::Cast(mCookieJarSettings)
19336 ->SetTopLevelWindowContextId(
19337 net::CookieJarSettings::Cast(inProcessParent->CookieJarSettings())
19338 ->GetTopLevelWindowContextId());
19339 } else if (opener && shouldInheritFrom(opener->GetDocument())) {
19340 mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
19342 nsTArray<uint8_t> randomKey;
19343 nsresult rv = opener->GetDocument()
19344 ->CookieJarSettings()
19345 ->GetFingerprintingRandomizationKey(randomKey);
19347 if (NS_SUCCEEDED(rv)) {
19348 net::CookieJarSettings::Cast(mCookieJarSettings)
19349 ->SetFingerprintingRandomizationKey(randomKey);
19351 } else {
19352 mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
19354 if (IsTopLevelContentDocument()) {
19355 net::CookieJarSettings::Cast(mCookieJarSettings)
19356 ->SetTopLevelWindowContextId(InnerWindowID());
19360 if (auto* wgc = GetWindowGlobalChild()) {
19361 net::CookieJarSettingsArgs csArgs;
19363 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
19364 // Update cookie settings in the parent process
19365 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
19366 NS_WARNING(
19367 "Failed to update document's cookie jar settings on the "
19368 "WindowGlobalParent");
19373 return mCookieJarSettings;
19376 bool Document::UsingStorageAccess() {
19377 if (WindowContext* wc = GetWindowContext()) {
19378 return wc->GetUsingStorageAccess();
19381 // If we don't yet have a window context, we have to use the decision
19382 // from the Document's Channel's LoadInfo directly.
19383 if (!mChannel) {
19384 return false;
19387 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
19388 return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
19391 bool Document::HasStorageAccessPermissionGrantedByAllowList() {
19392 // We only care about if the document gets the storage permission via the
19393 // allow list here. So we don't check the storage access cache in the inner
19394 // window.
19396 if (!mChannel) {
19397 return false;
19400 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
19401 return loadInfo->GetStoragePermission() ==
19402 nsILoadInfo::StoragePermissionAllowListed;
19405 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
19406 if (!StaticPrefs::
19407 privacy_partition_always_partition_third_party_non_cookie_storage()) {
19408 return EffectiveCookiePrincipal();
19411 nsPIDOMWindowInner* inner = GetInnerWindow();
19412 if (!inner) {
19413 return NodePrincipal();
19416 // Return our cached storage principal if one exists.
19417 if (mActiveStoragePrincipal) {
19418 return mActiveStoragePrincipal;
19421 // Calling StorageAllowedForDocument will notify the ContentBlockLog. This
19422 // loads TrackingDBService.sys.mjs, making us potentially
19423 // fail // browser/base/content/test/performance/browser_startup.js. To avoid
19424 // that, we short-circuit the check here by allowing storage access to system
19425 // and addon principles, avoiding the test-failure.
19426 nsIPrincipal* principal = NodePrincipal();
19427 if (principal && (principal->IsSystemPrincipal() ||
19428 principal->GetIsAddonOrExpandedAddonPrincipal())) {
19429 return mActiveStoragePrincipal = NodePrincipal();
19432 auto cookieJarSettings = const_cast<Document*>(this)->CookieJarSettings();
19433 if (cookieJarSettings->GetIsOnContentBlockingAllowList()) {
19434 return mActiveStoragePrincipal = NodePrincipal();
19437 StorageAccess storageAccess = StorageAllowedForDocument(this);
19438 if (!ShouldPartitionStorage(storageAccess) ||
19439 !StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
19440 return mActiveStoragePrincipal = NodePrincipal();
19443 Unused << NS_WARN_IF(NS_FAILED(StoragePrincipalHelper::GetPrincipal(
19444 nsGlobalWindowInner::Cast(inner),
19445 StoragePrincipalHelper::eForeignPartitionedPrincipal,
19446 getter_AddRefs(mActiveStoragePrincipal))));
19447 return mActiveStoragePrincipal;
19450 nsIPrincipal* Document::EffectiveCookiePrincipal() const {
19451 nsPIDOMWindowInner* inner = GetInnerWindow();
19452 if (!inner) {
19453 return NodePrincipal();
19456 // Return our cached storage principal if one exists.
19458 // Handle special case where privacy_partition_always_partition_third_party
19459 // _non_cookie_storage is disabled and the loading document has
19460 // StorageAccess. The pref will lead to WindowGlobalChild::OnNewDocument
19461 // setting the documents StoragePrincipal on the parent to the documents
19462 // EffectiveCookiePrincipal. Since this happens before the WindowContext,
19463 // including possible StorageAccess, is set the PartitonedPrincipal will be
19464 // selected and cached. Since no change of permission occured it won't be
19465 // updated later. Avoid this by not using a cached PartitionedPrincipal if
19466 // the pref is disabled, this should rarely happen since the pref defaults to
19467 // true. See Bug 1899570.
19468 if (mActiveCookiePrincipal &&
19469 (StaticPrefs::
19470 privacy_partition_always_partition_third_party_non_cookie_storage() ||
19471 mActiveCookiePrincipal != mPartitionedPrincipal)) {
19472 return mActiveCookiePrincipal;
19475 // We use the lower-level ContentBlocking API here to ensure this
19476 // check doesn't send notifications.
19477 uint32_t rejectedReason = 0;
19478 if (ShouldAllowAccessFor(inner, GetDocumentURI(), &rejectedReason)) {
19479 return mActiveCookiePrincipal = NodePrincipal();
19482 // Let's use the storage principal only if we need to partition the cookie
19483 // jar. When the permission is granted, access will be different and the
19484 // normal principal will be used.
19485 if (ShouldPartitionStorage(rejectedReason) &&
19486 !StoragePartitioningEnabled(
19487 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
19488 return mActiveCookiePrincipal = NodePrincipal();
19491 return mActiveCookiePrincipal = mPartitionedPrincipal;
19494 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
19495 // If the document is sandboxed document or data: document, we should
19496 // get URI of the parent document.
19497 for (const Document* document = this;
19498 document && document->IsContentDocument();
19499 document = document->GetInProcessParentDocument()) {
19500 // The document URI may be about:blank even if it comes from actual web
19501 // site. Therefore, we need to check the URI of its principal.
19502 nsIPrincipal* principal = document->NodePrincipal();
19503 if (principal->GetIsNullPrincipal()) {
19504 continue;
19506 return principal;
19508 return nullptr;
19511 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
19512 mIsInitialDocumentInWindow = aIsInitialDocument;
19514 if (aIsInitialDocument && !mIsEverInitialDocumentInWindow) {
19515 mIsEverInitialDocumentInWindow = aIsInitialDocument;
19518 // Asynchronously tell the parent process that we are, or are no longer, the
19519 // initial document. This happens async.
19520 if (auto* wgc = GetWindowGlobalChild()) {
19521 wgc->SendSetIsInitialDocument(aIsInitialDocument);
19525 // static
19526 void Document::AddToplevelLoadingDocument(Document* aDoc) {
19527 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
19528 // Currently we're interested in foreground documents only, so bail out early.
19529 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
19530 return;
19533 if (!sLoadingForegroundTopLevelContentDocument) {
19534 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
19535 mozilla::ipc::IdleSchedulerChild* idleScheduler =
19536 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
19537 if (idleScheduler) {
19538 idleScheduler->SendRunningPrioritizedOperation();
19541 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
19542 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
19546 // static
19547 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
19548 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
19549 if (sLoadingForegroundTopLevelContentDocument) {
19550 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
19551 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
19552 delete sLoadingForegroundTopLevelContentDocument;
19553 sLoadingForegroundTopLevelContentDocument = nullptr;
19555 mozilla::ipc::IdleSchedulerChild* idleScheduler =
19556 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
19557 if (idleScheduler) {
19558 idleScheduler->SendPrioritizedOperationDone();
19564 ColorScheme Document::DefaultColorScheme() const {
19565 return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
19568 ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
19569 if (ShouldResistFingerprinting(RFPTarget::CSSPrefersColorScheme) &&
19570 aIgnoreRFP == IgnoreRFP::No) {
19571 return ColorScheme::Light;
19574 if (nsPresContext* pc = GetPresContext()) {
19575 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
19576 return *scheme;
19580 return PreferenceSheet::PrefsFor(*this).mColorScheme;
19583 bool Document::HasRecentlyStartedForegroundLoads() {
19584 if (!sLoadingForegroundTopLevelContentDocument) {
19585 return false;
19588 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
19589 ++i) {
19590 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
19591 // A page loaded in foreground could be in background now.
19592 if (!doc->IsInBackgroundWindow()) {
19593 nsPIDOMWindowInner* win = doc->GetInnerWindow();
19594 if (win) {
19595 Performance* perf = win->GetPerformance();
19596 if (perf &&
19597 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
19598 return true;
19604 // Didn't find any loading foreground documents, just clear the array.
19605 delete sLoadingForegroundTopLevelContentDocument;
19606 sLoadingForegroundTopLevelContentDocument = nullptr;
19608 mozilla::ipc::IdleSchedulerChild* idleScheduler =
19609 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
19610 if (idleScheduler) {
19611 idleScheduler->SendPrioritizedOperationDone();
19613 return false;
19616 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
19617 nsFrameLoader* aStaticCloneOf) {
19618 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
19619 clone->mElement = aElement;
19620 clone->mStaticCloneOf = aStaticCloneOf;
19623 bool Document::ShouldAvoidNativeTheme() const {
19624 return !IsInChromeDocShell() || XRE_IsContentProcess();
19627 bool Document::UseRegularPrincipal() const {
19628 return EffectiveStoragePrincipal() == NodePrincipal();
19631 bool Document::HasThirdPartyChannel() {
19632 nsCOMPtr<nsIChannel> channel = GetChannel();
19633 if (channel) {
19634 // We assume that the channel is a third-party by default.
19635 bool thirdParty = true;
19637 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
19638 components::ThirdPartyUtil::Service();
19639 if (!thirdPartyUtil) {
19640 return thirdParty;
19643 // Check that if the channel is a third-party to its parent.
19644 nsresult rv =
19645 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
19646 if (NS_FAILED(rv)) {
19647 // Assume third-party in case of failure
19648 thirdParty = true;
19651 return thirdParty;
19654 if (mParentDocument) {
19655 return mParentDocument->HasThirdPartyChannel();
19658 return false;
19661 bool Document::IsLikelyContentInaccessibleTopLevelAboutBlank() const {
19662 if (!mDocumentURI || !NS_IsAboutBlank(mDocumentURI)) {
19663 return false;
19665 // FIXME(emilio): This is not quite edge-case free. See bug 1860098.
19667 // For stuff in frames, that makes our per-document telemetry probes not
19668 // really reliable but doesn't affect the correctness of our page probes, so
19669 // it's not too terrible.
19670 BrowsingContext* bc = GetBrowsingContext();
19671 return bc && bc->IsTop() && !bc->GetTopLevelCreatedByWebContent();
19674 bool Document::ShouldIncludeInTelemetry() const {
19675 if (!IsContentDocument() && !IsResourceDoc()) {
19676 return false;
19679 if (IsLikelyContentInaccessibleTopLevelAboutBlank()) {
19680 return false;
19683 nsIPrincipal* prin = NodePrincipal();
19684 // TODO(emilio): Should this use GetIsContentPrincipal() +
19685 // GetPrecursorPrincipal() instead (accounting for add-ons separately)?
19686 return !(prin->GetIsAddonOrExpandedAddonPrincipal() ||
19687 prin->IsSystemPrincipal() || prin->SchemeIs("about") ||
19688 prin->SchemeIs("chrome") || prin->SchemeIs("resource"));
19691 void Document::GetConnectedShadowRoots(
19692 nsTArray<RefPtr<ShadowRoot>>& aOut) const {
19693 AppendToArray(aOut, mComposedShadowRoots);
19696 void Document::AddMediaElementWithMSE() {
19697 if (mMediaElementWithMSECount++ == 0) {
19698 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
19699 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
19704 void Document::RemoveMediaElementWithMSE() {
19705 MOZ_ASSERT(mMediaElementWithMSECount > 0);
19706 if (--mMediaElementWithMSECount == 0) {
19707 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
19708 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
19713 void Document::UnregisterFromMemoryReportingForDataDocument() {
19714 if (!mAddedToMemoryReportingAsDataDocument) {
19715 return;
19717 mAddedToMemoryReportingAsDataDocument = false;
19718 nsIGlobalObject* global = GetScopeObject();
19719 if (global) {
19720 if (nsPIDOMWindowInner* win = global->GetAsInnerWindow()) {
19721 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
19722 this);
19726 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
19727 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
19728 mOOPChildrenLoading.AppendElement(aChild);
19729 if (mOOPChildrenLoading.Length() == 1) {
19730 // Let's block unload so that we're blocked from going into the BFCache
19731 // until the child has actually notified us that it has done loading.
19732 BlockOnload();
19736 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
19737 // aChild will not be in the list if nsDocLoader::Stop() was called, since
19738 // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
19739 // so we don't need to call DocLoaderIsEmpty in that case.
19740 if (mOOPChildrenLoading.RemoveElement(aChild)) {
19741 if (mOOPChildrenLoading.IsEmpty()) {
19742 UnblockOnload(false);
19744 RefPtr<nsDocLoader> docLoader(mDocumentContainer);
19745 if (docLoader) {
19746 docLoader->OOPChildrenLoadingIsEmpty();
19751 void Document::ClearOOPChildrenLoading() {
19752 nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
19753 mOOPChildrenLoading.SwapElements(oopChildrenLoading);
19754 if (!oopChildrenLoading.IsEmpty()) {
19755 UnblockOnload(false);
19759 bool Document::MayHaveDOMActivateListeners() const {
19760 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
19761 return inner->HasDOMActivateEventListeners();
19764 // If we can't get information from the window object, default to true.
19765 return true;
19768 HighlightRegistry& Document::HighlightRegistry() {
19769 if (!mHighlightRegistry) {
19770 mHighlightRegistry = MakeRefPtr<class HighlightRegistry>(this);
19772 return *mHighlightRegistry;
19775 FragmentDirective* Document::FragmentDirective() {
19776 if (!mFragmentDirective) {
19777 mFragmentDirective = MakeRefPtr<class FragmentDirective>(this);
19779 return mFragmentDirective;
19782 RadioGroupContainer& Document::OwnedRadioGroupContainer() {
19783 if (!mRadioGroupContainer) {
19784 mRadioGroupContainer = MakeUnique<RadioGroupContainer>();
19786 return *mRadioGroupContainer;
19789 void Document::UpdateHiddenByContentVisibilityForAnimations() {
19790 for (AnimationTimeline* timeline : Timelines()) {
19791 timeline->UpdateHiddenByContentVisibility();
19795 void Document::SetAllowDeclarativeShadowRoots(
19796 bool aAllowDeclarativeShadowRoots) {
19797 mAllowDeclarativeShadowRoots = aAllowDeclarativeShadowRoots;
19800 bool Document::AllowsDeclarativeShadowRoots() const {
19801 return mAllowDeclarativeShadowRoots;
19804 /* static */
19805 already_AddRefed<Document> Document::ParseHTMLUnsafe(
19806 GlobalObject& aGlobal, const TrustedHTMLOrString& aHTML,
19807 ErrorResult& aError) {
19808 nsCOMPtr<nsIURI> uri;
19809 NS_NewURI(getter_AddRefs(uri), "about:blank");
19810 if (!uri) {
19811 return nullptr;
19814 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
19816 constexpr nsLiteralString sink = u"Document parseHTMLUnsafe"_ns;
19817 Maybe<nsAutoString> compliantStringHolder;
19818 const nsAString* compliantString =
19819 TrustedTypeUtils::GetTrustedTypesCompliantString(
19820 aHTML, sink, kTrustedTypesOnlySinkGroup, *global,
19821 compliantStringHolder, aError);
19822 if (aError.Failed()) {
19823 return nullptr;
19826 nsCOMPtr<Document> doc;
19827 aError =
19828 NS_NewHTMLDocument(getter_AddRefs(doc), aGlobal.GetSubjectPrincipal(),
19829 aGlobal.GetSubjectPrincipal());
19830 if (aError.Failed()) {
19831 return nullptr;
19834 doc->SetAllowDeclarativeShadowRoots(true);
19835 doc->SetDocumentURI(uri);
19837 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
19838 do_QueryInterface(aGlobal.GetAsSupports());
19839 doc->SetScriptHandlingObject(scriptHandlingObject);
19840 doc->SetDocumentCharacterSet(UTF_8_ENCODING);
19841 aError = nsContentUtils::ParseDocumentHTML(*compliantString, doc, false);
19842 if (aError.Failed()) {
19843 return nullptr;
19846 return doc.forget();
19849 bool Document::MutationEventsEnabled() {
19850 if (StaticPrefs::dom_mutation_events_enabled()) {
19851 return true;
19853 if (mMutationEventsEnabled.isNothing()) {
19854 mMutationEventsEnabled.emplace(
19855 NodePrincipal()->IsURIInPrefList("dom.mutation_events.forceEnable"));
19857 return mMutationEventsEnabled.value();
19860 } // namespace mozilla::dom