Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / base / Document.cpp
blobd7fc1cec2ed2b925f273c2e41d19e7bc5cf5cfda
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * Base class for all our document implementations.
9 */
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/DocumentInlines.h"
14 #include <inttypes.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <algorithm>
18 #include <cstddef>
19 #include <cstdint>
20 #include <initializer_list>
21 #include <iterator>
22 #include <limits>
23 #include <type_traits>
24 #include "Attr.h"
25 #include "ErrorList.h"
26 #include "ExpandedPrincipal.h"
27 #include "MainThreadUtils.h"
28 #include "MobileViewportManager.h"
29 #include "NodeUbiReporting.h"
30 #include "PLDHashTable.h"
31 #include "StorageAccessPermissionRequest.h"
32 #include "ThirdPartyUtil.h"
33 #include "domstubs.h"
34 #include "gfxPlatform.h"
35 #include "imgIContainer.h"
36 #include "imgLoader.h"
37 #include "imgRequestProxy.h"
38 #include "js/Value.h"
39 #include "jsapi.h"
40 #include "mozAutoDocUpdate.h"
41 #include "mozIDOMWindow.h"
42 #include "mozIThirdPartyUtil.h"
43 #include "mozilla/AntiTrackingUtils.h"
44 #include "mozilla/ArrayIterator.h"
45 #include "mozilla/ArrayUtils.h"
46 #include "mozilla/AsyncEventDispatcher.h"
47 #include "mozilla/Base64.h"
48 #include "mozilla/BasePrincipal.h"
49 #include "mozilla/CSSEnabledState.h"
50 #include "mozilla/ContentBlockingAllowList.h"
51 #include "mozilla/ContentBlockingNotifier.h"
52 #include "mozilla/ContentBlockingUserInteraction.h"
53 #include "mozilla/ContentPrincipal.h"
54 #include "mozilla/CycleCollectedJSContext.h"
55 #include "mozilla/DebugOnly.h"
56 #include "mozilla/ProfilerMarkers.h"
57 #include "mozilla/AttributeStyles.h"
58 #include "mozilla/DocumentStyleRootIterator.h"
59 #include "mozilla/EditorBase.h"
60 #include "mozilla/EditorCommands.h"
61 #include "mozilla/Encoding.h"
62 #include "mozilla/ErrorResult.h"
63 #include "mozilla/EventDispatcher.h"
64 #include "mozilla/EventListenerManager.h"
65 #include "mozilla/EventQueue.h"
66 #include "mozilla/EventStateManager.h"
67 #include "mozilla/ExtensionPolicyService.h"
68 #include "mozilla/FullscreenChange.h"
69 #include "mozilla/GlobalStyleSheetCache.h"
70 #include "mozilla/MappedDeclarationsBuilder.h"
71 #include "mozilla/HTMLEditor.h"
72 #include "mozilla/HoldDropJSObjects.h"
73 #include "mozilla/IdentifierMapEntry.h"
74 #include "mozilla/InputTaskManager.h"
75 #include "mozilla/IntegerRange.h"
76 #include "mozilla/InternalMutationEvent.h"
77 #include "mozilla/Likely.h"
78 #include "mozilla/Logging.h"
79 #include "mozilla/LookAndFeel.h"
80 #include "mozilla/MacroForEach.h"
81 #include "mozilla/Maybe.h"
82 #include "mozilla/MediaFeatureChange.h"
83 #include "mozilla/MediaManager.h"
84 #include "mozilla/MemoryReporting.h"
85 #include "mozilla/NullPrincipal.h"
86 #include "mozilla/OriginAttributes.h"
87 #include "mozilla/OwningNonNull.h"
88 #include "mozilla/PendingFullscreenEvent.h"
89 #include "mozilla/PermissionDelegateHandler.h"
90 #include "mozilla/PermissionManager.h"
91 #include "mozilla/Preferences.h"
92 #include "mozilla/PreloadHashKey.h"
93 #include "mozilla/PresShell.h"
94 #include "mozilla/PresShellForwards.h"
95 #include "mozilla/PresShellInlines.h"
96 #include "mozilla/PseudoStyleType.h"
97 #include "mozilla/RefCountType.h"
98 #include "mozilla/RelativeTo.h"
99 #include "mozilla/RestyleManager.h"
100 #include "mozilla/ReverseIterator.h"
101 #include "mozilla/SchedulerGroup.h"
102 #include "mozilla/ScrollTimelineAnimationTracker.h"
103 #include "mozilla/SMILAnimationController.h"
104 #include "mozilla/SMILTimeContainer.h"
105 #include "mozilla/ScopeExit.h"
106 #include "mozilla/Components.h"
107 #include "mozilla/SVGUtils.h"
108 #include "mozilla/ServoStyleConsts.h"
109 #include "mozilla/ServoTypes.h"
110 #include "mozilla/SizeOfState.h"
111 #include "mozilla/Span.h"
112 #include "mozilla/Sprintf.h"
113 #include "mozilla/StaticAnalysisFunctions.h"
114 #include "mozilla/StaticPrefs_apz.h"
115 #include "mozilla/StaticPrefs_browser.h"
116 #include "mozilla/StaticPrefs_docshell.h"
117 #include "mozilla/StaticPrefs_dom.h"
118 #include "mozilla/StaticPrefs_fission.h"
119 #include "mozilla/StaticPrefs_full_screen_api.h"
120 #include "mozilla/StaticPrefs_layout.h"
121 #include "mozilla/StaticPrefs_network.h"
122 #include "mozilla/StaticPrefs_page_load.h"
123 #include "mozilla/StaticPrefs_privacy.h"
124 #include "mozilla/StaticPrefs_security.h"
125 #include "mozilla/StaticPrefs_widget.h"
126 #include "mozilla/StaticPresData.h"
127 #include "mozilla/StorageAccess.h"
128 #include "mozilla/StoragePrincipalHelper.h"
129 #include "mozilla/StyleSheet.h"
130 #include "mozilla/Telemetry.h"
131 #include "mozilla/TelemetryScalarEnums.h"
132 #include "mozilla/TextControlElement.h"
133 #include "mozilla/TextEditor.h"
134 #include "mozilla/TypedEnumBits.h"
135 #include "mozilla/URLDecorationStripper.h"
136 #include "mozilla/URLExtraData.h"
137 #include "mozilla/Unused.h"
138 #include "mozilla/css/ImageLoader.h"
139 #include "mozilla/css/Loader.h"
140 #include "mozilla/css/Rule.h"
141 #include "mozilla/css/SheetParsingMode.h"
142 #include "mozilla/dom/AnonymousContent.h"
143 #include "mozilla/dom/BlobURLProtocolHandler.h"
144 #include "mozilla/dom/BrowserChild.h"
145 #include "mozilla/dom/BrowsingContext.h"
146 #include "mozilla/dom/BrowsingContextGroup.h"
147 #include "mozilla/dom/CanonicalBrowsingContext.h"
148 #include "mozilla/dom/CanvasRenderingContextHelper.h"
149 #include "mozilla/dom/CDATASection.h"
150 #include "mozilla/dom/CSPDictionariesBinding.h"
151 #include "mozilla/dom/ChromeObserver.h"
152 #include "mozilla/dom/ClientInfo.h"
153 #include "mozilla/dom/ClientState.h"
154 #include "mozilla/dom/Comment.h"
155 #include "mozilla/dom/ContentChild.h"
156 #include "mozilla/dom/CSSBinding.h"
157 #include "mozilla/dom/CSSCustomPropertyRegisteredEvent.h"
158 #include "mozilla/dom/DOMImplementation.h"
159 #include "mozilla/dom/DOMIntersectionObserver.h"
160 #include "mozilla/dom/DOMStringList.h"
161 #include "mozilla/dom/DocGroup.h"
162 #include "mozilla/dom/DocumentBinding.h"
163 #include "mozilla/dom/DocumentFragment.h"
164 #include "mozilla/dom/DocumentL10n.h"
165 #include "mozilla/dom/DocumentTimeline.h"
166 #include "mozilla/dom/DocumentType.h"
167 #include "mozilla/dom/ElementBinding.h"
168 #include "mozilla/dom/ErrorEvent.h"
169 #include "mozilla/dom/Event.h"
170 #include "mozilla/dom/EventListenerBinding.h"
171 #include "mozilla/dom/FailedCertSecurityInfoBinding.h"
172 #include "mozilla/dom/FeaturePolicy.h"
173 #include "mozilla/dom/FeaturePolicyUtils.h"
174 #include "mozilla/dom/FontFaceSet.h"
175 #include "mozilla/dom/FragmentDirective.h"
176 #include "mozilla/dom/fragmentdirectives_ffi_generated.h"
177 #include "mozilla/dom/FromParser.h"
178 #include "mozilla/dom/HighlightRegistry.h"
179 #include "mozilla/dom/HTMLAllCollection.h"
180 #include "mozilla/dom/HTMLBodyElement.h"
181 #include "mozilla/dom/HTMLCollectionBinding.h"
182 #include "mozilla/dom/HTMLDialogElement.h"
183 #include "mozilla/dom/HTMLFormElement.h"
184 #include "mozilla/dom/HTMLIFrameElement.h"
185 #include "mozilla/dom/HTMLImageElement.h"
186 #include "mozilla/dom/HTMLInputElement.h"
187 #include "mozilla/dom/HTMLLinkElement.h"
188 #include "mozilla/dom/HTMLMediaElement.h"
189 #include "mozilla/dom/HTMLMetaElement.h"
190 #include "mozilla/dom/HTMLSharedElement.h"
191 #include "mozilla/dom/HTMLTextAreaElement.h"
192 #include "mozilla/dom/ImageTracker.h"
193 #include "mozilla/dom/InspectorUtils.h"
194 #include "mozilla/dom/Link.h"
195 #include "mozilla/dom/MediaQueryList.h"
196 #include "mozilla/dom/MediaSource.h"
197 #include "mozilla/dom/MutationObservers.h"
198 #include "mozilla/dom/NameSpaceConstants.h"
199 #include "mozilla/dom/Navigator.h"
200 #include "mozilla/dom/NetErrorInfoBinding.h"
201 #include "mozilla/dom/NodeInfo.h"
202 #include "mozilla/dom/NodeIterator.h"
203 #include "mozilla/dom/nsHTTPSOnlyUtils.h"
204 #include "mozilla/dom/PContentChild.h"
205 #include "mozilla/dom/PWindowGlobalChild.h"
206 #include "mozilla/dom/PageTransitionEvent.h"
207 #include "mozilla/dom/PageTransitionEventBinding.h"
208 #include "mozilla/dom/Performance.h"
209 #include "mozilla/dom/PermissionMessageUtils.h"
210 #include "mozilla/dom/PostMessageEvent.h"
211 #include "mozilla/dom/ProcessingInstruction.h"
212 #include "mozilla/dom/Promise.h"
213 #include "mozilla/dom/PromiseNativeHandler.h"
214 #include "mozilla/dom/ResizeObserver.h"
215 #include "mozilla/dom/RustTypes.h"
216 #include "mozilla/dom/SVGElement.h"
217 #include "mozilla/dom/SVGDocument.h"
218 #include "mozilla/dom/SVGSVGElement.h"
219 #include "mozilla/dom/SVGUseElement.h"
220 #include "mozilla/dom/ScriptLoader.h"
221 #include "mozilla/dom/ScriptSettings.h"
222 #include "mozilla/dom/Selection.h"
223 #include "mozilla/dom/ServiceWorkerContainer.h"
224 #include "mozilla/dom/ServiceWorkerDescriptor.h"
225 #include "mozilla/dom/ServiceWorkerManager.h"
226 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
227 #include "mozilla/dom/ShadowRoot.h"
228 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
229 #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
230 #include "mozilla/dom/StyleSheetList.h"
231 #include "mozilla/dom/StyleSheetRemovedEvent.h"
232 #include "mozilla/dom/StyleSheetRemovedEventBinding.h"
233 #include "mozilla/dom/TimeoutManager.h"
234 #include "mozilla/dom/ToggleEvent.h"
235 #include "mozilla/dom/Touch.h"
236 #include "mozilla/dom/TouchEvent.h"
237 #include "mozilla/dom/TreeOrderedArrayInlines.h"
238 #include "mozilla/dom/TreeWalker.h"
239 #include "mozilla/dom/URL.h"
240 #include "mozilla/dom/UseCounterMetrics.h"
241 #include "mozilla/dom/UserActivation.h"
242 #include "mozilla/dom/WakeLockJS.h"
243 #include "mozilla/dom/WakeLockSentinel.h"
244 #include "mozilla/dom/WindowBinding.h"
245 #include "mozilla/dom/WindowContext.h"
246 #include "mozilla/dom/WindowGlobalChild.h"
247 #include "mozilla/dom/WindowProxyHolder.h"
248 #include "mozilla/dom/WorkerDocumentListener.h"
249 #include "mozilla/dom/XPathEvaluator.h"
250 #include "mozilla/dom/XPathExpression.h"
251 #include "mozilla/dom/nsCSPContext.h"
252 #include "mozilla/dom/nsCSPUtils.h"
253 #include "mozilla/extensions/WebExtensionPolicy.h"
254 #include "mozilla/fallible.h"
255 #include "mozilla/gfx/BaseCoord.h"
256 #include "mozilla/gfx/BaseSize.h"
257 #include "mozilla/gfx/Coord.h"
258 #include "mozilla/gfx/Point.h"
259 #include "mozilla/gfx/ScaleFactor.h"
260 #include "mozilla/glean/GleanMetrics.h"
261 #include "mozilla/intl/LocaleService.h"
262 #include "mozilla/ipc/IdleSchedulerChild.h"
263 #include "mozilla/ipc/MessageChannel.h"
264 #include "mozilla/net/ChannelEventQueue.h"
265 #include "mozilla/net/CookieJarSettings.h"
266 #include "mozilla/net/NeckoChannelParams.h"
267 #include "mozilla/net/RequestContextService.h"
268 #include "nsAboutProtocolUtils.h"
269 #include "nsAlgorithm.h"
270 #include "nsAttrValue.h"
271 #include "nsAttrValueInlines.h"
272 #include "nsBaseHashtable.h"
273 #include "nsBidiUtils.h"
274 #include "nsCRT.h"
275 #include "nsCSSPropertyID.h"
276 #include "nsCSSProps.h"
277 #include "nsCSSPseudoElements.h"
278 #include "nsCSSRendering.h"
279 #include "nsCanvasFrame.h"
280 #include "nsCaseTreatment.h"
281 #include "nsCharsetSource.h"
282 #include "nsCommandManager.h"
283 #include "nsCommandParams.h"
284 #include "nsComponentManagerUtils.h"
285 #include "nsContentCreatorFunctions.h"
286 #include "nsContentList.h"
287 #include "nsContentPermissionHelper.h"
288 #include "nsContentSecurityUtils.h"
289 #include "nsContentUtils.h"
290 #include "nsCoord.h"
291 #include "nsCycleCollectionNoteChild.h"
292 #include "nsCycleCollectionTraversalCallback.h"
293 #include "nsDOMAttributeMap.h"
294 #include "nsDOMCaretPosition.h"
295 #include "nsDOMNavigationTiming.h"
296 #include "nsDOMString.h"
297 #include "nsDeviceContext.h"
298 #include "nsDocShell.h"
299 #include "nsDocShellLoadTypes.h"
300 #include "nsEffectiveTLDService.h"
301 #include "nsError.h"
302 #include "nsEscape.h"
303 #include "nsFocusManager.h"
304 #include "nsFrameLoader.h"
305 #include "nsFrameLoaderOwner.h"
306 #include "nsGenericHTMLElement.h"
307 #include "nsGlobalWindowInner.h"
308 #include "nsGlobalWindowOuter.h"
309 #include "nsHTMLDocument.h"
310 #include "nsHtml5Module.h"
311 #include "nsHtml5Parser.h"
312 #include "nsHtml5TreeOpExecutor.h"
313 #include "nsIAsyncShutdown.h"
314 #include "nsIAuthPrompt.h"
315 #include "nsIAuthPrompt2.h"
316 #include "nsIBFCacheEntry.h"
317 #include "nsIBaseWindow.h"
318 #include "nsIBrowserChild.h"
319 #include "nsIBrowserUsage.h"
320 #include "nsICSSLoaderObserver.h"
321 #include "nsICategoryManager.h"
322 #include "nsICertOverrideService.h"
323 #include "nsIContent.h"
324 #include "nsIContentInlines.h"
325 #include "nsIContentPolicy.h"
326 #include "nsIContentSecurityPolicy.h"
327 #include "nsIContentSink.h"
328 #include "nsICookieJarSettings.h"
329 #include "nsICookieService.h"
330 #include "nsIDOMXULCommandDispatcher.h"
331 #include "nsIDocShell.h"
332 #include "nsIDocShellTreeItem.h"
333 #include "nsIDocumentActivity.h"
334 #include "nsIDocumentEncoder.h"
335 #include "nsIDocumentLoader.h"
336 #include "nsIDocumentLoaderFactory.h"
337 #include "nsIDocumentObserver.h"
338 #include "nsIDNSService.h"
339 #include "nsIEditingSession.h"
340 #include "nsIEditor.h"
341 #include "nsIEffectiveTLDService.h"
342 #include "nsIFile.h"
343 #include "nsIFileChannel.h"
344 #include "nsIFrame.h"
345 #include "nsIGlobalObject.h"
346 #include "nsIHTMLCollection.h"
347 #include "nsIHttpChannel.h"
348 #include "nsIHttpChannelInternal.h"
349 #include "nsIIOService.h"
350 #include "nsIImageLoadingContent.h"
351 #include "nsIInlineSpellChecker.h"
352 #include "nsIInputStreamChannel.h"
353 #include "nsIInterfaceRequestorUtils.h"
354 #include "nsILayoutHistoryState.h"
355 #include "nsIMultiPartChannel.h"
356 #include "nsIMutationObserver.h"
357 #include "nsINSSErrorsService.h"
358 #include "nsINamed.h"
359 #include "nsINodeList.h"
360 #include "nsIObjectLoadingContent.h"
361 #include "nsIObserverService.h"
362 #include "nsIPermission.h"
363 #include "nsIPrompt.h"
364 #include "nsIPropertyBag2.h"
365 #include "nsIPublicKeyPinningService.h"
366 #include "nsIReferrerInfo.h"
367 #include "nsIRefreshURI.h"
368 #include "nsIRequest.h"
369 #include "nsIRequestContext.h"
370 #include "nsIRunnable.h"
371 #include "nsISHEntry.h"
372 #include "nsIScriptElement.h"
373 #include "nsIScriptError.h"
374 #include "nsIScriptGlobalObject.h"
375 #include "nsIScriptSecurityManager.h"
376 #include "nsISecurityConsoleMessage.h"
377 #include "nsISelectionController.h"
378 #include "nsISerialEventTarget.h"
379 #include "nsISimpleEnumerator.h"
380 #include "nsISiteSecurityService.h"
381 #include "nsISocketProvider.h"
382 #include "nsISpeculativeConnect.h"
383 #include "nsIStructuredCloneContainer.h"
384 #include "nsIThread.h"
385 #include "nsITimedChannel.h"
386 #include "nsITimer.h"
387 #include "nsITransportSecurityInfo.h"
388 #include "nsIURIMutator.h"
389 #include "nsIVariant.h"
390 #include "nsIWeakReference.h"
391 #include "nsIWebNavigation.h"
392 #include "nsIWidget.h"
393 #include "nsIX509Cert.h"
394 #include "nsIX509CertValidity.h"
395 #include "nsIXMLContentSink.h"
396 #include "nsIHTMLContentSink.h"
397 #include "nsIXULRuntime.h"
398 #include "nsImageLoadingContent.h"
399 #include "nsImportModule.h"
400 #include "nsLanguageAtomService.h"
401 #include "nsLayoutUtils.h"
402 #include "nsMimeTypes.h"
403 #include "nsNetCID.h"
404 #include "nsNetUtil.h"
405 #include "nsNodeInfoManager.h"
406 #include "nsObjectLoadingContent.h"
407 #include "nsPIDOMWindowInlines.h"
408 #include "nsPIWindowRoot.h"
409 #include "nsPoint.h"
410 #include "nsPointerHashKeys.h"
411 #include "nsPresContext.h"
412 #include "nsQueryFrame.h"
413 #include "nsQueryObject.h"
414 #include "nsRange.h"
415 #include "nsRect.h"
416 #include "nsRefreshDriver.h"
417 #include "nsSandboxFlags.h"
418 #include "nsSerializationHelper.h"
419 #include "nsServiceManagerUtils.h"
420 #include "nsStringFlags.h"
421 #include "nsStyleUtil.h"
422 #include "nsStringIterator.h"
423 #include "nsStyleSheetService.h"
424 #include "nsStyleStruct.h"
425 #include "nsTextNode.h"
426 #include "nsUnicharUtils.h"
427 #include "nsWrapperCache.h"
428 #include "nsWrapperCacheInlines.h"
429 #include "nsXPCOMCID.h"
430 #include "nsXULAppAPI.h"
431 #include "prthread.h"
432 #include "prtime.h"
433 #include "prtypes.h"
434 #include "xpcpublic.h"
436 // XXX Must be included after mozilla/Encoding.h
437 #include "encoding_rs.h"
439 #include "mozilla/dom/XULBroadcastManager.h"
440 #include "mozilla/dom/XULPersist.h"
441 #include "nsIAppWindow.h"
442 #include "nsXULPrototypeDocument.h"
443 #include "nsXULCommandDispatcher.h"
444 #include "nsXULPopupManager.h"
445 #include "nsIDocShellTreeOwner.h"
447 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
448 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
449 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
450 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
452 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
454 mozilla::LazyLogModule gPageCacheLog("PageCache");
455 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
456 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
457 mozilla::LazyLogModule gUseCountersLog("UseCounters");
459 namespace mozilla {
460 namespace dom {
462 class Document::HeaderData {
463 public:
464 HeaderData(nsAtom* aField, const nsAString& aData)
465 : mField(aField), mData(aData) {}
467 ~HeaderData() {
468 // Delete iteratively to avoid blowing up the stack, though it shouldn't
469 // happen in practice.
470 UniquePtr<HeaderData> next = std::move(mNext);
471 while (next) {
472 next = std::move(next->mNext);
476 RefPtr<nsAtom> mField;
477 nsString mData;
478 UniquePtr<HeaderData> mNext;
481 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
482 nullptr;
484 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
485 static LazyLogModule gCspPRLog("CSP");
486 LazyLogModule gUserInteractionPRLog("UserInteraction");
488 static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
489 nsIHttpChannel** aHttpChannel) {
490 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
491 if (httpChannel) {
492 httpChannel.forget(aHttpChannel);
493 return NS_OK;
496 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
497 if (!multipart) {
498 *aHttpChannel = nullptr;
499 return NS_OK;
502 nsCOMPtr<nsIChannel> baseChannel;
503 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
504 if (NS_WARN_IF(NS_FAILED(rv))) {
505 return rv;
508 httpChannel = do_QueryInterface(baseChannel);
509 httpChannel.forget(aHttpChannel);
511 return NS_OK;
514 } // namespace dom
516 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
518 IdentifierMapEntry::IdentifierMapEntry(
519 const IdentifierMapEntry::DependentAtomOrString* aKey)
520 : mKey(aKey ? *aKey : nullptr) {}
522 void IdentifierMapEntry::Traverse(
523 nsCycleCollectionTraversalCallback* aCallback) {
524 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
525 "mIdentifierMap mNameContentList");
526 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
528 if (mImageElement) {
529 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
530 "mIdentifierMap mImageElement element");
531 nsIContent* imageElement = mImageElement;
532 aCallback->NoteXPCOMChild(imageElement);
536 bool IdentifierMapEntry::IsEmpty() {
537 return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
538 !mImageElement;
541 bool IdentifierMapEntry::HasNameElement() const {
542 return mNameContentList && mNameContentList->Length() != 0;
545 void IdentifierMapEntry::AddContentChangeCallback(
546 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
547 if (!mChangeCallbacks) {
548 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
551 ChangeCallback cc = {aCallback, aData, aForImage};
552 mChangeCallbacks->PutEntry(cc);
555 void IdentifierMapEntry::RemoveContentChangeCallback(
556 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
557 if (!mChangeCallbacks) return;
558 ChangeCallback cc = {aCallback, aData, aForImage};
559 mChangeCallbacks->RemoveEntry(cc);
560 if (mChangeCallbacks->Count() == 0) {
561 mChangeCallbacks = nullptr;
565 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
566 Element* aNewElement,
567 bool aImageOnly) {
568 if (!mChangeCallbacks) return;
570 for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) {
571 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
572 // Don't fire image changes for non-image observers, and don't fire element
573 // changes for image observers when an image override is active.
574 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
575 continue;
578 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
579 iter.Remove();
584 void IdentifierMapEntry::AddIdElement(Element* aElement) {
585 MOZ_ASSERT(aElement, "Must have element");
586 MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
588 size_t index = mIdContentList.Insert(*aElement);
589 if (index == 0) {
590 Element* oldElement = mIdContentList->SafeElementAt(1);
591 FireChangeCallbacks(oldElement, aElement);
595 void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
596 MOZ_ASSERT(aElement, "Missing element");
598 // This should only be called while the document is in an update.
599 // Assertions near the call to this method guarantee this.
601 // This could fire in OOM situations
602 // Only assert this in HTML documents for now as XUL does all sorts of weird
603 // crap.
604 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
605 mIdContentList->Contains(aElement),
606 "Removing id entry that doesn't exist");
608 // XXXbz should this ever Compact() I guess when all the content is gone
609 // we'll just get cleaned up in the natural order of things...
610 Element* currentElement = mIdContentList->SafeElementAt(0);
611 mIdContentList.RemoveElement(*aElement);
612 if (currentElement == aElement) {
613 FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
617 void IdentifierMapEntry::SetImageElement(Element* aElement) {
618 Element* oldElement = GetImageIdElement();
619 mImageElement = aElement;
620 Element* newElement = GetImageIdElement();
621 if (oldElement != newElement) {
622 FireChangeCallbacks(oldElement, newElement, true);
626 void IdentifierMapEntry::ClearAndNotify() {
627 Element* currentElement = mIdContentList->SafeElementAt(0);
628 mIdContentList.Clear();
629 if (currentElement) {
630 FireChangeCallbacks(currentElement, nullptr);
632 mNameContentList = nullptr;
633 if (mImageElement) {
634 SetImageElement(nullptr);
636 mChangeCallbacks = nullptr;
639 namespace dom {
641 class SimpleHTMLCollection final : public nsSimpleContentList,
642 public nsIHTMLCollection {
643 public:
644 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
646 NS_DECL_ISUPPORTS_INHERITED
648 virtual nsINode* GetParentObject() override {
649 return nsSimpleContentList::GetParentObject();
651 virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
652 virtual Element* GetElementAt(uint32_t aIndex) override {
653 return mElements.SafeElementAt(aIndex)->AsElement();
656 virtual Element* GetFirstNamedElement(const nsAString& aName,
657 bool& aFound) override {
658 aFound = false;
659 RefPtr<nsAtom> name = NS_Atomize(aName);
660 for (uint32_t i = 0; i < mElements.Length(); i++) {
661 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
662 Element* element = mElements[i]->AsElement();
663 if (element->GetID() == name ||
664 (element->HasName() &&
665 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
666 aFound = true;
667 return element;
670 return nullptr;
673 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
674 AutoTArray<nsAtom*, 8> atoms;
675 for (uint32_t i = 0; i < mElements.Length(); i++) {
676 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
677 Element* element = mElements[i]->AsElement();
679 nsAtom* id = element->GetID();
680 MOZ_ASSERT(id != nsGkAtoms::_empty);
681 if (id && !atoms.Contains(id)) {
682 atoms.AppendElement(id);
685 if (element->HasName()) {
686 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
687 MOZ_ASSERT(name && name != nsGkAtoms::_empty);
688 if (name && !atoms.Contains(name)) {
689 atoms.AppendElement(name);
694 nsString* names = aNames.AppendElements(atoms.Length());
695 for (uint32_t i = 0; i < atoms.Length(); i++) {
696 atoms[i]->ToString(names[i]);
700 virtual JSObject* GetWrapperPreserveColorInternal() override {
701 return nsWrapperCache::GetWrapperPreserveColor();
703 virtual void PreserveWrapperInternal(
704 nsISupports* aScriptObjectHolder) override {
705 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
707 virtual JSObject* WrapObject(JSContext* aCx,
708 JS::Handle<JSObject*> aGivenProto) override {
709 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
712 using nsBaseContentList::Item;
714 private:
715 virtual ~SimpleHTMLCollection() = default;
718 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
719 nsIHTMLCollection)
721 } // namespace dom
723 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
724 if (!mNameContentList) {
725 mNameContentList = new dom::SimpleHTMLCollection(aNode);
728 mNameContentList->AppendElement(aElement);
731 void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
732 if (mNameContentList) {
733 mNameContentList->RemoveElement(aElement);
737 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const {
738 Element* idElement = GetIdElement();
739 return idElement &&
740 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
743 size_t IdentifierMapEntry::SizeOfExcludingThis(
744 MallocSizeOf aMallocSizeOf) const {
745 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
748 // Helper structs for the content->subdoc map
750 class SubDocMapEntry : public PLDHashEntryHdr {
751 public:
752 // Both of these are strong references
753 dom::Element* mKey; // must be first, to look like PLDHashEntryStub
754 dom::Document* mSubDocument;
757 class OnloadBlocker final : public nsIRequest {
758 public:
759 OnloadBlocker() = default;
761 NS_DECL_ISUPPORTS
762 NS_DECL_NSIREQUEST
764 private:
765 ~OnloadBlocker() = default;
768 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
770 NS_IMETHODIMP
771 OnloadBlocker::GetName(nsACString& aResult) {
772 aResult.AssignLiteral("about:document-onload-blocker");
773 return NS_OK;
776 NS_IMETHODIMP
777 OnloadBlocker::IsPending(bool* _retval) {
778 *_retval = true;
779 return NS_OK;
782 NS_IMETHODIMP
783 OnloadBlocker::GetStatus(nsresult* status) {
784 *status = NS_OK;
785 return NS_OK;
788 NS_IMETHODIMP OnloadBlocker::SetCanceledReason(const nsACString& aReason) {
789 return SetCanceledReasonImpl(aReason);
792 NS_IMETHODIMP OnloadBlocker::GetCanceledReason(nsACString& aReason) {
793 return GetCanceledReasonImpl(aReason);
796 NS_IMETHODIMP OnloadBlocker::CancelWithReason(nsresult aStatus,
797 const nsACString& aReason) {
798 return CancelWithReasonImpl(aStatus, aReason);
800 NS_IMETHODIMP
801 OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
802 NS_IMETHODIMP
803 OnloadBlocker::Suspend(void) { return NS_OK; }
804 NS_IMETHODIMP
805 OnloadBlocker::Resume(void) { return NS_OK; }
807 NS_IMETHODIMP
808 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
809 *aLoadGroup = nullptr;
810 return NS_OK;
813 NS_IMETHODIMP
814 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
816 NS_IMETHODIMP
817 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
818 *aLoadFlags = nsIRequest::LOAD_NORMAL;
819 return NS_OK;
822 NS_IMETHODIMP
823 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
824 return GetTRRModeImpl(aTRRMode);
827 NS_IMETHODIMP
828 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
829 return SetTRRModeImpl(aTRRMode);
832 NS_IMETHODIMP
833 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
835 // ==================================================================
837 namespace dom {
839 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
841 Document* ExternalResourceMap::RequestResource(
842 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
843 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
844 // If we ever start allowing non-same-origin loads here, we might need to do
845 // something interesting with aRequestingPrincipal even for the hashtable
846 // gets.
847 MOZ_ASSERT(aURI, "Must have a URI");
848 MOZ_ASSERT(aRequestingNode, "Must have a node");
849 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
850 *aPendingLoad = nullptr;
851 if (mHaveShutDown) {
852 return nullptr;
855 // First, make sure we strip the ref from aURI.
856 nsCOMPtr<nsIURI> clone;
857 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
858 if (NS_FAILED(rv) || !clone) {
859 return nullptr;
862 ExternalResource* resource;
863 mMap.Get(clone, &resource);
864 if (resource) {
865 return resource->mDocument;
868 bool loadStartSucceeded =
869 mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
870 if (!loadEntry) {
871 loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
873 if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
874 aRequestingNode))) {
875 return false;
879 RefPtr<PendingLoad> load(loadEntry.Data());
880 load.forget(aPendingLoad);
881 return true;
883 if (!loadStartSucceeded) {
884 // Make sure we don't thrash things by trying this load again, since
885 // chances are it failed for good reasons (security check, etc).
886 // This must be done outside the WithEntryHandle functor, as it accesses
887 // mPendingLoads.
888 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
891 return nullptr;
894 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
895 nsTArray<RefPtr<Document>> docs(mMap.Count());
896 for (const auto& entry : mMap.Values()) {
897 if (Document* doc = entry->mDocument) {
898 docs.AppendElement(doc);
902 for (auto& doc : docs) {
903 if (aCallback(*doc) == CallState::Stop) {
904 return;
909 void ExternalResourceMap::Traverse(
910 nsCycleCollectionTraversalCallback* aCallback) const {
911 // mPendingLoads will get cleared out as the requests complete, so
912 // no need to worry about those here.
913 for (const auto& entry : mMap) {
914 ExternalResourceMap::ExternalResource* resource = entry.GetWeak();
916 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
917 "mExternalResourceMap.mMap entry"
918 "->mDocument");
919 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
921 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
922 "mExternalResourceMap.mMap entry"
923 "->mViewer");
924 aCallback->NoteXPCOMChild(resource->mViewer);
926 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
927 "mExternalResourceMap.mMap entry"
928 "->mLoadGroup");
929 aCallback->NoteXPCOMChild(resource->mLoadGroup);
933 void ExternalResourceMap::HideViewers() {
934 for (const auto& entry : mMap) {
935 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
936 if (viewer) {
937 viewer->Hide();
942 void ExternalResourceMap::ShowViewers() {
943 for (const auto& entry : mMap) {
944 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
945 if (viewer) {
946 viewer->Show();
951 void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
952 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
954 if (aFromDoc->IsShowing()) {
955 aToDoc->OnPageShow(true, nullptr);
959 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
960 nsIDocumentViewer* aViewer,
961 nsILoadGroup* aLoadGroup,
962 Document* aDisplayDocument) {
963 MOZ_ASSERT(aURI, "Unexpected call");
964 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
965 "Must have both or neither");
967 RefPtr<PendingLoad> load;
968 mPendingLoads.Remove(aURI, getter_AddRefs(load));
970 nsresult rv = NS_OK;
972 nsCOMPtr<Document> doc;
973 if (aViewer) {
974 doc = aViewer->GetDocument();
975 NS_ASSERTION(doc, "Must have a document");
977 doc->SetDisplayDocument(aDisplayDocument);
979 // Make sure that hiding our viewer will tear down its presentation.
980 aViewer->SetSticky(false);
982 rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
983 if (NS_SUCCEEDED(rv)) {
984 rv = aViewer->Open(nullptr, nullptr);
987 if (NS_FAILED(rv)) {
988 doc = nullptr;
989 aViewer = nullptr;
990 aLoadGroup = nullptr;
994 ExternalResource* newResource =
995 mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get();
997 newResource->mDocument = doc;
998 newResource->mViewer = aViewer;
999 newResource->mLoadGroup = aLoadGroup;
1000 if (doc) {
1001 if (nsPresContext* pc = doc->GetPresContext()) {
1002 pc->RecomputeBrowsingContextDependentData();
1004 TransferShowingState(aDisplayDocument, doc);
1007 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
1008 for (uint32_t i = 0; i < obs.Length(); ++i) {
1009 obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
1010 nullptr);
1013 return rv;
1016 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
1017 nsIRequestObserver)
1019 NS_IMETHODIMP
1020 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
1021 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
1022 if (map.HaveShutDown()) {
1023 return NS_BINDING_ABORTED;
1026 nsCOMPtr<nsIDocumentViewer> viewer;
1027 nsCOMPtr<nsILoadGroup> loadGroup;
1028 nsresult rv =
1029 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
1031 // Make sure to do this no matter what
1032 nsresult rv2 =
1033 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
1034 if (NS_FAILED(rv)) {
1035 return rv;
1037 if (NS_FAILED(rv2)) {
1038 mTargetListener = nullptr;
1039 return rv2;
1042 return mTargetListener->OnStartRequest(aRequest);
1045 nsresult ExternalResourceMap::PendingLoad::SetupViewer(
1046 nsIRequest* aRequest, nsIDocumentViewer** aViewer,
1047 nsILoadGroup** aLoadGroup) {
1048 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
1049 *aViewer = nullptr;
1050 *aLoadGroup = nullptr;
1052 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
1053 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
1055 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
1056 if (httpChannel) {
1057 bool requestSucceeded;
1058 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
1059 !requestSucceeded) {
1060 // Bail out on this load, since it looks like we have an HTTP error page
1061 return NS_BINDING_ABORTED;
1065 nsAutoCString type;
1066 chan->GetContentType(type);
1068 nsCOMPtr<nsILoadGroup> loadGroup;
1069 chan->GetLoadGroup(getter_AddRefs(loadGroup));
1071 // Give this document its own loadgroup
1072 nsCOMPtr<nsILoadGroup> newLoadGroup =
1073 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1074 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
1075 newLoadGroup->SetLoadGroup(loadGroup);
1077 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1078 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1080 nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
1081 new LoadgroupCallbacks(callbacks);
1082 newLoadGroup->SetNotificationCallbacks(newCallbacks);
1084 // This is some serious hackery cribbed from docshell
1085 nsCOMPtr<nsICategoryManager> catMan =
1086 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
1087 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
1088 nsCString contractId;
1089 nsresult rv =
1090 catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
1091 NS_ENSURE_SUCCESS(rv, rv);
1092 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
1093 do_GetService(contractId.get());
1094 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
1096 nsCOMPtr<nsIDocumentViewer> viewer;
1097 nsCOMPtr<nsIStreamListener> listener;
1098 rv = docLoaderFactory->CreateInstance(
1099 "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
1100 getter_AddRefs(listener), getter_AddRefs(viewer));
1101 NS_ENSURE_SUCCESS(rv, rv);
1102 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
1104 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
1105 if (!parser) {
1106 /// We don't want to deal with the various fake documents yet
1107 return NS_ERROR_NOT_IMPLEMENTED;
1110 // We can't handle HTML and other weird things here yet.
1111 nsIContentSink* sink = parser->GetContentSink();
1112 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
1113 if (!xmlSink) {
1114 return NS_ERROR_NOT_IMPLEMENTED;
1117 listener.swap(mTargetListener);
1118 viewer.forget(aViewer);
1119 newLoadGroup.forget(aLoadGroup);
1120 return NS_OK;
1123 NS_IMETHODIMP
1124 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
1125 nsIInputStream* aStream,
1126 uint64_t aOffset,
1127 uint32_t aCount) {
1128 // mTargetListener might be null if SetupViewer or AddExternalResource failed.
1129 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
1130 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
1131 return NS_BINDING_ABORTED;
1133 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1136 NS_IMETHODIMP
1137 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
1138 nsresult aStatus) {
1139 // mTargetListener might be null if SetupViewer or AddExternalResource failed
1140 if (mTargetListener) {
1141 nsCOMPtr<nsIStreamListener> listener;
1142 mTargetListener.swap(listener);
1143 return listener->OnStopRequest(aRequest, aStatus);
1146 return NS_OK;
1149 nsresult ExternalResourceMap::PendingLoad::StartLoad(
1150 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
1151 MOZ_ASSERT(aURI, "Must have a URI");
1152 MOZ_ASSERT(aRequestingNode, "Must have a node");
1153 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
1155 nsCOMPtr<nsILoadGroup> loadGroup =
1156 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
1158 nsresult rv = NS_OK;
1159 nsCOMPtr<nsIChannel> channel;
1160 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
1161 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1162 nsIContentPolicy::TYPE_OTHER,
1163 nullptr, // aPerformanceStorage
1164 loadGroup);
1165 NS_ENSURE_SUCCESS(rv, rv);
1167 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1168 if (httpChannel) {
1169 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
1170 Unused << NS_WARN_IF(NS_FAILED(rv));
1173 mURI = aURI;
1175 return channel->AsyncOpen(this);
1178 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
1179 nsIInterfaceRequestor)
1181 #define IMPL_SHIM(_i) \
1182 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
1184 IMPL_SHIM(nsILoadContext)
1185 IMPL_SHIM(nsIProgressEventSink)
1186 IMPL_SHIM(nsIChannelEventSink)
1188 #undef IMPL_SHIM
1190 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
1192 #define TRY_SHIM(_i) \
1193 PR_BEGIN_MACRO \
1194 if (IID_IS(_i)) { \
1195 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
1196 if (!real) { \
1197 return NS_NOINTERFACE; \
1199 nsCOMPtr<_i> shim = new _i##Shim(this, real); \
1200 shim.forget(aSink); \
1201 return NS_OK; \
1203 PR_END_MACRO
1205 NS_IMETHODIMP
1206 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
1207 void** aSink) {
1208 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
1209 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
1210 return mCallbacks->GetInterface(aIID, aSink);
1213 *aSink = nullptr;
1215 TRY_SHIM(nsILoadContext);
1216 TRY_SHIM(nsIProgressEventSink);
1217 TRY_SHIM(nsIChannelEventSink);
1219 return NS_NOINTERFACE;
1222 #undef TRY_SHIM
1223 #undef IID_IS
1225 ExternalResourceMap::ExternalResource::~ExternalResource() {
1226 if (mViewer) {
1227 mViewer->Close(nullptr);
1228 mViewer->Destroy();
1232 // ==================================================================
1233 // =
1234 // ==================================================================
1236 // If we ever have an nsIDocumentObserver notification for stylesheet title
1237 // changes we should update the list from that instead of overriding
1238 // EnsureFresh.
1239 class DOMStyleSheetSetList final : public DOMStringList {
1240 public:
1241 explicit DOMStyleSheetSetList(Document* aDocument);
1243 void Disconnect() { mDocument = nullptr; }
1245 virtual void EnsureFresh() override;
1247 protected:
1248 Document* mDocument; // Our document; weak ref. It'll let us know if it
1249 // dies.
1252 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
1253 : mDocument(aDocument) {
1254 NS_ASSERTION(mDocument, "Must have document!");
1257 void DOMStyleSheetSetList::EnsureFresh() {
1258 MOZ_ASSERT(NS_IsMainThread());
1260 mNames.Clear();
1262 if (!mDocument) {
1263 return; // Spec says "no exceptions", and we have no style sets if we have
1264 // no document, for sure
1267 size_t count = mDocument->SheetCount();
1268 nsAutoString title;
1269 for (size_t index = 0; index < count; index++) {
1270 StyleSheet* sheet = mDocument->SheetAt(index);
1271 NS_ASSERTION(sheet, "Null sheet in sheet list!");
1272 sheet->GetTitle(title);
1273 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
1274 return;
1279 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1281 // ==================================================================
1282 // =
1283 // ==================================================================
1285 Document::InternalCommandDataHashtable*
1286 Document::sInternalCommandDataHashtable = nullptr;
1288 // static
1289 void Document::Shutdown() {
1290 if (sInternalCommandDataHashtable) {
1291 sInternalCommandDataHashtable->Clear();
1292 delete sInternalCommandDataHashtable;
1293 sInternalCommandDataHashtable = nullptr;
1297 Document::Document(const char* aContentType)
1298 : nsINode(nullptr),
1299 DocumentOrShadowRoot(this),
1300 mCharacterSet(WINDOWS_1252_ENCODING),
1301 mCharacterSetSource(0),
1302 mParentDocument(nullptr),
1303 mCachedRootElement(nullptr),
1304 mNodeInfoManager(nullptr),
1305 #ifdef DEBUG
1306 mStyledLinksCleared(false),
1307 #endif
1308 mCachedStateObjectValid(false),
1309 mBlockAllMixedContent(false),
1310 mBlockAllMixedContentPreloads(false),
1311 mUpgradeInsecureRequests(false),
1312 mUpgradeInsecurePreloads(false),
1313 mDevToolsWatchingDOMMutations(false),
1314 mBidiEnabled(false),
1315 mMayNeedFontPrefsUpdate(true),
1316 mMathMLEnabled(false),
1317 mIsInitialDocumentInWindow(false),
1318 mIsEverInitialDocumentInWindow(false),
1319 mIgnoreDocGroupMismatches(false),
1320 mLoadedAsData(false),
1321 mAddedToMemoryReportingAsDataDocument(false),
1322 mMayStartLayout(true),
1323 mHaveFiredTitleChange(false),
1324 mIsShowing(false),
1325 mVisible(true),
1326 mRemovedFromDocShell(false),
1327 // mAllowDNSPrefetch starts true, so that we can always reliably && it
1328 // with various values that might disable it. Since we never prefetch
1329 // unless we get a window, and in that case the docshell value will get
1330 // &&-ed in, this is safe.
1331 mAllowDNSPrefetch(true),
1332 mIsStaticDocument(false),
1333 mCreatingStaticClone(false),
1334 mHasPrintCallbacks(false),
1335 mInUnlinkOrDeletion(false),
1336 mHasHadScriptHandlingObject(false),
1337 mIsBeingUsedAsImage(false),
1338 mChromeRulesEnabled(false),
1339 mInChromeDocShell(false),
1340 mIsSyntheticDocument(false),
1341 mHasLinksToUpdateRunnable(false),
1342 mFlushingPendingLinkUpdates(false),
1343 mMayHaveDOMMutationObservers(false),
1344 mMayHaveAnimationObservers(false),
1345 mHasCSPDeliveredThroughHeader(false),
1346 mBFCacheDisallowed(false),
1347 mHasHadDefaultView(false),
1348 mStyleSheetChangeEventsEnabled(false),
1349 mDevToolsAnonymousAndShadowEventsEnabled(false),
1350 mIsSrcdocDocument(false),
1351 mHasDisplayDocument(false),
1352 mFontFaceSetDirty(true),
1353 mDidFireDOMContentLoaded(true),
1354 mFrameRequestCallbacksScheduled(false),
1355 mIsTopLevelContentDocument(false),
1356 mIsContentDocument(false),
1357 mDidCallBeginLoad(false),
1358 mEncodingMenuDisabled(false),
1359 mLinksEnabled(true),
1360 mIsSVGGlyphsDocument(false),
1361 mInDestructor(false),
1362 mIsGoingAway(false),
1363 mStyleSetFilled(false),
1364 mQuirkSheetAdded(false),
1365 mContentEditableSheetAdded(false),
1366 mDesignModeSheetAdded(false),
1367 mMayHaveTitleElement(false),
1368 mDOMLoadingSet(false),
1369 mDOMInteractiveSet(false),
1370 mDOMCompleteSet(false),
1371 mAutoFocusFired(false),
1372 mScrolledToRefAlready(false),
1373 mChangeScrollPosWhenScrollingToRef(false),
1374 mDelayFrameLoaderInitialization(false),
1375 mSynchronousDOMContentLoaded(false),
1376 mMaybeServiceWorkerControlled(false),
1377 mAllowZoom(false),
1378 mValidScaleFloat(false),
1379 mValidMinScale(false),
1380 mValidMaxScale(false),
1381 mWidthStrEmpty(false),
1382 mParserAborted(false),
1383 mReportedDocumentUseCounters(false),
1384 mHasReportedShadowDOMUsage(false),
1385 mHasDelayedRefreshEvent(false),
1386 mLoadEventFiring(false),
1387 mSkipLoadEventAfterClose(false),
1388 mDisableCookieAccess(false),
1389 mDisableDocWrite(false),
1390 mTooDeepWriteRecursion(false),
1391 mPendingMaybeEditingStateChanged(false),
1392 mHasBeenEditable(false),
1393 mHasWarnedAboutZoom(false),
1394 mIsRunningExecCommandByContent(false),
1395 mIsRunningExecCommandByChromeOrAddon(false),
1396 mSetCompleteAfterDOMContentLoaded(false),
1397 mDidHitCompleteSheetCache(false),
1398 mUseCountersInitialized(false),
1399 mShouldReportUseCounters(false),
1400 mShouldSendPageUseCounters(false),
1401 mUserHasInteracted(false),
1402 mHasUserInteractionTimerScheduled(false),
1403 mShouldResistFingerprinting(false),
1404 mCloningForSVGUse(false),
1405 mAllowDeclarativeShadowRoots(false),
1406 mSuspendDOMNotifications(false),
1407 mXMLDeclarationBits(0),
1408 mOnloadBlockCount(0),
1409 mWriteLevel(0),
1410 mContentEditableCount(0),
1411 mEditingState(EditingState::eOff),
1412 mCompatMode(eCompatibility_FullStandards),
1413 mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
1414 mAncestorIsLoading(false),
1415 mVisibilityState(dom::VisibilityState::Hidden),
1416 mType(eUnknown),
1417 mDefaultElementType(0),
1418 mAllowXULXBL(eTriUnset),
1419 mSkipDTDSecurityChecks(false),
1420 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
1421 mSandboxFlags(0),
1422 mPartID(0),
1423 mMarkedCCGeneration(0),
1424 mPresShell(nullptr),
1425 mSubtreeModifiedDepth(0),
1426 mPreloadPictureDepth(0),
1427 mEventsSuppressed(0),
1428 mIgnoreDestructiveWritesCounter(0),
1429 mStaticCloneCount(0),
1430 mWindow(nullptr),
1431 mBFCacheEntry(nullptr),
1432 mInSyncOperationCount(0),
1433 mBlockDOMContentLoaded(0),
1434 mUpdateNestLevel(0),
1435 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
1436 mViewportType(Unknown),
1437 mViewportFit(ViewportFitType::Auto),
1438 mHeaderData(nullptr),
1439 mServoRestyleRootDirtyBits(0),
1440 mThrowOnDynamicMarkupInsertionCounter(0),
1441 mIgnoreOpensDuringUnloadCounter(0),
1442 mSavedResolution(1.0f),
1443 mGeneration(0),
1444 mCachedTabSizeGeneration(0),
1445 mNextFormNumber(0),
1446 mNextControlNumber(0),
1447 mPreloadService(this),
1448 mShouldNotifyFetchSuccess(false),
1449 mShouldNotifyFormOrPasswordRemoved(false) {
1450 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
1452 SetIsInDocument();
1453 SetIsConnected(true);
1455 // Create these unconditionally, they will be used to warn about the `zoom`
1456 // property, even if use counters are disabled.
1457 mStyleUseCounters.reset(Servo_UseCounters_Create());
1459 SetContentType(nsDependentCString(aContentType));
1461 // Start out mLastStyleSheetSet as null, per spec
1462 SetDOMStringToNull(mLastStyleSheetSet);
1464 // void state used to differentiate an empty source from an unselected source
1465 mPreloadPictureFoundSource.SetIsVoid(true);
1467 RecomputeLanguageFromCharset();
1469 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
1470 mReferrerInfo = new dom::ReferrerInfo(nullptr);
1473 #ifndef ANDROID
1474 // unused by GeckoView
1475 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
1476 if (NS_WARN_IF(!aWin)) {
1477 return false;
1480 nsIURI* uri = aWin->GetDocumentURI();
1481 if (NS_WARN_IF(!uri)) {
1482 return false;
1484 // getSpec is an expensive operation, hence we first check the scheme
1485 // to see if the caller is actually an about: page.
1486 if (!uri->SchemeIs("about")) {
1487 return false;
1490 nsAutoCString aboutSpec;
1491 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
1492 NS_ENSURE_SUCCESS(rv, false);
1494 return aboutSpec.EqualsASCII(aSpec);
1496 #endif
1498 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
1499 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1500 #ifdef ANDROID
1501 // GeckoView uses data URLs for error pages, so for now just check for any
1502 // error page
1503 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1504 #else
1505 return win && IsAboutErrorPage(win, "neterror");
1506 #endif
1509 bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
1510 JSObject* aObject) {
1511 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1512 #ifdef ANDROID
1513 // GeckoView uses data URLs for error pages, so for now just check for any
1514 // error page
1515 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1516 #else
1517 return win && IsAboutErrorPage(win, "httpsonlyerror");
1518 #endif
1521 already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
1522 bool aIsTemporary, ErrorResult& aError) {
1523 RefPtr<Promise> promise = Promise::Create(GetScopeObject(), aError,
1524 Promise::ePropagateUserInteraction);
1525 if (aError.Failed()) {
1526 return nullptr;
1529 nsresult rv = NS_OK;
1530 if (NS_WARN_IF(!mFailedChannel)) {
1531 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1532 return promise.forget();
1535 nsCOMPtr<nsIURI> failedChannelURI;
1536 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
1537 if (!failedChannelURI) {
1538 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1539 return promise.forget();
1542 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI);
1543 if (!innerURI) {
1544 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1545 return promise.forget();
1548 nsAutoCString host;
1549 innerURI->GetAsciiHost(host);
1550 int32_t port;
1551 innerURI->GetPort(&port);
1553 nsCOMPtr<nsITransportSecurityInfo> tsi;
1554 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1555 if (NS_WARN_IF(NS_FAILED(rv))) {
1556 promise->MaybeReject(rv);
1557 return promise.forget();
1559 if (NS_WARN_IF(!tsi)) {
1560 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1561 return promise.forget();
1564 nsCOMPtr<nsIX509Cert> cert;
1565 rv = tsi->GetServerCert(getter_AddRefs(cert));
1566 if (NS_WARN_IF(NS_FAILED(rv))) {
1567 promise->MaybeReject(rv);
1568 return promise.forget();
1570 if (NS_WARN_IF(!cert)) {
1571 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1572 return promise.forget();
1575 if (XRE_IsContentProcess()) {
1576 ContentChild* cc = ContentChild::GetSingleton();
1577 MOZ_ASSERT(cc);
1578 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1579 cc->SendAddCertException(cert, host, port, attrs, aIsTemporary)
1580 ->Then(GetCurrentSerialEventTarget(), __func__,
1581 [promise](const mozilla::MozPromise<
1582 nsresult, mozilla::ipc::ResponseRejectReason,
1583 true>::ResolveOrRejectValue& aValue) {
1584 if (aValue.IsResolve()) {
1585 promise->MaybeResolve(aValue.ResolveValue());
1586 } else {
1587 promise->MaybeRejectWithUndefined();
1590 return promise.forget();
1593 if (XRE_IsParentProcess()) {
1594 nsCOMPtr<nsICertOverrideService> overrideService =
1595 do_GetService(NS_CERTOVERRIDE_CONTRACTID);
1596 if (!overrideService) {
1597 promise->MaybeReject(NS_ERROR_FAILURE);
1598 return promise.forget();
1601 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1602 rv = overrideService->RememberValidityOverride(host, port, attrs, cert,
1603 aIsTemporary);
1604 if (NS_WARN_IF(NS_FAILED(rv))) {
1605 promise->MaybeReject(rv);
1606 return promise.forget();
1609 promise->MaybeResolveWithUndefined();
1610 return promise.forget();
1613 promise->MaybeReject(NS_ERROR_FAILURE);
1614 return promise.forget();
1617 void Document::ReloadWithHttpsOnlyException() {
1618 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
1619 wgc->SendReloadWithHttpsOnlyException();
1623 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
1624 nsresult rv = NS_OK;
1625 if (NS_WARN_IF(!mFailedChannel)) {
1626 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1627 return;
1630 nsCOMPtr<nsITransportSecurityInfo> tsi;
1631 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1632 if (NS_WARN_IF(NS_FAILED(rv))) {
1633 aRv.Throw(rv);
1634 return;
1636 if (NS_WARN_IF(!tsi)) {
1637 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1638 return;
1641 nsAutoString errorCodeString;
1642 rv = tsi->GetErrorCodeString(errorCodeString);
1643 if (NS_WARN_IF(NS_FAILED(rv))) {
1644 aRv.Throw(rv);
1645 return;
1647 aInfo.mErrorCodeString.Assign(errorCodeString);
1650 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
1651 JSObject* aObject) {
1652 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1653 #ifdef ANDROID
1654 // GeckoView uses data URLs for error pages, so for now just check for any
1655 // error page
1656 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1657 #else
1658 return win && IsAboutErrorPage(win, "certerror");
1659 #endif
1662 bool Document::CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject) {
1663 RefPtr<BasePrincipal> principal =
1664 BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx));
1666 if (!principal) {
1667 return false;
1670 // We allow the privilege SSA to be called from system principal.
1671 if (principal->IsSystemPrincipal()) {
1672 return true;
1675 // We only allow calling the privilege SSA from the content script of the
1676 // webcompat extension.
1677 if (auto* policy = principal->ContentScriptAddonPolicy()) {
1678 nsAutoString addonID;
1679 policy->GetId(addonID);
1681 return addonID.EqualsLiteral("webcompat@mozilla.org");
1684 return false;
1687 bool Document::IsErrorPage() const {
1688 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
1689 return loadInfo && loadInfo->GetLoadErrorPage();
1692 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
1693 ErrorResult& aRv) {
1694 nsresult rv = NS_OK;
1695 if (NS_WARN_IF(!mFailedChannel)) {
1696 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1697 return;
1700 nsCOMPtr<nsITransportSecurityInfo> tsi;
1701 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1702 if (NS_WARN_IF(NS_FAILED(rv))) {
1703 aRv.Throw(rv);
1704 return;
1706 if (NS_WARN_IF(!tsi)) {
1707 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1708 return;
1711 nsAutoString errorCodeString;
1712 rv = tsi->GetErrorCodeString(errorCodeString);
1713 if (NS_WARN_IF(NS_FAILED(rv))) {
1714 aRv.Throw(rv);
1715 return;
1717 aInfo.mErrorCodeString.Assign(errorCodeString);
1719 nsITransportSecurityInfo::OverridableErrorCategory errorCategory;
1720 rv = tsi->GetOverridableErrorCategory(&errorCategory);
1721 if (NS_WARN_IF(NS_FAILED(rv))) {
1722 aRv.Throw(rv);
1723 return;
1725 switch (errorCategory) {
1726 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST:
1727 aInfo.mOverridableErrorCategory =
1728 dom::OverridableErrorCategory::Trust_error;
1729 break;
1730 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN:
1731 aInfo.mOverridableErrorCategory =
1732 dom::OverridableErrorCategory::Domain_mismatch;
1733 break;
1734 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME:
1735 aInfo.mOverridableErrorCategory =
1736 dom::OverridableErrorCategory::Expired_or_not_yet_valid;
1737 break;
1738 default:
1739 aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Unset;
1740 break;
1743 nsCOMPtr<nsIX509Cert> cert;
1744 nsCOMPtr<nsIX509CertValidity> validity;
1745 rv = tsi->GetServerCert(getter_AddRefs(cert));
1746 if (NS_WARN_IF(NS_FAILED(rv))) {
1747 aRv.Throw(rv);
1748 return;
1750 if (NS_WARN_IF(!cert)) {
1751 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1752 return;
1755 rv = cert->GetValidity(getter_AddRefs(validity));
1756 if (NS_WARN_IF(NS_FAILED(rv))) {
1757 aRv.Throw(rv);
1758 return;
1760 if (NS_WARN_IF(!validity)) {
1761 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1762 return;
1765 PRTime validityResult;
1766 rv = validity->GetNotBefore(&validityResult);
1767 if (NS_WARN_IF(NS_FAILED(rv))) {
1768 aRv.Throw(rv);
1769 return;
1771 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1773 rv = validity->GetNotAfter(&validityResult);
1774 if (NS_WARN_IF(NS_FAILED(rv))) {
1775 aRv.Throw(rv);
1776 return;
1778 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1780 nsAutoString issuerCommonName;
1781 nsAutoString certChainPEMString;
1782 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
1783 int64_t maxValidity = std::numeric_limits<int64_t>::max();
1784 int64_t minValidity = 0;
1785 PRTime notBefore, notAfter;
1786 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
1787 rv = tsi->GetFailedCertChain(failedCertArray);
1788 if (NS_WARN_IF(NS_FAILED(rv))) {
1789 aRv.Throw(rv);
1790 return;
1793 if (NS_WARN_IF(failedCertArray.IsEmpty())) {
1794 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1795 return;
1798 for (const auto& certificate : failedCertArray) {
1799 rv = certificate->GetIssuerCommonName(issuerCommonName);
1800 if (NS_WARN_IF(NS_FAILED(rv))) {
1801 aRv.Throw(rv);
1802 return;
1805 rv = certificate->GetValidity(getter_AddRefs(validity));
1806 if (NS_WARN_IF(NS_FAILED(rv))) {
1807 aRv.Throw(rv);
1808 return;
1810 if (NS_WARN_IF(!validity)) {
1811 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1812 return;
1815 rv = validity->GetNotBefore(&notBefore);
1816 if (NS_WARN_IF(NS_FAILED(rv))) {
1817 aRv.Throw(rv);
1818 return;
1821 rv = validity->GetNotAfter(&notAfter);
1822 if (NS_WARN_IF(NS_FAILED(rv))) {
1823 aRv.Throw(rv);
1824 return;
1827 notBefore = std::max(minValidity, notBefore);
1828 notAfter = std::min(maxValidity, notAfter);
1829 nsTArray<uint8_t> certArray;
1830 rv = certificate->GetRawDER(certArray);
1831 if (NS_WARN_IF(NS_FAILED(rv))) {
1832 aRv.Throw(rv);
1833 return;
1836 nsAutoString der64;
1837 rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()),
1838 certArray.Length(), der64);
1839 if (NS_WARN_IF(NS_FAILED(rv))) {
1840 aRv.Throw(rv);
1841 return;
1843 if (!certChainStrings.AppendElement(der64, fallible)) {
1844 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1845 return;
1849 aInfo.mIssuerCommonName.Assign(issuerCommonName);
1850 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
1851 aInfo.mCertValidityRangeNotBefore =
1852 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
1854 int32_t errorCode;
1855 rv = tsi->GetErrorCode(&errorCode);
1856 if (NS_WARN_IF(NS_FAILED(rv))) {
1857 aRv.Throw(rv);
1858 return;
1861 nsCOMPtr<nsINSSErrorsService> nsserr =
1862 do_GetService("@mozilla.org/nss_errors_service;1");
1863 if (NS_WARN_IF(!nsserr)) {
1864 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1865 return;
1867 nsresult res;
1868 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
1869 if (NS_WARN_IF(NS_FAILED(rv))) {
1870 aRv.Throw(rv);
1871 return;
1873 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
1874 if (NS_WARN_IF(NS_FAILED(rv))) {
1875 aRv.Throw(rv);
1876 return;
1879 OriginAttributes attrs;
1880 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1881 nsCOMPtr<nsIURI> aURI;
1882 mFailedChannel->GetURI(getter_AddRefs(aURI));
1883 if (XRE_IsContentProcess()) {
1884 ContentChild* cc = ContentChild::GetSingleton();
1885 MOZ_ASSERT(cc);
1886 cc->SendIsSecureURI(aURI, attrs, &aInfo.mHasHSTS);
1887 } else {
1888 nsCOMPtr<nsISiteSecurityService> sss =
1889 do_GetService(NS_SSSERVICE_CONTRACTID);
1890 if (NS_WARN_IF(!sss)) {
1891 return;
1893 Unused << NS_WARN_IF(
1894 NS_FAILED(sss->IsSecureURI(aURI, attrs, &aInfo.mHasHSTS)));
1896 nsCOMPtr<nsIPublicKeyPinningService> pkps =
1897 do_GetService(NS_PKPSERVICE_CONTRACTID);
1898 if (NS_WARN_IF(!pkps)) {
1899 return;
1901 Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP)));
1904 bool Document::IsAboutPage() const {
1905 return NodePrincipal()->SchemeIs("about");
1908 void Document::ConstructUbiNode(void* storage) {
1909 JS::ubi::Concrete<Document>::construct(storage, this);
1912 void Document::LoadEventFired() {
1913 // Object used to collect some telemetry data so we don't need to query for it
1914 // twice.
1915 glean::perf::PageLoadExtra pageLoadEventData;
1917 // Accumulate timing data located in each document's realm and report to
1918 // telemetry.
1919 AccumulateJSTelemetry(pageLoadEventData);
1921 // Collect page load timings
1922 AccumulatePageLoadTelemetry(pageLoadEventData);
1924 // Record page load event
1925 RecordPageLoadEventTelemetry(pageLoadEventData);
1927 // Release the JS bytecode cache from its wait on the load event, and
1928 // potentially dispatch the encoding of the bytecode.
1929 if (ScriptLoader()) {
1930 ScriptLoader()->LoadEventFired();
1934 void Document::RecordPageLoadEventTelemetry(
1935 glean::perf::PageLoadExtra& aEventTelemetryData) {
1936 // If the page load time is empty, then the content wasn't something we want
1937 // to report (i.e. not a top level document).
1938 if (!aEventTelemetryData.loadTime) {
1939 return;
1941 MOZ_ASSERT(IsTopLevelContentDocument());
1943 nsPIDOMWindowOuter* window = GetWindow();
1944 if (!window) {
1945 return;
1948 nsIDocShell* docshell = window->GetDocShell();
1949 if (!docshell) {
1950 return;
1953 nsAutoCString loadTypeStr;
1954 switch (docshell->GetLoadType()) {
1955 case LOAD_NORMAL:
1956 case LOAD_NORMAL_REPLACE:
1957 case LOAD_NORMAL_BYPASS_CACHE:
1958 case LOAD_NORMAL_BYPASS_PROXY:
1959 case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
1960 loadTypeStr.Append("NORMAL");
1961 break;
1962 case LOAD_HISTORY:
1963 loadTypeStr.Append("HISTORY");
1964 break;
1965 case LOAD_RELOAD_NORMAL:
1966 case LOAD_RELOAD_BYPASS_CACHE:
1967 case LOAD_RELOAD_BYPASS_PROXY:
1968 case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
1969 case LOAD_REFRESH:
1970 case LOAD_REFRESH_REPLACE:
1971 case LOAD_RELOAD_CHARSET_CHANGE:
1972 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
1973 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
1974 loadTypeStr.Append("RELOAD");
1975 break;
1976 case LOAD_LINK:
1977 loadTypeStr.Append("LINK");
1978 break;
1979 case LOAD_STOP_CONTENT:
1980 case LOAD_STOP_CONTENT_AND_REPLACE:
1981 loadTypeStr.Append("STOP");
1982 break;
1983 case LOAD_ERROR_PAGE:
1984 loadTypeStr.Append("ERROR");
1985 break;
1986 default:
1987 loadTypeStr.Append("OTHER");
1988 break;
1991 nsCOMPtr<nsIEffectiveTLDService> tldService =
1992 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
1993 if (tldService && mReferrerInfo &&
1994 (docshell->GetLoadType() & nsIDocShell::LOAD_CMD_NORMAL)) {
1995 nsAutoCString currentBaseDomain, referrerBaseDomain;
1996 nsCOMPtr<nsIURI> referrerURI = mReferrerInfo->GetComputedReferrer();
1997 if (referrerURI) {
1998 auto result = NS_SUCCEEDED(
1999 tldService->GetBaseDomain(referrerURI, 0, referrerBaseDomain));
2000 if (result) {
2001 bool sameOrigin = false;
2002 NodePrincipal()->IsSameOrigin(referrerURI, &sameOrigin);
2003 aEventTelemetryData.sameOriginNav = mozilla::Some(sameOrigin);
2008 aEventTelemetryData.loadType = mozilla::Some(loadTypeStr);
2010 // Sending a glean ping must be done on the parent process.
2011 if (ContentChild* cc = ContentChild::GetSingleton()) {
2012 cc->SendRecordPageLoadEvent(aEventTelemetryData);
2016 void Document::AccumulatePageLoadTelemetry(
2017 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2018 // Interested only in top level documents for real websites that are in the
2019 // foreground.
2020 if (!ShouldIncludeInTelemetry() || !IsTopLevelContentDocument() ||
2021 !GetNavigationTiming() ||
2022 !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
2023 return;
2026 if (!GetChannel()) {
2027 return;
2030 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
2031 if (!timedChannel) {
2032 return;
2035 // Default duration is 0, use this to check for bogus negative values.
2036 const TimeDuration zeroDuration;
2038 TimeStamp responseStart;
2039 timedChannel->GetResponseStart(&responseStart);
2041 TimeStamp redirectStart, redirectEnd;
2042 timedChannel->GetRedirectStart(&redirectStart);
2043 timedChannel->GetRedirectEnd(&redirectEnd);
2045 uint8_t redirectCount;
2046 timedChannel->GetRedirectCount(&redirectCount);
2047 if (redirectCount) {
2048 aEventTelemetryDataOut.redirectCount =
2049 mozilla::Some(static_cast<uint32_t>(redirectCount));
2052 if (!redirectStart.IsNull() && !redirectEnd.IsNull()) {
2053 TimeDuration redirectTime = redirectEnd - redirectStart;
2054 if (redirectTime > zeroDuration) {
2055 aEventTelemetryDataOut.redirectTime =
2056 mozilla::Some(static_cast<uint32_t>(redirectTime.ToMilliseconds()));
2060 TimeStamp dnsLookupStart, dnsLookupEnd;
2061 timedChannel->GetDomainLookupStart(&dnsLookupStart);
2062 timedChannel->GetDomainLookupEnd(&dnsLookupEnd);
2064 if (!dnsLookupStart.IsNull() && !dnsLookupEnd.IsNull()) {
2065 TimeDuration dnsLookupTime = dnsLookupEnd - dnsLookupStart;
2066 if (dnsLookupTime > zeroDuration) {
2067 aEventTelemetryDataOut.dnsLookupTime =
2068 mozilla::Some(static_cast<uint32_t>(dnsLookupTime.ToMilliseconds()));
2072 TimeStamp navigationStart =
2073 GetNavigationTiming()->GetNavigationStartTimeStamp();
2075 if (!responseStart || !navigationStart) {
2076 return;
2079 nsAutoCString dnsKey("Native");
2080 nsAutoCString http3Key;
2081 nsAutoCString http3WithPriorityKey;
2082 nsAutoCString earlyHintKey;
2083 nsCOMPtr<nsIHttpChannelInternal> httpChannel =
2084 do_QueryInterface(GetChannel());
2085 if (httpChannel) {
2086 bool resolvedByTRR = false;
2087 Unused << httpChannel->GetIsResolvedByTRR(&resolvedByTRR);
2088 if (resolvedByTRR) {
2089 if (nsCOMPtr<nsIDNSService> dns =
2090 do_GetService(NS_DNSSERVICE_CONTRACTID)) {
2091 dns->GetTRRDomainKey(dnsKey);
2092 } else {
2093 // Failed to get the DNS service.
2094 dnsKey = "(fail)"_ns;
2096 aEventTelemetryDataOut.trrDomain = mozilla::Some(dnsKey);
2099 uint32_t major;
2100 uint32_t minor;
2101 if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
2102 if (major == 3) {
2103 http3Key = "http3"_ns;
2104 nsCOMPtr<nsIHttpChannel> httpChannel2 = do_QueryInterface(GetChannel());
2105 nsCString header;
2106 if (httpChannel2 &&
2107 NS_SUCCEEDED(
2108 httpChannel2->GetResponseHeader("priority"_ns, header)) &&
2109 !header.IsEmpty()) {
2110 http3WithPriorityKey = "with_priority"_ns;
2111 } else {
2112 http3WithPriorityKey = "without_priority"_ns;
2114 } else if (major == 2) {
2115 bool supportHttp3 = false;
2116 if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
2117 supportHttp3 = false;
2119 if (supportHttp3) {
2120 http3Key = "supports_http3"_ns;
2124 aEventTelemetryDataOut.httpVer = mozilla::Some(major);
2127 uint32_t earlyHintType = 0;
2128 Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
2129 if (earlyHintType & LinkStyle::ePRECONNECT) {
2130 earlyHintKey.Append("preconnect_"_ns);
2132 if (earlyHintType & LinkStyle::ePRELOAD) {
2133 earlyHintKey.Append("preload_"_ns);
2134 earlyHintKey.Append(mPreloadService.GetEarlyHintUsed() ? "1"_ns : "0"_ns);
2138 TimeStamp asyncOpen;
2139 timedChannel->GetAsyncOpen(&asyncOpen);
2140 if (asyncOpen) {
2141 Telemetry::AccumulateTimeDelta(Telemetry::DNS_PERF_FIRST_BYTE_MS, dnsKey,
2142 asyncOpen, responseStart);
2145 // First Contentful Composite
2146 if (TimeStamp firstContentfulComposite =
2147 GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) {
2148 glean::performance_pageload::fcp.AccumulateRawDuration(
2149 firstContentfulComposite - navigationStart);
2151 if (!http3Key.IsEmpty()) {
2152 Telemetry::AccumulateTimeDelta(
2153 Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key,
2154 navigationStart, firstContentfulComposite);
2157 if (!http3WithPriorityKey.IsEmpty()) {
2158 Telemetry::AccumulateTimeDelta(
2159 Telemetry::H3P_PERF_FIRST_CONTENTFUL_PAINT_MS, http3WithPriorityKey,
2160 navigationStart, firstContentfulComposite);
2163 if (!earlyHintKey.IsEmpty()) {
2164 Telemetry::AccumulateTimeDelta(
2165 Telemetry::EH_PERF_FIRST_CONTENTFUL_PAINT_MS, earlyHintKey,
2166 navigationStart, firstContentfulComposite);
2169 Telemetry::AccumulateTimeDelta(
2170 Telemetry::DNS_PERF_FIRST_CONTENTFUL_PAINT_MS, dnsKey, navigationStart,
2171 firstContentfulComposite);
2173 glean::performance_pageload::fcp_responsestart.AccumulateRawDuration(
2174 firstContentfulComposite - responseStart);
2176 TimeDuration fcpTime = firstContentfulComposite - navigationStart;
2177 if (fcpTime > zeroDuration) {
2178 aEventTelemetryDataOut.fcpTime =
2179 mozilla::Some(static_cast<uint32_t>(fcpTime.ToMilliseconds()));
2183 // Report the most up to date LCP time. For our histogram we actually report
2184 // this on page unload.
2185 if (TimeStamp lcpTime =
2186 GetNavigationTiming()->GetLargestContentfulRenderTimeStamp()) {
2187 aEventTelemetryDataOut.lcpTime = mozilla::Some(
2188 static_cast<uint32_t>((lcpTime - navigationStart).ToMilliseconds()));
2191 // Load event
2192 if (TimeStamp loadEventStart =
2193 GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
2194 glean::performance_pageload::load_time.AccumulateRawDuration(
2195 loadEventStart - navigationStart);
2196 if (!http3Key.IsEmpty()) {
2197 Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
2198 http3Key, navigationStart, loadEventStart);
2201 if (!http3WithPriorityKey.IsEmpty()) {
2202 Telemetry::AccumulateTimeDelta(Telemetry::H3P_PERF_PAGE_LOAD_TIME_MS,
2203 http3WithPriorityKey, navigationStart,
2204 loadEventStart);
2207 if (!earlyHintKey.IsEmpty()) {
2208 Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_PAGE_LOAD_TIME_MS,
2209 earlyHintKey, navigationStart,
2210 loadEventStart);
2213 glean::performance_pageload::load_time_responsestart.AccumulateRawDuration(
2214 loadEventStart - responseStart);
2216 TimeDuration responseTime = responseStart - navigationStart;
2217 if (responseTime > zeroDuration) {
2218 aEventTelemetryDataOut.responseTime =
2219 mozilla::Some(static_cast<uint32_t>(responseTime.ToMilliseconds()));
2222 TimeDuration loadTime = loadEventStart - navigationStart;
2223 if (loadTime > zeroDuration) {
2224 aEventTelemetryDataOut.loadTime =
2225 mozilla::Some(static_cast<uint32_t>(loadTime.ToMilliseconds()));
2230 void Document::AccumulateJSTelemetry(
2231 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2232 if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry()) {
2233 return;
2236 if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
2237 return;
2240 AutoJSContext cx;
2241 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
2242 JSAutoRealm ar(cx, globalObject);
2243 JS::JSTimers timers = JS::GetJSTimers(cx);
2245 if (!timers.executionTime.IsZero()) {
2246 glean::javascript_pageload::execution_time.AccumulateRawDuration(
2247 timers.executionTime);
2248 aEventTelemetryDataOut.jsExecTime = mozilla::Some(
2249 static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
2252 if (!timers.delazificationTime.IsZero()) {
2253 glean::javascript_pageload::delazification_time.AccumulateRawDuration(
2254 timers.delazificationTime);
2257 if (!timers.xdrEncodingTime.IsZero()) {
2258 glean::javascript_pageload::xdr_encode_time.AccumulateRawDuration(
2259 timers.xdrEncodingTime);
2262 if (!timers.baselineCompileTime.IsZero()) {
2263 glean::javascript_pageload::baseline_compile_time.AccumulateRawDuration(
2264 timers.baselineCompileTime);
2267 if (!timers.gcTime.IsZero()) {
2268 glean::javascript_pageload::gc_time.AccumulateRawDuration(timers.gcTime);
2271 if (!timers.protectTime.IsZero()) {
2272 glean::javascript_pageload::protect_time.AccumulateRawDuration(
2273 timers.protectTime);
2277 Document::~Document() {
2278 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
2279 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
2280 "Can't be top-level and a resource doc at the same time");
2282 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
2284 if (IsTopLevelContentDocument()) {
2285 RemoveToplevelLoadingDocument(this);
2287 // don't report for about: pages
2288 if (!IsAboutPage()) {
2289 if (MOZ_UNLIKELY(mMathMLEnabled)) {
2290 ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
2293 if (IsHTMLDocument()) {
2294 switch (GetCompatibilityMode()) {
2295 case eCompatibility_FullStandards:
2296 Telemetry::AccumulateCategorical(
2297 Telemetry::LABELS_QUIRKS_MODE::FullStandards);
2298 break;
2299 case eCompatibility_AlmostStandards:
2300 Telemetry::AccumulateCategorical(
2301 Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
2302 break;
2303 case eCompatibility_NavQuirks:
2304 Telemetry::AccumulateCategorical(
2305 Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
2306 break;
2307 default:
2308 MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
2309 break;
2315 mInDestructor = true;
2316 mInUnlinkOrDeletion = true;
2318 mozilla::DropJSObjects(this);
2320 // Clear mObservers to keep it in sync with the mutationobserver list
2321 mObservers.Clear();
2323 mIntersectionObservers.Clear();
2325 if (mStyleSheetSetList) {
2326 mStyleSheetSetList->Disconnect();
2329 if (mAnimationController) {
2330 mAnimationController->Disconnect();
2333 MOZ_ASSERT(mTimelines.isEmpty());
2335 mParentDocument = nullptr;
2337 // Kill the subdocument map, doing this will release its strong
2338 // references, if any.
2339 mSubDocuments = nullptr;
2341 nsAutoScriptBlocker scriptBlocker;
2343 // Destroy link map now so we don't waste time removing
2344 // links one by one
2345 DestroyElementMaps();
2347 // Invalidate cached array of child nodes
2348 InvalidateChildNodes();
2350 // We should not have child nodes when destructor is called,
2351 // since child nodes keep their owner document alive.
2352 MOZ_ASSERT(!HasChildren());
2354 mCachedRootElement = nullptr;
2356 for (auto& sheets : mAdditionalSheets) {
2357 UnlinkStyleSheets(sheets);
2360 if (mAttributeStyles) {
2361 mAttributeStyles->SetOwningDocument(nullptr);
2364 if (mListenerManager) {
2365 mListenerManager->Disconnect();
2366 UnsetFlags(NODE_HAS_LISTENERMANAGER);
2369 if (mScriptLoader) {
2370 mScriptLoader->DropDocumentReference();
2373 if (mCSSLoader) {
2374 // Could be null here if Init() failed or if we have been unlinked.
2375 mCSSLoader->DropDocumentReference();
2378 if (mStyleImageLoader) {
2379 mStyleImageLoader->DropDocumentReference();
2382 if (mXULBroadcastManager) {
2383 mXULBroadcastManager->DropDocumentReference();
2386 if (mXULPersist) {
2387 mXULPersist->DropDocumentReference();
2390 if (mPermissionDelegateHandler) {
2391 mPermissionDelegateHandler->DropDocumentReference();
2394 mHeaderData = nullptr;
2396 mPendingTitleChangeEvent.Revoke();
2398 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
2399 "must not have media query lists left");
2401 if (mNodeInfoManager) {
2402 mNodeInfoManager->DropDocumentReference();
2405 if (mDocGroup) {
2406 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
2407 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
2410 UnlinkOriginalDocumentIfStatic();
2412 UnregisterFromMemoryReportingForDataDocument();
2415 void Document::DropStyleSet() { mStyleSet = nullptr; }
2417 NS_INTERFACE_TABLE_HEAD(Document)
2418 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
2419 NS_INTERFACE_TABLE_BEGIN
2420 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
2421 NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
2422 NS_INTERFACE_TABLE_ENTRY(Document, Document)
2423 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
2424 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
2425 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
2426 NS_INTERFACE_TABLE_END
2427 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
2428 NS_INTERFACE_MAP_END
2430 NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
2431 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Document, LastRelease())
2433 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2434 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2435 EventListenerManager* elm = tmp->GetExistingListenerManager();
2436 if (elm) {
2437 elm->MarkForCC();
2439 return true;
2441 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2443 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2444 return Element::CanSkipInCC(tmp);
2445 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2447 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2448 return Element::CanSkipThis(tmp);
2449 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2451 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2452 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2453 char name[512];
2454 nsAutoCString loadedAsData;
2455 if (tmp->IsLoadedAsData()) {
2456 loadedAsData.AssignLiteral("data");
2457 } else {
2458 loadedAsData.AssignLiteral("normal");
2460 uint32_t nsid = tmp->GetDefaultNamespaceID();
2461 nsAutoCString uri;
2462 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2463 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2464 "(xhtml)", "(XLink)", "(XSLT)",
2465 "(MathML)", "(RDF)", "(XUL)"};
2466 if (nsid < ArrayLength(kNSURIs)) {
2467 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2468 kNSURIs[nsid], uri.get());
2469 } else {
2470 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2472 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2473 } else {
2474 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2477 if (!nsINode::Traverse(tmp, cb)) {
2478 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2481 tmp->mExternalResourceMap.Traverse(&cb);
2483 // Traverse all Document pointer members.
2484 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2485 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2486 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2487 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2488 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2489 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFragmentDirective)
2490 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry)
2492 // Traverse all Document nsCOMPtrs.
2493 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2494 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2495 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2496 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2497 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2499 DocumentOrShadowRoot::Traverse(tmp, cb);
2501 if (tmp->mRadioGroupContainer) {
2502 RadioGroupContainer::Traverse(tmp->mRadioGroupContainer.get(), cb);
2505 for (auto& sheets : tmp->mAdditionalSheets) {
2506 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2509 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2510 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadObserver)
2511 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementsObservedForLastRememberedSize)
2512 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2513 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2514 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2515 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2516 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2517 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2518 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker)
2519 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2520 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2521 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2522 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2523 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2524 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2525 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2526 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2527 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2528 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2529 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2530 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2531 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2532 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2533 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2534 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2535 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2536 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
2538 // Traverse all our nsCOMArrays.
2539 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2541 // Traverse animation components
2542 if (tmp->mAnimationController) {
2543 tmp->mAnimationController->Traverse(&cb);
2546 if (tmp->mSubDocuments) {
2547 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2548 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2550 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2551 cb.NoteXPCOMChild(entry->mKey);
2552 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2553 "mSubDocuments entry->mSubDocument");
2554 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2558 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2560 // We own only the items in mDOMMediaQueryLists that have listeners;
2561 // this reference is managed by their AddListener and RemoveListener
2562 // methods.
2563 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2564 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2565 if (mql->HasListeners() &&
2566 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2567 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2568 cb.NoteXPCOMChild(static_cast<EventTarget*>(mql));
2572 // XXX: This should be not needed once bug 1569185 lands.
2573 for (const auto& entry : tmp->mL10nProtoElements) {
2574 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2575 cb.NoteXPCOMChild(entry.GetKey());
2576 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
2579 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2580 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2581 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2582 mPendingFrameStaticClones[i].mStaticCloneOf);
2585 for (auto& tableEntry : tmp->mActiveLocks) {
2586 ImplCycleCollectionTraverse(cb, *tableEntry.GetModifiableData(),
2587 "mActiveLocks entry", 0);
2589 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2591 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2593 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document)
2594 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
2595 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject)
2596 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2598 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2599 tmp->mInUnlinkOrDeletion = true;
2601 tmp->SetStateObject(nullptr);
2603 // Clear out our external resources
2604 tmp->mExternalResourceMap.Shutdown();
2606 nsAutoScriptBlocker scriptBlocker;
2608 nsINode::Unlink(tmp);
2610 while (tmp->HasChildren()) {
2611 // Hold a strong ref to the node when we remove it, because we may be
2612 // the last reference to it.
2613 // If this code changes, change the corresponding code in Document's
2614 // unlink impl and ContentUnbinder::UnbindSubtree.
2615 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2616 tmp->DisconnectChild(child);
2617 child->UnbindFromTree();
2620 tmp->UnlinkOriginalDocumentIfStatic();
2622 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2624 tmp->SetScriptGlobalObject(nullptr);
2626 for (auto& sheets : tmp->mAdditionalSheets) {
2627 tmp->UnlinkStyleSheets(sheets);
2630 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2631 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2632 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadObserver)
2633 NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementsObservedForLastRememberedSize);
2634 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2635 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2636 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2637 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFragmentDirective)
2638 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry)
2639 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2640 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2641 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2642 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2643 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2644 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2645 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2646 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2647 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker)
2648 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2649 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2650 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2651 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2652 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2653 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2654 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2655 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2656 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2657 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2658 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2659 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2660 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2661 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2662 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2663 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2664 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2665 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2667 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
2668 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
2669 tmp->mDocGroup);
2671 tmp->mDocGroup = nullptr;
2673 if (tmp->IsTopLevelContentDocument()) {
2674 RemoveToplevelLoadingDocument(tmp);
2677 tmp->mParentDocument = nullptr;
2679 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2681 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2683 if (tmp->mListenerManager) {
2684 tmp->mListenerManager->Disconnect();
2685 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2686 tmp->mListenerManager = nullptr;
2689 if (tmp->mStyleSheetSetList) {
2690 tmp->mStyleSheetSetList->Disconnect();
2691 tmp->mStyleSheetSetList = nullptr;
2694 tmp->mSubDocuments = nullptr;
2696 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
2697 MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
2698 "How did we get here without our presshell going away "
2699 "first?");
2701 DocumentOrShadowRoot::Unlink(tmp);
2703 tmp->mRadioGroupContainer = nullptr;
2705 // Document has a pretty complex destructor, so we're going to
2706 // assume that *most* cycles you actually want to break somewhere
2707 // else, and not unlink an awful lot here.
2709 tmp->mExpandoAndGeneration.OwnerUnlinked();
2711 if (tmp->mAnimationController) {
2712 tmp->mAnimationController->Unlink();
2715 tmp->mPendingTitleChangeEvent.Revoke();
2717 if (tmp->mCSSLoader) {
2718 tmp->mCSSLoader->DropDocumentReference();
2719 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2722 // We own only the items in mDOMMediaQueryLists that have listeners;
2723 // this reference is managed by their AddListener and RemoveListener
2724 // methods.
2725 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2726 MediaQueryList* next =
2727 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2728 mql->Disconnect();
2729 mql = next;
2732 tmp->mPendingFrameStaticClones.Clear();
2734 tmp->mActiveLocks.Clear();
2736 tmp->mInUnlinkOrDeletion = false;
2738 tmp->UnregisterFromMemoryReportingForDataDocument();
2740 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2741 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2742 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2743 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2745 nsresult Document::Init(nsIPrincipal* aPrincipal,
2746 nsIPrincipal* aPartitionedPrincipal) {
2747 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2748 return NS_ERROR_ALREADY_INITIALIZED;
2751 // Force initialization.
2752 mOnloadBlocker = new OnloadBlocker();
2753 mStyleImageLoader = new css::ImageLoader(this);
2755 mNodeInfoManager = new nsNodeInfoManager(this, aPrincipal);
2757 // mNodeInfo keeps NodeInfoManager alive!
2758 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2759 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2760 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2761 "Bad NodeType in aNodeInfo");
2763 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2765 mCSSLoader = new css::Loader(this);
2766 // Assume we're not quirky, until we know otherwise
2767 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2769 // If after creation the owner js global is not set for a document
2770 // we use the default compartment for this document, instead of creating
2771 // wrapper in some random compartment when the document is exposed to js
2772 // via some events.
2773 nsCOMPtr<nsIGlobalObject> global =
2774 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2775 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2776 mScopeObject = do_GetWeakReference(global);
2777 MOZ_ASSERT(mScopeObject);
2779 mScriptLoader = new dom::ScriptLoader(this);
2781 // we need to create a policy here so getting the policy within
2782 // ::Policy() can *always* return a non null policy
2783 mFeaturePolicy = new dom::FeaturePolicy(this);
2784 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2786 if (aPrincipal) {
2787 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2788 } else {
2789 RecomputeResistFingerprinting();
2792 return NS_OK;
2795 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2797 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2798 PropertyTable().RemoveAllPropertiesFor(aNode);
2801 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2802 nsCOMPtr<nsIURI> uri;
2803 nsCOMPtr<nsIPrincipal> principal;
2804 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2805 if (aChannel) {
2806 mIsInPrivateBrowsing = NS_UsePrivateBrowsing(aChannel);
2808 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2809 // nsScriptSecurityManager::GetChannelResultPrincipals.
2810 // Note: this should match the uri used for the OnNewURI call in
2811 // nsDocShell::CreateDocumentViewer.
2812 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2814 nsIScriptSecurityManager* securityManager =
2815 nsContentUtils::GetSecurityManager();
2816 if (securityManager) {
2817 securityManager->GetChannelResultPrincipals(
2818 aChannel, getter_AddRefs(principal),
2819 getter_AddRefs(partitionedPrincipal));
2823 bool equal = principal->Equals(partitionedPrincipal);
2825 principal = MaybeDowngradePrincipal(principal);
2826 if (equal) {
2827 partitionedPrincipal = principal;
2828 } else {
2829 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2832 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2834 // Note that, since mTiming does not change during a reset, the
2835 // navigationStart time remains unchanged and therefore any future new
2836 // timeline will have the same global clock time as the old one.
2837 mDocumentTimeline = nullptr;
2839 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
2840 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
2841 mDocumentBaseURI = baseURI.forget();
2842 mChromeXHRDocBaseURI = nullptr;
2846 mChannel = aChannel;
2847 RecomputeResistFingerprinting();
2850 void Document::DisconnectNodeTree() {
2851 // Delete references to sub-documents and kill the subdocument map,
2852 // if any. This is not strictly needed, but makes the node tree
2853 // teardown a bit faster.
2854 mSubDocuments = nullptr;
2856 bool oldVal = mInUnlinkOrDeletion;
2857 mInUnlinkOrDeletion = true;
2858 { // Scope for update
2859 MOZ_AUTO_DOC_UPDATE(this, true);
2861 // Destroy link map now so we don't waste time removing
2862 // links one by one
2863 DestroyElementMaps();
2865 // Invalidate cached array of child nodes
2866 InvalidateChildNodes();
2868 while (HasChildren()) {
2869 nsMutationGuard::DidMutate();
2870 nsCOMPtr<nsIContent> content = GetLastChild();
2871 nsIContent* previousSibling = content->GetPreviousSibling();
2872 DisconnectChild(content);
2873 if (content == mCachedRootElement) {
2874 // Immediately clear mCachedRootElement, now that it's been removed
2875 // from mChildren, so that GetRootElement() will stop returning this
2876 // now-stale value.
2877 mCachedRootElement = nullptr;
2879 MutationObservers::NotifyContentRemoved(this, content, previousSibling);
2880 content->UnbindFromTree();
2882 MOZ_ASSERT(!mCachedRootElement,
2883 "After removing all children, there should be no root elem");
2885 mInUnlinkOrDeletion = oldVal;
2888 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2889 nsIPrincipal* aPrincipal,
2890 nsIPrincipal* aPartitionedPrincipal) {
2891 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2892 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2894 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2895 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2897 mSecurityInfo = nullptr;
2899 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2900 if (!aLoadGroup || group != aLoadGroup) {
2901 mDocumentLoadGroup = nullptr;
2904 DisconnectNodeTree();
2906 // Reset our stylesheets
2907 ResetStylesheetsToURI(aURI);
2909 // Release the listener manager
2910 if (mListenerManager) {
2911 mListenerManager->Disconnect();
2912 mListenerManager = nullptr;
2915 // Release the stylesheets list.
2916 mDOMStyleSheets = nullptr;
2918 // Release our principal after tearing down the document, rather than before.
2919 // This ensures that, during teardown, the document and the dying window
2920 // (which already nulled out its document pointer and cached the principal)
2921 // have matching principals.
2922 SetPrincipals(nullptr, nullptr);
2924 // Clear the original URI so SetDocumentURI sets it.
2925 mOriginalURI = nullptr;
2927 SetDocumentURI(aURI);
2928 mChromeXHRDocURI = nullptr;
2929 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
2930 // mDocumentURI.
2931 mDocumentBaseURI = nullptr;
2932 mChromeXHRDocBaseURI = nullptr;
2934 if (aLoadGroup) {
2935 nsCOMPtr<nsIInterfaceRequestor> callbacks;
2936 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
2937 if (callbacks) {
2938 nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
2939 if (loadContext) {
2940 // This is asserting that if we previously set mIsInPrivateBrowsing
2941 // to true from the channel in Document::Reset, that the loadContext
2942 // also believes it to be true.
2943 // MOZ_ASSERT(!mIsInPrivateBrowsing ||
2944 // mIsInPrivateBrowsing == loadContext->UsePrivateBrowsing());
2945 mIsInPrivateBrowsing = loadContext->UsePrivateBrowsing();
2949 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
2950 // there was an assertion here that aLoadGroup was not null. This
2951 // is no longer valid: nsDocShell::SetDocument does not create a
2952 // load group, and it works just fine
2954 // XXXbz what does "just fine" mean exactly? And given that there
2955 // is no nsDocShell::SetDocument, what is this talking about?
2957 if (IsContentDocument()) {
2958 // Inform the associated request context about this load start so
2959 // any of its internal load progress flags gets reset.
2960 nsCOMPtr<nsIRequestContextService> rcsvc =
2961 net::RequestContextService::GetOrCreate();
2962 if (rcsvc) {
2963 nsCOMPtr<nsIRequestContext> rc;
2964 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
2965 if (rc) {
2966 rc->BeginLoad();
2972 mLastModified.Truncate();
2973 // XXXbz I guess we're assuming that the caller will either pass in
2974 // a channel with a useful type or call SetContentType?
2975 SetContentType(""_ns);
2976 mContentLanguage = nullptr;
2977 mBaseTarget.Truncate();
2979 mXMLDeclarationBits = 0;
2981 // Now get our new principal
2982 if (aPrincipal) {
2983 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2984 } else {
2985 nsIScriptSecurityManager* securityManager =
2986 nsContentUtils::GetSecurityManager();
2987 if (securityManager) {
2988 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
2990 if (!loadContext && aLoadGroup) {
2991 nsCOMPtr<nsIInterfaceRequestor> cbs;
2992 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
2993 loadContext = do_GetInterface(cbs);
2996 MOZ_ASSERT(loadContext,
2997 "must have a load context or pass in an explicit principal");
2999 nsCOMPtr<nsIPrincipal> principal;
3000 nsresult rv = securityManager->GetLoadContextContentPrincipal(
3001 mDocumentURI, loadContext, getter_AddRefs(principal));
3002 if (NS_SUCCEEDED(rv)) {
3003 SetPrincipals(principal, principal);
3008 if (mFontFaceSet) {
3009 mFontFaceSet->RefreshStandardFontLoadPrincipal();
3012 // Refresh the principal on the realm.
3013 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
3014 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
3018 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
3019 nsIPrincipal* aPrincipal) {
3020 if (!aPrincipal) {
3021 return nullptr;
3024 // We can't load a document with an expanded principal. If we're given one,
3025 // automatically downgrade it to the last principal it subsumes (which is the
3026 // extension principal, in the case of extension content scripts).
3027 auto* basePrin = BasePrincipal::Cast(aPrincipal);
3028 if (basePrin->Is<ExpandedPrincipal>()) {
3029 MOZ_DIAGNOSTIC_ASSERT(false,
3030 "Should never try to create a document with "
3031 "an expanded principal");
3033 auto* expanded = basePrin->As<ExpandedPrincipal>();
3034 return do_AddRef(expanded->AllowList().LastElement());
3037 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
3038 // We basically want the parent document here, but because this is very
3039 // early in the load, GetInProcessParentDocument() returns null, so we use
3040 // the docshell hierarchy to get this information instead.
3041 if (RefPtr<BrowsingContext> parent =
3042 mDocumentContainer->GetBrowsingContext()->GetParent()) {
3043 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
3044 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
3045 nsCOMPtr<nsIPrincipal> nullPrincipal =
3046 NullPrincipal::CreateWithoutOriginAttributes();
3047 return nullPrincipal.forget();
3051 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
3052 return principal.forget();
3055 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
3056 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3057 ServoStyleSet& styleSet = EnsureStyleSet();
3059 // lowest index first
3060 int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
3062 size_t count = styleSet.SheetCount(StyleOrigin::Author);
3063 size_t index = 0;
3064 for (; index < count; index++) {
3065 auto* sheet = styleSet.SheetAt(StyleOrigin::Author, index);
3066 MOZ_ASSERT(sheet);
3067 int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
3068 if (sheetDocIndex > newDocIndex) {
3069 break;
3072 // If the sheet is not owned by the document it can be an author
3073 // sheet registered at nsStyleSheetService or an additional author
3074 // sheet on the document, which means the new
3075 // doc sheet should end up before it.
3076 if (sheetDocIndex < 0) {
3077 if (sheetService) {
3078 auto& authorSheets = *sheetService->AuthorStyleSheets();
3079 if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
3080 break;
3083 if (sheet == GetFirstAdditionalAuthorSheet()) {
3084 break;
3089 return index;
3092 void Document::ResetStylesheetsToURI(nsIURI* aURI) {
3093 MOZ_ASSERT(aURI);
3095 ClearAdoptedStyleSheets();
3096 ServoStyleSet& styleSet = EnsureStyleSet();
3098 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
3099 for (auto& sheet : Reversed(aSheetList)) {
3100 sheet->ClearAssociatedDocumentOrShadowRoot();
3101 if (mStyleSetFilled) {
3102 styleSet.RemoveStyleSheet(*sheet);
3105 aSheetList.Clear();
3107 ClearSheetList(mStyleSheets);
3108 for (auto& sheets : mAdditionalSheets) {
3109 ClearSheetList(sheets);
3111 if (mStyleSetFilled) {
3112 if (auto* ss = nsStyleSheetService::GetInstance()) {
3113 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
3114 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
3115 if (sheet->IsApplicable()) {
3116 styleSet.RemoveStyleSheet(*sheet);
3122 // Now reset our inline style and attribute sheets.
3123 if (mAttributeStyles) {
3124 mAttributeStyles->Reset();
3125 mAttributeStyles->SetOwningDocument(this);
3126 } else {
3127 mAttributeStyles = new AttributeStyles(this);
3130 if (mStyleSetFilled) {
3131 FillStyleSetDocumentSheets();
3133 if (styleSet.StyleSheetsHaveChanged()) {
3134 ApplicableStylesChanged();
3139 static void AppendSheetsToStyleSet(
3140 ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
3141 for (StyleSheet* sheet : Reversed(aSheets)) {
3142 aStyleSet->AppendStyleSheet(*sheet);
3146 void Document::FillStyleSetUserAndUASheets() {
3147 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
3148 // ordering.
3150 // The document will fill in the document sheets when we create the presshell
3151 auto* cache = GlobalStyleSheetCache::Singleton();
3153 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3154 MOZ_ASSERT(sheetService,
3155 "should never be creating a StyleSet after the style sheet "
3156 "service has gone");
3158 ServoStyleSet& styleSet = EnsureStyleSet();
3159 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
3160 styleSet.AppendStyleSheet(*sheet);
3163 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
3164 : cache->GetUserContentSheet();
3165 if (sheet) {
3166 styleSet.AppendStyleSheet(*sheet);
3169 styleSet.AppendStyleSheet(*cache->UASheet());
3171 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
3172 styleSet.AppendStyleSheet(*cache->MathMLSheet());
3175 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
3176 styleSet.AppendStyleSheet(*cache->SVGSheet());
3179 styleSet.AppendStyleSheet(*cache->HTMLSheet());
3181 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
3182 styleSet.AppendStyleSheet(*cache->NoFramesSheet());
3185 styleSet.AppendStyleSheet(*cache->CounterStylesSheet());
3187 // Only load the full XUL sheet if we'll need it.
3188 if (LoadsFullXULStyleSheetUpFront()) {
3189 styleSet.AppendStyleSheet(*cache->XULSheet());
3192 styleSet.AppendStyleSheet(*cache->FormsSheet());
3193 styleSet.AppendStyleSheet(*cache->ScrollbarsSheet());
3195 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
3196 styleSet.AppendStyleSheet(*sheet);
3199 MOZ_ASSERT(!mQuirkSheetAdded);
3200 if (NeedsQuirksSheet()) {
3201 styleSet.AppendStyleSheet(*cache->QuirkSheet());
3202 mQuirkSheetAdded = true;
3206 void Document::FillStyleSet() {
3207 MOZ_ASSERT(!mStyleSetFilled);
3208 FillStyleSetUserAndUASheets();
3209 FillStyleSetDocumentSheets();
3210 mStyleSetFilled = true;
3213 void Document::RemoveContentEditableStyleSheets() {
3214 MOZ_ASSERT(IsHTMLOrXHTML());
3216 ServoStyleSet& styleSet = EnsureStyleSet();
3217 auto* cache = GlobalStyleSheetCache::Singleton();
3218 bool changed = false;
3219 if (mDesignModeSheetAdded) {
3220 styleSet.RemoveStyleSheet(*cache->DesignModeSheet());
3221 mDesignModeSheetAdded = false;
3222 changed = true;
3224 if (mContentEditableSheetAdded) {
3225 styleSet.RemoveStyleSheet(*cache->ContentEditableSheet());
3226 mContentEditableSheetAdded = false;
3227 changed = true;
3229 if (changed) {
3230 MOZ_ASSERT(mStyleSetFilled);
3231 ApplicableStylesChanged();
3235 void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
3236 MOZ_ASSERT(IsHTMLOrXHTML());
3237 MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
3238 "Caller should ensure we're being rendered");
3240 ServoStyleSet& styleSet = EnsureStyleSet();
3241 auto* cache = GlobalStyleSheetCache::Singleton();
3242 bool changed = false;
3243 if (!mContentEditableSheetAdded) {
3244 styleSet.AppendStyleSheet(*cache->ContentEditableSheet());
3245 mContentEditableSheetAdded = true;
3246 changed = true;
3248 if (mDesignModeSheetAdded != aDesignMode) {
3249 if (mDesignModeSheetAdded) {
3250 styleSet.RemoveStyleSheet(*cache->DesignModeSheet());
3251 } else {
3252 styleSet.AppendStyleSheet(*cache->DesignModeSheet());
3254 mDesignModeSheetAdded = !mDesignModeSheetAdded;
3255 changed = true;
3257 if (changed) {
3258 ApplicableStylesChanged();
3262 void Document::FillStyleSetDocumentSheets() {
3263 ServoStyleSet& styleSet = EnsureStyleSet();
3264 MOZ_ASSERT(styleSet.SheetCount(StyleOrigin::Author) == 0,
3265 "Style set already has document sheets?");
3267 // Sheets are added in reverse order to avoid worst-case time complexity when
3268 // looking up the index of a sheet.
3270 // Note that usually appending is faster (rebuilds less stuff in the
3271 // styleset), but in this case it doesn't matter since we're filling the
3272 // styleset from scratch anyway.
3273 for (StyleSheet* sheet : Reversed(mStyleSheets)) {
3274 if (sheet->IsApplicable()) {
3275 styleSet.AddDocStyleSheet(*sheet);
3279 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
3280 if (aSheet.IsApplicable()) {
3281 styleSet.AddDocStyleSheet(aSheet);
3285 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3286 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
3287 styleSet.AppendStyleSheet(*sheet);
3290 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eAgentSheet]);
3291 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eUserSheet]);
3292 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eAuthorSheet]);
3295 void Document::CompatibilityModeChanged() {
3296 MOZ_ASSERT(IsHTMLOrXHTML());
3297 CSSLoader()->SetCompatibilityMode(mCompatMode);
3299 if (mStyleSet) {
3300 mStyleSet->CompatibilityModeChanged();
3302 if (!mStyleSetFilled) {
3303 MOZ_ASSERT(!mQuirkSheetAdded);
3304 return;
3307 MOZ_ASSERT(mStyleSet);
3308 if (PresShell* presShell = GetPresShell()) {
3309 // Selectors may have become case-sensitive / case-insensitive, the stylist
3310 // has already performed the relevant invalidation.
3311 presShell->EnsureStyleFlush();
3313 if (mQuirkSheetAdded == NeedsQuirksSheet()) {
3314 return;
3316 auto* cache = GlobalStyleSheetCache::Singleton();
3317 StyleSheet* sheet = cache->QuirkSheet();
3318 if (mQuirkSheetAdded) {
3319 mStyleSet->RemoveStyleSheet(*sheet);
3320 } else {
3321 mStyleSet->AppendStyleSheet(*sheet);
3323 mQuirkSheetAdded = !mQuirkSheetAdded;
3324 ApplicableStylesChanged();
3327 void Document::SetCompatibilityMode(nsCompatibility aMode) {
3328 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
3329 "Bad compat mode for XHTML document!");
3331 if (mCompatMode == aMode) {
3332 return;
3334 mCompatMode = aMode;
3335 CompatibilityModeChanged();
3336 // Trigger recomputation of the nsViewportInfo the next time it's queried.
3337 mViewportType = Unknown;
3340 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
3341 uint32_t aSandboxFlags,
3342 nsIChannel* aChannel) {
3343 // If the document permits allow-top-navigation and
3344 // allow-top-navigation-by-user-activation this will permit all top
3345 // navigation.
3346 if (aSandboxFlags != SANDBOXED_NONE &&
3347 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
3348 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
3349 nsContentUtils::ReportToConsole(
3350 nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
3351 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
3352 "BothAllowTopNavigationAndUserActivationPresent");
3354 // If the document is sandboxed (via the HTML5 iframe sandbox
3355 // attribute) and both the allow-scripts and allow-same-origin
3356 // keywords are supplied, the sandboxed document can call into its
3357 // parent document and remove its sandboxing entirely - we print a
3358 // warning to the web console in this case.
3359 if (aSandboxFlags & SANDBOXED_NAVIGATION &&
3360 !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
3361 !(aSandboxFlags & SANDBOXED_ORIGIN)) {
3362 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
3363 MOZ_ASSERT(bc->IsInProcess());
3365 RefPtr<BrowsingContext> parentBC = bc->GetParent();
3366 if (!parentBC || !parentBC->IsInProcess()) {
3367 // If parent document is not in process, then by construction it
3368 // cannot be same origin.
3369 return;
3372 // Don't warn if our parent is not the top-level document.
3373 if (!parentBC->IsTopContent()) {
3374 return;
3377 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
3378 MOZ_ASSERT(parentDocShell);
3380 nsCOMPtr<nsIChannel> parentChannel;
3381 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
3382 if (!parentChannel) {
3383 return;
3385 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
3386 if (NS_FAILED(rv)) {
3387 return;
3390 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
3391 nsCOMPtr<nsIURI> iframeUri;
3392 parentChannel->GetURI(getter_AddRefs(iframeUri));
3393 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3394 "Iframe Sandbox"_ns, parentDocument,
3395 nsContentUtils::eSECURITY_PROPERTIES,
3396 "BothAllowScriptsAndSameOriginPresent",
3397 nsTArray<nsString>(), iframeUri);
3401 bool Document::IsSynthesized() {
3402 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
3403 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
3406 // static
3407 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
3408 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
3409 return principal && (principal->IsSystemPrincipal() ||
3410 principal->GetIsAddonOrExpandedAddonPrincipal());
3413 static void CheckIsBadPolicy(nsILoadInfo::CrossOriginOpenerPolicy aPolicy,
3414 BrowsingContext* aContext, nsIChannel* aChannel) {
3415 #if defined(EARLY_BETA_OR_EARLIER)
3416 auto requireCORP =
3417 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
3419 if (aContext->GetOpenerPolicy() == aPolicy ||
3420 (aContext->GetOpenerPolicy() != requireCORP && aPolicy != requireCORP)) {
3421 return;
3424 nsCOMPtr<nsIURI> uri;
3425 bool hasURI = NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(uri)));
3427 bool isViewSource = hasURI && uri->SchemeIs("view-source");
3429 nsCString contentType;
3430 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3431 bool isPDFJS = bag &&
3432 NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3433 contentType)) &&
3434 contentType.EqualsLiteral(APPLICATION_PDF);
3436 MOZ_DIAGNOSTIC_ASSERT(!isViewSource,
3437 "Bug 1834864: Assert due to view-source.");
3438 MOZ_DIAGNOSTIC_ASSERT(!isPDFJS, "Bug 1834864: Assert due to pdfjs.");
3439 MOZ_DIAGNOSTIC_ASSERT(aPolicy == requireCORP,
3440 "Assert due to clearing REQUIRE_CORP.");
3441 MOZ_DIAGNOSTIC_ASSERT(aContext->GetOpenerPolicy() == requireCORP,
3442 "Assert due to setting REQUIRE_CORP.");
3443 #endif // defined(EARLY_BETA_OR_EARLIER)
3446 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
3447 nsILoadGroup* aLoadGroup,
3448 nsISupports* aContainer,
3449 nsIStreamListener** aDocListener,
3450 bool aReset) {
3451 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
3452 nsCOMPtr<nsIURI> uri;
3453 aChannel->GetURI(getter_AddRefs(uri));
3454 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
3455 ("DOCUMENT %p StartDocumentLoad %s", this,
3456 uri ? uri->GetSpecOrDefault().get() : ""));
3459 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
3460 "Bad readyState");
3461 SetReadyStateInternal(READYSTATE_LOADING);
3463 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
3464 mLoadedAsData = true;
3465 SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true);
3466 // We need to disable script & style loading in this case.
3467 // We leave them disabled even in EndLoad(), and let anyone
3468 // who puts the document on display to worry about enabling.
3470 // Do not load/process scripts when loading as data
3471 ScriptLoader()->SetEnabled(false);
3473 // styles
3474 CSSLoader()->SetEnabled(
3475 false); // Do not load/process styles when loading as data
3476 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
3477 // Allow CSS, but not scripts
3478 ScriptLoader()->SetEnabled(false);
3481 mMayStartLayout = false;
3482 MOZ_ASSERT(!mReadyForIdle,
3483 "We should never hit DOMContentLoaded before this point");
3485 if (aReset) {
3486 Reset(aChannel, aLoadGroup);
3489 nsAutoCString contentType;
3490 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3491 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3492 contentType))) ||
3493 NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
3494 // XXX this is only necessary for viewsource:
3495 nsACString::const_iterator start, end, semicolon;
3496 contentType.BeginReading(start);
3497 contentType.EndReading(end);
3498 semicolon = start;
3499 FindCharInReadable(';', semicolon, end);
3500 SetContentType(Substring(start, semicolon));
3503 RetrieveRelevantHeaders(aChannel);
3505 mChannel = aChannel;
3506 RecomputeResistFingerprinting();
3507 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
3508 if (inStrmChan) {
3509 bool isSrcdocChannel;
3510 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
3511 if (isSrcdocChannel) {
3512 mIsSrcdocDocument = true;
3516 if (mChannel) {
3517 nsLoadFlags loadFlags;
3518 mChannel->GetLoadFlags(&loadFlags);
3519 bool isDocument = false;
3520 mChannel->GetIsDocument(&isDocument);
3521 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
3522 IsSynthesized() && XRE_IsContentProcess()) {
3523 ContentChild::UpdateCookieStatus(mChannel);
3526 // Store the security info for future use.
3527 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
3530 // If this document is being loaded by a docshell, copy its sandbox flags
3531 // to the document, and store the fullscreen enabled flag. These are
3532 // immutable after being set here.
3533 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
3535 // If this is an error page, don't inherit sandbox flags
3536 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3537 if (docShell && !loadInfo->GetLoadErrorPage()) {
3538 mSandboxFlags = loadInfo->GetSandboxFlags();
3539 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
3542 // Set the opener policy for the top level content document.
3543 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
3544 nsILoadInfo::CrossOriginOpenerPolicy policy =
3545 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
3546 if (IsTopLevelContentDocument() && httpChan &&
3547 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
3548 docShell->GetBrowsingContext()) {
3549 CheckIsBadPolicy(policy, docShell->GetBrowsingContext(), aChannel);
3551 // Setting the opener policy on a discarded context has no effect.
3552 Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
3555 // The CSP directives upgrade-insecure-requests as well as
3556 // block-all-mixed-content not only apply to the toplevel document,
3557 // but also to nested documents. The loadInfo of a subdocument
3558 // load already holds the correct flag, so let's just set it here
3559 // on the document. Please note that we set the appropriate preload
3560 // bits just for the sake of completeness here, because the preloader
3561 // does not reach into subdocuments.
3562 mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
3563 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3564 mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
3565 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3567 // HTTPS-Only Mode flags
3568 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
3569 // sub-resources and sub-documents.
3570 mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
3572 nsresult rv = InitReferrerInfo(aChannel);
3573 NS_ENSURE_SUCCESS(rv, rv);
3575 rv = InitCOEP(aChannel);
3576 NS_ENSURE_SUCCESS(rv, rv);
3578 // HACK: Calling EnsureIPCPoliciesRead() here will parse the CSP using the
3579 // context's current mSelfURI (which is still the previous mSelfURI),
3580 // bypassing some internal bugs with 'self' and iframe inheritance.
3581 // Not calling it here results in the mSelfURI being the current mSelfURI and
3582 // not the previous which breaks said inheritance.
3583 // https://bugzilla.mozilla.org/show_bug.cgi?id=1793560#ch-8
3584 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3585 if (cspToInherit) {
3586 cspToInherit->EnsureIPCPoliciesRead();
3589 rv = InitCSP(aChannel);
3590 NS_ENSURE_SUCCESS(rv, rv);
3592 // Initialize FeaturePolicy
3593 rv = InitFeaturePolicy(aChannel);
3594 NS_ENSURE_SUCCESS(rv, rv);
3596 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3597 NS_ENSURE_SUCCESS(rv, rv);
3599 // Generally XFO and CSP frame-ancestors is handled within
3600 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3601 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3602 nsContentPolicyType internalContentType =
3603 loadInfo->InternalContentPolicyType();
3604 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3605 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3606 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3608 nsresult status;
3609 aChannel->GetStatus(&status);
3610 if (status == NS_ERROR_XFO_VIOLATION) {
3611 // stop! ERROR page!
3612 // But before we have to reset the principal of the document
3613 // because the onload() event fires before the error page
3614 // is displayed and we do not want the enclosing document
3615 // to access the contentDocument.
3616 RefPtr<NullPrincipal> nullPrincipal =
3617 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3618 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3619 // and also GetInnerWindow() is still null at this point, before
3620 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3621 // within Document.cpp
3622 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3623 SetPrincipals(nullPrincipal, nullPrincipal);
3627 return NS_OK;
3630 void Document::SetLoadedAsData(bool aLoadedAsData,
3631 bool aConsiderForMemoryReporting) {
3632 mLoadedAsData = aLoadedAsData;
3633 if (aConsiderForMemoryReporting) {
3634 nsIGlobalObject* global = GetScopeObject();
3635 if (global) {
3636 if (nsPIDOMWindowInner* window = global->GetAsInnerWindow()) {
3637 nsGlobalWindowInner::Cast(window)
3638 ->RegisterDataDocumentForMemoryReporting(this);
3644 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3646 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
3648 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3649 return mPreloadCSP;
3652 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3653 mPreloadCSP = aPreloadCSP;
3656 void Document::GetCspJSON(nsString& aJSON) {
3657 aJSON.Truncate();
3659 if (!mCSP) {
3660 dom::CSPPolicies jsonPolicies;
3661 jsonPolicies.ToJSON(aJSON);
3662 return;
3664 mCSP->ToJSON(aJSON);
3667 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3668 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3669 nsAutoString messageTag;
3670 aMessages[i]->GetTag(messageTag);
3672 nsAutoString category;
3673 aMessages[i]->GetCategory(category);
3675 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3676 NS_ConvertUTF16toUTF8(category), this,
3677 nsContentUtils::eSECURITY_PROPERTIES,
3678 NS_ConvertUTF16toUTF8(messageTag).get());
3682 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3683 nsresult rv = NS_OK;
3684 if (!aSpeculative) {
3685 // 1) apply settings from regular CSP
3686 if (mCSP) {
3687 // Set up 'block-all-mixed-content' if not already inherited
3688 // from the parent context or set by any other CSP.
3689 if (!mBlockAllMixedContent) {
3690 bool block = false;
3691 rv = mCSP->GetBlockAllMixedContent(&block);
3692 NS_ENSURE_SUCCESS_VOID(rv);
3693 mBlockAllMixedContent = block;
3695 if (!mBlockAllMixedContentPreloads) {
3696 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3699 // Set up 'upgrade-insecure-requests' if not already inherited
3700 // from the parent context or set by any other CSP.
3701 if (!mUpgradeInsecureRequests) {
3702 bool upgrade = false;
3703 rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
3704 NS_ENSURE_SUCCESS_VOID(rv);
3705 mUpgradeInsecureRequests = upgrade;
3707 if (!mUpgradeInsecurePreloads) {
3708 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3710 // Update csp settings in the parent process
3711 if (auto* wgc = GetWindowGlobalChild()) {
3712 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3713 mUpgradeInsecureRequests);
3716 return;
3719 // 2) apply settings from speculative csp
3720 if (mPreloadCSP) {
3721 if (!mBlockAllMixedContentPreloads) {
3722 bool block = false;
3723 rv = mPreloadCSP->GetBlockAllMixedContent(&block);
3724 NS_ENSURE_SUCCESS_VOID(rv);
3725 mBlockAllMixedContent = block;
3727 if (!mUpgradeInsecurePreloads) {
3728 bool upgrade = false;
3729 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
3730 NS_ENSURE_SUCCESS_VOID(rv);
3731 mUpgradeInsecurePreloads = upgrade;
3736 nsresult Document::InitCSP(nsIChannel* aChannel) {
3737 MOZ_ASSERT(!mScriptGlobalObject,
3738 "CSP must be initialized before mScriptGlobalObject is set!");
3740 // If this is a data document - no need to set CSP.
3741 if (mLoadedAsData) {
3742 return NS_OK;
3745 // If this is an image, no need to set a CSP. Otherwise SVG images
3746 // served with a CSP might block internally applied inline styles.
3747 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3748 if (loadInfo->GetExternalContentPolicyType() ==
3749 ExtContentPolicy::TYPE_IMAGE ||
3750 loadInfo->GetExternalContentPolicyType() ==
3751 ExtContentPolicy::TYPE_IMAGESET) {
3752 return NS_OK;
3755 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3757 // If there is a CSP that needs to be inherited from whatever
3758 // global is considered the client of the document fetch then
3759 // we query it here from the loadinfo in case the newly created
3760 // document needs to inherit the CSP. See:
3761 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3762 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
3763 if (inheritedCSP) {
3764 mCSP = loadInfo->GetCspToInherit();
3767 // If there is no CSP to inherit, then we create a new CSP here so
3768 // that history entries always have the right reference in case a
3769 // Meta CSP gets dynamically added after the history entry has
3770 // already been created.
3771 if (!mCSP) {
3772 mCSP = new nsCSPContext();
3775 // Always overwrite the requesting context of the CSP so that any new
3776 // 'self' keyword added to an inherited CSP translates correctly.
3777 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3778 if (NS_WARN_IF(NS_FAILED(rv))) {
3779 return rv;
3782 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3784 nsCOMPtr<nsIHttpChannel> httpChannel;
3785 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3786 if (NS_WARN_IF(NS_FAILED(rv))) {
3787 return rv;
3790 if (httpChannel) {
3791 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3792 tCspHeaderValue);
3794 Unused << httpChannel->GetResponseHeader(
3795 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3797 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3798 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3800 // Check if this is a document from a WebExtension.
3801 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3802 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3804 // If there's no CSP to apply, go ahead and return early
3805 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
3806 cspROHeaderValue.IsEmpty()) {
3807 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3808 nsCOMPtr<nsIURI> chanURI;
3809 aChannel->GetURI(getter_AddRefs(chanURI));
3810 nsAutoCString aspec;
3811 chanURI->GetAsciiSpec(aspec);
3812 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3813 ("no CSP for document, %s", aspec.get()));
3816 return NS_OK;
3819 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3820 ("Document is an add-on or CSP header specified %p", this));
3822 // ----- if the doc is an addon, apply its CSP.
3823 if (addonPolicy) {
3824 mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
3826 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3827 // Bug 1548468: Move CSP off ExpandedPrincipal
3828 // Currently the LoadInfo holds the source of truth for every resource load
3829 // because LoadInfo::GetCsp() queries the CSP from an ExpandedPrincipal
3830 // (and not from the Client) if the load was triggered by an extension.
3831 auto* basePrin = BasePrincipal::Cast(principal);
3832 if (basePrin->Is<ExpandedPrincipal>()) {
3833 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3837 // ----- if there's a full-strength CSP header, apply it.
3838 if (!cspHeaderValue.IsEmpty()) {
3839 mHasCSPDeliveredThroughHeader = true;
3840 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3841 NS_ENSURE_SUCCESS(rv, rv);
3844 // ----- if there's a report-only CSP header, apply it.
3845 if (!cspROHeaderValue.IsEmpty()) {
3846 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3847 NS_ENSURE_SUCCESS(rv, rv);
3850 // ----- Enforce sandbox policy if supplied in CSP header
3851 // The document may already have some sandbox flags set (e.g. if the document
3852 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3853 // directive, intersect the CSP sandbox flags with the existing flags. This
3854 // corresponds to the _least_ permissive policy.
3855 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3856 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3857 NS_ENSURE_SUCCESS(rv, rv);
3859 // Probably the iframe sandbox attribute already caused the creation of a
3860 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3861 // and no one has been created yet.
3862 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3863 !(mSandboxFlags & SANDBOXED_ORIGIN);
3865 mSandboxFlags |= cspSandboxFlags;
3867 if (needNewNullPrincipal) {
3868 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3869 // Skip setting the content blocking allowlist principal to NullPrincipal.
3870 // The principal is only used to enable/disable trackingprotection via
3871 // permission and can be shared with the top level sandboxed site.
3872 // See Bug 1654546.
3873 SetPrincipals(principal, principal);
3876 ApplySettingsFromCSP(false);
3877 return NS_OK;
3880 static Document* GetInProcessParentDocumentFrom(BrowsingContext* aContext) {
3881 BrowsingContext* parentContext = aContext->GetParent();
3882 if (!parentContext) {
3883 return nullptr;
3886 WindowContext* windowContext = parentContext->GetCurrentWindowContext();
3887 if (!windowContext) {
3888 return nullptr;
3891 return windowContext->GetDocument();
3894 already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
3895 BrowsingContext* browsingContext = GetBrowsingContext();
3896 if (!browsingContext) {
3897 return nullptr;
3899 if (!browsingContext->IsContentSubframe()) {
3900 return nullptr;
3903 HTMLIFrameElement* iframe =
3904 HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement());
3905 if (iframe) {
3906 return do_AddRef(iframe->FeaturePolicy());
3909 if (XRE_IsParentProcess()) {
3910 return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy());
3913 if (Document* parentDocument =
3914 GetInProcessParentDocumentFrom(browsingContext)) {
3915 return do_AddRef(parentDocument->FeaturePolicy());
3918 WindowContext* windowContext = browsingContext->GetCurrentWindowContext();
3919 if (!windowContext) {
3920 return nullptr;
3923 WindowGlobalChild* child = windowContext->GetWindowGlobalChild();
3924 if (!child) {
3925 return nullptr;
3928 return do_AddRef(child->GetContainerFeaturePolicy());
3931 void Document::InitFeaturePolicy() {
3932 MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created");
3934 mFeaturePolicy->ResetDeclaredPolicy();
3936 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
3938 RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
3939 if (parentPolicy) {
3940 // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
3941 mFeaturePolicy->InheritPolicy(parentPolicy);
3942 mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
3946 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
3947 InitFeaturePolicy();
3949 // We don't want to parse the http Feature-Policy header if this pref is off.
3950 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
3951 return NS_OK;
3954 nsCOMPtr<nsIHttpChannel> httpChannel;
3955 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3956 if (NS_WARN_IF(NS_FAILED(rv))) {
3957 return rv;
3960 if (!httpChannel) {
3961 return NS_OK;
3964 // query the policy from the header
3965 nsAutoCString value;
3966 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
3967 if (NS_SUCCEEDED(rv)) {
3968 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
3969 NodePrincipal(), nullptr);
3972 return NS_OK;
3975 void Document::EnsureNotEnteringAndExitFullscreen() {
3976 Document::ClearPendingFullscreenRequests(this);
3977 if (GetFullscreenElement()) {
3978 Document::AsyncExitFullscreen(this);
3982 void Document::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
3983 mReferrerInfo = aReferrerInfo;
3984 mCachedReferrerInfoForInternalCSSAndSVGResources = nullptr;
3985 mCachedURLData = nullptr;
3988 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
3989 MOZ_ASSERT(mReferrerInfo);
3990 MOZ_ASSERT(mPreloadReferrerInfo);
3992 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
3993 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
3994 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
3995 // they have an opaque origin.
3996 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
3997 if (BrowsingContext* bc = GetBrowsingContext()) {
3998 // At this point the document is not fully created and mParentDocument has
3999 // not been set yet,
4000 Document* parentDoc = bc->GetEmbedderElement()
4001 ? bc->GetEmbedderElement()->OwnerDoc()
4002 : nullptr;
4003 if (parentDoc) {
4004 SetReferrerInfo(parentDoc->GetReferrerInfo());
4005 mPreloadReferrerInfo = mReferrerInfo;
4006 return NS_OK;
4009 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
4010 "srcdoc without null principal as toplevel!");
4014 nsCOMPtr<nsIHttpChannel> httpChannel;
4015 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4016 if (NS_WARN_IF(NS_FAILED(rv))) {
4017 return rv;
4020 if (!httpChannel) {
4021 return NS_OK;
4024 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo()) {
4025 SetReferrerInfo(referrerInfo);
4028 // Override policy if we get one from Referrerr-Policy header
4029 mozilla::dom::ReferrerPolicy policy =
4030 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
4031 nsCOMPtr<nsIReferrerInfo> clone =
4032 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
4033 ->CloneWithNewPolicy(policy);
4034 SetReferrerInfo(clone);
4035 mPreloadReferrerInfo = mReferrerInfo;
4036 return NS_OK;
4039 nsresult Document::InitCOEP(nsIChannel* aChannel) {
4040 nsCOMPtr<nsIHttpChannel> httpChannel;
4041 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4042 if (NS_FAILED(rv)) {
4043 return NS_OK;
4046 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
4048 if (!intChannel) {
4049 return NS_OK;
4052 nsILoadInfo::CrossOriginEmbedderPolicy policy =
4053 nsILoadInfo::EMBEDDER_POLICY_NULL;
4054 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(
4055 mTrials.IsEnabled(OriginTrial::CoepCredentialless), &policy))) {
4056 mEmbedderPolicy = Some(policy);
4059 return NS_OK;
4062 void Document::StopDocumentLoad() {
4063 if (mParser) {
4064 mParserAborted = true;
4065 mParser->Terminate();
4069 void Document::SetDocumentURI(nsIURI* aURI) {
4070 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
4071 mDocumentURI = aURI;
4072 // This loosely implements §3.4.1 of Text Fragments
4073 // https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives
4074 // Unlike specified in the spec, the fragment directive is not stripped from
4075 // the URL in the session history entry. Instead it is removed when the URL is
4076 // set in the `Document`. Also, instead of storing the `uninvokedDirective` in
4077 // `Document` as mentioned in the spec, the extracted directives are moved to
4078 // the `FragmentDirective` object which deals with finding the ranges to
4079 // highlight in `ScrollToRef()`.
4080 // XXX(:jjaschke): This is only a temporary solution.
4081 // https://bugzil.la/1881429 is filed for revisiting this.
4082 nsTArray<TextDirective> textDirectives;
4083 FragmentDirective::ParseAndRemoveFragmentDirectiveFromFragment(
4084 mDocumentURI, &textDirectives);
4085 FragmentDirective()->SetTextDirectives(std::move(textDirectives));
4087 nsIURI* newBase = GetDocBaseURI();
4089 mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI);
4091 bool equalBases = false;
4092 // Changing just the ref of a URI does not change how relative URIs would
4093 // resolve wrt to it, so we can treat the bases as equal as long as they're
4094 // equal ignoring the ref.
4095 if (oldBase && newBase) {
4096 oldBase->EqualsExceptRef(newBase, &equalBases);
4097 } else {
4098 equalBases = !oldBase && !newBase;
4101 // If this is the first time we're setting the document's URI, set the
4102 // document's original URI.
4103 if (!mOriginalURI) mOriginalURI = mDocumentURI;
4105 // If changing the document's URI changed the base URI of the document, we
4106 // need to refresh the hrefs of all the links on the page.
4107 if (!equalBases) {
4108 mCachedURLData = nullptr;
4109 RefreshLinkHrefs();
4112 // Recalculate our base domain
4113 mBaseDomain.Truncate();
4114 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
4115 if (thirdPartyUtil) {
4116 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
4119 // Tell our WindowGlobalParent that the document's URI has been changed.
4120 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
4121 wgc->SetDocumentURI(mDocumentURI);
4125 static void GetFormattedTimeString(PRTime aTime, bool aUniversal,
4126 nsAString& aFormattedTimeString) {
4127 PRExplodedTime prtime;
4128 PR_ExplodeTime(aTime, aUniversal ? PR_GMTParameters : PR_LocalTimeParameters,
4129 &prtime);
4130 // "MM/DD/YYYY hh:mm:ss"
4131 char formatedTime[24];
4132 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
4133 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
4134 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
4135 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
4136 } else {
4137 // If we for whatever reason failed to find the last modified time
4138 // (or even the current time), fall back to what NS4.x returned.
4139 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
4143 void Document::GetLastModified(nsAString& aLastModified) const {
4144 if (!mLastModified.IsEmpty()) {
4145 aLastModified.Assign(mLastModified);
4146 } else {
4147 GetFormattedTimeString(PR_Now(),
4148 ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
4149 aLastModified);
4153 static void IncrementExpandoGeneration(Document& aDoc) {
4154 ++aDoc.mExpandoAndGeneration.generation;
4157 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
4158 MOZ_ASSERT(
4159 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
4160 "Only put elements that need to be exposed as document['name'] in "
4161 "the named table.");
4163 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
4165 // Null for out-of-memory
4166 if (entry) {
4167 if (!entry->HasNameElement() &&
4168 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4169 IncrementExpandoGeneration(*this);
4171 entry->AddNameElement(this, aElement);
4175 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
4176 // Speed up document teardown
4177 if (mIdentifierMap.Count() == 0) return;
4179 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
4180 if (!entry) // Could be false if the element was anonymous, hence never added
4181 return;
4183 entry->RemoveNameElement(aElement);
4184 if (!entry->HasNameElement() &&
4185 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4186 IncrementExpandoGeneration(*this);
4190 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
4191 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
4193 if (entry) { /* True except on OOM */
4194 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4195 !entry->HasNameElement() &&
4196 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4197 IncrementExpandoGeneration(*this);
4199 entry->AddIdElement(aElement);
4203 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
4204 NS_ASSERTION(aId, "huhwhatnow?");
4206 // Speed up document teardown
4207 if (mIdentifierMap.Count() == 0) {
4208 return;
4211 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
4212 if (!entry) // Can be null for XML elements with changing ids.
4213 return;
4215 entry->RemoveIdElement(aElement);
4216 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4217 !entry->HasNameElement() &&
4218 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4219 IncrementExpandoGeneration(*this);
4221 if (entry->IsEmpty()) {
4222 mIdentifierMap.RemoveEntry(entry);
4226 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
4227 bool aPreload) {
4228 ReferrerPolicyEnum policy =
4229 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
4230 // The empty string "" corresponds to no referrer policy, causing a fallback
4231 // to a referrer policy defined elsewhere.
4232 if (policy == ReferrerPolicy::_empty) {
4233 return;
4236 MOZ_ASSERT(mReferrerInfo);
4237 MOZ_ASSERT(mPreloadReferrerInfo);
4239 if (aPreload) {
4240 mPreloadReferrerInfo =
4241 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
4242 ->CloneWithNewPolicy(policy);
4243 } else {
4244 nsCOMPtr<nsIReferrerInfo> clone =
4245 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
4246 ->CloneWithNewPolicy(policy);
4247 SetReferrerInfo(clone);
4251 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
4252 nsIPrincipal* aNewPartitionedPrincipal) {
4253 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
4254 if (aNewPrincipal && mAllowDNSPrefetch &&
4255 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
4256 if (aNewPrincipal->SchemeIs("https")) {
4257 mAllowDNSPrefetch = false;
4261 mCSSLoader->DeregisterFromSheetCache();
4263 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
4264 mPartitionedPrincipal = aNewPartitionedPrincipal;
4266 mCachedURLData = nullptr;
4268 mCSSLoader->RegisterInSheetCache();
4270 RecomputeResistFingerprinting();
4272 #ifdef DEBUG
4273 // Validate that the docgroup is set correctly by calling its getter and
4274 // triggering its sanity check.
4276 // If we're setting the principal to null, we don't want to perform the check,
4277 // as the document is entering an intermediate state where it does not have a
4278 // principal. It will be given another real principal shortly which we will
4279 // check. It's not unsafe to have a document which has a null principal in the
4280 // same docgroup as another document, so this should not be a problem.
4281 if (aNewPrincipal) {
4282 GetDocGroup();
4284 #endif
4287 #ifdef DEBUG
4288 void Document::AssertDocGroupMatchesKey() const {
4289 // Sanity check that we have an up-to-date and accurate docgroup
4290 // We only check if the principal when we can get the browsing context.
4292 // Note that we can be invoked during cycle collection, so we need to handle
4293 // the browsingcontext being partially unlinked - normally you shouldn't
4294 // null-check `Group()` as it shouldn't return nullptr.
4295 if (!GetBrowsingContext() || !GetBrowsingContext()->Group()) {
4296 return;
4299 if (mDocGroup && mDocGroup->GetBrowsingContextGroup()) {
4300 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
4301 GetBrowsingContext()->Group());
4303 // GetKey() can fail, e.g. after the TLD service has shut down.
4304 nsAutoCString docGroupKey;
4305 nsresult rv = mozilla::dom::DocGroup::GetKey(
4306 NodePrincipal(),
4307 GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated(),
4308 docGroupKey);
4309 if (NS_SUCCEEDED(rv)) {
4310 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
4314 #endif
4316 nsresult Document::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) const {
4317 return SchedulerGroup::Dispatch(std::move(aRunnable));
4320 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
4321 bool aIsTracking) {
4322 if (aIsTracking) {
4323 mTrackingScripts.Insert(aURL);
4324 } else {
4325 MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
4329 bool Document::IsScriptTracking(JSContext* aCx) const {
4330 JS::AutoFilename filename;
4331 if (!JS::DescribeScriptedCaller(aCx, &filename)) {
4332 return false;
4334 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
4337 void Document::GetContentType(nsAString& aContentType) {
4338 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
4341 void Document::SetContentType(const nsACString& aContentType) {
4342 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
4343 aContentType.EqualsLiteral("application/xhtml+xml")) {
4344 mDefaultElementType = kNameSpaceID_XHTML;
4347 mCachedEncoder = nullptr;
4348 mContentType = aContentType;
4351 bool Document::HasPendingInitialTranslation() {
4352 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
4355 bool Document::HasPendingL10nMutations() const {
4356 return mDocumentL10n && mDocumentL10n->HasPendingMutations();
4359 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
4360 JS::Rooted<JSObject*> object(aCx, aObject);
4361 nsCOMPtr<nsIPrincipal> callerPrincipal =
4362 nsContentUtils::SubjectPrincipal(aCx);
4363 nsGlobalWindowInner* win = xpc::WindowOrNull(object);
4364 bool allowed = false;
4365 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
4366 &allowed);
4367 return allowed;
4370 void Document::LocalizationLinkAdded(Element* aLinkElement) {
4371 if (!AllowsL10n()) {
4372 return;
4375 nsAutoString href;
4376 aLinkElement->GetAttr(nsGkAtoms::href, href);
4378 if (!mDocumentL10n) {
4379 Element* elem = GetDocumentElement();
4380 MOZ_DIAGNOSTIC_ASSERT(elem);
4382 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
4383 mDocumentL10n = DocumentL10n::Create(this, isSync);
4384 if (NS_WARN_IF(!mDocumentL10n)) {
4385 return;
4389 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
4391 if (mReadyState >= READYSTATE_INTERACTIVE) {
4392 nsContentUtils::AddScriptRunner(NewRunnableMethod(
4393 "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
4394 &DocumentL10n::TriggerInitialTranslation));
4395 } else {
4396 if (!mDocumentL10n->mBlockingLayout) {
4397 // Our initial translation is going to block layout start. Make sure
4398 // we don't fire the load event until after that stops happening and
4399 // layout has a chance to start.
4400 BlockOnload();
4401 mDocumentL10n->mBlockingLayout = true;
4406 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
4407 if (!AllowsL10n()) {
4408 return;
4411 if (mDocumentL10n) {
4412 nsAutoString href;
4413 aLinkElement->GetAttr(nsGkAtoms::href, href);
4414 uint32_t remaining =
4415 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
4416 if (remaining == 0) {
4417 if (mDocumentL10n->mBlockingLayout) {
4418 mDocumentL10n->mBlockingLayout = false;
4419 UnblockOnload(/* aFireSync = */ false);
4421 mDocumentL10n = nullptr;
4427 * This method should be called once the end of the l10n
4428 * resource container has been parsed.
4430 * In XUL this is the end of the first </linkset>,
4431 * In XHTML/HTML this is the end of </head>.
4433 * This milestone is used to allow for batch
4434 * localization context I/O and building done
4435 * once when all resources in the document have been
4436 * collected.
4438 void Document::OnL10nResourceContainerParsed() {
4439 // XXX: This is a scaffolding for where we might inject prefetch
4440 // in bug 1717241.
4443 void Document::OnParsingCompleted() {
4444 // Let's call it again, in case the resource
4445 // container has not been closed, and only
4446 // now we're closing the document.
4447 OnL10nResourceContainerParsed();
4449 if (mDocumentL10n) {
4450 RefPtr<DocumentL10n> l10n = mDocumentL10n;
4451 l10n->TriggerInitialTranslation();
4455 void Document::InitialTranslationCompleted(bool aL10nCached) {
4456 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4457 // This means we blocked the load event in LocalizationLinkAdded. It's
4458 // important that the load blocker removal here be async, because our caller
4459 // will notify the content sink after us, and we want the content sync's
4460 // work to happen before the load event fires.
4461 mDocumentL10n->mBlockingLayout = false;
4462 UnblockOnload(/* aFireSync = */ false);
4465 mL10nProtoElements.Clear();
4467 nsXULPrototypeDocument* proto = GetPrototype();
4468 if (proto) {
4469 proto->SetIsL10nCached(aL10nCached);
4473 bool Document::AllowsL10n() const {
4474 if (IsStaticDocument()) {
4475 // We don't allow l10n on static documents, because the nodes are already
4476 // cloned translated, and static docs don't get parsed so we never
4477 // TriggerInitialTranslation, etc, so a load blocker would keep hanging
4478 // forever.
4479 return false;
4481 bool allowed = false;
4482 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4483 return allowed;
4486 bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
4487 JSObject* /*unused*/
4489 MOZ_ASSERT(NS_IsMainThread());
4491 return nsContentUtils::IsSystemCaller(aCx) ||
4492 StaticPrefs::dom_animations_api_timelines_enabled();
4495 DocumentTimeline* Document::Timeline() {
4496 if (!mDocumentTimeline) {
4497 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4500 return mDocumentTimeline;
4503 SVGSVGElement* Document::GetSVGRootElement() const {
4504 Element* root = GetRootElement();
4505 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4506 return nullptr;
4508 return static_cast<SVGSVGElement*>(root);
4511 /* Return true if the document is in the focused top-level window, and is an
4512 * ancestor of the focused DOMWindow. */
4513 bool Document::HasFocus(ErrorResult& rv) const {
4514 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4515 if (!fm) {
4516 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4517 return false;
4520 BrowsingContext* bc = GetBrowsingContext();
4521 if (!bc) {
4522 return false;
4525 if (!fm->IsInActiveWindow(bc)) {
4526 return false;
4529 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
4532 bool Document::ThisDocumentHasFocus() const {
4533 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4534 return fm && fm->GetFocusedWindow() &&
4535 fm->GetFocusedWindow()->GetExtantDoc() == this;
4538 void Document::GetDesignMode(nsAString& aDesignMode) {
4539 if (IsInDesignMode()) {
4540 aDesignMode.AssignLiteral("on");
4541 } else {
4542 aDesignMode.AssignLiteral("off");
4546 void Document::SetDesignMode(const nsAString& aDesignMode,
4547 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4548 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4551 static void NotifyEditableStateChange(Document& aDoc) {
4552 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4553 nsMutationGuard g;
4554 #endif
4555 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4556 node = node->GetNextNode(&aDoc)) {
4557 if (auto* element = Element::FromNode(node)) {
4558 element->UpdateEditableState(true);
4561 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4564 void Document::SetDesignMode(const nsAString& aDesignMode,
4565 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4566 ErrorResult& rv) {
4567 if (aSubjectPrincipal.isSome() &&
4568 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4569 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4570 return;
4572 const bool editableMode = IsInDesignMode();
4573 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4574 SetEditableFlag(!editableMode);
4575 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4576 // state of all descendant elements of it. Update that now.
4577 NotifyEditableStateChange(*this);
4578 rv = EditingStateChanged();
4582 nsCommandManager* Document::GetMidasCommandManager() {
4583 // check if we have it cached
4584 if (mMidasCommandManager) {
4585 return mMidasCommandManager;
4588 nsPIDOMWindowOuter* window = GetWindow();
4589 if (!window) {
4590 return nullptr;
4593 nsIDocShell* docshell = window->GetDocShell();
4594 if (!docshell) {
4595 return nullptr;
4598 mMidasCommandManager = docshell->GetCommandManager();
4599 return mMidasCommandManager;
4602 // static
4603 void Document::EnsureInitializeInternalCommandDataHashtable() {
4604 if (sInternalCommandDataHashtable) {
4605 return;
4607 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
4608 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4609 // clang-format off
4610 sInternalCommandDataHashtable->InsertOrUpdate(
4611 u"bold"_ns,
4612 InternalCommandData(
4613 "cmd_bold",
4614 Command::FormatBold,
4615 ExecCommandParam::Ignore,
4616 StyleUpdatingCommand::GetInstance,
4617 CommandOnTextEditor::Disabled));
4618 sInternalCommandDataHashtable->InsertOrUpdate(
4619 u"italic"_ns,
4620 InternalCommandData(
4621 "cmd_italic",
4622 Command::FormatItalic,
4623 ExecCommandParam::Ignore,
4624 StyleUpdatingCommand::GetInstance,
4625 CommandOnTextEditor::Disabled));
4626 sInternalCommandDataHashtable->InsertOrUpdate(
4627 u"underline"_ns,
4628 InternalCommandData(
4629 "cmd_underline",
4630 Command::FormatUnderline,
4631 ExecCommandParam::Ignore,
4632 StyleUpdatingCommand::GetInstance,
4633 CommandOnTextEditor::Disabled));
4634 sInternalCommandDataHashtable->InsertOrUpdate(
4635 u"strikethrough"_ns,
4636 InternalCommandData(
4637 "cmd_strikethrough",
4638 Command::FormatStrikeThrough,
4639 ExecCommandParam::Ignore,
4640 StyleUpdatingCommand::GetInstance,
4641 CommandOnTextEditor::Disabled));
4642 sInternalCommandDataHashtable->InsertOrUpdate(
4643 u"subscript"_ns,
4644 InternalCommandData(
4645 "cmd_subscript",
4646 Command::FormatSubscript,
4647 ExecCommandParam::Ignore,
4648 StyleUpdatingCommand::GetInstance,
4649 CommandOnTextEditor::Disabled));
4650 sInternalCommandDataHashtable->InsertOrUpdate(
4651 u"superscript"_ns,
4652 InternalCommandData(
4653 "cmd_superscript",
4654 Command::FormatSuperscript,
4655 ExecCommandParam::Ignore,
4656 StyleUpdatingCommand::GetInstance,
4657 CommandOnTextEditor::Disabled));
4658 sInternalCommandDataHashtable->InsertOrUpdate(
4659 u"cut"_ns,
4660 InternalCommandData(
4661 "cmd_cut",
4662 Command::Cut,
4663 ExecCommandParam::Ignore,
4664 CutCommand::GetInstance,
4665 CommandOnTextEditor::Enabled));
4666 sInternalCommandDataHashtable->InsertOrUpdate(
4667 u"copy"_ns,
4668 InternalCommandData(
4669 "cmd_copy",
4670 Command::Copy,
4671 ExecCommandParam::Ignore,
4672 CopyCommand::GetInstance,
4673 CommandOnTextEditor::Enabled));
4674 sInternalCommandDataHashtable->InsertOrUpdate(
4675 u"paste"_ns,
4676 InternalCommandData(
4677 "cmd_paste",
4678 Command::Paste,
4679 ExecCommandParam::Ignore,
4680 PasteCommand::GetInstance,
4681 CommandOnTextEditor::Enabled));
4682 sInternalCommandDataHashtable->InsertOrUpdate(
4683 u"delete"_ns,
4684 InternalCommandData(
4685 "cmd_deleteCharBackward",
4686 Command::DeleteCharBackward,
4687 ExecCommandParam::Ignore,
4688 DeleteCommand::GetInstance,
4689 CommandOnTextEditor::Enabled));
4690 sInternalCommandDataHashtable->InsertOrUpdate(
4691 u"forwarddelete"_ns,
4692 InternalCommandData(
4693 "cmd_deleteCharForward",
4694 Command::DeleteCharForward,
4695 ExecCommandParam::Ignore,
4696 DeleteCommand::GetInstance,
4697 CommandOnTextEditor::Enabled));
4698 sInternalCommandDataHashtable->InsertOrUpdate(
4699 u"selectall"_ns,
4700 InternalCommandData(
4701 "cmd_selectAll",
4702 Command::SelectAll,
4703 ExecCommandParam::Ignore,
4704 SelectAllCommand::GetInstance,
4705 CommandOnTextEditor::Enabled));
4706 sInternalCommandDataHashtable->InsertOrUpdate(
4707 u"undo"_ns,
4708 InternalCommandData(
4709 "cmd_undo",
4710 Command::HistoryUndo,
4711 ExecCommandParam::Ignore,
4712 UndoCommand::GetInstance,
4713 CommandOnTextEditor::Enabled));
4714 sInternalCommandDataHashtable->InsertOrUpdate(
4715 u"redo"_ns,
4716 InternalCommandData(
4717 "cmd_redo",
4718 Command::HistoryRedo,
4719 ExecCommandParam::Ignore,
4720 RedoCommand::GetInstance,
4721 CommandOnTextEditor::Enabled));
4722 sInternalCommandDataHashtable->InsertOrUpdate(
4723 u"indent"_ns,
4724 InternalCommandData("cmd_indent",
4725 Command::FormatIndent,
4726 ExecCommandParam::Ignore,
4727 IndentCommand::GetInstance,
4728 CommandOnTextEditor::Disabled));
4729 sInternalCommandDataHashtable->InsertOrUpdate(
4730 u"outdent"_ns,
4731 InternalCommandData(
4732 "cmd_outdent",
4733 Command::FormatOutdent,
4734 ExecCommandParam::Ignore,
4735 OutdentCommand::GetInstance,
4736 CommandOnTextEditor::Disabled));
4737 sInternalCommandDataHashtable->InsertOrUpdate(
4738 u"backcolor"_ns,
4739 InternalCommandData(
4740 "cmd_highlight",
4741 Command::FormatBackColor,
4742 ExecCommandParam::String,
4743 HighlightColorStateCommand::GetInstance,
4744 CommandOnTextEditor::Disabled));
4745 sInternalCommandDataHashtable->InsertOrUpdate(
4746 u"hilitecolor"_ns,
4747 InternalCommandData(
4748 "cmd_highlight",
4749 Command::FormatBackColor,
4750 ExecCommandParam::String,
4751 HighlightColorStateCommand::GetInstance,
4752 CommandOnTextEditor::Disabled));
4753 sInternalCommandDataHashtable->InsertOrUpdate(
4754 u"forecolor"_ns,
4755 InternalCommandData(
4756 "cmd_fontColor",
4757 Command::FormatFontColor,
4758 ExecCommandParam::String,
4759 FontColorStateCommand::GetInstance,
4760 CommandOnTextEditor::Disabled));
4761 sInternalCommandDataHashtable->InsertOrUpdate(
4762 u"fontname"_ns,
4763 InternalCommandData(
4764 "cmd_fontFace",
4765 Command::FormatFontName,
4766 ExecCommandParam::String,
4767 FontFaceStateCommand::GetInstance,
4768 CommandOnTextEditor::Disabled));
4769 sInternalCommandDataHashtable->InsertOrUpdate(
4770 u"fontsize"_ns,
4771 InternalCommandData(
4772 "cmd_fontSize",
4773 Command::FormatFontSize,
4774 ExecCommandParam::String,
4775 FontSizeStateCommand::GetInstance,
4776 CommandOnTextEditor::Disabled));
4777 sInternalCommandDataHashtable->InsertOrUpdate(
4778 u"inserthorizontalrule"_ns,
4779 InternalCommandData(
4780 "cmd_insertHR",
4781 Command::InsertHorizontalRule,
4782 ExecCommandParam::Ignore,
4783 InsertTagCommand::GetInstance,
4784 CommandOnTextEditor::Disabled));
4785 sInternalCommandDataHashtable->InsertOrUpdate(
4786 u"createlink"_ns,
4787 InternalCommandData(
4788 "cmd_insertLinkNoUI",
4789 Command::InsertLink,
4790 ExecCommandParam::String,
4791 InsertTagCommand::GetInstance,
4792 CommandOnTextEditor::Disabled));
4793 sInternalCommandDataHashtable->InsertOrUpdate(
4794 u"insertimage"_ns,
4795 InternalCommandData(
4796 "cmd_insertImageNoUI",
4797 Command::InsertImage,
4798 ExecCommandParam::String,
4799 InsertTagCommand::GetInstance,
4800 CommandOnTextEditor::Disabled));
4801 sInternalCommandDataHashtable->InsertOrUpdate(
4802 u"inserthtml"_ns,
4803 InternalCommandData(
4804 "cmd_insertHTML",
4805 Command::InsertHTML,
4806 ExecCommandParam::String,
4807 InsertHTMLCommand::GetInstance,
4808 // TODO: Chromium inserts text content of the document fragment
4809 // created from the param.
4810 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
4811 CommandOnTextEditor::Disabled));
4812 sInternalCommandDataHashtable->InsertOrUpdate(
4813 u"inserttext"_ns,
4814 InternalCommandData(
4815 "cmd_insertText",
4816 Command::InsertText,
4817 ExecCommandParam::String,
4818 InsertPlaintextCommand::GetInstance,
4819 CommandOnTextEditor::Enabled));
4820 sInternalCommandDataHashtable->InsertOrUpdate(
4821 u"justifyleft"_ns,
4822 InternalCommandData(
4823 "cmd_align",
4824 Command::FormatJustifyLeft,
4825 ExecCommandParam::Ignore, // Will be set to "left"
4826 AlignCommand::GetInstance,
4827 CommandOnTextEditor::Disabled));
4828 sInternalCommandDataHashtable->InsertOrUpdate(
4829 u"justifyright"_ns,
4830 InternalCommandData(
4831 "cmd_align",
4832 Command::FormatJustifyRight,
4833 ExecCommandParam::Ignore, // Will be set to "right"
4834 AlignCommand::GetInstance,
4835 CommandOnTextEditor::Disabled));
4836 sInternalCommandDataHashtable->InsertOrUpdate(
4837 u"justifycenter"_ns,
4838 InternalCommandData(
4839 "cmd_align",
4840 Command::FormatJustifyCenter,
4841 ExecCommandParam::Ignore, // Will be set to "center"
4842 AlignCommand::GetInstance,
4843 CommandOnTextEditor::Disabled));
4844 sInternalCommandDataHashtable->InsertOrUpdate(
4845 u"justifyfull"_ns,
4846 InternalCommandData(
4847 "cmd_align",
4848 Command::FormatJustifyFull,
4849 ExecCommandParam::Ignore, // Will be set to "justify"
4850 AlignCommand::GetInstance,
4851 CommandOnTextEditor::Disabled));
4852 sInternalCommandDataHashtable->InsertOrUpdate(
4853 u"removeformat"_ns,
4854 InternalCommandData(
4855 "cmd_removeStyles",
4856 Command::FormatRemove,
4857 ExecCommandParam::Ignore,
4858 RemoveStylesCommand::GetInstance,
4859 CommandOnTextEditor::Disabled));
4860 sInternalCommandDataHashtable->InsertOrUpdate(
4861 u"unlink"_ns,
4862 InternalCommandData(
4863 "cmd_removeLinks",
4864 Command::FormatRemoveLink,
4865 ExecCommandParam::Ignore,
4866 StyleUpdatingCommand::GetInstance,
4867 CommandOnTextEditor::Disabled));
4868 sInternalCommandDataHashtable->InsertOrUpdate(
4869 u"insertorderedlist"_ns,
4870 InternalCommandData(
4871 "cmd_ol",
4872 Command::InsertOrderedList,
4873 ExecCommandParam::Ignore,
4874 ListCommand::GetInstance,
4875 CommandOnTextEditor::Disabled));
4876 sInternalCommandDataHashtable->InsertOrUpdate(
4877 u"insertunorderedlist"_ns,
4878 InternalCommandData(
4879 "cmd_ul",
4880 Command::InsertUnorderedList,
4881 ExecCommandParam::Ignore,
4882 ListCommand::GetInstance,
4883 CommandOnTextEditor::Disabled));
4884 sInternalCommandDataHashtable->InsertOrUpdate(
4885 u"insertparagraph"_ns,
4886 InternalCommandData(
4887 "cmd_insertParagraph",
4888 Command::InsertParagraph,
4889 ExecCommandParam::Ignore,
4890 InsertParagraphCommand::GetInstance,
4891 CommandOnTextEditor::Enabled));
4892 sInternalCommandDataHashtable->InsertOrUpdate(
4893 u"insertlinebreak"_ns,
4894 InternalCommandData(
4895 "cmd_insertLineBreak",
4896 Command::InsertLineBreak,
4897 ExecCommandParam::Ignore,
4898 InsertLineBreakCommand::GetInstance,
4899 CommandOnTextEditor::Enabled));
4900 sInternalCommandDataHashtable->InsertOrUpdate(
4901 u"formatblock"_ns,
4902 InternalCommandData(
4903 "cmd_formatBlock",
4904 Command::FormatBlock,
4905 ExecCommandParam::String,
4906 FormatBlockStateCommand::GetInstance,
4907 CommandOnTextEditor::Disabled));
4908 sInternalCommandDataHashtable->InsertOrUpdate(
4909 u"styleWithCSS"_ns,
4910 InternalCommandData(
4911 "cmd_setDocumentUseCSS",
4912 Command::SetDocumentUseCSS,
4913 ExecCommandParam::Boolean,
4914 SetDocumentStateCommand::GetInstance,
4915 CommandOnTextEditor::FallThrough));
4916 sInternalCommandDataHashtable->InsertOrUpdate(
4917 u"usecss"_ns, // Legacy command
4918 InternalCommandData(
4919 "cmd_setDocumentUseCSS",
4920 Command::SetDocumentUseCSS,
4921 ExecCommandParam::InvertedBoolean,
4922 SetDocumentStateCommand::GetInstance,
4923 CommandOnTextEditor::FallThrough));
4924 sInternalCommandDataHashtable->InsertOrUpdate(
4925 u"contentReadOnly"_ns,
4926 InternalCommandData(
4927 "cmd_setDocumentReadOnly",
4928 Command::SetDocumentReadOnly,
4929 ExecCommandParam::Boolean,
4930 SetDocumentStateCommand::GetInstance,
4931 CommandOnTextEditor::Enabled));
4932 sInternalCommandDataHashtable->InsertOrUpdate(
4933 u"insertBrOnReturn"_ns,
4934 InternalCommandData(
4935 "cmd_insertBrOnReturn",
4936 Command::SetDocumentInsertBROnEnterKeyPress,
4937 ExecCommandParam::Boolean,
4938 SetDocumentStateCommand::GetInstance,
4939 CommandOnTextEditor::FallThrough));
4940 sInternalCommandDataHashtable->InsertOrUpdate(
4941 u"defaultParagraphSeparator"_ns,
4942 InternalCommandData(
4943 "cmd_defaultParagraphSeparator",
4944 Command::SetDocumentDefaultParagraphSeparator,
4945 ExecCommandParam::String,
4946 SetDocumentStateCommand::GetInstance,
4947 CommandOnTextEditor::FallThrough));
4948 sInternalCommandDataHashtable->InsertOrUpdate(
4949 u"enableObjectResizing"_ns,
4950 InternalCommandData(
4951 "cmd_enableObjectResizing",
4952 Command::ToggleObjectResizers,
4953 ExecCommandParam::Boolean,
4954 SetDocumentStateCommand::GetInstance,
4955 CommandOnTextEditor::FallThrough));
4956 sInternalCommandDataHashtable->InsertOrUpdate(
4957 u"enableInlineTableEditing"_ns,
4958 InternalCommandData(
4959 "cmd_enableInlineTableEditing",
4960 Command::ToggleInlineTableEditor,
4961 ExecCommandParam::Boolean,
4962 SetDocumentStateCommand::GetInstance,
4963 CommandOnTextEditor::FallThrough));
4964 sInternalCommandDataHashtable->InsertOrUpdate(
4965 u"enableAbsolutePositionEditing"_ns,
4966 InternalCommandData(
4967 "cmd_enableAbsolutePositionEditing",
4968 Command::ToggleAbsolutePositionEditor,
4969 ExecCommandParam::Boolean,
4970 SetDocumentStateCommand::GetInstance,
4971 CommandOnTextEditor::FallThrough));
4972 sInternalCommandDataHashtable->InsertOrUpdate(
4973 u"enableCompatibleJoinSplitDirection"_ns,
4974 InternalCommandData("cmd_enableCompatibleJoinSplitNodeDirection",
4975 Command::EnableCompatibleJoinSplitNodeDirection,
4976 ExecCommandParam::Boolean,
4977 SetDocumentStateCommand::GetInstance,
4978 CommandOnTextEditor::FallThrough));
4979 #if 0
4980 // with empty string
4981 sInternalCommandDataHashtable->InsertOrUpdate(
4982 u"justifynone"_ns,
4983 InternalCommandData(
4984 "cmd_align",
4985 Command::Undefined,
4986 ExecCommandParam::Ignore,
4987 nullptr,
4988 CommandOnTextEditor::Disabled)); // Not implemented yet.
4989 // REQUIRED SPECIAL REVIEW special review
4990 sInternalCommandDataHashtable->InsertOrUpdate(
4991 u"saveas"_ns,
4992 InternalCommandData(
4993 "cmd_saveAs",
4994 Command::Undefined,
4995 ExecCommandParam::Boolean,
4996 nullptr,
4997 CommandOnTextEditor::FallThrough)); // Not implemented yet.
4998 // REQUIRED SPECIAL REVIEW special review
4999 sInternalCommandDataHashtable->InsertOrUpdate(
5000 u"print"_ns,
5001 InternalCommandData(
5002 "cmd_print",
5003 Command::Undefined,
5004 ExecCommandParam::Boolean,
5005 nullptr,
5006 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5007 #endif // #if 0
5008 // clang-format on
5011 Document::InternalCommandData Document::ConvertToInternalCommand(
5012 const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */,
5013 nsAString* aAdjustedValue /* = nullptr */) {
5014 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
5015 EnsureInitializeInternalCommandDataHashtable();
5016 InternalCommandData commandData;
5017 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
5018 return InternalCommandData();
5020 // Ignore if the command is disabled by a corresponding pref due to Gecko
5021 // specific.
5022 switch (commandData.mCommand) {
5023 case Command::SetDocumentReadOnly:
5024 if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() &&
5025 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) {
5026 return InternalCommandData();
5028 break;
5029 case Command::SetDocumentInsertBROnEnterKeyPress:
5030 MOZ_DIAGNOSTIC_ASSERT(
5031 aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn"));
5032 if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) {
5033 return InternalCommandData();
5035 break;
5036 default:
5037 break;
5039 if (!aAdjustedValue) {
5040 // No further work to do
5041 return commandData;
5043 switch (commandData.mExecCommandParam) {
5044 case ExecCommandParam::Ignore:
5045 // Just have to copy it, no checking
5046 switch (commandData.mCommand) {
5047 case Command::FormatJustifyLeft:
5048 aAdjustedValue->AssignLiteral("left");
5049 break;
5050 case Command::FormatJustifyRight:
5051 aAdjustedValue->AssignLiteral("right");
5052 break;
5053 case Command::FormatJustifyCenter:
5054 aAdjustedValue->AssignLiteral("center");
5055 break;
5056 case Command::FormatJustifyFull:
5057 aAdjustedValue->AssignLiteral("justify");
5058 break;
5059 default:
5060 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
5061 EditorCommandParamType::None);
5062 break;
5064 return commandData;
5066 case ExecCommandParam::Boolean:
5067 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5068 EditorCommandParamType::Bool));
5069 // If this is a boolean value and it's not explicitly false (e.g. no
5070 // value). We default to "true" (see bug 301490).
5071 if (!aValue.LowerCaseEqualsLiteral("false")) {
5072 aAdjustedValue->AssignLiteral("true");
5073 } else {
5074 aAdjustedValue->AssignLiteral("false");
5076 return commandData;
5078 case ExecCommandParam::InvertedBoolean:
5079 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5080 EditorCommandParamType::Bool));
5081 // For old backwards commands we invert the check.
5082 if (aValue.LowerCaseEqualsLiteral("false")) {
5083 aAdjustedValue->AssignLiteral("true");
5084 } else {
5085 aAdjustedValue->AssignLiteral("false");
5087 return commandData;
5089 case ExecCommandParam::String:
5090 MOZ_ASSERT(!!(
5091 EditorCommand::GetParamType(commandData.mCommand) &
5092 (EditorCommandParamType::String | EditorCommandParamType::CString)));
5093 switch (commandData.mCommand) {
5094 case Command::FormatBlock: {
5095 const char16_t* start = aValue.BeginReading();
5096 const char16_t* end = aValue.EndReading();
5097 if (start != end && *start == '<' && *(end - 1) == '>') {
5098 ++start;
5099 --end;
5101 // XXX Should we reorder this array with actual usage?
5102 static const nsStaticAtom* kFormattableBlockTags[] = {
5103 // clang-format off
5104 nsGkAtoms::address,
5105 nsGkAtoms::article,
5106 nsGkAtoms::aside,
5107 nsGkAtoms::blockquote,
5108 nsGkAtoms::dd,
5109 nsGkAtoms::div,
5110 nsGkAtoms::dl,
5111 nsGkAtoms::dt,
5112 nsGkAtoms::footer,
5113 nsGkAtoms::h1,
5114 nsGkAtoms::h2,
5115 nsGkAtoms::h3,
5116 nsGkAtoms::h4,
5117 nsGkAtoms::h5,
5118 nsGkAtoms::h6,
5119 nsGkAtoms::header,
5120 nsGkAtoms::hgroup,
5121 nsGkAtoms::main,
5122 nsGkAtoms::nav,
5123 nsGkAtoms::p,
5124 nsGkAtoms::pre,
5125 nsGkAtoms::section,
5126 // clang-format on
5128 nsAutoString value(nsDependentSubstring(start, end));
5129 ToLowerCase(value);
5130 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
5131 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
5132 if (valueAtom == kTag) {
5133 kTag->ToString(*aAdjustedValue);
5134 return commandData;
5137 return InternalCommandData();
5139 case Command::FormatFontSize: {
5140 // Per editing spec as of April 23, 2012, we need to reject the value
5141 // if it's not a valid floating-point number surrounded by optional
5142 // whitespace. Otherwise, we parse it as a legacy font size. For
5143 // now, we just parse as a legacy font size regardless (matching
5144 // WebKit) -- bug 747879.
5145 int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
5146 if (!size) {
5147 return InternalCommandData();
5149 MOZ_ASSERT(aAdjustedValue->IsEmpty());
5150 aAdjustedValue->AppendInt(size);
5151 return commandData;
5153 case Command::InsertImage:
5154 case Command::InsertLink:
5155 if (aValue.IsEmpty()) {
5156 // Invalid value, return false
5157 return InternalCommandData();
5159 aAdjustedValue->Assign(aValue);
5160 return commandData;
5161 case Command::SetDocumentDefaultParagraphSeparator:
5162 if (!aValue.LowerCaseEqualsLiteral("div") &&
5163 !aValue.LowerCaseEqualsLiteral("p") &&
5164 !aValue.LowerCaseEqualsLiteral("br")) {
5165 // Invalid value
5166 return InternalCommandData();
5168 aAdjustedValue->Assign(aValue);
5169 return commandData;
5170 default:
5171 aAdjustedValue->Assign(aValue);
5172 return commandData;
5175 default:
5176 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
5177 return InternalCommandData();
5181 Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
5182 Document& aDocument, const InternalCommandData& aCommandData)
5183 : mCommandData(aCommandData) {
5184 // We'll retrieve an editor with current DOM tree and layout information.
5185 // However, JS may have already hidden or remove exposed root content of
5186 // the editor. Therefore, we need the latest layout information here.
5187 aDocument.FlushPendingNotifications(FlushType::Layout);
5188 if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) {
5189 mDoNothing = true;
5190 return;
5193 if (nsPresContext* presContext = aDocument.GetPresContext()) {
5194 // Consider context of command handling which is automatically resolved
5195 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5196 // The order is:
5197 // 1. TextEditor if there is an active element and it has TextEditor like
5198 // <input type="text"> or <textarea>.
5199 // 2. HTMLEditor for the document, if there is.
5200 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
5201 if (aCommandData.IsCutOrCopyCommand()) {
5202 // Note that we used to use DocShell to handle `cut` and `copy` command
5203 // for dispatching corresponding events for making possible web apps to
5204 // implement their own editor without editable elements but supports
5205 // standard shortcut keys, etc. In this case, we prefer to use active
5206 // element's editor to keep same behavior.
5207 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5208 } else {
5209 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5210 mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
5211 if (!mActiveEditor) {
5212 mActiveEditor = mHTMLEditor;
5217 // Then, retrieve editor command class instance which should handle it
5218 // and can handle it now.
5219 if (!mActiveEditor) {
5220 // If the command is available without editor, we should redirect the
5221 // command to focused descendant with DocShell.
5222 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5223 mDoNothing = true;
5224 return;
5226 return;
5229 // Otherwise, we should use EditorCommand instance (which is singleton
5230 // instance) when it's enabled.
5231 mEditorCommand = aCommandData.mGetEditorCommandFunc
5232 ? aCommandData.mGetEditorCommandFunc()
5233 : nullptr;
5234 if (!mEditorCommand) {
5235 mDoNothing = true;
5236 mActiveEditor = nullptr;
5237 mHTMLEditor = nullptr;
5238 return;
5241 if (IsCommandEnabled()) {
5242 return;
5245 // If the EditorCommand instance is disabled, we should do nothing if
5246 // the command requires an editor.
5247 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5248 // Do nothing if editor specific commands is disabled (bug 760052).
5249 mDoNothing = true;
5250 return;
5253 // Otherwise, we should redirect it to focused descendant with DocShell.
5254 mEditorCommand = nullptr;
5255 mActiveEditor = nullptr;
5256 mHTMLEditor = nullptr;
5259 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
5260 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
5261 switch (mCommandData.mCommandOnTextEditor) {
5262 case CommandOnTextEditor::Enabled:
5263 return mActiveEditor;
5264 case CommandOnTextEditor::Disabled:
5265 return mActiveEditor && mActiveEditor->IsTextEditor()
5266 ? nullptr
5267 : mActiveEditor.get();
5268 case CommandOnTextEditor::FallThrough:
5269 return mHTMLEditor;
5271 return nullptr;
5274 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
5275 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
5276 // Make sure frames are up to date, since that can affect whether
5277 // we're editable.
5278 doc->FlushPendingNotifications(FlushType::Frames);
5280 EditorBase* targetEditor = GetTargetEditor();
5281 if (targetEditor && targetEditor->IsTextEditor()) {
5282 // FYI: When `disabled` attribute is set, `TextEditor` treats it as
5283 // "readonly" too.
5284 return !targetEditor->IsReadonly();
5286 return aDocument->IsEditingOn();
5289 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
5290 EditorBase* targetEditor = GetTargetEditor();
5291 if (!targetEditor) {
5292 return false;
5294 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5295 return MOZ_KnownLive(mEditorCommand)
5296 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
5299 nsresult Document::AutoEditorCommandTarget::DoCommand(
5300 nsIPrincipal* aPrincipal) const {
5301 MOZ_ASSERT(!DoNothing());
5302 MOZ_ASSERT(mEditorCommand);
5303 EditorBase* targetEditor = GetTargetEditor();
5304 if (!targetEditor) {
5305 return NS_SUCCESS_DOM_NO_OPERATION;
5307 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5308 return MOZ_KnownLive(mEditorCommand)
5309 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
5310 aPrincipal);
5313 template <typename ParamType>
5314 nsresult Document::AutoEditorCommandTarget::DoCommandParam(
5315 const ParamType& aParam, nsIPrincipal* aPrincipal) const {
5316 MOZ_ASSERT(!DoNothing());
5317 MOZ_ASSERT(mEditorCommand);
5318 EditorBase* targetEditor = GetTargetEditor();
5319 if (!targetEditor) {
5320 return NS_SUCCESS_DOM_NO_OPERATION;
5322 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5323 return MOZ_KnownLive(mEditorCommand)
5324 ->DoCommandParam(mCommandData.mCommand, aParam,
5325 MOZ_KnownLive(*targetEditor), aPrincipal);
5328 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
5329 nsCommandParams& aParams) const {
5330 MOZ_ASSERT(mEditorCommand);
5331 EditorBase* targetEditor = GetTargetEditor();
5332 if (!targetEditor) {
5333 return NS_OK;
5335 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5336 return MOZ_KnownLive(mEditorCommand)
5337 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
5338 MOZ_KnownLive(targetEditor), nullptr);
5341 Document::AutoRunningExecCommandMarker::AutoRunningExecCommandMarker(
5342 Document& aDocument, nsIPrincipal* aPrincipal)
5343 : mDocument(aDocument),
5344 mTreatAsUserInput(EditorBase::TreatAsUserInput(aPrincipal)),
5345 mHasBeenRunningByContent(aDocument.mIsRunningExecCommandByContent),
5346 mHasBeenRunningByChromeOrAddon(
5347 aDocument.mIsRunningExecCommandByChromeOrAddon) {
5348 if (mTreatAsUserInput) {
5349 aDocument.mIsRunningExecCommandByChromeOrAddon = true;
5350 } else {
5351 aDocument.mIsRunningExecCommandByContent = true;
5355 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
5356 const nsAString& aValue,
5357 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
5358 // Only allow on HTML documents.
5359 if (!IsHTMLOrXHTML()) {
5360 aRv.ThrowInvalidStateError(
5361 "execCommand is only supported on HTML documents");
5362 return false;
5364 // Otherwise, don't throw exception for compatibility with Chrome.
5366 // if they are requesting UI from us, let's fail since we have no UI
5367 if (aShowUI) {
5368 return false;
5371 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
5372 // this might add some ugly JS dependencies?
5374 nsAutoString adjustedValue;
5375 InternalCommandData commandData =
5376 ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
5377 switch (commandData.mCommand) {
5378 case Command::DoNothing:
5379 return false;
5380 case Command::SetDocumentReadOnly:
5381 SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly);
5382 break;
5383 case Command::EnableCompatibleJoinSplitNodeDirection:
5384 // We didn't allow to enable the legacy behavior once we've enabled the
5385 // new behavior by default. For keeping the behavior at supporting both
5386 // mode, we should keep returning `false` if the web app to enable the
5387 // legacy mode. Additionally, we don't support the legacy direction
5388 // anymore. Therefore, we can return `false` here even if the caller is
5389 // an addon or chrome script.
5390 if (!adjustedValue.EqualsLiteral("true")) {
5391 return false;
5393 break;
5394 default:
5395 break;
5398 AutoRunningExecCommandMarker markRunningExecCommand(*this,
5399 &aSubjectPrincipal);
5401 // If we're running an execCommand, we should just return false.
5402 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
5403 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
5404 !markRunningExecCommand.IsSafeToRun()) {
5405 return false;
5408 // Do security check first.
5409 if (commandData.IsCutOrCopyCommand()) {
5410 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
5411 // We have rejected the event due to it not being performed in an
5412 // input-driven context therefore, we report the error to the console.
5413 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
5414 this, nsContentUtils::eDOM_PROPERTIES,
5415 "ExecCommandCutCopyDeniedNotInputDriven");
5416 return false;
5418 } else if (commandData.IsPasteCommand()) {
5419 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
5420 nsGkAtoms::clipboardRead)) {
5421 return false;
5425 // Next, consider context of command handling which is automatically resolved
5426 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5427 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5428 if (commandData.IsAvailableOnlyWhenEditable() &&
5429 !editCommandTarget.IsEditable(this)) {
5430 return false;
5433 if (editCommandTarget.DoNothing()) {
5434 return false;
5437 // If we cannot use EditorCommand instance directly, we need to handle the
5438 // command with traditional path (i.e., with DocShell or nsCommandManager).
5439 if (!editCommandTarget.IsEditor()) {
5440 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
5442 // Special case clipboard write commands like Command::Cut and
5443 // Command::Copy. For such commands, we need the behaviour from
5444 // nsWindowRoot::GetControllers() which is to look at the focused element,
5445 // and defer to a focused textbox's controller. The code past taken by
5446 // other commands in ExecCommand() always uses the window directly, rather
5447 // than deferring to the textbox, which is desireable for most editor
5448 // commands, but not these commands (as those should allow copying out of
5449 // embedded editors). This behaviour is invoked if we call DoCommand()
5450 // directly on the docShell.
5451 // XXX This means that we allow web app to pick up selected content in
5452 // descendant document and write it into the clipboard when a
5453 // descendant document has focus. However, Chromium does not allow
5454 // this and this seems that it's not good behavior from point of view
5455 // of security. We should treat this issue in another bug.
5456 if (commandData.IsCutOrCopyCommand()) {
5457 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
5458 if (!docShell) {
5459 return false;
5461 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
5462 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
5463 return false;
5465 return NS_SUCCEEDED(rv);
5468 // Otherwise (currently, only clipboard read commands like Command::Paste),
5469 // we don't need to redirect the command to focused subdocument.
5470 // Therefore, we should handle it with nsCommandManager as used to be.
5471 // It may dispatch only preceding event of editing on non-editable element
5472 // to make web apps possible to handle standard shortcut key, etc in
5473 // their own editor.
5474 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5475 if (!commandManager) {
5476 return false;
5479 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5480 if (!window) {
5481 return false;
5484 // Return false for disabled commands (bug 760052)
5485 if (!commandManager->IsCommandEnabled(
5486 nsDependentCString(commandData.mXULCommandName), window)) {
5487 return false;
5490 MOZ_ASSERT(commandData.IsPasteCommand() ||
5491 commandData.mCommand == Command::SelectAll);
5492 nsresult rv =
5493 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
5494 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5497 // Now, our target is fixed to the editor. So, we can use EditorCommand
5498 // in EditorCommandTarget directly.
5500 EditorCommandParamType paramType =
5501 EditorCommand::GetParamType(commandData.mCommand);
5503 // If we don't have meaningful parameter or the EditorCommand does not
5504 // require additional parameter, we can use `DoCommand()`.
5505 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
5506 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
5507 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
5508 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5511 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
5512 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
5513 // a `bool` value.
5514 if (!!(paramType & EditorCommandParamType::Bool)) {
5515 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
5516 adjustedValue.EqualsLiteral("false"));
5517 nsresult rv = editCommandTarget.DoCommandParam(
5518 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
5519 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5522 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
5523 // in this case. However, `paramType` may contain both `String` and
5524 // `CString` but in such case, we should use `DoCommandParam()` which
5525 // takes `nsAString`. So, we should check whether `paramType` contains
5526 // `String` or not first.
5527 if (!!(paramType & EditorCommandParamType::String)) {
5528 MOZ_ASSERT(!adjustedValue.IsVoid());
5529 nsresult rv =
5530 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
5531 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5534 // Finally, `paramType` should have `CString`. We should use
5535 // `DoCommandParam()` which takes `nsACString`.
5536 if (!!(paramType & EditorCommandParamType::CString)) {
5537 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
5538 MOZ_ASSERT(!utf8Value.IsVoid());
5539 nsresult rv =
5540 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
5541 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5544 MOZ_ASSERT_UNREACHABLE(
5545 "Not yet implemented to handle new EditorCommandParamType");
5546 return false;
5549 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
5550 nsIPrincipal& aSubjectPrincipal,
5551 ErrorResult& aRv) {
5552 // Only allow on HTML documents.
5553 if (!IsHTMLOrXHTML()) {
5554 aRv.ThrowInvalidStateError(
5555 "queryCommandEnabled is only supported on HTML documents");
5556 return false;
5558 // Otherwise, don't throw exception for compatibility with Chrome.
5560 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5561 switch (commandData.mCommand) {
5562 case Command::DoNothing:
5563 return false;
5564 case Command::SetDocumentReadOnly:
5565 SetUseCounter(
5566 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5567 break;
5568 case Command::SetDocumentInsertBROnEnterKeyPress:
5569 SetUseCounter(
5570 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5571 break;
5572 default:
5573 break;
5576 // cut & copy are always allowed
5577 if (commandData.IsCutOrCopyCommand()) {
5578 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
5581 // Report false for restricted commands
5582 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
5583 return false;
5586 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5587 if (commandData.IsAvailableOnlyWhenEditable() &&
5588 !editCommandTarget.IsEditable(this)) {
5589 return false;
5592 if (editCommandTarget.IsEditor()) {
5593 return editCommandTarget.IsCommandEnabled();
5596 // get command manager and dispatch command to our window if it's acceptable
5597 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5598 if (!commandManager) {
5599 return false;
5602 nsPIDOMWindowOuter* window = GetWindow();
5603 if (!window) {
5604 return false;
5607 return commandManager->IsCommandEnabled(
5608 nsDependentCString(commandData.mXULCommandName), window);
5611 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5612 ErrorResult& aRv) {
5613 // Only allow on HTML documents.
5614 if (!IsHTMLOrXHTML()) {
5615 aRv.ThrowInvalidStateError(
5616 "queryCommandIndeterm is only supported on HTML documents");
5617 return false;
5619 // Otherwise, don't throw exception for compatibility with Chrome.
5621 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5622 if (commandData.mCommand == Command::DoNothing) {
5623 return false;
5626 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5627 if (commandData.IsAvailableOnlyWhenEditable() &&
5628 !editCommandTarget.IsEditable(this)) {
5629 return false;
5631 RefPtr<nsCommandParams> params = new nsCommandParams();
5632 if (editCommandTarget.IsEditor()) {
5633 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5634 return false;
5636 } else {
5637 // get command manager and dispatch command to our window if it's acceptable
5638 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5639 if (!commandManager) {
5640 return false;
5643 nsPIDOMWindowOuter* window = GetWindow();
5644 if (!window) {
5645 return false;
5648 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5649 window, params))) {
5650 return false;
5654 // If command does not have a state_mixed value, this call fails and sets
5655 // retval to false. This is fine -- we want to return false in that case
5656 // anyway (bug 738385), so we just don't throw regardless.
5657 return params->GetBool("state_mixed");
5660 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5661 ErrorResult& aRv) {
5662 // Only allow on HTML documents.
5663 if (!IsHTMLOrXHTML()) {
5664 aRv.ThrowInvalidStateError(
5665 "queryCommandState is only supported on HTML documents");
5666 return false;
5668 // Otherwise, don't throw exception for compatibility with Chrome.
5670 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5671 switch (commandData.mCommand) {
5672 case Command::DoNothing:
5673 return false;
5674 case Command::SetDocumentReadOnly:
5675 SetUseCounter(
5676 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5677 break;
5678 case Command::SetDocumentInsertBROnEnterKeyPress:
5679 SetUseCounter(
5680 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5681 break;
5682 default:
5683 break;
5686 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5687 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5688 // return false always.
5689 return false;
5692 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5693 if (commandData.IsAvailableOnlyWhenEditable() &&
5694 !editCommandTarget.IsEditable(this)) {
5695 return false;
5697 RefPtr<nsCommandParams> params = new nsCommandParams();
5698 if (editCommandTarget.IsEditor()) {
5699 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5700 return false;
5702 } else {
5703 // get command manager and dispatch command to our window if it's acceptable
5704 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5705 if (!commandManager) {
5706 return false;
5709 nsPIDOMWindowOuter* window = GetWindow();
5710 if (!window) {
5711 return false;
5714 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5715 window, params))) {
5716 return false;
5720 // handle alignment as a special case (possibly other commands too?)
5721 // Alignment is special because the external api is individual
5722 // commands but internally we use cmd_align with different
5723 // parameters. When getting the state of this command, we need to
5724 // return the boolean for this particular alignment rather than the
5725 // string of 'which alignment is this?'
5726 switch (commandData.mCommand) {
5727 case Command::FormatJustifyLeft: {
5728 nsAutoCString currentValue;
5729 nsresult rv = params->GetCString("state_attribute", currentValue);
5730 if (NS_FAILED(rv)) {
5731 return false;
5733 return currentValue.EqualsLiteral("left");
5735 case Command::FormatJustifyRight: {
5736 nsAutoCString currentValue;
5737 nsresult rv = params->GetCString("state_attribute", currentValue);
5738 if (NS_FAILED(rv)) {
5739 return false;
5741 return currentValue.EqualsLiteral("right");
5743 case Command::FormatJustifyCenter: {
5744 nsAutoCString currentValue;
5745 nsresult rv = params->GetCString("state_attribute", currentValue);
5746 if (NS_FAILED(rv)) {
5747 return false;
5749 return currentValue.EqualsLiteral("center");
5751 case Command::FormatJustifyFull: {
5752 nsAutoCString currentValue;
5753 nsresult rv = params->GetCString("state_attribute", currentValue);
5754 if (NS_FAILED(rv)) {
5755 return false;
5757 return currentValue.EqualsLiteral("justify");
5759 default:
5760 break;
5763 // If command does not have a state_all value, this call fails and sets
5764 // retval to false. This is fine -- we want to return false in that case
5765 // anyway (bug 738385), so we just succeed and return false regardless.
5766 return params->GetBool("state_all");
5769 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5770 CallerType aCallerType, ErrorResult& aRv) {
5771 // Only allow on HTML documents.
5772 if (!IsHTMLOrXHTML()) {
5773 aRv.ThrowInvalidStateError(
5774 "queryCommandSupported is only supported on HTML documents");
5775 return false;
5777 // Otherwise, don't throw exception for compatibility with Chrome.
5779 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5780 switch (commandData.mCommand) {
5781 case Command::DoNothing:
5782 return false;
5783 case Command::SetDocumentReadOnly:
5784 SetUseCounter(
5785 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5786 break;
5787 case Command::SetDocumentInsertBROnEnterKeyPress:
5788 SetUseCounter(
5789 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5790 break;
5791 default:
5792 break;
5795 // Gecko technically supports all the clipboard commands including
5796 // cut/copy/paste, but non-privileged content will be unable to call
5797 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5798 // may also be disallowed to be called from non-privileged content.
5799 // For that reason, we report the support status of corresponding
5800 // command accordingly.
5801 if (aCallerType != CallerType::System) {
5802 if (commandData.IsPasteCommand()) {
5803 return false;
5805 if (commandData.IsCutOrCopyCommand() &&
5806 !StaticPrefs::dom_allow_cut_copy()) {
5807 // XXXbz should we worry about correctly reporting "true" in the
5808 // "restricted, but we're an addon with clipboardWrite permissions" case?
5809 // See also nsContentUtils::IsCutCopyAllowed.
5810 return false;
5814 // aHTMLCommandName is supported if it can be converted to a Midas command
5815 return true;
5818 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5819 nsAString& aValue, ErrorResult& aRv) {
5820 aValue.Truncate();
5822 // Only allow on HTML documents.
5823 if (!IsHTMLOrXHTML()) {
5824 aRv.ThrowInvalidStateError(
5825 "queryCommandValue is only supported on HTML documents");
5826 return;
5828 // Otherwise, don't throw exception for compatibility with Chrome.
5830 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5831 switch (commandData.mCommand) {
5832 case Command::DoNothing:
5833 // Return empty string
5834 return;
5835 case Command::SetDocumentReadOnly:
5836 SetUseCounter(
5837 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5838 break;
5839 case Command::SetDocumentInsertBROnEnterKeyPress:
5840 SetUseCounter(
5841 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5842 break;
5843 default:
5844 break;
5847 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5848 if (commandData.IsAvailableOnlyWhenEditable() &&
5849 !editCommandTarget.IsEditable(this)) {
5850 return;
5852 RefPtr<nsCommandParams> params = new nsCommandParams();
5853 if (editCommandTarget.IsEditor()) {
5854 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5855 return;
5858 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5859 return;
5861 } else {
5862 // get command manager and dispatch command to our window if it's acceptable
5863 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5864 if (!commandManager) {
5865 return;
5868 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5869 if (!window) {
5870 return;
5873 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5874 return;
5877 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5878 window, params))) {
5879 return;
5883 // If command does not have a state_attribute value, this call fails, and
5884 // aValue will wind up being the empty string. This is fine -- we want to
5885 // return "" in that case anyway (bug 738385), so we just return NS_OK
5886 // regardless.
5887 nsAutoCString result;
5888 params->GetCString("state_attribute", result);
5889 CopyUTF8toUTF16(result, aValue);
5892 void Document::MaybeEditingStateChanged() {
5893 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
5894 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
5895 if (nsContentUtils::IsSafeToRunScript()) {
5896 EditingStateChanged();
5897 } else if (!mInDestructor) {
5898 nsContentUtils::AddScriptRunner(
5899 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
5900 &Document::MaybeEditingStateChanged));
5905 void Document::NotifyFetchOrXHRSuccess() {
5906 if (mShouldNotifyFetchSuccess) {
5907 nsContentUtils::DispatchEventOnlyToChrome(
5908 this, this, u"DOMDocFetchSuccess"_ns, CanBubble::eNo, Cancelable::eNo,
5909 /* DefaultAction */ nullptr);
5913 void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
5914 mShouldNotifyFetchSuccess = aShouldNotify;
5917 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
5918 mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
5921 void Document::TearingDownEditor() {
5922 if (IsEditingOn()) {
5923 mEditingState = EditingState::eTearingDown;
5924 if (IsHTMLOrXHTML()) {
5925 RemoveContentEditableStyleSheets();
5930 nsresult Document::TurnEditingOff() {
5931 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
5933 nsPIDOMWindowOuter* window = GetWindow();
5934 if (!window) {
5935 return NS_ERROR_FAILURE;
5938 nsIDocShell* docshell = window->GetDocShell();
5939 if (!docshell) {
5940 return NS_ERROR_FAILURE;
5943 bool isBeingDestroyed = false;
5944 docshell->IsBeingDestroyed(&isBeingDestroyed);
5945 if (isBeingDestroyed) {
5946 return NS_ERROR_FAILURE;
5949 nsCOMPtr<nsIEditingSession> editSession;
5950 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5951 NS_ENSURE_SUCCESS(rv, rv);
5953 // turn editing off
5954 rv = editSession->TearDownEditorOnWindow(window);
5955 NS_ENSURE_SUCCESS(rv, rv);
5957 mEditingState = EditingState::eOff;
5959 // Editor resets selection since it is being destroyed. But if focus is
5960 // still into editable control, we have to initialize selection again.
5961 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
5962 if (RefPtr<TextControlElement> textControlElement =
5963 TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
5964 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
5965 textEditor->ReinitializeSelection(*textControlElement);
5970 return NS_OK;
5973 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
5974 nsIDocShell* docShell = aWindow->GetDocShell();
5975 if (!docShell) {
5976 return false;
5978 return docShell->GetPresShell() != nullptr;
5981 HTMLEditor* Document::GetHTMLEditor() const {
5982 nsPIDOMWindowOuter* window = GetWindow();
5983 if (!window) {
5984 return nullptr;
5987 nsIDocShell* docshell = window->GetDocShell();
5988 if (!docshell) {
5989 return nullptr;
5992 return docshell->GetHTMLEditor();
5995 nsresult Document::EditingStateChanged() {
5996 if (mRemovedFromDocShell) {
5997 return NS_OK;
6000 if (mEditingState == EditingState::eSettingUp ||
6001 mEditingState == EditingState::eTearingDown) {
6002 // XXX We shouldn't recurse
6003 return NS_OK;
6006 const bool designMode = IsInDesignMode();
6007 EditingState newState =
6008 designMode ? EditingState::eDesignMode
6009 : (mContentEditableCount > 0 ? EditingState::eContentEditable
6010 : EditingState::eOff);
6011 if (mEditingState == newState) {
6012 // No changes in editing mode.
6013 return NS_OK;
6016 const bool thisDocumentHasFocus = ThisDocumentHasFocus();
6017 if (newState == EditingState::eOff) {
6018 // Editing is being turned off.
6019 nsAutoScriptBlocker scriptBlocker;
6020 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6021 NotifyEditableStateChange(*this);
6022 nsresult rv = TurnEditingOff();
6023 // If this document has focus and the editing state of this document
6024 // becomes "off", it means that HTMLEditor won't handle any inputs nor
6025 // modify the DOM tree. However, HTMLEditor may not receive `blur`
6026 // event for this state change since this may occur without focus change.
6027 // Therefore, let's notify HTMLEditor of this editing state change.
6028 // Note that even if focusedElement is an editable text control element,
6029 // it becomes not editable from HTMLEditor point of view since text
6030 // control elements are manged by TextEditor.
6031 RefPtr<Element> focusedElement =
6032 nsFocusManager::GetFocusManager()
6033 ? nsFocusManager::GetFocusManager()->GetFocusedElement()
6034 : nullptr;
6035 DebugOnly<nsresult> rvIgnored =
6036 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6037 htmlEditor, *this, focusedElement);
6038 NS_WARNING_ASSERTION(
6039 NS_SUCCEEDED(rvIgnored),
6040 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but "
6041 "ignored");
6042 return rv;
6045 // Flush out style changes on our _parent_ document, if any, so that
6046 // our check for a presshell won't get stale information.
6047 if (mParentDocument) {
6048 mParentDocument->FlushPendingNotifications(FlushType::Style);
6051 // get editing session, make sure this is a strong reference so the
6052 // window can't get deleted during the rest of this call.
6053 const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6054 if (!window) {
6055 return NS_ERROR_FAILURE;
6058 nsIDocShell* docshell = window->GetDocShell();
6059 if (!docshell) {
6060 return NS_ERROR_FAILURE;
6063 // FlushPendingNotifications might destroy our docshell.
6064 bool isBeingDestroyed = false;
6065 docshell->IsBeingDestroyed(&isBeingDestroyed);
6066 if (isBeingDestroyed) {
6067 return NS_ERROR_FAILURE;
6070 nsCOMPtr<nsIEditingSession> editSession;
6071 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6072 NS_ENSURE_SUCCESS(rv, rv);
6074 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
6075 if (htmlEditor) {
6076 // We might already have an editor if it was set up for mail, let's see
6077 // if this is actually the case.
6078 uint32_t flags = 0;
6079 htmlEditor->GetFlags(&flags);
6080 if (flags & nsIEditor::eEditorMailMask) {
6081 // We already have a mail editor, then we should not attempt to create
6082 // another one.
6083 return NS_OK;
6087 if (!HasPresShell(window)) {
6088 // We should not make the window editable or setup its editor.
6089 // It's probably style=display:none.
6090 return NS_OK;
6093 bool makeWindowEditable = mEditingState == EditingState::eOff;
6094 bool spellRecheckAll = false;
6095 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
6096 htmlEditor = nullptr;
6099 EditingState oldState = mEditingState;
6100 nsAutoEditingState push(this, EditingState::eSettingUp);
6102 RefPtr<PresShell> presShell = GetPresShell();
6103 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
6105 // If we're entering the design mode from non-editable state, put the
6106 // selection at the beginning of the document for compatibility reasons.
6107 bool collapseSelectionAtBeginningOfDocument =
6108 designMode && oldState == EditingState::eOff;
6109 // However, mEditingState may be eOff even if there is some
6110 // `contenteditable` area and selection has been initialized for it because
6111 // mEditingState for `contenteditable` may have been scheduled to modify
6112 // when safe. In such case, we should not reinitialize selection.
6113 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
6114 Selection* selection =
6115 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
6116 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
6117 if (selection && selection->RangeCount()) {
6118 // Perhaps, we don't need to check whether the selection is in
6119 // an editing host or not because all contents will be editable
6120 // in designMode. (And we don't want to make this code so complicated
6121 // because of legacy API.)
6122 collapseSelectionAtBeginningOfDocument = false;
6126 MOZ_ASSERT(mStyleSetFilled);
6128 // Before making this window editable, we need to modify UA style sheet
6129 // because new style may change whether focused element will be focusable
6130 // or not.
6131 if (IsHTMLOrXHTML()) {
6132 AddContentEditableStyleSheetsToStyleSet(designMode);
6135 if (designMode) {
6136 // designMode is being turned on (overrides contentEditable).
6137 spellRecheckAll = oldState == EditingState::eContentEditable;
6140 // Adjust focused element with new style but blur event shouldn't be fired
6141 // until mEditingState is modified with newState.
6142 nsAutoScriptBlocker scriptBlocker;
6143 if (designMode) {
6144 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6145 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6146 window, nsFocusManager::eOnlyCurrentWindow,
6147 getter_AddRefs(focusedWindow));
6148 if (focusedContent) {
6149 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
6150 bool clearFocus = focusedFrame
6151 ? !focusedFrame->IsFocusable()
6152 : !focusedContent->IsFocusableWithoutStyle();
6153 if (clearFocus) {
6154 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
6155 fm->ClearFocus(window);
6156 // If we need to dispatch blur event, we should put off after
6157 // modifying mEditingState since blur event handler may change
6158 // designMode state again.
6159 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
6165 if (makeWindowEditable) {
6166 // Editing is being turned on (through designMode or contentEditable)
6167 // Turn on editor.
6168 // XXX This can cause flushing which can change the editing state, so make
6169 // sure to avoid recursing.
6170 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
6171 NS_ENSURE_SUCCESS(rv, rv);
6174 // XXX Need to call TearDownEditorOnWindow for all failures.
6175 htmlEditor = docshell->GetHTMLEditor();
6176 if (!htmlEditor) {
6177 // Return NS_OK even though we've failed to create an editor here. This
6178 // is so that the setter of designMode on non-HTML documents does not
6179 // fail.
6180 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
6181 // would detect that we can't support the mimetype if appropriate and
6182 // would fall onto the eEditorErrorCantEditMimeType path.
6183 return NS_OK;
6186 if (collapseSelectionAtBeginningOfDocument) {
6187 htmlEditor->BeginningOfDocument();
6190 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6191 nsContentUtils::AddScriptBlocker();
6195 mEditingState = newState;
6196 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6197 nsContentUtils::RemoveScriptBlocker();
6198 // If mEditingState is overwritten by another call and already disabled
6199 // the editing, we shouldn't keep making window editable.
6200 if (mEditingState == EditingState::eOff) {
6201 return NS_OK;
6205 if (makeWindowEditable) {
6206 // TODO: We should do this earlier in this method.
6207 // Previously, we called `ExecCommand` with `insertBrOnReturn` command
6208 // whose argument is false here. Then, if it returns error, we
6209 // stopped making it editable. However, after bug 1697078 fixed,
6210 // `ExecCommand` returns error only when the document is not XHTML's
6211 // nor HTML's. Therefore, we use same error handling for now.
6212 if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
6213 // Editor setup failed. Editing is not on after all.
6214 // XXX Should we reset the editable flag on nodes?
6215 editSession->TearDownEditorOnWindow(window);
6216 mEditingState = EditingState::eOff;
6217 return NS_ERROR_DOM_INVALID_STATE_ERR;
6219 // Set the editor to not insert <br> elements on return when in <p> elements
6220 // by default.
6221 htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
6224 // Resync the editor's spellcheck state.
6225 if (spellRecheckAll) {
6226 nsCOMPtr<nsISelectionController> selectionController =
6227 htmlEditor->GetSelectionController();
6228 if (NS_WARN_IF(!selectionController)) {
6229 return NS_ERROR_FAILURE;
6232 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
6233 nsISelectionController::SELECTION_SPELLCHECK);
6234 if (spellCheckSelection) {
6235 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
6238 htmlEditor->SyncRealTimeSpell();
6240 MaybeDispatchCheckKeyPressEventModelEvent();
6242 // If this document keeps having focus and the HTMLEditor is in the design
6243 // mode, it may not receive `focus` event for this editing state change since
6244 // this may occur without a focus change. Therefore, let's notify HTMLEditor
6245 // of this editing state change.
6246 if (thisDocumentHasFocus && htmlEditor->IsInDesignMode() &&
6247 ThisDocumentHasFocus()) {
6248 DebugOnly<nsresult> rvIgnored =
6249 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, nullptr);
6250 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6251 "HTMLEditor::FocusedElementOrDocumentBecomesEditable()"
6252 " failed, but ignored");
6255 return NS_OK;
6258 // Helper class, used below in ChangeContentEditableCount().
6259 class DeferredContentEditableCountChangeEvent : public Runnable {
6260 public:
6261 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
6262 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
6263 mDoc(aDoc),
6264 mElement(aElement) {}
6266 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
6267 if (mElement && mElement->OwnerDoc() == mDoc) {
6268 RefPtr<Document> doc = std::move(mDoc);
6269 RefPtr<Element> element = std::move(mElement);
6270 doc->DeferredContentEditableCountChange(element);
6272 return NS_OK;
6275 private:
6276 RefPtr<Document> mDoc;
6277 RefPtr<Element> mElement;
6280 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
6281 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
6282 "Trying to decrement too much.");
6284 mContentEditableCount += aChange;
6286 if (aElement) {
6287 nsContentUtils::AddScriptRunner(
6288 new DeferredContentEditableCountChangeEvent(this, aElement));
6292 void Document::DeferredContentEditableCountChange(Element* aElement) {
6293 const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
6294 const bool elementHasFocus =
6295 aElement && fm && fm->GetFocusedElement() == aElement;
6296 if (elementHasFocus) {
6297 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
6298 // When contenteditable of aElement is changed and HTMLEditor works with it
6299 // or needs to start working with it, HTMLEditor may not receive `focus`
6300 // event nor `blur` event because this may occur without a focus change.
6301 // Therefore, we need to notify HTMLEditor of this contenteditable attribute
6302 // change.
6303 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6304 if (aElement->HasFlag(NODE_IS_EDITABLE)) {
6305 if (htmlEditor) {
6306 DebugOnly<nsresult> rvIgnored =
6307 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this,
6308 aElement);
6309 NS_WARNING_ASSERTION(
6310 NS_SUCCEEDED(rvIgnored),
6311 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6312 "ignored");
6314 } else {
6315 DebugOnly<nsresult> rvIgnored =
6316 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6317 htmlEditor, *this, aElement);
6318 NS_WARNING_ASSERTION(
6319 NS_SUCCEEDED(rvIgnored),
6320 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, "
6321 "but ignored");
6325 if (mParser ||
6326 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
6327 return;
6330 EditingState oldState = mEditingState;
6332 nsresult rv = EditingStateChanged();
6333 NS_ENSURE_SUCCESS_VOID(rv);
6335 if (oldState == mEditingState &&
6336 mEditingState == EditingState::eContentEditable) {
6337 // We just changed the contentEditable state of a node, we need to reset
6338 // the spellchecking state of that node.
6339 if (aElement) {
6340 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6341 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
6342 DebugOnly<nsresult> rvIgnored = htmlEditor->GetInlineSpellChecker(
6343 false, getter_AddRefs(spellChecker));
6344 NS_WARNING_ASSERTION(
6345 NS_SUCCEEDED(rvIgnored),
6346 "EditorBase::GetInlineSpellChecker() failed, but ignored");
6348 if (spellChecker &&
6349 aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) {
6350 RefPtr<nsRange> range = nsRange::Create(aElement);
6351 IgnoredErrorResult res;
6352 range->SelectNode(*aElement, res);
6353 if (res.Failed()) {
6354 // The node might be detached from the document at this point,
6355 // which would cause this call to fail. In this case, we can
6356 // safely ignore the contenteditable count change.
6357 return;
6360 rv = spellChecker->SpellCheckRange(range);
6361 NS_ENSURE_SUCCESS_VOID(rv);
6367 // aElement causes creating new HTMLEditor and the element had and keep
6368 // having focus, the HTMLEditor won't receive `focus` event. Therefore, we
6369 // need to notify HTMLEditor of it becomes editable.
6370 if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) &&
6371 fm->GetFocusedElement() == aElement) {
6372 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6373 DebugOnly<nsresult> rvIgnored =
6374 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement);
6375 NS_WARNING_ASSERTION(
6376 NS_SUCCEEDED(rvIgnored),
6377 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6378 "ignored");
6383 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
6384 // Currently, we need to check only when we're becoming editable for
6385 // contenteditable.
6386 if (mEditingState != EditingState::eContentEditable) {
6387 return;
6390 if (mHasBeenEditable) {
6391 return;
6393 mHasBeenEditable = true;
6395 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
6396 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
6397 // with proper keypress event for the active web app.
6398 WidgetEvent checkEvent(true, eUnidentifiedEvent);
6399 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
6400 checkEvent.mFlags.mCancelable = false;
6401 checkEvent.mFlags.mBubbles = false;
6402 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
6403 // Post the event rather than dispatching it synchronously because we need
6404 // a call of SetKeyPressEventModel() before first key input. Therefore, we
6405 // can avoid paying unnecessary runtime cost for most web apps.
6406 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
6409 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
6410 PresShell* presShell = GetPresShell();
6411 if (!presShell) {
6412 return;
6414 presShell->SetKeyPressEventModel(aKeyPressEventModel);
6417 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
6419 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
6420 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
6421 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
6422 aFocusTime >= mLastFocusTime);
6423 mLastFocusTime = aFocusTime;
6426 void Document::GetReferrer(nsACString& aReferrer) const {
6427 aReferrer.Truncate();
6428 if (!mReferrerInfo) {
6429 return;
6432 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
6433 if (!referrer) {
6434 return;
6437 URLDecorationStripper::StripTrackingIdentifiers(referrer, aReferrer);
6440 void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
6441 aCookie.Truncate(); // clear current cookie in case service fails;
6442 // no cookie isn't an error condition.
6444 if (mDisableCookieAccess) {
6445 return;
6448 // If the document's sandboxed origin flag is set, then reading cookies
6449 // is prohibited.
6450 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6451 aRv.ThrowSecurityError(
6452 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6453 "flag.");
6454 return;
6457 StorageAccess storageAccess = CookieAllowedForDocument(this);
6458 if (storageAccess == StorageAccess::eDeny) {
6459 return;
6462 if (ShouldPartitionStorage(storageAccess) &&
6463 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6464 return;
6467 // If the document is a cookie-averse Document... return the empty string.
6468 if (IsCookieAverse()) {
6469 return;
6472 // not having a cookie service isn't an error
6473 nsCOMPtr<nsICookieService> service =
6474 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6475 if (service) {
6476 nsAutoCString cookie;
6477 service->GetCookieStringFromDocument(this, cookie);
6478 // CopyUTF8toUTF16 doesn't handle error
6479 // because it assumes that the input is valid.
6480 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
6484 void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
6485 if (mDisableCookieAccess) {
6486 return;
6489 // If the document's sandboxed origin flag is set, then setting cookies
6490 // is prohibited.
6491 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6492 aRv.ThrowSecurityError(
6493 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6494 "flag.");
6495 return;
6498 StorageAccess storageAccess = CookieAllowedForDocument(this);
6499 if (storageAccess == StorageAccess::eDeny) {
6500 return;
6503 if (ShouldPartitionStorage(storageAccess) &&
6504 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6505 return;
6508 // If the document is a cookie-averse Document... do nothing.
6509 if (IsCookieAverse()) {
6510 return;
6513 if (!mDocumentURI) {
6514 return;
6517 // not having a cookie service isn't an error
6518 nsCOMPtr<nsICookieService> service =
6519 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6520 if (!service) {
6521 return;
6524 NS_ConvertUTF16toUTF8 cookie(aCookie);
6525 nsresult rv = service->SetCookieStringFromDocument(this, cookie);
6527 // No warning messages here.
6528 if (NS_FAILED(rv)) {
6529 return;
6532 nsCOMPtr<nsIObserverService> observerService =
6533 mozilla::services::GetObserverService();
6534 if (observerService) {
6535 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
6536 nsString(aCookie).get());
6540 ReferrerPolicy Document::GetReferrerPolicy() const {
6541 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
6542 : ReferrerPolicy::_empty;
6545 void Document::GetAlinkColor(nsAString& aAlinkColor) {
6546 aAlinkColor.Truncate();
6548 HTMLBodyElement* body = GetBodyElement();
6549 if (body) {
6550 body->GetALink(aAlinkColor);
6554 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
6555 HTMLBodyElement* body = GetBodyElement();
6556 if (body) {
6557 body->SetALink(aAlinkColor);
6561 void Document::GetLinkColor(nsAString& aLinkColor) {
6562 aLinkColor.Truncate();
6564 HTMLBodyElement* body = GetBodyElement();
6565 if (body) {
6566 body->GetLink(aLinkColor);
6570 void Document::SetLinkColor(const nsAString& aLinkColor) {
6571 HTMLBodyElement* body = GetBodyElement();
6572 if (body) {
6573 body->SetLink(aLinkColor);
6577 void Document::GetVlinkColor(nsAString& aVlinkColor) {
6578 aVlinkColor.Truncate();
6580 HTMLBodyElement* body = GetBodyElement();
6581 if (body) {
6582 body->GetVLink(aVlinkColor);
6586 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
6587 HTMLBodyElement* body = GetBodyElement();
6588 if (body) {
6589 body->SetVLink(aVlinkColor);
6593 void Document::GetBgColor(nsAString& aBgColor) {
6594 aBgColor.Truncate();
6596 HTMLBodyElement* body = GetBodyElement();
6597 if (body) {
6598 body->GetBgColor(aBgColor);
6602 void Document::SetBgColor(const nsAString& aBgColor) {
6603 HTMLBodyElement* body = GetBodyElement();
6604 if (body) {
6605 body->SetBgColor(aBgColor);
6609 void Document::GetFgColor(nsAString& aFgColor) {
6610 aFgColor.Truncate();
6612 HTMLBodyElement* body = GetBodyElement();
6613 if (body) {
6614 body->GetText(aFgColor);
6618 void Document::SetFgColor(const nsAString& aFgColor) {
6619 HTMLBodyElement* body = GetBodyElement();
6620 if (body) {
6621 body->SetText(aFgColor);
6625 void Document::CaptureEvents() {
6626 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
6629 void Document::ReleaseEvents() {
6630 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
6633 HTMLAllCollection* Document::All() {
6634 if (!mAll) {
6635 mAll = new HTMLAllCollection(this);
6637 return mAll;
6640 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
6641 if (mIsSrcdocDocument) {
6642 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
6643 if (inStrmChan) {
6644 return inStrmChan->GetSrcdocData(aSrcdocData);
6647 aSrcdocData = VoidString();
6648 return NS_OK;
6651 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
6652 nsPIDOMWindowOuter* win = GetWindow();
6653 if (!win) {
6654 return nullptr;
6656 return WindowProxyHolder(win->GetBrowsingContext());
6659 nsIContent* Document::GetUnretargetedFocusedContent(
6660 IncludeChromeOnly aIncludeChromeOnly) const {
6661 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6662 if (!window) {
6663 return nullptr;
6665 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6666 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6667 window, nsFocusManager::eOnlyCurrentWindow,
6668 getter_AddRefs(focusedWindow));
6669 if (!focusedContent) {
6670 return nullptr;
6672 // be safe and make sure the element is from this document
6673 if (focusedContent->OwnerDoc() != this) {
6674 return nullptr;
6676 if (focusedContent->ChromeOnlyAccess() &&
6677 aIncludeChromeOnly == IncludeChromeOnly::No) {
6678 return focusedContent->FindFirstNonChromeOnlyAccessContent();
6680 return focusedContent;
6683 Element* Document::GetActiveElement() {
6684 // Get the focused element.
6685 Element* focusedElement = GetRetargetedFocusedElement();
6686 if (focusedElement) {
6687 return focusedElement;
6690 // No focused element anywhere in this document. Try to get the BODY.
6691 if (IsHTMLOrXHTML()) {
6692 Element* bodyElement = AsHTMLDocument()->GetBody();
6693 if (bodyElement) {
6694 return bodyElement;
6696 // Special case to handle the transition to XHTML from XUL documents
6697 // where there currently isn't a body element, but we need to match the
6698 // XUL behavior. This should be removed when bug 1540278 is resolved.
6699 if (nsContentUtils::IsChromeDoc(this)) {
6700 Element* docElement = GetDocumentElement();
6701 if (docElement && docElement->IsXULElement()) {
6702 return docElement;
6705 // Because of IE compatibility, return null when html document doesn't have
6706 // a body.
6707 return nullptr;
6710 // If we couldn't get a BODY, return the root element.
6711 return GetDocumentElement();
6714 Element* Document::GetCurrentScript() {
6715 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
6716 return el;
6719 void Document::ReleaseCapture() const {
6720 // only release the capture if the caller can access it. This prevents a
6721 // page from stopping a scrollbar grab for example.
6722 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
6723 if (node && nsContentUtils::CanCallerAccess(node)) {
6724 PresShell::ReleaseCapturingContent();
6728 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
6729 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
6730 return mChromeXHRDocBaseURI;
6733 return GetDocBaseURI();
6736 void Document::SetBaseURI(nsIURI* aURI) {
6737 if (!aURI && !mDocumentBaseURI) {
6738 return;
6741 // Don't do anything if the URI wasn't actually changed.
6742 if (aURI && mDocumentBaseURI) {
6743 bool equalBases = false;
6744 mDocumentBaseURI->Equals(aURI, &equalBases);
6745 if (equalBases) {
6746 return;
6750 mDocumentBaseURI = aURI;
6751 mCachedURLData = nullptr;
6752 RefreshLinkHrefs();
6755 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
6756 const nsAString& aURI) {
6757 RefPtr<nsIURI> resolvedURI;
6758 MOZ_TRY(
6759 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
6760 return OwningNonNull<nsIURI>(std::move(resolvedURI));
6763 nsIReferrerInfo* Document::ReferrerInfoForInternalCSSAndSVGResources() {
6764 if (!mCachedReferrerInfoForInternalCSSAndSVGResources) {
6765 mCachedReferrerInfoForInternalCSSAndSVGResources =
6766 ReferrerInfo::CreateForInternalCSSAndSVGResources(this);
6768 return mCachedReferrerInfoForInternalCSSAndSVGResources;
6771 URLExtraData* Document::DefaultStyleAttrURLData() {
6772 MOZ_ASSERT(NS_IsMainThread());
6773 if (!mCachedURLData) {
6774 mCachedURLData = new URLExtraData(
6775 GetDocBaseURI(), ReferrerInfoForInternalCSSAndSVGResources(),
6776 NodePrincipal());
6778 return mCachedURLData;
6781 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
6782 if (mCharacterSet != aEncoding) {
6783 mCharacterSet = aEncoding;
6784 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
6785 RecomputeLanguageFromCharset();
6787 if (nsPresContext* context = GetPresContext()) {
6788 context->DocumentCharSetChanged(aEncoding);
6793 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
6794 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
6797 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
6798 aData.Truncate();
6799 const HeaderData* data = mHeaderData.get();
6800 while (data) {
6801 if (data->mField == aHeaderField) {
6802 aData = data->mData;
6803 break;
6805 data = data->mNext.get();
6809 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
6810 if (!aHeaderField) {
6811 NS_ERROR("null headerField");
6812 return;
6815 if (!mHeaderData) {
6816 if (!aData.IsEmpty()) { // don't bother storing empty string
6817 mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
6819 } else {
6820 HeaderData* data = mHeaderData.get();
6821 UniquePtr<HeaderData>* lastPtr = &mHeaderData;
6822 bool found = false;
6823 do { // look for existing and replace
6824 if (data->mField == aHeaderField) {
6825 if (!aData.IsEmpty()) {
6826 data->mData.Assign(aData);
6827 } else { // don't store empty string
6828 // Note that data->mNext is moved to a temporary before the old value
6829 // of *lastPtr is deleted.
6830 *lastPtr = std::move(data->mNext);
6832 found = true;
6834 break;
6836 lastPtr = &data->mNext;
6837 data = lastPtr->get();
6838 } while (data);
6840 if (!aData.IsEmpty() && !found) {
6841 // didn't find, append
6842 *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
6846 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
6847 if (aData.IsEmpty()) {
6848 mContentLanguage = nullptr;
6849 } else {
6850 mContentLanguage = NS_AtomizeMainThread(aData);
6852 mMayNeedFontPrefsUpdate = true;
6853 if (auto* presContext = GetPresContext()) {
6854 presContext->ContentLanguageChanged();
6858 if (aHeaderField == nsGkAtoms::origin_trial) {
6859 mTrials.UpdateFromToken(aData, NodePrincipal());
6860 if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) {
6861 InitCOEP(mChannel);
6863 // If we still don't have a WindowContext, WindowContext::OnNewDocument
6864 // will take care of this.
6865 if (WindowContext* ctx = GetWindowContext()) {
6866 if (mEmbedderPolicy) {
6867 Unused << ctx->SetEmbedderPolicy(mEmbedderPolicy.value());
6873 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
6874 SetPreferredStyleSheetSet(aData);
6877 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
6878 // We get into this code before we have a script global yet, so get to our
6879 // container via mDocumentContainer.
6880 if (mDocumentContainer) {
6881 // Note: using mDocumentURI instead of mBaseURI here, for consistency
6882 // (used to just use the current URI of our webnavigation, but that
6883 // should really be the same thing). Note that this code can run
6884 // before the current URI of the webnavigation has been updated, so we
6885 // can't assert equality here.
6886 mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
6890 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
6891 mAllowDNSPrefetch) {
6892 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
6893 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
6896 if (aHeaderField == nsGkAtoms::handheldFriendly) {
6897 mViewportType = Unknown;
6901 void Document::SetEarlyHints(
6902 nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) {
6903 mEarlyHints = std::move(aEarlyHints);
6906 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
6907 NotNull<const Encoding*>& aEncoding,
6908 nsHtml5TreeOpExecutor* aExecutor) {
6909 if (aChannel) {
6910 nsAutoCString charsetVal;
6911 nsresult rv = aChannel->GetContentCharset(charsetVal);
6912 if (NS_SUCCEEDED(rv)) {
6913 const Encoding* preferred = Encoding::ForLabel(charsetVal);
6914 if (preferred) {
6915 if (aExecutor && preferred == REPLACEMENT_ENCODING) {
6916 aExecutor->ComplainAboutBogusProtocolCharset(this, false);
6918 aEncoding = WrapNotNull(preferred);
6919 aCharsetSource = kCharsetFromChannel;
6920 return;
6921 } else if (aExecutor && !charsetVal.IsEmpty()) {
6922 aExecutor->ComplainAboutBogusProtocolCharset(this, true);
6928 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
6929 #ifdef DEBUG
6930 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
6931 const Element* element = Element::FromNode(node);
6932 if (!element) {
6933 continue;
6935 MOZ_ASSERT(!element->HasServoData());
6937 #endif
6940 already_AddRefed<PresShell> Document::CreatePresShell(
6941 nsPresContext* aContext, nsViewManager* aViewManager) {
6942 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
6944 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
6946 AssertNoStaleServoDataIn(*this);
6948 RefPtr<PresShell> presShell = new PresShell(this);
6949 // Note: we don't hold a ref to the shell (it holds a ref to us)
6950 mPresShell = presShell;
6952 if (!mStyleSetFilled) {
6953 FillStyleSet();
6956 presShell->Init(aContext, aViewManager);
6957 if (RefPtr<class HighlightRegistry> highlightRegistry = mHighlightRegistry) {
6958 highlightRegistry->AddHighlightSelectionsToFrameSelection();
6960 // Gaining a shell causes changes in how media queries are evaluated, so
6961 // invalidate that.
6962 aContext->MediaFeatureValuesChanged(
6963 {MediaFeatureChange::kAllChanges},
6964 MediaFeatureChangePropagation::JustThisDocument);
6966 // Make sure to never paint if we belong to an invisible DocShell.
6967 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
6968 if (docShell && docShell->IsInvisible()) {
6969 presShell->SetNeverPainting(true);
6972 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
6973 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
6974 presShell.get(), docShell.get()));
6976 mExternalResourceMap.ShowViewers();
6978 UpdateFrameRequestCallbackSchedulingState();
6980 if (mDocumentL10n) {
6981 // In case we already accumulated mutations,
6982 // we'll trigger the refresh driver now.
6983 mDocumentL10n->OnCreatePresShell();
6986 if (HasAutoFocusCandidates()) {
6987 ScheduleFlushAutoFocusCandidates();
6989 // Now that we have a shell, we might have @font-face rules (the presence of a
6990 // shell may change which rules apply to us). We don't need to do anything
6991 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
6992 // is ready to update we'll flush the font set.
6993 MarkUserFontSetDirty();
6995 // Take the author style disabled state from the top browsing cvontext.
6996 // (PageStyleChild.sys.mjs ensures this is up to date.)
6997 if (BrowsingContext* bc = GetBrowsingContext()) {
6998 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
7001 return presShell.forget();
7004 void Document::UpdateFrameRequestCallbackSchedulingState(
7005 PresShell* aOldPresShell) {
7006 // If this condition changes to depend on some other variable, make sure to
7007 // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
7008 // that variable can change. Also consider if you should change
7009 // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
7010 // condition.
7011 bool shouldBeScheduled =
7012 WouldScheduleFrameRequestCallbacks() && !mFrameRequestManager.IsEmpty();
7013 if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
7014 // nothing to do
7015 return;
7018 PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
7019 MOZ_RELEASE_ASSERT(presShell);
7021 nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
7022 if (shouldBeScheduled) {
7023 rd->ScheduleFrameRequestCallbacks(this);
7024 } else {
7025 rd->RevokeFrameRequestCallbacks(this);
7028 mFrameRequestCallbacksScheduled = shouldBeScheduled;
7031 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
7032 MOZ_ASSERT(aCallbacks.IsEmpty());
7033 mFrameRequestManager.Take(aCallbacks);
7034 // No need to manually remove ourselves from the refresh driver; it will
7035 // handle that part. But we do have to update our state.
7036 mFrameRequestCallbacksScheduled = false;
7039 bool Document::ShouldThrottleFrameRequests() const {
7040 if (mStaticCloneCount > 0) {
7041 // Even if we're not visible, a static clone may be, so run at full speed.
7042 return false;
7045 if (Hidden()) {
7046 // We're not visible (probably in a background tab or the bf cache).
7047 return true;
7050 if (!mPresShell) {
7051 // Can't do anything smarter. We don't run frame requests in documents
7052 // without a pres shell anyways.
7053 return false;
7056 if (!mPresShell->IsActive()) {
7057 // The pres shell is not active (we're an invisible OOP iframe or such), so
7058 // throttle.
7059 return true;
7062 if (mPresShell->IsPaintingSuppressed()) {
7063 // Historically we have throttled frame requests until we've painted at
7064 // least once, so keep doing that.
7065 return true;
7068 if (mPresShell->IsUnderHiddenEmbedderElement()) {
7069 // For display: none and visibility: hidden we always throttle, for
7070 // consistency with OOP iframes.
7071 return true;
7074 Element* el = GetEmbedderElement();
7075 if (!el) {
7076 // If we're not in-process, our refresh driver is throttled separately (via
7077 // PresShell::SetIsActive, so not much more we can do here.
7078 return false;
7081 if (!StaticPrefs::layout_throttle_in_process_iframes()) {
7082 return false;
7085 // Note that because we have to scroll this document into view at least once
7086 // to unthrottle it, we will drop one requestAnimationFrame frame when a
7087 // document that previously wasn't visible scrolls into view. This is
7088 // acceptable / unlikely to be human-perceivable, though we could improve on
7089 // it if needed by adding an intersection margin or something of that sort.
7090 const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
7091 *el->OwnerDoc(), /* aRoot = */ nullptr, /* aRootMargin = */ nullptr);
7092 const IntersectionOutput output =
7093 DOMIntersectionObserver::Intersect(input, *el);
7094 return !output.Intersects();
7097 void Document::DeletePresShell() {
7098 mExternalResourceMap.HideViewers();
7099 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
7100 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
7101 presContext->RefreshDriver()->CancelFlushAutoFocus(this);
7104 // When our shell goes away, request that all our images be immediately
7105 // discarded, so we don't carry around decoded image data for a document we
7106 // no longer intend to paint.
7107 ImageTracker()->RequestDiscardAll();
7109 // Now that we no longer have a shell, we need to forget about any FontFace
7110 // objects for @font-face rules that came from the style set. There's no need
7111 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
7112 // no point on it.
7113 MarkUserFontSetDirty();
7115 if (IsEditingOn()) {
7116 TurnEditingOff();
7119 PresShell* oldPresShell = mPresShell;
7120 mPresShell = nullptr;
7121 UpdateFrameRequestCallbackSchedulingState(oldPresShell);
7123 ClearStaleServoData();
7124 AssertNoStaleServoDataIn(*this);
7126 mStyleSet->ShellDetachedFromDocument();
7127 mStyleSetFilled = false;
7128 mQuirkSheetAdded = false;
7129 mContentEditableSheetAdded = false;
7130 mDesignModeSheetAdded = false;
7133 void Document::DisallowBFCaching(uint32_t aStatus) {
7134 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
7135 if (!mBFCacheDisallowed) {
7136 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
7137 wgc->SendUpdateBFCacheStatus(aStatus, 0);
7140 mBFCacheDisallowed = true;
7143 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
7144 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
7146 if (mPresShell) {
7147 if (aEntry) {
7148 mPresShell->StopObservingRefreshDriver();
7149 } else if (mBFCacheEntry) {
7150 mPresShell->StartObservingRefreshDriver();
7153 mBFCacheEntry = aEntry;
7156 bool Document::RemoveFromBFCacheSync() {
7157 bool removed = false;
7158 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
7159 entry->RemoveFromBFCacheSync();
7160 removed = true;
7161 } else if (!IsCurrentActiveDocument()) {
7162 // In the old bfcache implementation while the new page is loading, but
7163 // before nsIDocumentViewer.show() has been called, the previous page
7164 // doesn't yet have nsIBFCacheEntry. However, the previous page isn't the
7165 // current active document anymore.
7166 DisallowBFCaching();
7167 removed = true;
7170 if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
7171 if (BrowsingContext* bc = GetBrowsingContext()) {
7172 if (bc->IsInBFCache()) {
7173 ContentChild* cc = ContentChild::GetSingleton();
7174 // IPC is asynchronous but the caller is supposed to check the return
7175 // value. The reason for 'Sync' in the method name is that the old
7176 // implementation may run scripts. There is Async variant in
7177 // the old session history implementation for the cases where
7178 // synchronous operation isn't safe.
7179 cc->SendRemoveFromBFCache(bc->Top());
7180 removed = true;
7184 return removed;
7187 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
7188 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
7190 NS_RELEASE(e->mKey);
7191 if (e->mSubDocument) {
7192 e->mSubDocument->SetParentDocument(nullptr);
7193 NS_RELEASE(e->mSubDocument);
7197 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
7198 SubDocMapEntry* e =
7199 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
7201 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
7202 NS_ADDREF(e->mKey);
7204 e->mSubDocument = nullptr;
7207 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
7208 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
7210 if (!aSubDoc) {
7211 // aSubDoc is nullptr, remove the mapping
7213 if (mSubDocuments) {
7214 mSubDocuments->Remove(aElement);
7216 } else {
7217 if (!mSubDocuments) {
7218 // Create a new hashtable
7220 static const PLDHashTableOps hash_table_ops = {
7221 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
7222 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
7224 mSubDocuments =
7225 MakeUnique<PLDHashTable>(&hash_table_ops, sizeof(SubDocMapEntry));
7228 // Add a mapping to the hash table
7229 auto entry =
7230 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
7232 if (!entry) {
7233 return NS_ERROR_OUT_OF_MEMORY;
7236 if (entry->mSubDocument) {
7237 entry->mSubDocument->SetParentDocument(nullptr);
7239 // Release the old sub document
7240 NS_RELEASE(entry->mSubDocument);
7243 entry->mSubDocument = aSubDoc;
7244 NS_ADDREF(entry->mSubDocument);
7246 aSubDoc->SetParentDocument(this);
7249 return NS_OK;
7252 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
7253 if (mSubDocuments && aContent->IsElement()) {
7254 auto entry = static_cast<SubDocMapEntry*>(
7255 mSubDocuments->Search(aContent->AsElement()));
7257 if (entry) {
7258 return entry->mSubDocument;
7262 return nullptr;
7265 Element* Document::GetEmbedderElement() const {
7266 // We check if we're the active document in our BrowsingContext
7267 // by comparing against its document, rather than checking if the
7268 // WindowContext is cached, since mWindow may be null when we're
7269 // called (such as in nsPresContext::Init).
7270 if (BrowsingContext* bc = GetBrowsingContext()) {
7271 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
7274 return nullptr;
7277 Element* Document::GetRootElement() const {
7278 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
7279 ? mCachedRootElement
7280 : GetRootElementInternal();
7283 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
7285 Element* Document::GetRootElementInternal() const {
7286 // We invoke GetRootElement() immediately before the servo traversal, so we
7287 // should always have a cache hit from Servo.
7288 MOZ_ASSERT(NS_IsMainThread());
7290 // Loop backwards because any non-elements, such as doctypes and PIs
7291 // are likely to appear before the root element.
7292 for (nsIContent* child = GetLastChild(); child;
7293 child = child->GetPreviousSibling()) {
7294 if (Element* element = Element::FromNode(child)) {
7295 const_cast<Document*>(this)->mCachedRootElement = element;
7296 return element;
7300 const_cast<Document*>(this)->mCachedRootElement = nullptr;
7301 return nullptr;
7304 void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
7305 bool aNotify, ErrorResult& aRv) {
7306 if (aKid->IsElement() && GetRootElement()) {
7307 NS_WARNING("Inserting root element when we already have one");
7308 aRv.ThrowHierarchyRequestError("There is already a root element.");
7309 return;
7312 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
7315 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
7316 Maybe<mozAutoDocUpdate> updateBatch;
7317 if (aKid->IsElement()) {
7318 updateBatch.emplace(this, aNotify);
7319 // Destroy the link map up front before we mess with the child list.
7320 DestroyElementMaps();
7323 // Preemptively clear mCachedRootElement, since we may be about to remove it
7324 // from our child list, and we don't want to return this maybe-obsolete value
7325 // from any GetRootElement() calls that happen inside of RemoveChildNode().
7326 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
7327 // GetRootElement() calls until after it's removed the child from mChildren.
7328 // Any call before that point would restore this soon-to-be-obsolete cached
7329 // answer, and our clearing here would be fruitless.)
7330 mCachedRootElement = nullptr;
7331 nsINode::RemoveChildNode(aKid, aNotify);
7332 MOZ_ASSERT(mCachedRootElement != aKid,
7333 "Stale pointer in mCachedRootElement, after we tried to clear it "
7334 "(maybe somebody called GetRootElement() too early?)");
7337 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
7338 if (mStyleSetFilled) {
7339 EnsureStyleSet().AddDocStyleSheet(aSheet);
7340 ApplicableStylesChanged();
7344 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
7345 EnsureStyleSet().RecordShadowStyleChange(aShadowRoot);
7346 ApplicableStylesChanged(/* aKnownInShadowTree= */ true);
7349 void Document::ApplicableStylesChanged(bool aKnownInShadowTree) {
7350 // TODO(emilio): if we decide to resolve style in display: none iframes, then
7351 // we need to always track style changes and remove the mStyleSetFilled.
7352 if (!mStyleSetFilled) {
7353 return;
7355 if (!aKnownInShadowTree) {
7356 MarkUserFontSetDirty();
7358 PresShell* ps = GetPresShell();
7359 if (!ps) {
7360 return;
7363 ps->EnsureStyleFlush();
7364 nsPresContext* pc = ps->GetPresContext();
7365 if (!pc) {
7366 return;
7369 if (!aKnownInShadowTree) {
7370 pc->MarkCounterStylesDirty();
7371 pc->MarkFontFeatureValuesDirty();
7372 pc->MarkFontPaletteValuesDirty();
7374 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
7377 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
7378 if (mStyleSetFilled) {
7379 mStyleSet->RemoveStyleSheet(aSheet);
7380 ApplicableStylesChanged();
7384 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
7385 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
7387 if (aSheet.IsApplicable()) {
7388 AddStyleSheetToStyleSets(aSheet);
7392 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
7393 const bool applicable = aSheet.IsApplicable();
7394 // If we're actually in the document style sheet list
7395 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
7396 if (applicable) {
7397 AddStyleSheetToStyleSets(aSheet);
7398 } else {
7399 RemoveStyleSheetFromStyleSets(aSheet);
7404 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
7405 if (!StyleSheetChangeEventsEnabled()) {
7406 return;
7409 StyleSheetApplicableStateChangeEventInit init;
7410 init.mBubbles = true;
7411 init.mCancelable = true;
7412 init.mStylesheet = &aSheet;
7413 init.mApplicable = aSheet.IsApplicable();
7415 RefPtr<StyleSheetApplicableStateChangeEvent> event =
7416 StyleSheetApplicableStateChangeEvent::Constructor(
7417 this, u"StyleSheetApplicableStateChanged"_ns, init);
7418 event->SetTrusted(true);
7419 event->SetTarget(this);
7420 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7421 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7422 asyncDispatcher->PostDOMEvent();
7425 void Document::PostStyleSheetRemovedEvent(StyleSheet& aSheet) {
7426 if (!StyleSheetChangeEventsEnabled()) {
7427 return;
7430 StyleSheetRemovedEventInit init;
7431 init.mBubbles = true;
7432 init.mCancelable = false;
7433 init.mStylesheet = &aSheet;
7435 RefPtr<StyleSheetRemovedEvent> event =
7436 StyleSheetRemovedEvent::Constructor(this, u"StyleSheetRemoved"_ns, init);
7437 event->SetTrusted(true);
7438 event->SetTarget(this);
7439 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7440 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7441 asyncDispatcher->PostDOMEvent();
7444 void Document::PostCustomPropertyRegistered(
7445 const PropertyDefinition& aDefinition) {
7446 if (!StyleSheetChangeEventsEnabled()) {
7447 return;
7450 CSSCustomPropertyRegisteredEventInit init;
7451 init.mBubbles = true;
7452 init.mCancelable = false;
7454 InspectorCSSPropertyDefinition property;
7456 property.mName.Append(aDefinition.mName);
7457 property.mSyntax.Append(aDefinition.mSyntax);
7458 property.mInherits = aDefinition.mInherits;
7459 if (aDefinition.mInitialValue.WasPassed()) {
7460 property.mInitialValue.Append(aDefinition.mInitialValue.Value());
7461 } else {
7462 property.mInitialValue.SetIsVoid(true);
7464 property.mFromJS = true;
7465 init.mPropertyDefinition = property;
7467 RefPtr<CSSCustomPropertyRegisteredEvent> event =
7468 CSSCustomPropertyRegisteredEvent::Constructor(
7469 this, u"csscustompropertyregistered"_ns, init);
7470 event->SetTrusted(true);
7471 event->SetTarget(this);
7472 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7473 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7474 asyncDispatcher->PostDOMEvent();
7477 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
7478 nsIURI* aSheetURI) {
7479 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
7480 bool bEqual;
7481 nsIURI* uri = aSheets[i]->GetSheetURI();
7483 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
7484 return i;
7487 return -1;
7490 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
7491 nsIURI* aSheetURI) {
7492 MOZ_ASSERT(aSheetURI, "null arg");
7494 // Checking if we have loaded this one already.
7495 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
7496 return NS_ERROR_INVALID_ARG;
7498 // Loading the sheet sync.
7499 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
7501 css::SheetParsingMode parsingMode;
7502 switch (aType) {
7503 case Document::eAgentSheet:
7504 parsingMode = css::eAgentSheetFeatures;
7505 break;
7507 case Document::eUserSheet:
7508 parsingMode = css::eUserSheetFeatures;
7509 break;
7511 case Document::eAuthorSheet:
7512 parsingMode = css::eAuthorSheetFeatures;
7513 break;
7515 default:
7516 MOZ_CRASH("impossible value for aType");
7519 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
7520 css::Loader::UseSystemPrincipal::Yes);
7521 if (result.isErr()) {
7522 return result.unwrapErr();
7525 RefPtr<StyleSheet> sheet = result.unwrap();
7527 sheet->SetAssociatedDocumentOrShadowRoot(this);
7528 MOZ_ASSERT(sheet->IsApplicable());
7530 return AddAdditionalStyleSheet(aType, sheet);
7533 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
7534 StyleSheet* aSheet) {
7535 if (mAdditionalSheets[aType].Contains(aSheet)) {
7536 return NS_ERROR_INVALID_ARG;
7539 if (!aSheet->IsApplicable()) {
7540 return NS_ERROR_INVALID_ARG;
7543 mAdditionalSheets[aType].AppendElement(aSheet);
7545 if (mStyleSetFilled) {
7546 EnsureStyleSet().AppendStyleSheet(*aSheet);
7547 ApplicableStylesChanged();
7549 return NS_OK;
7552 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
7553 nsIURI* aSheetURI) {
7554 MOZ_ASSERT(aSheetURI);
7556 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
7558 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
7559 if (i >= 0) {
7560 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
7561 sheets.RemoveElementAt(i);
7563 if (!mIsGoingAway) {
7564 MOZ_ASSERT(sheetRef->IsApplicable());
7565 if (mStyleSetFilled) {
7566 EnsureStyleSet().RemoveStyleSheet(*sheetRef);
7567 ApplicableStylesChanged();
7570 sheetRef->ClearAssociatedDocumentOrShadowRoot();
7574 nsIGlobalObject* Document::GetScopeObject() const {
7575 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
7576 return scope;
7579 DocGroup* Document::GetDocGroupOrCreate() {
7580 if (!mDocGroup && GetBrowsingContext()) {
7581 BrowsingContextGroup* group = GetBrowsingContext()->Group();
7582 MOZ_ASSERT(group);
7584 nsAutoCString docGroupKey;
7585 nsresult rv = mozilla::dom::DocGroup::GetKey(
7586 NodePrincipal(), group->IsPotentiallyCrossOriginIsolated(),
7587 docGroupKey);
7588 if (NS_SUCCEEDED(rv)) {
7589 mDocGroup = group->AddDocument(docGroupKey, this);
7592 return mDocGroup;
7595 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
7596 mScopeObject = do_GetWeakReference(aGlobal);
7597 if (aGlobal) {
7598 mHasHadScriptHandlingObject = true;
7600 nsPIDOMWindowInner* window = aGlobal->GetAsInnerWindow();
7601 if (!window) {
7602 return;
7605 // Same origin data documents should have the same docGroup as their scope
7606 // window.
7607 if (mLoadedAsData && window->GetExtantDoc() &&
7608 window->GetExtantDoc() != this &&
7609 window->GetExtantDoc()->NodePrincipal() == NodePrincipal()) {
7610 DocGroup* docGroup = window->GetExtantDoc()->GetDocGroup();
7612 if (docGroup) {
7613 if (!mDocGroup) {
7614 mDocGroup = docGroup;
7615 mDocGroup->AddDocument(this);
7616 } else {
7617 MOZ_ASSERT(mDocGroup == docGroup,
7618 "Data document has a mismatched doc group?");
7620 #ifdef DEBUG
7621 AssertDocGroupMatchesKey();
7622 #endif
7623 return;
7626 MOZ_ASSERT_UNREACHABLE(
7627 "Scope window doesn't have DocGroup when creating data document?");
7628 // ... but fall through to be safe.
7631 BrowsingContextGroup* browsingContextGroup =
7632 window->GetBrowsingContextGroup();
7634 // We should already have the principal, and now that we have been added
7635 // to a window, we should be able to join a DocGroup!
7636 nsAutoCString docGroupKey;
7637 nsresult rv = mozilla::dom::DocGroup::GetKey(
7638 NodePrincipal(),
7639 browsingContextGroup->IsPotentiallyCrossOriginIsolated(), docGroupKey);
7640 if (mDocGroup) {
7641 if (NS_SUCCEEDED(rv)) {
7642 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
7644 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
7645 browsingContextGroup);
7646 } else {
7647 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
7649 MOZ_ASSERT(mDocGroup);
7652 MOZ_ASSERT_IF(
7653 mNodeInfoManager->GetArenaAllocator(),
7654 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
7658 bool Document::ContainsEMEContent() {
7659 nsPIDOMWindowInner* win = GetInnerWindow();
7660 // Note this case is different from checking just media elements in that
7661 // it covers when we've created MediaKeys but not associated them with a
7662 // media element.
7663 return win && win->HasActiveMediaKeysInstance();
7666 bool Document::ContainsMSEContent() {
7667 bool containsMSE = false;
7668 EnumerateActivityObservers([&containsMSE](nsISupports* aSupports) {
7669 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7670 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7671 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
7672 if (ms) {
7673 containsMSE = true;
7677 return containsMSE;
7680 static void NotifyActivityChangedCallback(nsISupports* aSupports) {
7681 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7682 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7683 mediaElem->NotifyOwnerDocumentActivityChanged();
7685 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
7686 do_QueryInterface(aSupports));
7687 if (objectDocumentActivity) {
7688 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
7689 } else {
7690 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
7691 do_QueryInterface(aSupports));
7692 if (imageLoadingContent) {
7693 auto* ilc =
7694 static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
7695 ilc->NotifyOwnerDocumentActivityChanged();
7700 void Document::NotifyActivityChanged() {
7701 EnumerateActivityObservers(NotifyActivityChangedCallback);
7702 // https://w3c.github.io/screen-wake-lock/#handling-document-loss-of-full-activity
7703 if (!IsActive()) {
7704 UnlockAllWakeLocks(WakeLockType::Screen);
7708 void Document::SetContainer(nsDocShell* aContainer) {
7709 if (aContainer) {
7710 mDocumentContainer = aContainer;
7711 } else {
7712 mDocumentContainer = WeakPtr<nsDocShell>();
7715 mInChromeDocShell =
7716 aContainer && aContainer->GetBrowsingContext()->IsChrome();
7718 NotifyActivityChanged();
7720 // IsTopLevelWindowInactive depends on the docshell, so
7721 // update the cached value now that it's available.
7722 UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false);
7723 if (!aContainer) {
7724 return;
7727 BrowsingContext* context = aContainer->GetBrowsingContext();
7728 MOZ_ASSERT_IF(context && mDocGroup,
7729 context->Group() == mDocGroup->GetBrowsingContextGroup());
7730 if (context && context->IsContent()) {
7731 SetIsTopLevelContentDocument(context->IsTopContent());
7732 SetIsContentDocument(true);
7733 } else {
7734 SetIsTopLevelContentDocument(false);
7735 SetIsContentDocument(false);
7739 nsISupports* Document::GetContainer() const {
7740 return static_cast<nsIDocShell*>(mDocumentContainer);
7743 void Document::SetScriptGlobalObject(
7744 nsIScriptGlobalObject* aScriptGlobalObject) {
7745 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
7746 mAnimationController->IsPausedByType(
7747 SMILTimeContainer::PAUSE_PAGEHIDE |
7748 SMILTimeContainer::PAUSE_BEGIN),
7749 "Clearing window pointer while animations are unpaused");
7751 if (mScriptGlobalObject && !aScriptGlobalObject) {
7752 // We're detaching from the window. We need to grab a pointer to
7753 // our layout history state now.
7754 mLayoutHistoryState = GetLayoutHistoryState();
7756 // Also make sure to remove our onload blocker now if we haven't done it yet
7757 if (mOnloadBlockCount != 0) {
7758 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
7759 if (loadGroup) {
7760 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
7764 if (GetController().isSome()) {
7765 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
7766 loader->ClearCacheForControlledDocument(this);
7769 // We may become controlled again if this document comes back out
7770 // of bfcache. Clear our state to allow that to happen. Only
7771 // clear this flag if we are actually controlled, though, so pages
7772 // that were force reloaded don't become controlled when they
7773 // come out of bfcache.
7774 mMaybeServiceWorkerControlled = false;
7777 if (GetWindowContext()) {
7778 // The document is about to lose its window, so this is a good time to
7779 // send our page use counters, while we still have access to our
7780 // WindowContext.
7782 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
7783 // catches some cases of documents losing their window that don't
7784 // get in here.)
7785 SendPageUseCounters();
7789 // BlockOnload() might be called before mScriptGlobalObject is set.
7790 // We may need to add the blocker once mScriptGlobalObject is set.
7791 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
7793 mScriptGlobalObject = aScriptGlobalObject;
7795 if (needOnloadBlocker) {
7796 EnsureOnloadBlocker();
7799 UpdateFrameRequestCallbackSchedulingState();
7801 if (aScriptGlobalObject) {
7802 // Go back to using the docshell for the layout history state
7803 mLayoutHistoryState = nullptr;
7804 SetScopeObject(aScriptGlobalObject);
7805 mHasHadDefaultView = true;
7807 if (mAllowDNSPrefetch) {
7808 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7809 if (docShell) {
7810 #ifdef DEBUG
7811 nsCOMPtr<nsIWebNavigation> webNav =
7812 do_GetInterface(aScriptGlobalObject);
7813 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
7814 "Unexpected container or script global?");
7815 #endif
7816 bool allowDNSPrefetch;
7817 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
7818 mAllowDNSPrefetch = allowDNSPrefetch;
7822 // If we are set in a window that is already focused we should remember this
7823 // as the time the document gained focus.
7824 if (HasFocus(IgnoreErrors())) {
7825 SetLastFocusTime(TimeStamp::Now());
7829 // Remember the pointer to our window (or lack there of), to avoid
7830 // having to QI every time it's asked for.
7831 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
7832 mWindow = window;
7834 // Now that we know what our window is, we can flush the CSP errors to the
7835 // Web Console. We are flushing all messages that occurred and were stored in
7836 // the queue prior to this point.
7837 if (mCSP) {
7838 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
7841 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
7842 do_QueryInterface(GetChannel());
7843 if (internalChannel) {
7844 nsCOMArray<nsISecurityConsoleMessage> messages;
7845 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
7846 MOZ_ASSERT(NS_SUCCEEDED(rv));
7847 SendToConsole(messages);
7850 // Set our visibility state, but do not fire the event. This is correct
7851 // because either we're coming out of bfcache (in which case IsVisible() will
7852 // still test false at this point and no state change will happen) or we're
7853 // doing the initial document load and don't want to fire the event for this
7854 // change.
7856 // When the visibility is changed, notify it to observers.
7857 // Some observers need the notification, for example HTMLMediaElement uses
7858 // it to update internal media resource allocation.
7859 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
7860 // creation are already done before Document::SetScriptGlobalObject() call.
7861 // MediaDecoder decides whether starting decoding is decided based on
7862 // document's visibility. When the MediaDecoder is created,
7863 // Document::SetScriptGlobalObject() is not yet called and document is
7864 // hidden state. Therefore the MediaDecoder decides that decoding is
7865 // not yet necessary. But soon after Document::SetScriptGlobalObject()
7866 // call, the document becomes not hidden. At the time, MediaDecoder needs
7867 // to know it and needs to start updating decoding.
7868 UpdateVisibilityState(DispatchVisibilityChange::No);
7870 // The global in the template contents owner document should be the same.
7871 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
7872 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
7875 // Tell the script loader about the new global object.
7876 if (mScriptLoader && !IsTemplateContentsOwner()) {
7877 mScriptLoader->SetGlobalObject(mScriptGlobalObject);
7880 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
7881 mScriptGlobalObject && GetChannel()) {
7882 // If we are shift-reloaded, don't associate with a ServiceWorker.
7883 if (mDocumentContainer->IsForceReloading()) {
7884 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
7885 return;
7888 mMaybeServiceWorkerControlled = true;
7892 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
7893 MOZ_ASSERT(!mScriptGlobalObject,
7894 "Do not call this when mScriptGlobalObject is set!");
7895 if (mHasHadDefaultView) {
7896 return nullptr;
7899 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
7900 do_QueryReferent(mScopeObject);
7901 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
7902 if (win) {
7903 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
7904 if (!outer || outer->GetCurrentInnerWindow() != win) {
7905 NS_WARNING("Wrong inner/outer window combination!");
7906 return nullptr;
7909 return scriptHandlingObject;
7911 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
7912 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
7913 "Wrong script object!");
7914 if (aScriptObject) {
7915 SetScopeObject(aScriptObject);
7916 mHasHadDefaultView = false;
7920 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
7921 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
7922 // Let's use mScriptGlobalObject. Even if the document is already removed from
7923 // the docshell, the outer window might be still obtainable from the it.
7924 nsCOMPtr<nsPIDOMWindowOuter> win;
7925 if (mRemovedFromDocShell) {
7926 // The docshell returns the outer window we are done.
7927 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
7928 if (kungFuDeathGrip) {
7929 win = kungFuDeathGrip->GetWindow();
7931 } else {
7932 if (nsCOMPtr<nsPIDOMWindowInner> inner =
7933 do_QueryInterface(mScriptGlobalObject)) {
7934 // mScriptGlobalObject is always the inner window, let's get the outer.
7935 win = inner->GetOuterWindow();
7939 return win;
7942 bool Document::InternalAllowXULXBL() {
7943 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
7944 mAllowXULXBL = eTriTrue;
7945 return true;
7948 mAllowXULXBL = eTriFalse;
7949 return false;
7952 // Note: We don't hold a reference to the document observer; we assume
7953 // that it has a live reference to the document.
7954 void Document::AddObserver(nsIDocumentObserver* aObserver) {
7955 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
7956 "Observer already in the list");
7957 mObservers.AppendElement(aObserver);
7958 AddMutationObserver(aObserver);
7961 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
7962 // If we're in the process of destroying the document (and we're
7963 // informing the observers of the destruction), don't remove the
7964 // observers from the list. This is not a big deal, since we
7965 // don't hold a live reference to the observers.
7966 if (!mInDestructor) {
7967 RemoveMutationObserver(aObserver);
7968 return mObservers.RemoveElement(aObserver);
7971 return mObservers.Contains(aObserver);
7974 void Document::BeginUpdate() {
7975 ++mUpdateNestLevel;
7976 nsContentUtils::AddScriptBlocker();
7977 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
7980 void Document::EndUpdate() {
7981 const bool reset = !mPendingMaybeEditingStateChanged;
7982 mPendingMaybeEditingStateChanged = true;
7984 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
7986 --mUpdateNestLevel;
7988 nsContentUtils::RemoveScriptBlocker();
7990 if (mXULBroadcastManager) {
7991 mXULBroadcastManager->MaybeBroadcast();
7994 if (reset) {
7995 mPendingMaybeEditingStateChanged = false;
7997 MaybeEditingStateChanged();
8000 void Document::BeginLoad() {
8001 if (IsEditingOn()) {
8002 // Reset() blows away all event listeners in the document, and our
8003 // editor relies heavily on those. Midas is turned on, to make it
8004 // work, re-initialize it to give it a chance to add its event
8005 // listeners again.
8007 TurnEditingOff();
8008 EditingStateChanged();
8011 MOZ_ASSERT(!mDidCallBeginLoad);
8012 mDidCallBeginLoad = true;
8014 // Block onload here to prevent having to deal with blocking and
8015 // unblocking it while we know the document is loading.
8016 BlockOnload();
8017 mDidFireDOMContentLoaded = false;
8018 BlockDOMContentLoaded();
8020 if (mScriptLoader) {
8021 mScriptLoader->BeginDeferringScripts();
8024 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
8027 void Document::MozSetImageElement(const nsAString& aImageElementId,
8028 Element* aElement) {
8029 if (aImageElementId.IsEmpty()) return;
8031 // Hold a script blocker while calling SetImageElement since that can call
8032 // out to id-observers
8033 nsAutoScriptBlocker scriptBlocker;
8035 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
8036 if (entry) {
8037 entry->SetImageElement(aElement);
8038 if (entry->IsEmpty()) {
8039 mIdentifierMap.RemoveEntry(entry);
8044 void Document::DispatchContentLoadedEvents() {
8045 // If you add early returns from this method, make sure you're
8046 // calling UnblockOnload properly.
8048 // Unpin references to preloaded images
8049 mPreloadingImages.Clear();
8051 // DOM manipulation after content loaded should not care if the element
8052 // came from the preloader.
8053 mPreloadedPreconnects.Clear();
8055 if (mTiming) {
8056 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
8059 // Dispatch observer notification to notify observers document is interactive.
8060 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
8061 if (os) {
8062 nsIPrincipal* principal = NodePrincipal();
8063 os->NotifyObservers(ToSupports(this),
8064 principal->IsSystemPrincipal()
8065 ? "chrome-document-interactive"
8066 : "content-document-interactive",
8067 nullptr);
8070 // Fire a DOM event notifying listeners that this document has been
8071 // loaded (excluding images and other loads initiated by this
8072 // document).
8073 nsContentUtils::DispatchTrustedEvent(this, this, u"DOMContentLoaded"_ns,
8074 CanBubble::eYes, Cancelable::eNo);
8076 if (auto* const window = GetInnerWindow()) {
8077 const RefPtr<ServiceWorkerContainer> serviceWorker =
8078 window->Navigator()->ServiceWorker();
8080 // This could cause queued messages from a service worker to get
8081 // dispatched on serviceWorker.
8082 serviceWorker->StartMessages();
8085 if (MayStartLayout()) {
8086 MaybeResolveReadyForIdle();
8089 if (mTiming) {
8090 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
8093 // If this document is a [i]frame, fire a DOMFrameContentLoaded
8094 // event on all parent documents notifying that the HTML (excluding
8095 // other external files such as images and stylesheets) in a frame
8096 // has finished loading.
8098 // target_frame is the [i]frame element that will be used as the
8099 // target for the event. It's the [i]frame whose content is done
8100 // loading.
8101 nsCOMPtr<Element> target_frame = GetEmbedderElement();
8103 if (target_frame && target_frame->IsInComposedDoc()) {
8104 nsCOMPtr<Document> parent = target_frame->OwnerDoc();
8105 while (parent) {
8106 RefPtr<Event> event;
8107 if (parent) {
8108 IgnoredErrorResult ignored;
8109 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
8112 if (event) {
8113 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
8115 event->SetTarget(target_frame);
8116 event->SetTrusted(true);
8118 // To dispatch this event we must manually call
8119 // EventDispatcher::Dispatch() on the ancestor document since the
8120 // target is not in the same document, so the event would never reach
8121 // the ancestor document if we used the normal event
8122 // dispatching code.
8124 WidgetEvent* innerEvent = event->WidgetEventPtr();
8125 if (innerEvent) {
8126 nsEventStatus status = nsEventStatus_eIgnore;
8128 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
8129 EventDispatcher::Dispatch(parent, context, innerEvent, event,
8130 &status);
8135 parent = parent->GetInProcessParentDocument();
8139 nsPIDOMWindowInner* inner = GetInnerWindow();
8140 if (inner) {
8141 inner->NoteDOMContentLoaded();
8144 // TODO
8145 if (mMaybeServiceWorkerControlled) {
8146 using mozilla::dom::ServiceWorkerManager;
8147 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
8148 if (swm) {
8149 Maybe<ClientInfo> clientInfo = GetClientInfo();
8150 if (clientInfo.isSome()) {
8151 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
8156 if (mSetCompleteAfterDOMContentLoaded) {
8157 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
8158 mSetCompleteAfterDOMContentLoaded = false;
8161 UnblockOnload(true);
8164 void Document::EndLoad() {
8165 bool turnOnEditing =
8166 mParser && (IsInDesignMode() || mContentEditableCount > 0);
8168 #if defined(DEBUG)
8169 // only assert if nothing stopped the load on purpose
8170 if (!mParserAborted) {
8171 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
8173 #endif
8175 // EndLoad may have been called without a matching call to BeginLoad, in the
8176 // case of a failed parse (for example, due to timeout). In such a case, we
8177 // still want to execute part of this code to do appropriate cleanup, but we
8178 // gate part of it because it is intended to match 1-for-1 with calls to
8179 // BeginLoad. We have an explicit flag bit for this purpose, since it's
8180 // complicated and error prone to derive this condition from other related
8181 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
8183 // Part 1: Code that always executes to cleanup end of parsing, whether
8184 // that parsing was successful or not.
8186 // Drop the ref to our parser, if any, but keep hold of the sink so that we
8187 // can flush it from FlushPendingNotifications as needed. We might have to
8188 // do that to get a StartLayout() to happen.
8189 if (mParser) {
8190 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
8191 mParser = nullptr;
8194 // Update the attributes on the PerformanceNavigationTiming before notifying
8195 // the onload observers.
8196 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
8197 if (RefPtr<Performance> performance = window->GetPerformance()) {
8198 performance->UpdateNavigationTimingEntry();
8202 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
8204 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
8206 if (!mDidCallBeginLoad) {
8207 return;
8209 mDidCallBeginLoad = false;
8211 UnblockDOMContentLoaded();
8213 if (turnOnEditing) {
8214 EditingStateChanged();
8217 if (!GetWindow()) {
8218 // This is a document that's not in a window. For example, this could be an
8219 // XMLHttpRequest responseXML document, or a document created via DOMParser
8220 // or DOMImplementation. We don't reach this code normally for such
8221 // documents (which is not obviously correct), but can reach it via
8222 // document.open()/document.close().
8224 // Such documents don't fire load events, but per spec should set their
8225 // readyState to "complete" when parsing and all loading of subresources is
8226 // done. Parsing is done now, and documents not in a window don't load
8227 // subresources, so just go ahead and mark ourselves as complete.
8228 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
8229 /* updateTimingInformation = */ false);
8231 // Reset mSkipLoadEventAfterClose just in case.
8232 mSkipLoadEventAfterClose = false;
8236 void Document::UnblockDOMContentLoaded() {
8237 MOZ_ASSERT(mBlockDOMContentLoaded);
8238 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
8239 return;
8242 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
8243 ("DOCUMENT %p UnblockDOMContentLoaded", this));
8245 mDidFireDOMContentLoaded = true;
8246 if (PresShell* presShell = GetPresShell()) {
8247 presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
8250 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
8251 if (!mSynchronousDOMContentLoaded) {
8252 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8253 nsCOMPtr<nsIRunnable> ev =
8254 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
8255 &Document::DispatchContentLoadedEvents);
8256 Dispatch(ev.forget());
8257 } else {
8258 DispatchContentLoadedEvents();
8262 void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) {
8263 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
8264 "Someone forgot a scriptblocker");
8265 NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged,
8266 (this, aElement, aStateMask));
8269 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
8270 StyleRuleChangeKind) {
8271 if (aSheet.IsApplicable()) {
8272 ApplicableStylesChanged();
8276 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
8277 if (aRule.IsIncompleteImportRule()) {
8278 return;
8281 if (aSheet.IsApplicable()) {
8282 ApplicableStylesChanged();
8286 void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
8287 if (aSheet.IsApplicable()) {
8288 ApplicableStylesChanged();
8292 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
8293 if (aSheet.IsApplicable()) {
8294 ApplicableStylesChanged();
8298 static Element* GetCustomContentContainer(PresShell* aPresShell) {
8299 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
8300 return nullptr;
8303 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
8306 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
8307 bool aForce, ErrorResult& aRv) {
8308 RefPtr<PresShell> shell = GetPresShell();
8309 if (aForce && !GetCustomContentContainer(shell)) {
8310 FlushPendingNotifications(FlushType::Layout);
8311 shell = GetPresShell();
8314 nsAutoScriptBlocker scriptBlocker;
8316 RefPtr<AnonymousContent> anonContent = AnonymousContent::Create(*this);
8317 if (!anonContent) {
8318 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
8319 return nullptr;
8322 mAnonymousContents.AppendElement(anonContent);
8324 if (RefPtr<Element> container = GetCustomContentContainer(shell)) {
8325 // If the container is empty and we have other anon content we should be
8326 // about to show all the other anonymous content nodes.
8327 if (container->HasChildren() || mAnonymousContents.Length() == 1) {
8328 container->AppendChildTo(anonContent->Host(), true, IgnoreErrors());
8329 if (auto* canvasFrame = shell->GetCanvasFrame()) {
8330 canvasFrame->ShowCustomContentContainer();
8335 return anonContent.forget();
8338 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
8339 PresShell* aPresShell) {
8340 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
8341 if (!container) {
8342 return;
8344 container->RemoveChild(*aAnonContent.Host(), IgnoreErrors());
8347 void Document::RemoveAnonymousContent(AnonymousContent& aContent) {
8348 nsAutoScriptBlocker scriptBlocker;
8350 auto index = mAnonymousContents.IndexOf(&aContent);
8351 if (index == mAnonymousContents.NoIndex) {
8352 return;
8355 mAnonymousContents.RemoveElementAt(index);
8356 RemoveAnonContentFromCanvas(aContent, GetPresShell());
8358 if (mAnonymousContents.IsEmpty() &&
8359 GetCustomContentContainer(GetPresShell())) {
8360 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
8364 Element* Document::GetAnonRootIfInAnonymousContentContainer(
8365 nsINode* aNode) const {
8366 if (!aNode->IsInNativeAnonymousSubtree()) {
8367 return nullptr;
8370 PresShell* presShell = GetPresShell();
8371 if (!presShell || !presShell->GetCanvasFrame()) {
8372 return nullptr;
8375 nsAutoScriptBlocker scriptBlocker;
8376 nsCOMPtr<Element> customContainer =
8377 presShell->GetCanvasFrame()->GetCustomContentContainer();
8378 if (!customContainer) {
8379 return nullptr;
8382 // An arbitrary number of elements can be inserted as children of the custom
8383 // container frame. We want the one that was added that contains aNode, so
8384 // we need to keep track of the last child separately using |child| here.
8385 nsINode* child = aNode;
8386 nsINode* parent = aNode->GetParentNode();
8387 while (parent && parent->IsInNativeAnonymousSubtree()) {
8388 if (parent == customContainer) {
8389 return Element::FromNode(child);
8391 child = parent;
8392 parent = child->GetParentNode();
8394 return nullptr;
8397 Maybe<ClientInfo> Document::GetClientInfo() const {
8398 if (const Document* orig = GetOriginalDocument()) {
8399 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
8400 return info;
8404 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8405 return inner->GetClientInfo();
8408 return Maybe<ClientInfo>();
8411 Maybe<ClientState> Document::GetClientState() const {
8412 if (const Document* orig = GetOriginalDocument()) {
8413 if (Maybe<ClientState> state = orig->GetClientState()) {
8414 return state;
8418 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8419 return inner->GetClientState();
8422 return Maybe<ClientState>();
8425 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
8426 if (const Document* orig = GetOriginalDocument()) {
8427 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
8428 return controller;
8432 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8433 return inner->GetController();
8436 return Maybe<ServiceWorkerDescriptor>();
8440 // Document interface
8442 DocumentType* Document::GetDoctype() const {
8443 for (nsIContent* child = GetFirstChild(); child;
8444 child = child->GetNextSibling()) {
8445 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
8446 return static_cast<DocumentType*>(child);
8449 return nullptr;
8452 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
8453 if (!mDOMImplementation) {
8454 nsCOMPtr<nsIURI> uri;
8455 NS_NewURI(getter_AddRefs(uri), "about:blank");
8456 if (!uri) {
8457 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
8458 return nullptr;
8460 bool hasHadScriptObject = true;
8461 nsIScriptGlobalObject* scriptObject =
8462 GetScriptHandlingObject(hasHadScriptObject);
8463 if (!scriptObject && hasHadScriptObject) {
8464 rv.Throw(NS_ERROR_UNEXPECTED);
8465 return nullptr;
8467 mDOMImplementation = new DOMImplementation(
8468 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
8471 return mDOMImplementation;
8474 bool IsLowercaseASCII(const nsAString& aValue) {
8475 int32_t len = aValue.Length();
8476 for (int32_t i = 0; i < len; ++i) {
8477 char16_t c = aValue[i];
8478 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
8479 return false;
8482 return true;
8485 already_AddRefed<Element> Document::CreateElement(
8486 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8487 ErrorResult& rv) {
8488 rv = nsContentUtils::CheckQName(aTagName, false);
8489 if (rv.Failed()) {
8490 return nullptr;
8493 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
8494 nsAutoString lcTagName;
8495 if (needsLowercase) {
8496 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
8499 const nsString* is = nullptr;
8500 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
8501 if (aOptions.IsElementCreationOptions()) {
8502 const ElementCreationOptions& options =
8503 aOptions.GetAsElementCreationOptions();
8505 if (options.mIs.WasPassed()) {
8506 is = &options.mIs.Value();
8509 // Check 'pseudo' and throw an exception if it's not one allowed
8510 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
8511 if (options.mPseudo.WasPassed()) {
8512 Maybe<PseudoStyleType> type =
8513 nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
8514 if (!type || *type == PseudoStyleType::NotPseudo ||
8515 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
8516 rv.ThrowNotSupportedError("Invalid pseudo-element");
8517 return nullptr;
8519 pseudoType = *type;
8523 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
8524 nullptr, mDefaultElementType, is);
8526 if (pseudoType != PseudoStyleType::NotPseudo) {
8527 elem->SetPseudoElementType(pseudoType);
8530 return elem.forget();
8533 already_AddRefed<Element> Document::CreateElementNS(
8534 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8535 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
8536 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8537 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8538 mNodeInfoManager, ELEMENT_NODE,
8539 getter_AddRefs(nodeInfo));
8540 if (rv.Failed()) {
8541 return nullptr;
8544 const nsString* is = nullptr;
8545 if (aOptions.IsElementCreationOptions()) {
8546 const ElementCreationOptions& options =
8547 aOptions.GetAsElementCreationOptions();
8548 if (options.mIs.WasPassed()) {
8549 is = &options.mIs.Value();
8553 nsCOMPtr<Element> element;
8554 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
8555 NOT_FROM_PARSER, is);
8556 if (rv.Failed()) {
8557 return nullptr;
8560 return element.forget();
8563 already_AddRefed<Element> Document::CreateXULElement(
8564 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8565 ErrorResult& aRv) {
8566 aRv = nsContentUtils::CheckQName(aTagName, false);
8567 if (aRv.Failed()) {
8568 return nullptr;
8571 const nsString* is = nullptr;
8572 if (aOptions.IsElementCreationOptions()) {
8573 const ElementCreationOptions& options =
8574 aOptions.GetAsElementCreationOptions();
8575 if (options.mIs.WasPassed()) {
8576 is = &options.mIs.Value();
8580 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
8581 if (!elem) {
8582 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
8583 return nullptr;
8585 return elem.forget();
8588 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
8589 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8590 return text.forget();
8593 already_AddRefed<nsTextNode> Document::CreateTextNode(
8594 const nsAString& aData) const {
8595 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8596 // Don't notify; this node is still being created.
8597 text->SetText(aData, false);
8598 return text.forget();
8601 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
8602 RefPtr<DocumentFragment> frag =
8603 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
8604 return frag.forget();
8607 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
8608 already_AddRefed<dom::Comment> Document::CreateComment(
8609 const nsAString& aData) const {
8610 RefPtr<dom::Comment> comment =
8611 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
8613 // Don't notify; this node is still being created.
8614 comment->SetText(aData, false);
8615 return comment.forget();
8618 already_AddRefed<CDATASection> Document::CreateCDATASection(
8619 const nsAString& aData, ErrorResult& rv) {
8620 if (IsHTMLDocument()) {
8621 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8622 return nullptr;
8625 if (FindInReadable(u"]]>"_ns, aData)) {
8626 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8627 return nullptr;
8630 RefPtr<CDATASection> cdata =
8631 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
8633 // Don't notify; this node is still being created.
8634 cdata->SetText(aData, false);
8636 return cdata.forget();
8639 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
8640 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
8641 nsresult res = nsContentUtils::CheckQName(aTarget, false);
8642 if (NS_FAILED(res)) {
8643 rv.Throw(res);
8644 return nullptr;
8647 if (FindInReadable(u"?>"_ns, aData)) {
8648 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8649 return nullptr;
8652 RefPtr<ProcessingInstruction> pi =
8653 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
8655 return pi.forget();
8658 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
8659 ErrorResult& rv) {
8660 if (!mNodeInfoManager) {
8661 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8662 return nullptr;
8665 nsresult res = nsContentUtils::CheckQName(aName, false);
8666 if (NS_FAILED(res)) {
8667 rv.Throw(res);
8668 return nullptr;
8671 nsAutoString name;
8672 if (IsHTMLDocument()) {
8673 nsContentUtils::ASCIIToLower(aName, name);
8674 } else {
8675 name = aName;
8678 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8679 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
8680 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
8681 if (NS_FAILED(res)) {
8682 rv.Throw(res);
8683 return nullptr;
8686 RefPtr<Attr> attribute =
8687 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8688 return attribute.forget();
8691 already_AddRefed<Attr> Document::CreateAttributeNS(
8692 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8693 ErrorResult& rv) {
8694 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8695 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8696 mNodeInfoManager, ATTRIBUTE_NODE,
8697 getter_AddRefs(nodeInfo));
8698 if (rv.Failed()) {
8699 return nullptr;
8702 RefPtr<Attr> attribute =
8703 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8704 return attribute.forget();
8707 void Document::ScheduleForPresAttrEvaluation(Element* aElement) {
8708 MOZ_ASSERT(aElement->IsInComposedDoc());
8709 DebugOnly<bool> inserted = mLazyPresElements.EnsureInserted(aElement);
8710 MOZ_ASSERT(inserted);
8711 if (aElement->HasServoData()) {
8712 // TODO(emilio): RESTYLE_SELF is too strong, there should be no need to
8713 // re-selector-match, but right now this is needed to pick up the new mapped
8714 // attributes. We need something like RESTYLE_STYLE_ATTRIBUTE but for mapped
8715 // attributes.
8716 nsLayoutUtils::PostRestyleEvent(aElement, RestyleHint::RESTYLE_SELF,
8717 nsChangeHint(0));
8721 void Document::UnscheduleForPresAttrEvaluation(Element* aElement) {
8722 mLazyPresElements.Remove(aElement);
8725 void Document::DoResolveScheduledPresAttrs() {
8726 MOZ_ASSERT(!mLazyPresElements.IsEmpty());
8727 for (Element* el : mLazyPresElements) {
8728 MOZ_ASSERT(el->IsInComposedDoc(),
8729 "Un-schedule when removing from the document");
8730 MOZ_ASSERT(el->IsPendingMappedAttributeEvaluation());
8731 if (auto* svg = SVGElement::FromNode(el)) {
8732 // SVG does its own (very similar) thing, for now at least.
8733 svg->UpdateMappedDeclarationBlock();
8734 } else {
8735 MappedDeclarationsBuilder builder(*el, *this,
8736 el->GetMappedAttributeStyle());
8737 auto function = el->GetAttributeMappingFunction();
8738 function(builder);
8739 el->SetMappedDeclarationBlock(builder.TakeDeclarationBlock());
8741 MOZ_ASSERT(!el->IsPendingMappedAttributeEvaluation());
8743 mLazyPresElements.Clear();
8746 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
8747 const {
8748 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
8750 for (const nsWeakPtr& weakNode : mBlockedNodesByClassifier) {
8751 if (nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode)) {
8752 // Consider only nodes to which we have managed to get strong references.
8753 // Coping with nullptrs since it's expected for nodes to disappear when
8754 // nobody else is referring to them.
8755 list->AppendElement(node);
8759 return list.forget();
8762 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
8763 aSheetSet.Truncate();
8765 // Look through our sheets, find the selected set title
8766 size_t count = SheetCount();
8767 nsAutoString title;
8768 for (size_t index = 0; index < count; index++) {
8769 StyleSheet* sheet = SheetAt(index);
8770 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8772 if (sheet->Disabled()) {
8773 // Disabled sheets don't affect the currently selected set
8774 continue;
8777 sheet->GetTitle(title);
8779 if (aSheetSet.IsEmpty()) {
8780 aSheetSet = title;
8781 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
8782 // Sheets from multiple sets enabled; return null string, per spec.
8783 SetDOMStringToNull(aSheetSet);
8784 return;
8789 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
8790 if (DOMStringIsNull(aSheetSet)) {
8791 return;
8794 // Must update mLastStyleSheetSet before doing anything else with stylesheets
8795 // or CSSLoaders.
8796 mLastStyleSheetSet = aSheetSet;
8797 EnableStyleSheetsForSetInternal(aSheetSet, true);
8800 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
8801 mPreferredStyleSheetSet = aSheetSet;
8802 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
8803 // spec.
8804 if (DOMStringIsNull(mLastStyleSheetSet)) {
8805 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
8806 // per spec. The idea here is that we're changing our preferred set and
8807 // that shouldn't change the value of lastStyleSheetSet. Also, we're
8808 // using the Internal version so we can update the CSSLoader and not have
8809 // to worry about null strings.
8810 EnableStyleSheetsForSetInternal(aSheetSet, true);
8814 DOMStringList* Document::StyleSheetSets() {
8815 if (!mStyleSheetSetList) {
8816 mStyleSheetSetList = new DOMStyleSheetSetList(this);
8818 return mStyleSheetSetList;
8821 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
8822 // Per spec, passing in null is a no-op.
8823 if (!DOMStringIsNull(aSheetSet)) {
8824 // Note: must make sure to not change the CSSLoader's preferred sheet --
8825 // that value should be equal to either our lastStyleSheetSet (if that's
8826 // non-null) or to our preferredStyleSheetSet. And this method doesn't
8827 // change either of those.
8828 EnableStyleSheetsForSetInternal(aSheetSet, false);
8832 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
8833 bool aUpdateCSSLoader) {
8834 size_t count = SheetCount();
8835 nsAutoString title;
8836 for (size_t index = 0; index < count; index++) {
8837 StyleSheet* sheet = SheetAt(index);
8838 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8840 sheet->GetTitle(title);
8841 if (!title.IsEmpty()) {
8842 sheet->SetEnabled(title.Equals(aSheetSet));
8845 if (aUpdateCSSLoader) {
8846 CSSLoader()->DocumentStyleSheetSetChanged();
8848 if (EnsureStyleSet().StyleSheetsHaveChanged()) {
8849 ApplicableStylesChanged();
8853 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
8854 nsAutoCString charset;
8855 GetDocumentCharacterSet()->Name(charset);
8856 CopyASCIItoUTF16(charset, aCharacterSet);
8859 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
8860 ErrorResult& rv) const {
8861 nsINode* imported = &aNode;
8863 switch (imported->NodeType()) {
8864 case DOCUMENT_NODE: {
8865 break;
8867 case DOCUMENT_FRAGMENT_NODE:
8868 case ATTRIBUTE_NODE:
8869 case ELEMENT_NODE:
8870 case PROCESSING_INSTRUCTION_NODE:
8871 case TEXT_NODE:
8872 case CDATA_SECTION_NODE:
8873 case COMMENT_NODE:
8874 case DOCUMENT_TYPE_NODE: {
8875 return imported->Clone(aDeep, mNodeInfoManager, rv);
8877 default: {
8878 NS_WARNING("Don't know how to clone this nodetype for importNode.");
8882 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8883 return nullptr;
8886 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
8887 return nsRange::Create(this, 0, this, 0, rv);
8890 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
8891 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
8892 ErrorResult& rv) const {
8893 RefPtr<NodeIterator> iterator =
8894 new NodeIterator(&aRoot, aWhatToShow, aFilter);
8895 return iterator.forget();
8898 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
8899 uint32_t aWhatToShow,
8900 NodeFilter* aFilter,
8901 ErrorResult& rv) const {
8902 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
8903 return walker.forget();
8906 already_AddRefed<Location> Document::GetLocation() const {
8907 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
8909 if (!w) {
8910 return nullptr;
8913 return do_AddRef(w->Location());
8916 already_AddRefed<nsIURI> Document::GetDomainURI() {
8917 nsIPrincipal* principal = NodePrincipal();
8919 nsCOMPtr<nsIURI> uri;
8920 principal->GetDomain(getter_AddRefs(uri));
8921 if (uri) {
8922 return uri.forget();
8924 auto* basePrin = BasePrincipal::Cast(principal);
8925 basePrin->GetURI(getter_AddRefs(uri));
8926 return uri.forget();
8929 void Document::GetDomain(nsAString& aDomain) {
8930 nsCOMPtr<nsIURI> uri = GetDomainURI();
8932 if (!uri) {
8933 aDomain.Truncate();
8934 return;
8937 nsAutoCString hostName;
8938 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
8939 if (NS_SUCCEEDED(rv)) {
8940 CopyUTF8toUTF16(hostName, aDomain);
8941 } else {
8942 // If we can't get the host from the URI (e.g. about:, javascript:,
8943 // etc), just return an empty string.
8944 aDomain.Truncate();
8948 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
8949 if (!GetBrowsingContext()) {
8950 // If our browsing context is null; disallow setting domain
8951 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8952 return;
8955 if (mSandboxFlags & SANDBOXED_DOMAIN) {
8956 // We're sandboxed; disallow setting domain
8957 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8958 return;
8961 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
8962 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8963 return;
8966 if (aDomain.IsEmpty()) {
8967 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8968 return;
8971 nsCOMPtr<nsIURI> uri = GetDomainURI();
8972 if (!uri) {
8973 rv.Throw(NS_ERROR_FAILURE);
8974 return;
8977 // Check new domain - must be a superdomain of the current host
8978 // For example, a page from foo.bar.com may set domain to bar.com,
8979 // but not to ar.com, baz.com, or fi.foo.bar.com.
8981 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
8982 if (!newURI) {
8983 // Error: illegal domain
8984 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8985 return;
8988 if (GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated()) {
8989 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
8990 return;
8993 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
8994 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
8995 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
8996 wgc->SendSetDocumentDomain(WrapNotNull(newURI));
9000 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
9001 const nsACString& aHostString) {
9002 if (aHostString.IsEmpty()) {
9003 return nullptr;
9006 // Create new URI
9007 nsCOMPtr<nsIURI> uri = GetDomainURI();
9008 if (!uri) {
9009 return nullptr;
9012 nsresult rv;
9013 rv = NS_MutateURI(uri)
9014 .SetUserPass(""_ns)
9015 .SetPort(-1) // we want to reset the port number if needed.
9016 .SetHostPort(aHostString)
9017 .Finalize(uri);
9018 if (NS_FAILED(rv)) {
9019 return nullptr;
9022 return uri.forget();
9025 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
9026 const nsAString& aNewDomain, nsIURI* aOrigHost) {
9027 if (NS_WARN_IF(!aOrigHost)) {
9028 return nullptr;
9031 nsCOMPtr<nsIURI> newURI =
9032 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
9033 if (!newURI) {
9034 // Error: failed to parse input domain
9035 return nullptr;
9038 if (!IsValidDomain(aOrigHost, newURI)) {
9039 // Error: illegal domain
9040 return nullptr;
9043 nsAutoCString domain;
9044 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
9045 return nullptr;
9048 return CreateInheritingURIForHost(domain);
9051 /* static */
9052 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
9053 // Check new domain - must be a superdomain of the current host
9054 // For example, a page from foo.bar.com may set domain to bar.com,
9055 // but not to ar.com, baz.com, or fi.foo.bar.com.
9056 nsAutoCString current;
9057 nsAutoCString domain;
9058 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
9059 current.Truncate();
9061 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
9062 domain.Truncate();
9065 bool ok = current.Equals(domain);
9066 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
9067 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
9068 // We're golden if the new domain is the current page's base domain or a
9069 // subdomain of it.
9070 nsCOMPtr<nsIEffectiveTLDService> tldService =
9071 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
9072 if (!tldService) {
9073 return false;
9076 nsAutoCString currentBaseDomain;
9077 ok = NS_SUCCEEDED(
9078 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
9079 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
9080 (domain.Length() >= currentBaseDomain.Length()),
9081 "uh-oh! slight optimization wasn't valid somehow!");
9082 ok = ok && domain.Length() >= currentBaseDomain.Length();
9085 return ok;
9088 Element* Document::GetHtmlElement() const {
9089 Element* rootElement = GetRootElement();
9090 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
9091 return rootElement;
9092 return nullptr;
9095 Element* Document::GetHtmlChildElement(nsAtom* aTag) {
9096 Element* html = GetHtmlElement();
9097 if (!html) return nullptr;
9099 // Look for the element with aTag inside html. This needs to run
9100 // forwards to find the first such element.
9101 for (nsIContent* child = html->GetFirstChild(); child;
9102 child = child->GetNextSibling()) {
9103 if (child->IsHTMLElement(aTag)) return child->AsElement();
9105 return nullptr;
9108 nsGenericHTMLElement* Document::GetBody() {
9109 Element* html = GetHtmlElement();
9110 if (!html) {
9111 return nullptr;
9114 for (nsIContent* child = html->GetFirstChild(); child;
9115 child = child->GetNextSibling()) {
9116 if (child->IsHTMLElement(nsGkAtoms::body) ||
9117 child->IsHTMLElement(nsGkAtoms::frameset)) {
9118 return static_cast<nsGenericHTMLElement*>(child);
9122 return nullptr;
9125 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
9126 nsCOMPtr<Element> root = GetRootElement();
9128 // The body element must be either a body tag or a frameset tag. And we must
9129 // have a root element to be able to add kids to it.
9130 if (!newBody ||
9131 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
9132 rv.ThrowHierarchyRequestError(
9133 "The new body must be either a body tag or frameset tag.");
9134 return;
9137 if (!root) {
9138 rv.ThrowHierarchyRequestError("No root element.");
9139 return;
9142 // Use DOM methods so that we pass through the appropriate security checks.
9143 nsCOMPtr<Element> currentBody = GetBody();
9144 if (currentBody) {
9145 root->ReplaceChild(*newBody, *currentBody, rv);
9146 } else {
9147 root->AppendChild(*newBody, rv);
9151 HTMLSharedElement* Document::GetHead() {
9152 return static_cast<HTMLSharedElement*>(GetHeadElement());
9155 Element* Document::GetTitleElement() {
9156 // mMayHaveTitleElement will have been set to true if any HTML or SVG
9157 // <title> element has been bound to this document. So if it's false,
9158 // we know there is nothing to do here. This avoids us having to search
9159 // the whole DOM if someone calls document.title on a large document
9160 // without a title.
9161 if (!mMayHaveTitleElement) {
9162 return nullptr;
9165 Element* root = GetRootElement();
9166 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
9167 // In SVG, the document's title must be a child
9168 for (nsIContent* child = root->GetFirstChild(); child;
9169 child = child->GetNextSibling()) {
9170 if (child->IsSVGElement(nsGkAtoms::title)) {
9171 return child->AsElement();
9174 return nullptr;
9177 // We check the HTML namespace even for non-HTML documents, except SVG. This
9178 // matches the spec and the behavior of all tested browsers.
9179 for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
9180 if (node->IsHTMLElement(nsGkAtoms::title)) {
9181 return node->AsElement();
9184 return nullptr;
9187 void Document::GetTitle(nsAString& aTitle) {
9188 aTitle.Truncate();
9190 Element* rootElement = GetRootElement();
9191 if (!rootElement) {
9192 return;
9195 if (rootElement->IsXULElement()) {
9196 rootElement->GetAttr(nsGkAtoms::title, aTitle);
9197 } else if (Element* title = GetTitleElement()) {
9198 nsContentUtils::GetNodeTextContent(title, false, aTitle);
9199 } else {
9200 return;
9203 aTitle.CompressWhitespace();
9206 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
9207 Element* rootElement = GetRootElement();
9208 if (!rootElement) {
9209 return;
9212 if (rootElement->IsXULElement()) {
9213 aRv =
9214 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
9215 return;
9218 Maybe<mozAutoDocUpdate> updateBatch;
9219 nsCOMPtr<Element> title = GetTitleElement();
9220 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
9221 if (!title) {
9222 // Batch updates so that mutation events don't change "the title
9223 // element" under us
9224 updateBatch.emplace(this, true);
9225 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
9226 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
9227 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
9228 NOT_FROM_PARSER);
9229 if (!title) {
9230 return;
9232 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
9233 IgnoreErrors());
9235 } else if (rootElement->IsHTMLElement()) {
9236 if (!title) {
9237 // Batch updates so that mutation events don't change "the title
9238 // element" under us
9239 updateBatch.emplace(this, true);
9240 Element* head = GetHeadElement();
9241 if (!head) {
9242 return;
9245 RefPtr<mozilla::dom::NodeInfo> titleInfo;
9246 titleInfo = mNodeInfoManager->GetNodeInfo(
9247 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
9248 title = NS_NewHTMLTitleElement(titleInfo.forget());
9249 if (!title) {
9250 return;
9253 head->AppendChildTo(title, true, IgnoreErrors());
9255 } else {
9256 return;
9259 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
9262 class Document::TitleChangeEvent final : public Runnable {
9263 public:
9264 explicit TitleChangeEvent(Document* aDoc)
9265 : Runnable("Document::TitleChangeEvent"),
9266 mDoc(aDoc),
9267 mBlockOnload(aDoc->IsInChromeDocShell()) {
9268 if (mBlockOnload) {
9269 mDoc->BlockOnload();
9273 NS_IMETHOD Run() final {
9274 if (!mDoc) {
9275 return NS_OK;
9277 const RefPtr<Document> doc = mDoc;
9278 const bool blockOnload = mBlockOnload;
9279 mDoc = nullptr;
9280 doc->DoNotifyPossibleTitleChange();
9281 if (blockOnload) {
9282 doc->UnblockOnload(/* aFireSync = */ true);
9284 return NS_OK;
9287 void Revoke() {
9288 if (mDoc) {
9289 if (mBlockOnload) {
9290 mDoc->UnblockOnload(/* aFireSync = */ false);
9292 mDoc = nullptr;
9296 private:
9297 // Weak, caller is responsible for calling Revoke() when needed.
9298 Document* mDoc;
9299 // title changes should block the load event on chrome docshells, so that the
9300 // window title is consistently set by the time the top window is displayed.
9301 // Otherwise, some window manager integrations don't work properly,
9302 // see bug 1874766.
9303 const bool mBlockOnload = false;
9306 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
9307 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
9308 "Setting a title while unlinking or destroying the element?");
9309 if (mInUnlinkOrDeletion) {
9310 return;
9313 if (aBoundTitleElement) {
9314 mMayHaveTitleElement = true;
9317 if (mPendingTitleChangeEvent.IsPending()) {
9318 return;
9321 MOZ_RELEASE_ASSERT(NS_IsMainThread());
9322 RefPtr<TitleChangeEvent> event = new TitleChangeEvent(this);
9323 if (NS_WARN_IF(NS_FAILED(Dispatch(do_AddRef(event))))) {
9324 event->Revoke();
9325 return;
9327 mPendingTitleChangeEvent = std::move(event);
9330 void Document::DoNotifyPossibleTitleChange() {
9331 if (!mPendingTitleChangeEvent.IsPending()) {
9332 return;
9334 // Make sure the pending runnable method is cleared.
9335 mPendingTitleChangeEvent.Revoke();
9336 mHaveFiredTitleChange = true;
9338 nsAutoString title;
9339 GetTitle(title);
9341 if (RefPtr<PresShell> presShell = GetPresShell()) {
9342 nsCOMPtr<nsISupports> container =
9343 presShell->GetPresContext()->GetContainerWeak();
9344 if (container) {
9345 if (nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container)) {
9346 docShellWin->SetTitle(title);
9351 if (WindowGlobalChild* child = GetWindowGlobalChild()) {
9352 child->SendUpdateDocumentTitle(title);
9355 // Fire a DOM event for the title change.
9356 nsContentUtils::DispatchChromeEvent(this, this, u"DOMTitleChanged"_ns,
9357 CanBubble::eYes, Cancelable::eYes);
9359 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
9360 if (obs) {
9361 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
9365 already_AddRefed<MediaQueryList> Document::MatchMedia(
9366 const nsACString& aMediaQueryList, CallerType aCallerType) {
9367 RefPtr<MediaQueryList> result =
9368 new MediaQueryList(this, aMediaQueryList, aCallerType);
9370 mDOMMediaQueryLists.insertBack(result);
9372 return result.forget();
9375 void Document::SetMayStartLayout(bool aMayStartLayout) {
9376 mMayStartLayout = aMayStartLayout;
9377 if (MayStartLayout()) {
9378 // Before starting layout, check whether we're a toplevel chrome
9379 // window. If we are, setup some state so that we don't have to restyle
9380 // the whole tree after StartLayout.
9381 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
9382 // We're the chrome document!
9383 win->BeforeStartLayout();
9385 ReadyState state = GetReadyStateEnum();
9386 if (state >= READYSTATE_INTERACTIVE) {
9387 // DOMContentLoaded has fired already.
9388 MaybeResolveReadyForIdle();
9392 MaybeEditingStateChanged();
9395 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
9396 mInitializableFrameLoaders.RemoveElement(aLoader);
9397 // Don't even try to initialize.
9398 if (mInDestructor) {
9399 NS_WARNING(
9400 "Trying to initialize a frame loader while"
9401 "document is being deleted");
9402 return NS_ERROR_FAILURE;
9405 mInitializableFrameLoaders.AppendElement(aLoader);
9406 if (!mFrameLoaderRunner) {
9407 mFrameLoaderRunner =
9408 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9409 &Document::MaybeInitializeFinalizeFrameLoaders);
9410 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9411 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9413 return NS_OK;
9416 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
9417 nsIRunnable* aFinalizer) {
9418 mInitializableFrameLoaders.RemoveElement(aLoader);
9419 if (mInDestructor) {
9420 return NS_ERROR_FAILURE;
9423 LogRunnable::LogDispatch(aFinalizer);
9424 mFrameLoaderFinalizers.AppendElement(aFinalizer);
9425 if (!mFrameLoaderRunner) {
9426 mFrameLoaderRunner =
9427 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9428 &Document::MaybeInitializeFinalizeFrameLoaders);
9429 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9430 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9432 return NS_OK;
9435 void Document::MaybeInitializeFinalizeFrameLoaders() {
9436 if (mDelayFrameLoaderInitialization) {
9437 // This method will be recalled when !mDelayFrameLoaderInitialization.
9438 mFrameLoaderRunner = nullptr;
9439 return;
9442 // We're not in an update, but it is not safe to run scripts, so
9443 // postpone frameloader initialization and finalization.
9444 if (!nsContentUtils::IsSafeToRunScript()) {
9445 if (!mInDestructor && !mFrameLoaderRunner &&
9446 (mInitializableFrameLoaders.Length() ||
9447 mFrameLoaderFinalizers.Length())) {
9448 mFrameLoaderRunner = NewRunnableMethod(
9449 "Document::MaybeInitializeFinalizeFrameLoaders", this,
9450 &Document::MaybeInitializeFinalizeFrameLoaders);
9451 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9453 return;
9455 mFrameLoaderRunner = nullptr;
9457 // Don't use a temporary array for mInitializableFrameLoaders, because
9458 // loading a frame may cause some other frameloader to be removed from the
9459 // array. But be careful to keep the loader alive when starting the load!
9460 while (mInitializableFrameLoaders.Length()) {
9461 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
9462 mInitializableFrameLoaders.RemoveElementAt(0);
9463 NS_ASSERTION(loader, "null frameloader in the array?");
9464 loader->ReallyStartLoading();
9467 uint32_t length = mFrameLoaderFinalizers.Length();
9468 if (length > 0) {
9469 nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
9470 std::move(mFrameLoaderFinalizers);
9471 for (uint32_t i = 0; i < length; ++i) {
9472 LogRunnable::Run run(finalizers[i]);
9473 finalizers[i]->Run();
9478 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
9479 uint32_t length = mInitializableFrameLoaders.Length();
9480 for (uint32_t i = 0; i < length; ++i) {
9481 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
9482 mInitializableFrameLoaders.RemoveElementAt(i);
9483 return;
9488 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
9489 mPrototypeDocument = aPrototype;
9490 mSynchronousDOMContentLoaded = true;
9493 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
9494 return GetPermissionDelegateHandler();
9497 Document* Document::RequestExternalResource(
9498 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
9499 ExternalResourceLoad** aPendingLoad) {
9500 MOZ_ASSERT(aURI, "Must have a URI");
9501 MOZ_ASSERT(aRequestingNode, "Must have a node");
9502 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
9503 if (mDisplayDocument) {
9504 return mDisplayDocument->RequestExternalResource(
9505 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
9508 return mExternalResourceMap.RequestResource(
9509 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
9512 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
9513 mExternalResourceMap.EnumerateResources(aCallback);
9516 SMILAnimationController* Document::GetAnimationController() {
9517 // We create the animation controller lazily because most documents won't want
9518 // one and only SVG documents and the like will call this
9519 if (mAnimationController) return mAnimationController;
9520 // Refuse to create an Animation Controller for data documents.
9521 if (mLoadedAsData) return nullptr;
9523 mAnimationController = new SMILAnimationController(this);
9525 // If there's a presContext then check the animation mode and pause if
9526 // necessary.
9527 nsPresContext* context = GetPresContext();
9528 if (mAnimationController && context &&
9529 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
9530 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
9533 // If we're hidden (or being hidden), notify the newly-created animation
9534 // controller. (Skip this check for SVG-as-an-image documents, though,
9535 // because they don't get OnPageShow / OnPageHide calls).
9536 if (!mIsShowing && !mIsBeingUsedAsImage) {
9537 mAnimationController->OnPageHide();
9540 return mAnimationController;
9543 ScrollTimelineAnimationTracker*
9544 Document::GetOrCreateScrollTimelineAnimationTracker() {
9545 if (!mScrollTimelineAnimationTracker) {
9546 mScrollTimelineAnimationTracker = new ScrollTimelineAnimationTracker(this);
9549 return mScrollTimelineAnimationTracker;
9553 * Retrieve the "direction" property of the document.
9555 * @lina 01/09/2001
9557 void Document::GetDir(nsAString& aDirection) const {
9558 aDirection.Truncate();
9559 Element* rootElement = GetHtmlElement();
9560 if (rootElement) {
9561 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
9566 * Set the "direction" property of the document.
9568 * @lina 01/09/2001
9570 void Document::SetDir(const nsAString& aDirection) {
9571 Element* rootElement = GetHtmlElement();
9572 if (rootElement) {
9573 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
9577 nsIHTMLCollection* Document::Images() {
9578 if (!mImages) {
9579 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
9580 nsGkAtoms::img);
9582 return mImages;
9585 nsIHTMLCollection* Document::Embeds() {
9586 if (!mEmbeds) {
9587 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
9588 nsGkAtoms::embed);
9590 return mEmbeds;
9593 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9594 void* aData) {
9595 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
9596 aElement->HasAttr(nsGkAtoms::href);
9599 nsIHTMLCollection* Document::Links() {
9600 if (!mLinks) {
9601 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
9603 return mLinks;
9606 nsIHTMLCollection* Document::Forms() {
9607 if (!mForms) {
9608 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
9609 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
9610 nsGkAtoms::form);
9613 return mForms;
9616 nsIHTMLCollection* Document::Scripts() {
9617 if (!mScripts) {
9618 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
9619 nsGkAtoms::script);
9621 return mScripts;
9624 nsIHTMLCollection* Document::Applets() {
9625 if (!mApplets) {
9626 mApplets = new nsEmptyContentList(this);
9628 return mApplets;
9631 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9632 void* aData) {
9633 return aElement->IsHTMLElement(nsGkAtoms::a) &&
9634 aElement->HasAttr(nsGkAtoms::name);
9637 nsIHTMLCollection* Document::Anchors() {
9638 if (!mAnchors) {
9639 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
9641 return mAnchors;
9644 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
9645 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
9646 ErrorResult& rv) {
9647 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9648 "XOW should have caught this!");
9650 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
9651 if (!window) {
9652 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
9653 return nullptr;
9655 nsCOMPtr<nsPIDOMWindowOuter> outer =
9656 nsPIDOMWindowOuter::GetFromCurrentInner(window);
9657 if (!outer) {
9658 rv.Throw(NS_ERROR_NOT_INITIALIZED);
9659 return nullptr;
9661 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
9662 RefPtr<BrowsingContext> newBC;
9663 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
9664 if (!newBC) {
9665 return nullptr;
9667 return WindowProxyHolder(std::move(newBC));
9670 Document* Document::Open(const Optional<nsAString>& /* unused */,
9671 const Optional<nsAString>& /* unused */,
9672 ErrorResult& aError) {
9673 // Implements
9674 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
9676 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9677 "XOW should have caught this!");
9679 // Step 1 -- throw if we're an XML document.
9680 if (!IsHTMLDocument() || mDisableDocWrite) {
9681 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9682 return nullptr;
9685 // Step 2 -- throw if dynamic markup insertion should throw.
9686 if (ShouldThrowOnDynamicMarkupInsertion()) {
9687 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9688 return nullptr;
9691 // Step 3 -- get the entry document, so we can use it for security checks.
9692 nsCOMPtr<Document> callerDoc = GetEntryDocument();
9693 if (!callerDoc) {
9694 // If we're called from C++ or in some other way without an originating
9695 // document we can't do a document.open w/o changing the principal of the
9696 // document to something like about:blank (as that's the only sane thing to
9697 // do when we don't know the origin of this call), and since we can't
9698 // change the principals of a document for security reasons we'll have to
9699 // refuse to go ahead with this call.
9701 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9702 return nullptr;
9705 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
9706 // the entry document.
9707 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
9708 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9709 return nullptr;
9712 // Step 5 -- if we have an active parser with a nonzero script nesting level,
9713 // just no-op.
9714 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
9715 return this;
9718 // Step 6 -- check for open() during unload. Per spec, this is just a check
9719 // of the ignore-opens-during-unload counter, but our unload event code
9720 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
9721 // do), so we check for unload directly.
9722 if (ShouldIgnoreOpens()) {
9723 return this;
9726 RefPtr<nsDocShell> shell(mDocumentContainer);
9727 if (shell) {
9728 bool inUnload;
9729 shell->GetIsInUnload(&inUnload);
9730 if (inUnload) {
9731 return this;
9735 // At this point we know this is a valid-enough document.open() call
9736 // and not a no-op. Increment our use counter.
9737 SetUseCounter(eUseCounter_custom_DocumentOpen);
9739 // Step 7 -- stop existing navigation of our browsing context (and all other
9740 // loads it's doing) if we're the active document of our browsing context.
9741 // Note that we do not want to stop anything if there is no existing
9742 // navigation.
9743 if (shell && IsCurrentActiveDocument() &&
9744 shell->GetIsAttemptingToNavigate()) {
9745 shell->Stop(nsIWebNavigation::STOP_NETWORK);
9747 // The Stop call may have cancelled the onload blocker request or
9748 // prevented it from getting added, so we need to make sure it gets added
9749 // to the document again otherwise the document could have a non-zero
9750 // onload block count without the onload blocker request being in the
9751 // loadgroup.
9752 EnsureOnloadBlocker();
9755 // Step 8 -- clear event listeners out of our DOM tree
9756 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
9757 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
9758 elm->RemoveAllListeners();
9762 // Step 9 -- clear event listeners from our window, if we have one.
9764 // Note that we explicitly want the inner window, and only if we're its
9765 // document. We want to do this (per spec) even when we're not the "active
9766 // document", so we can't go through GetWindow(), because it might forward to
9767 // the wrong inner.
9768 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
9769 if (win->GetExtantDoc() == this) {
9770 if (EventListenerManager* elm =
9771 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
9772 elm->RemoveAllListeners();
9777 // If we have a parser that has a zero script nesting level, we need to
9778 // properly terminate it. We do that after we've removed all the event
9779 // listeners (so termination won't trigger event listeners if it does
9780 // something to the DOM), but before we remove all elements from the document
9781 // (so if termination does modify the DOM in some way we will just blow it
9782 // away immediately. See the similar code in WriteCommon that handles the
9783 // !IsInsertionPointDefined() case and should stay in sync with this code.
9784 if (mParser) {
9785 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
9786 "Why didn't we take the early return?");
9787 // Make sure we don't re-enter.
9788 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9789 mParser->Terminate();
9790 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9793 // Step 10 -- remove all our DOM kids without firing any mutation events.
9795 // We want to ignore any recursive calls to Open() that happen while
9796 // disconnecting the node tree. The spec doesn't say to do this, but the
9797 // spec also doesn't envision unload events on subframes firing while we do
9798 // this, while all browsers fire them in practice. See
9799 // <https://github.com/whatwg/html/issues/4611>.
9800 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9801 DisconnectNodeTree();
9804 // Step 11 -- if we're the current document in our docshell, do the
9805 // equivalent of pushState() with the new URL we should have.
9806 if (shell && IsCurrentActiveDocument()) {
9807 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
9808 if (callerDoc != this) {
9809 nsCOMPtr<nsIURI> noFragmentURI;
9810 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
9811 if (NS_WARN_IF(NS_FAILED(rv))) {
9812 aError.Throw(rv);
9813 return nullptr;
9815 newURI = std::move(noFragmentURI);
9818 // UpdateURLAndHistory might do various member-setting, so make sure we're
9819 // holding strong refs to all the refcounted args on the stack. We can
9820 // assume that our caller is holding on to "this" already.
9821 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
9822 bool equalURIs;
9823 nsresult rv = currentURI->Equals(newURI, &equalURIs);
9824 if (NS_WARN_IF(NS_FAILED(rv))) {
9825 aError.Throw(rv);
9826 return nullptr;
9828 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
9829 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
9830 /* aReplace = */ true, currentURI,
9831 equalURIs);
9832 if (NS_WARN_IF(NS_FAILED(rv))) {
9833 aError.Throw(rv);
9834 return nullptr;
9837 // And use the security info of the caller document as well, since
9838 // it's the thing providing our data.
9839 mSecurityInfo = callerDoc->GetSecurityInfo();
9841 // This is not mentioned in the spec, but I think that's a spec bug. See
9842 // <https://github.com/whatwg/html/issues/4299>. In any case, since our
9843 // URL may be changing away from about:blank here, we really want to unset
9844 // this flag no matter what, since only about:blank can be an initial
9845 // document.
9846 SetIsInitialDocument(false);
9848 // And let our docloader know that it will need to track our load event.
9849 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
9852 // Per spec nothing happens with our URI in other cases, though note
9853 // <https://github.com/whatwg/html/issues/4286>.
9855 // Note that we don't need to do anything here with base URIs per spec.
9856 // That said, this might be assuming that we implement
9857 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
9858 // correctly, which we don't right now for the about:blank case.
9860 // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
9861 mSkipLoadEventAfterClose = mLoadEventFiring;
9863 // Preliminary to steps 13-16. Set our ready state to uninitialized before
9864 // we do anything else, so we can then proceed to later ready state levels.
9865 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
9866 /* updateTimingInformation = */ false);
9867 // Reset a flag that affects readyState behavior.
9868 mSetCompleteAfterDOMContentLoaded = false;
9870 // Step 13 -- set our compat mode to standards.
9871 SetCompatibilityMode(eCompatibility_FullStandards);
9873 // Step 14 -- create a new parser associated with document. This also does
9874 // step 16 implicitly.
9875 mParserAborted = false;
9876 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
9877 mParser = parser;
9878 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
9879 nsresult rv = parser->StartExecutor();
9880 if (NS_WARN_IF(NS_FAILED(rv))) {
9881 aError.Throw(rv);
9882 return nullptr;
9885 // Clear out our form control state, because the state of controls
9886 // in the pre-open() document should not affect the state of
9887 // controls that are now going to be written.
9888 mLayoutHistoryState = nullptr;
9890 if (shell) {
9891 // Prepare the docshell and the document viewer for the impending
9892 // out-of-band document.write()
9893 shell->PrepareForNewContentModel();
9895 nsCOMPtr<nsIDocumentViewer> viewer;
9896 shell->GetDocViewer(getter_AddRefs(viewer));
9897 if (viewer) {
9898 viewer->LoadStart(this);
9902 // Step 15.
9903 SetReadyStateInternal(Document::READYSTATE_LOADING,
9904 /* updateTimingInformation = */ false);
9906 // Step 16 happened with step 14 above.
9908 // Step 17.
9909 return this;
9912 void Document::Close(ErrorResult& rv) {
9913 if (!IsHTMLDocument()) {
9914 // No calling document.close() on XHTML!
9916 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9917 return;
9920 if (ShouldThrowOnDynamicMarkupInsertion()) {
9921 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9922 return;
9925 if (!mParser || !mParser->IsScriptCreated()) {
9926 return;
9929 ++mWriteLevel;
9930 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
9931 ->Parse(u""_ns, nullptr, true);
9932 --mWriteLevel;
9935 void Document::WriteCommon(const Sequence<nsString>& aText,
9936 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
9937 // Fast path the common case
9938 if (aText.Length() == 1) {
9939 WriteCommon(aText[0], aNewlineTerminate, rv);
9940 } else {
9941 // XXXbz it would be nice if we could pass all the strings to the parser
9942 // without having to do all this copying and then ask it to start
9943 // parsing....
9944 nsString text;
9945 for (size_t i = 0; i < aText.Length(); ++i) {
9946 text.Append(aText[i]);
9948 WriteCommon(text, aNewlineTerminate, rv);
9952 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
9953 ErrorResult& aRv) {
9954 #ifdef DEBUG
9956 // Assert that we do not use or accidentally introduce doc.write()
9957 // in system privileged context or in any of our about: pages.
9958 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
9959 bool isAboutOrPrivContext = principal->IsSystemPrincipal();
9960 if (!isAboutOrPrivContext) {
9961 if (principal->SchemeIs("about")) {
9962 // about:blank inherits the security contetext and this assertion
9963 // is only meant for actual about: pages.
9964 nsAutoCString host;
9965 principal->GetHost(host);
9966 isAboutOrPrivContext = !host.EqualsLiteral("blank");
9969 // Some automated tests use an empty string to kick off some parsing
9970 // mechansims, but they do not do any harm since they use an empty string.
9971 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
9972 "do not use doc.write in privileged context!");
9974 #endif
9976 mTooDeepWriteRecursion =
9977 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
9978 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
9979 aRv.Throw(NS_ERROR_UNEXPECTED);
9980 return;
9983 if (!IsHTMLDocument() || mDisableDocWrite) {
9984 // No calling document.write*() on XHTML!
9986 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9987 return;
9990 if (ShouldThrowOnDynamicMarkupInsertion()) {
9991 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9992 return;
9995 if (mParserAborted) {
9996 // Hixie says aborting the parser doesn't undefine the insertion point.
9997 // However, since we null out mParser in that case, we track the
9998 // theoretically defined insertion point using mParserAborted.
9999 return;
10002 // Implement Step 4.1 of:
10003 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
10004 if (ShouldIgnoreOpens()) {
10005 return;
10008 void* key = GenerateParserKey();
10009 if (mParser && !mParser->IsInsertionPointDefined()) {
10010 if (mIgnoreDestructiveWritesCounter) {
10011 // Instead of implying a call to document.open(), ignore the call.
10012 nsContentUtils::ReportToConsole(
10013 nsIScriptError::warningFlag, "DOM Events"_ns, this,
10014 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
10015 return;
10017 // The spec doesn't tell us to ignore opens from here, but we need to
10018 // ensure opens are ignored here. See similar code in Open() that handles
10019 // the case of an existing parser which is not currently running script and
10020 // should stay in sync with this code.
10021 IgnoreOpensDuringUnload ignoreOpenGuard(this);
10022 mParser->Terminate();
10023 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
10026 if (!mParser) {
10027 if (mIgnoreDestructiveWritesCounter) {
10028 // Instead of implying a call to document.open(), ignore the call.
10029 nsContentUtils::ReportToConsole(
10030 nsIScriptError::warningFlag, "DOM Events"_ns, this,
10031 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
10032 return;
10035 Open({}, {}, aRv);
10037 // If Open() fails, or if it didn't create a parser (as it won't
10038 // if the user chose to not discard the current document through
10039 // onbeforeunload), don't write anything.
10040 if (aRv.Failed() || !mParser) {
10041 return;
10045 static constexpr auto new_line = u"\n"_ns;
10047 ++mWriteLevel;
10049 // This could be done with less code, but for performance reasons it
10050 // makes sense to have the code for two separate Parse() calls here
10051 // since the concatenation of strings costs more than we like. And
10052 // why pay that price when we don't need to?
10053 if (aNewlineTerminate) {
10054 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
10055 ->Parse(aText + new_line, key, false);
10056 } else {
10057 aRv =
10058 (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
10061 --mWriteLevel;
10063 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
10066 void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
10067 WriteCommon(aText, false, rv);
10070 void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
10071 WriteCommon(aText, true, rv);
10074 void* Document::GenerateParserKey(void) {
10075 if (!mScriptLoader) {
10076 // If we don't have a script loader, then the parser probably isn't parsing
10077 // anything anyway, so just return null.
10078 return nullptr;
10081 // The script loader provides us with the currently executing script element,
10082 // which is guaranteed to be unique per script.
10083 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
10084 if (script && mParser && mParser->IsScriptCreated()) {
10085 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
10086 if (creatorParser != mParser) {
10087 // Make scripts that aren't inserted by the active parser of this document
10088 // participate in the context of the script that document.open()ed
10089 // this document.
10090 return nullptr;
10093 return script;
10096 /* static */
10097 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
10098 nsAtom* aAtom, void* aData) {
10099 MOZ_ASSERT(aElement, "Must have element to work with!");
10101 if (!aElement->HasName()) {
10102 return false;
10105 nsString* elementName = static_cast<nsString*>(aData);
10106 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
10107 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
10108 eCaseMatters);
10111 /* static */
10112 void* Document::UseExistingNameString(nsINode* aRootNode,
10113 const nsString* aName) {
10114 return const_cast<nsString*>(aName);
10117 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
10118 if (mDocumentURI) {
10119 nsAutoCString uri;
10120 nsresult rv = mDocumentURI->GetSpec(uri);
10121 NS_ENSURE_SUCCESS(rv, rv);
10123 CopyUTF8toUTF16(uri, aDocumentURI);
10124 } else {
10125 aDocumentURI.Truncate();
10128 return NS_OK;
10131 // Alias of above
10132 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
10134 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
10135 CallerType aCallerType,
10136 ErrorResult& aRv) const {
10137 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
10138 aRv = GetDocumentURI(aDocumentURI);
10139 return;
10142 nsAutoCString uri;
10143 nsresult res = mChromeXHRDocURI->GetSpec(uri);
10144 if (NS_FAILED(res)) {
10145 aRv.Throw(res);
10146 return;
10148 CopyUTF8toUTF16(uri, aDocumentURI);
10151 nsIURI* Document::GetDocumentURIObject() const {
10152 if (!mChromeXHRDocURI) {
10153 return GetDocumentURI();
10156 return mChromeXHRDocURI;
10159 void Document::GetCompatMode(nsString& aCompatMode) const {
10160 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
10161 mCompatMode == eCompatibility_AlmostStandards ||
10162 mCompatMode == eCompatibility_FullStandards,
10163 "mCompatMode is neither quirks nor strict for this document");
10165 if (mCompatMode == eCompatibility_NavQuirks) {
10166 aCompatMode.AssignLiteral("BackCompat");
10167 } else {
10168 aCompatMode.AssignLiteral("CSS1Compat");
10172 } // namespace dom
10173 } // namespace mozilla
10175 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
10176 if (Element* element = Element::FromNode(aNode)) {
10177 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
10178 while (true) {
10179 RefPtr<Attr> attr;
10181 // Use an iterator to get an arbitrary attribute from the
10182 // cache. The iterator must be destroyed before any other
10183 // operations on mAttributeCache, to avoid hash table
10184 // assertions.
10185 auto iter = map->mAttributeCache.ConstIter();
10186 if (iter.Done()) {
10187 break;
10189 attr = iter.UserData();
10192 BlastSubtreeToPieces(attr);
10194 mozilla::DebugOnly<nsresult> rv =
10195 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
10196 attr->NodeInfo()->NameAtom(), false);
10198 // XXX Should we abort here?
10199 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
10203 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
10204 BlastSubtreeToPieces(shadow);
10205 element->UnattachShadow();
10209 while (aNode->HasChildren()) {
10210 nsIContent* node = aNode->GetFirstChild();
10211 BlastSubtreeToPieces(node);
10212 aNode->RemoveChildNode(node, false);
10216 namespace mozilla::dom {
10218 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv,
10219 bool aAcceptShadowRoot) {
10220 OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
10221 if (adoptedNode->IsShadowRoot() && !aAcceptShadowRoot) {
10222 rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
10223 return nullptr;
10226 // Scope firing mutation events so that we don't carry any state that
10227 // might be stale
10229 if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
10230 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
10234 nsAutoScriptBlocker scriptBlocker;
10236 switch (adoptedNode->NodeType()) {
10237 case ATTRIBUTE_NODE: {
10238 // Remove from ownerElement.
10239 OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
10241 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement();
10242 if (rv.Failed()) {
10243 return nullptr;
10246 if (ownerElement) {
10247 OwningNonNull<Attr> newAttr =
10248 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
10249 if (rv.Failed()) {
10250 return nullptr;
10254 break;
10256 case DOCUMENT_FRAGMENT_NODE:
10257 case ELEMENT_NODE:
10258 case PROCESSING_INSTRUCTION_NODE:
10259 case TEXT_NODE:
10260 case CDATA_SECTION_NODE:
10261 case COMMENT_NODE:
10262 case DOCUMENT_TYPE_NODE: {
10263 // Don't allow adopting a node's anonymous subtree out from under it.
10264 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
10265 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10266 return nullptr;
10269 // We don't want to adopt an element into its own contentDocument or into
10270 // a descendant contentDocument, so we check if the frameElement of this
10271 // document or any of its parents is the adopted node or one of its
10272 // descendants.
10273 RefPtr<BrowsingContext> bc = GetBrowsingContext();
10274 while (bc) {
10275 nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
10276 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
10277 rv.ThrowHierarchyRequestError(
10278 "Trying to adopt a node into its own contentDocument or a "
10279 "descendant contentDocument.");
10280 return nullptr;
10283 if (XRE_IsParentProcess()) {
10284 bc = bc->Canonical()->GetParentCrossChromeBoundary();
10285 } else {
10286 bc = bc->GetParent();
10290 // Remove from parent.
10291 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
10292 if (parent) {
10293 parent->RemoveChildNode(adoptedNode->AsContent(), true);
10294 } else {
10295 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
10298 break;
10300 case DOCUMENT_NODE: {
10301 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10302 return nullptr;
10304 default: {
10305 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
10307 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10308 return nullptr;
10312 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
10313 bool sameDocument = oldDocument == this;
10315 AutoJSContext cx;
10316 JS::Rooted<JSObject*> newScope(cx, nullptr);
10317 if (!sameDocument) {
10318 newScope = GetWrapper();
10319 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
10320 // Make sure cx is in a semi-sane compartment before we call WrapNative.
10321 // It's kind of irrelevant, given that we're passing aAllowWrapping =
10322 // false, and documents should always insist on being wrapped in an
10323 // canonical scope. But we try to pass something sane anyway.
10324 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
10325 JSAutoRealm ar(cx, globalObject);
10326 JS::Rooted<JS::Value> v(cx);
10327 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
10328 /* aAllowWrapping = */ false);
10329 if (rv.Failed()) return nullptr;
10330 newScope = &v.toObject();
10334 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
10335 if (rv.Failed()) {
10336 // Disconnect all nodes from their parents, since some have the old document
10337 // as their ownerDocument and some have this as their ownerDocument.
10338 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
10339 return nullptr;
10342 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
10343 "Should still be in the document we just got adopted into");
10345 return adoptedNode;
10348 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
10350 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
10351 const nsString& aScaleString) {
10352 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
10353 if (aScaleString.EqualsLiteral("device-width") ||
10354 aScaleString.EqualsLiteral("device-height")) {
10355 return Some(LayoutDeviceToScreenScale(10.0f));
10356 } else if (aScaleString.EqualsLiteral("yes")) {
10357 return Some(LayoutDeviceToScreenScale(1.0f));
10358 } else if (aScaleString.EqualsLiteral("no")) {
10359 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10360 } else if (aScaleString.IsEmpty()) {
10361 return Nothing();
10364 nsresult scaleErrorCode;
10365 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
10366 if (NS_FAILED(scaleErrorCode)) {
10367 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10370 if (scale < 0) {
10371 return Nothing();
10373 return Some(clamped(LayoutDeviceToScreenScale(scale), ViewportMinScale(),
10374 ViewportMaxScale()));
10377 void Document::ParseScalesInViewportMetaData(
10378 const ViewportMetaData& aViewportMetaData) {
10379 Maybe<LayoutDeviceToScreenScale> scale;
10381 scale = ParseScaleString(aViewportMetaData.mInitialScale);
10382 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
10383 mValidScaleFloat = scale.isSome();
10385 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
10386 // Chrome uses '5' for the fallback value of maximum-scale, we might
10387 // consider matching it in future.
10388 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
10389 mScaleMaxFloat = scale.valueOr(ViewportMaxScale());
10390 mValidMaxScale = scale.isSome();
10392 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
10393 mScaleMinFloat = scale.valueOr(ViewportMinScale());
10394 mValidMinScale = scale.isSome();
10396 // Resolve min-zoom and max-zoom values.
10397 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
10398 if (mValidMaxScale && mValidMinScale) {
10399 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
10403 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
10404 const nsAString& aHeightString,
10405 bool aHasValidScale) {
10406 // The width and height properties
10407 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
10409 // The width and height viewport <META> properties are translated into width
10410 // and height descriptors, setting the min-width/min-height value to
10411 // extend-to-zoom and the max-width/max-height value to the length from the
10412 // viewport <META> property as follows:
10414 // 1. Non-negative number values are translated to pixel lengths, clamped to
10415 // the range: [1px, 10000px]
10416 // 2. Negative number values are dropped
10417 // 3. device-width and device-height translate to 100vw and 100vh respectively
10418 // 4. Other keywords and unknown values are also dropped
10419 mMinWidth = nsViewportInfo::kAuto;
10420 mMaxWidth = nsViewportInfo::kAuto;
10421 if (!aWidthString.IsEmpty()) {
10422 mMinWidth = nsViewportInfo::kExtendToZoom;
10423 if (aWidthString.EqualsLiteral("device-width")) {
10424 mMaxWidth = nsViewportInfo::kDeviceSize;
10425 } else {
10426 nsresult widthErrorCode;
10427 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
10428 if (NS_FAILED(widthErrorCode)) {
10429 mMaxWidth = nsViewportInfo::kAuto;
10430 } else if (mMaxWidth >= 0.0f) {
10431 mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
10432 } else {
10433 mMaxWidth = nsViewportInfo::kAuto;
10436 } else if (aHasValidScale) {
10437 if (aHeightString.IsEmpty()) {
10438 mMinWidth = nsViewportInfo::kExtendToZoom;
10439 mMaxWidth = nsViewportInfo::kExtendToZoom;
10441 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
10442 mMinWidth = nsViewportInfo::kExtendToZoom;
10443 mMaxWidth = nsViewportInfo::kDeviceSize;
10446 mMinHeight = nsViewportInfo::kAuto;
10447 mMaxHeight = nsViewportInfo::kAuto;
10448 if (!aHeightString.IsEmpty()) {
10449 mMinHeight = nsViewportInfo::kExtendToZoom;
10450 if (aHeightString.EqualsLiteral("device-height")) {
10451 mMaxHeight = nsViewportInfo::kDeviceSize;
10452 } else {
10453 nsresult heightErrorCode;
10454 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
10455 if (NS_FAILED(heightErrorCode)) {
10456 mMaxHeight = nsViewportInfo::kAuto;
10457 } else if (mMaxHeight >= 0.0f) {
10458 mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
10459 } else {
10460 mMaxHeight = nsViewportInfo::kAuto;
10466 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
10467 MOZ_ASSERT(mPresShell);
10469 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
10470 // widget scale and the full zoom.
10471 nsPresContext* context = mPresShell->GetPresContext();
10472 // When querying the full zoom, get it from the device context rather than
10473 // directly from the pres context, because the device context's value can
10474 // include an adjustment necessary to keep the number of app units per device
10475 // pixel an integer, and we want the adjusted value.
10476 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
10477 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
10478 CSSToLayoutDeviceScale layoutDeviceScale =
10479 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
10481 CSSToScreenScale defaultScale =
10482 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
10484 // Special behaviour for desktop mode, provided we are not on an about: page,
10485 // or fullscreen.
10486 const bool fullscreen = Fullscreen();
10487 auto* bc = GetBrowsingContext();
10488 if (bc && bc->ForceDesktopViewport() && !IsAboutPage() && !fullscreen) {
10489 CSSCoord viewportWidth =
10490 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
10491 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
10492 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
10493 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
10494 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
10495 return nsViewportInfo(fakeDesktopSize, scaleToFit,
10496 nsViewportInfo::ZoomFlag::AllowZoom,
10497 nsViewportInfo::ZoomBehaviour::Mobile,
10498 nsViewportInfo::AutoScaleFlag::AutoScale);
10501 // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
10502 if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
10503 return nsViewportInfo(aDisplaySize, defaultScale,
10504 nsLayoutUtils::AllowZoomingForDocument(this)
10505 ? nsViewportInfo::ZoomFlag::AllowZoom
10506 : nsViewportInfo::ZoomFlag::DisallowZoom,
10507 StaticPrefs::apz_allow_zooming_out()
10508 ? nsViewportInfo::ZoomBehaviour::Mobile
10509 : nsViewportInfo::ZoomBehaviour::Desktop);
10512 // In cases where the width of the CSS viewport is less than or equal to the
10513 // width of the display (i.e. width <= device-width) then we disable
10514 // double-tap-to-zoom behaviour. See bug 941995 for details.
10516 switch (mViewportType) {
10517 case DisplayWidthHeight:
10518 return nsViewportInfo(aDisplaySize, defaultScale,
10519 nsViewportInfo::ZoomFlag::AllowZoom,
10520 nsViewportInfo::ZoomBehaviour::Mobile);
10521 case Unknown: {
10522 // We might early exit if the viewport is empty. Even if we don't,
10523 // at the end of this case we'll note that it was empty. Later, when
10524 // we're using the cached values, this will trigger alternate code paths.
10525 if (!mLastModifiedViewportMetaData) {
10526 // If the docType specifies that we are on a site optimized for mobile,
10527 // then we want to return specially crafted defaults for the viewport
10528 // info.
10529 if (RefPtr<DocumentType> docType = GetDoctype()) {
10530 nsAutoString docId;
10531 docType->GetPublicId(docId);
10532 if ((docId.Find(u"WAP") != -1) || (docId.Find(u"Mobile") != -1) ||
10533 (docId.Find(u"WML") != -1)) {
10534 // We're making an assumption that the docType can't change here
10535 mViewportType = DisplayWidthHeight;
10536 return nsViewportInfo(aDisplaySize, defaultScale,
10537 nsViewportInfo::ZoomFlag::AllowZoom,
10538 nsViewportInfo::ZoomBehaviour::Mobile);
10542 nsAutoString handheldFriendly;
10543 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
10544 if (handheldFriendly.EqualsLiteral("true")) {
10545 mViewportType = DisplayWidthHeight;
10546 return nsViewportInfo(aDisplaySize, defaultScale,
10547 nsViewportInfo::ZoomFlag::AllowZoom,
10548 nsViewportInfo::ZoomBehaviour::Mobile);
10552 ViewportMetaData metaData = GetViewportMetaData();
10554 // Parse initial-scale, minimum-scale and maximum-scale.
10555 ParseScalesInViewportMetaData(metaData);
10557 // Parse width and height properties
10558 // This function sets m{Min,Max}{Width,Height}.
10559 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
10560 mValidScaleFloat);
10562 mAllowZoom = true;
10563 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
10564 (metaData.mUserScalable.EqualsLiteral("no")) ||
10565 (metaData.mUserScalable.EqualsLiteral("false"))) {
10566 mAllowZoom = false;
10569 // Resolve viewport-fit value.
10570 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
10571 mViewportFit = ViewportFitType::Auto;
10572 if (!metaData.mViewportFit.IsEmpty()) {
10573 if (metaData.mViewportFit.EqualsLiteral("contain")) {
10574 mViewportFit = ViewportFitType::Contain;
10575 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
10576 mViewportFit = ViewportFitType::Cover;
10580 mWidthStrEmpty = metaData.mWidth.IsEmpty();
10582 mViewportType = Specified;
10583 [[fallthrough]];
10585 case Specified:
10586 default:
10587 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
10588 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
10589 bool effectiveValidMaxScale = mValidMaxScale;
10591 nsViewportInfo::ZoomFlag effectiveZoomFlag =
10592 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
10593 : nsViewportInfo::ZoomFlag::DisallowZoom;
10594 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
10595 // If the pref to force user-scalable is enabled, we ignore the values
10596 // from the meta-viewport tag for these properties and just assume they
10597 // allow the page to be scalable. Note in particular that this code is
10598 // in the "Specified" branch of the enclosing switch statement, so that
10599 // calls to GetViewportInfo always use the latest value of the
10600 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
10601 // return nsViewportInfo instances are all consistent with
10602 // browser_ui_zoom_force_user_scalable() already.
10603 effectiveMinScale = ViewportMinScale();
10604 effectiveMaxScale = ViewportMaxScale();
10605 effectiveValidMaxScale = true;
10606 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
10609 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
10610 auto ComputeExtendZoom = [&]() -> float {
10611 if (mValidScaleFloat && effectiveValidMaxScale) {
10612 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
10614 if (mValidScaleFloat) {
10615 return mScaleFloat.scale;
10617 if (effectiveValidMaxScale) {
10618 return effectiveMaxScale.scale;
10620 return nsViewportInfo::kAuto;
10623 // Resolving 'extend-to-zoom'
10624 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
10625 float extendZoom = ComputeExtendZoom();
10627 CSSCoord minWidth = mMinWidth;
10628 CSSCoord maxWidth = mMaxWidth;
10629 CSSCoord minHeight = mMinHeight;
10630 CSSCoord maxHeight = mMaxHeight;
10632 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
10633 // viewport size. We need to use this scaled size for any clamping of
10634 // width or height.
10635 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
10637 // Our min and max width and height values are mostly as specified by
10638 // the viewport declaration, but we make an exception for max width.
10639 // Max width, if auto, and if there's no initial-scale, will be set
10640 // to a default size. This is to support legacy site design with no
10641 // viewport declaration, and to do that using the same scheme as
10642 // Chrome does, in order to maintain web compatibility. Since the
10643 // default size has a complicated calculation, we fixup the maxWidth
10644 // value after setting it, above.
10645 if (maxWidth == nsViewportInfo::kAuto && !mValidScaleFloat) {
10646 if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
10647 bc->InRDMPane()) {
10648 // If RDM and touch simulation are active, then use the simulated
10649 // screen width to accommodate for cases where the screen width is
10650 // larger than the desktop viewport default.
10651 maxWidth = nsViewportInfo::Max(
10652 displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
10653 } else {
10654 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
10656 // Divide by fullZoom to stretch CSS pixel size of viewport in order
10657 // to keep device pixel size unchanged after full zoom applied.
10658 // See bug 974242.
10659 maxWidth /= fullZoom;
10661 // We set minWidth to ExtendToZoom, which will cause our later width
10662 // calculation to expand to maxWidth, if scale restrictions allow it.
10663 minWidth = nsViewportInfo::kExtendToZoom;
10666 // Resolve device-width and device-height first.
10667 if (maxWidth == nsViewportInfo::kDeviceSize) {
10668 maxWidth = displaySize.width;
10670 if (maxHeight == nsViewportInfo::kDeviceSize) {
10671 maxHeight = displaySize.height;
10673 if (extendZoom == nsViewportInfo::kAuto) {
10674 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10675 maxWidth = nsViewportInfo::kAuto;
10677 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10678 maxHeight = nsViewportInfo::kAuto;
10680 if (minWidth == nsViewportInfo::kExtendToZoom) {
10681 minWidth = maxWidth;
10683 if (minHeight == nsViewportInfo::kExtendToZoom) {
10684 minHeight = maxHeight;
10686 } else {
10687 CSSSize extendSize = displaySize / extendZoom;
10688 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10689 maxWidth = extendSize.width;
10691 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10692 maxHeight = extendSize.height;
10694 if (minWidth == nsViewportInfo::kExtendToZoom) {
10695 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
10697 if (minHeight == nsViewportInfo::kExtendToZoom) {
10698 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
10702 // Resolve initial width and height from min/max descriptors
10703 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
10704 CSSCoord width = nsViewportInfo::kAuto;
10705 if (minWidth != nsViewportInfo::kAuto ||
10706 maxWidth != nsViewportInfo::kAuto) {
10707 width = nsViewportInfo::Max(
10708 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
10710 CSSCoord height = nsViewportInfo::kAuto;
10711 if (minHeight != nsViewportInfo::kAuto ||
10712 maxHeight != nsViewportInfo::kAuto) {
10713 height = nsViewportInfo::Max(
10714 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
10717 // Resolve width value
10718 // https://drafts.csswg.org/css-device-adapt/#resolve-width
10719 if (width == nsViewportInfo::kAuto) {
10720 if (height == nsViewportInfo::kAuto || aDisplaySize.height == 0) {
10721 width = displaySize.width;
10722 } else {
10723 width = height * aDisplaySize.width / aDisplaySize.height;
10727 // Resolve height value
10728 // https://drafts.csswg.org/css-device-adapt/#resolve-height
10729 if (height == nsViewportInfo::kAuto) {
10730 if (aDisplaySize.width == 0) {
10731 height = displaySize.height;
10732 } else {
10733 height = width * aDisplaySize.height / aDisplaySize.width;
10736 MOZ_ASSERT(width != nsViewportInfo::kAuto &&
10737 height != nsViewportInfo::kAuto);
10739 CSSSize size(width, height);
10741 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
10742 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
10743 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
10745 nsViewportInfo::AutoSizeFlag sizeFlag =
10746 nsViewportInfo::AutoSizeFlag::FixedSize;
10747 if (mMaxWidth == nsViewportInfo::kDeviceSize ||
10748 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::kDeviceSize ||
10749 mScaleFloat.scale == 1.0f)) ||
10750 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::kAuto &&
10751 mMaxHeight < 0)) {
10752 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
10755 // FIXME: Resolving width and height should be done above 'Resolve width
10756 // value' and 'Resolve height value'.
10757 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
10758 size = displaySize;
10761 // The purpose of clamping the viewport width to a minimum size is to
10762 // prevent page authors from setting it to a ridiculously small value.
10763 // If the page is actually being rendered in a very small area (as might
10764 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
10765 // prevent the viewport from taking on that size.
10766 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
10768 size.width = clamped(size.width, effectiveMinSize.width,
10769 float(kViewportMaxSize.width));
10771 // Also recalculate the default zoom, if it wasn't specified in the
10772 // metadata, and the width is specified.
10773 if (!mValidScaleFloat && !mWidthStrEmpty) {
10774 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
10775 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
10778 size.height = clamped(size.height, effectiveMinSize.height,
10779 float(kViewportMaxSize.height));
10781 // In cases of user-scalable=no, if we have a positive scale, clamp it to
10782 // min and max, and then use the clamped value for the scale, the min, and
10783 // the max. If we don't have a positive scale, assert that we are setting
10784 // the auto scale flag.
10785 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
10786 scaleFloat > CSSToScreenScale(0.0f)) {
10787 scaleFloat = scaleMinFloat = scaleMaxFloat =
10788 clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
10790 MOZ_ASSERT(
10791 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
10792 "If we don't have a positive scale, we should be using auto scale.");
10794 // We need to perform a conversion, but only if the initial or maximum
10795 // scale were set explicitly by the user.
10796 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
10797 scaleFloat <= scaleMaxFloat) {
10798 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
10799 size.width = std::max(size.width, displaySize.width);
10800 size.height = std::max(size.height, displaySize.height);
10801 } else if (effectiveValidMaxScale) {
10802 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
10803 size.width = std::max(size.width, displaySize.width);
10804 size.height = std::max(size.height, displaySize.height);
10807 return nsViewportInfo(
10808 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
10809 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
10810 : nsViewportInfo::AutoScaleFlag::AutoScale,
10811 effectiveZoomFlag, mViewportFit);
10815 ViewportMetaData Document::GetViewportMetaData() const {
10816 return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
10817 : ViewportMetaData();
10820 void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
10821 mLastModifiedViewportMetaData = std::move(aData);
10822 // Trigger recomputation of the nsViewportInfo the next time it's queried.
10823 mViewportType = Unknown;
10825 AsyncEventDispatcher::RunDOMEventWhenSafe(
10826 *this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes,
10827 ChromeOnlyDispatch::eYes);
10830 EventListenerManager* Document::GetOrCreateListenerManager() {
10831 if (!mListenerManager) {
10832 mListenerManager =
10833 new EventListenerManager(static_cast<EventTarget*>(this));
10834 SetFlags(NODE_HAS_LISTENERMANAGER);
10837 return mListenerManager;
10840 EventListenerManager* Document::GetExistingListenerManager() const {
10841 return mListenerManager;
10844 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
10845 aVisitor.mCanHandle = true;
10846 // FIXME! This is a hack to make middle mouse paste working also in Editor.
10847 // Bug 329119
10848 aVisitor.mForceContentDispatch = true;
10850 // Load events must not propagate to |window| object, see bug 335251.
10851 if (aVisitor.mEvent->mMessage != eLoad) {
10852 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
10853 aVisitor.SetParentTarget(
10854 window ? window->GetTargetForEventTargetChain() : nullptr, false);
10858 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
10859 CallerType aCallerType,
10860 ErrorResult& rv) const {
10861 nsPresContext* presContext = GetPresContext();
10863 // Create event even without presContext.
10864 RefPtr<Event> ev =
10865 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
10866 nullptr, aEventType, aCallerType);
10867 if (!ev) {
10868 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10869 return nullptr;
10871 WidgetEvent* e = ev->WidgetEventPtr();
10872 e->mFlags.mBubbles = false;
10873 e->mFlags.mCancelable = false;
10874 return ev.forget();
10877 void Document::FlushPendingNotifications(FlushType aType) {
10878 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
10879 FlushPendingNotifications(flush);
10882 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
10883 FlushType flushType = aFlush.mFlushType;
10885 RefPtr<Document> documentOnStack = this;
10887 // We need to flush the sink for non-HTML documents (because the XML
10888 // parser still does insertion with deferred notifications). We
10889 // also need to flush the sink if this is a layout-related flush, to
10890 // make sure that layout is started as needed. But we can skip that
10891 // part if we have no presshell or if it's already done an initial
10892 // reflow.
10893 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
10894 mPresShell && !mPresShell->DidInitialize())) &&
10895 (mParser || mWeakSink)) {
10896 nsCOMPtr<nsIContentSink> sink;
10897 if (mParser) {
10898 sink = mParser->GetContentSink();
10899 } else {
10900 sink = do_QueryReferent(mWeakSink);
10901 if (!sink) {
10902 mWeakSink = nullptr;
10905 // Determine if it is safe to flush the sink notifications
10906 // by determining if it safe to flush all the presshells.
10907 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
10908 sink->FlushPendingNotifications(flushType);
10912 // Should we be flushing pending binding constructors in here?
10914 if (flushType <= FlushType::ContentAndNotify) {
10915 // Nothing to do here
10916 return;
10919 // If we have a parent we must flush the parent too to ensure that our
10920 // container is reflowed if its size was changed.
10922 // We do it only if the subdocument and the parent can observe each other
10923 // synchronously (that is, if we're not cross-origin), to avoid work that is
10924 // not observable, and if the parent document has finished loading all its
10925 // render-blocking stylesheets and may start laying out the document, to avoid
10926 // unnecessary flashes of unstyled content on the parent document. Note that
10927 // this last bit means that size-dependent media queries in this document may
10928 // produce incorrect results temporarily.
10930 // But if it's not safe to flush ourselves, then don't flush the parent, since
10931 // that can cause things like resizes of our frame's widget, which we can't
10932 // handle while flushing is unsafe.
10933 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
10934 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
10935 ChangesToFlush parentFlush = aFlush;
10936 if (flushType >= FlushType::Style) {
10937 // Since media queries mean that a size change of our container can affect
10938 // style, we need to promote a style flush on ourself to a layout flush on
10939 // our parent, since we need our container to be the correct size to
10940 // determine the correct style.
10941 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
10943 mParentDocument->FlushPendingNotifications(parentFlush);
10946 if (RefPtr<PresShell> presShell = GetPresShell()) {
10947 presShell->FlushPendingNotifications(aFlush);
10951 void Document::FlushExternalResources(FlushType aType) {
10952 NS_ASSERTION(
10953 aType >= FlushType::Style,
10954 "should only need to flush for style or higher in external resources");
10955 if (GetDisplayDocument()) {
10956 return;
10959 EnumerateExternalResources([aType](Document& aDoc) {
10960 aDoc.FlushPendingNotifications(aType);
10961 return CallState::Continue;
10965 void Document::SetXMLDeclaration(const char16_t* aVersion,
10966 const char16_t* aEncoding,
10967 const int32_t aStandalone) {
10968 if (!aVersion || *aVersion == '\0') {
10969 mXMLDeclarationBits = 0;
10970 return;
10973 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
10975 if (aEncoding && *aEncoding != '\0') {
10976 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
10979 if (aStandalone == 1) {
10980 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
10981 XML_DECLARATION_BITS_STANDALONE_YES;
10982 } else if (aStandalone == 0) {
10983 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
10987 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
10988 nsAString& aStandalone) {
10989 aVersion.Truncate();
10990 aEncoding.Truncate();
10991 aStandalone.Truncate();
10993 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
10994 return;
10997 // always until we start supporting 1.1 etc.
10998 aVersion.AssignLiteral("1.0");
11000 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
11001 // This is what we have stored, not necessarily what was written
11002 // in the original
11003 GetCharacterSet(aEncoding);
11006 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
11007 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
11008 aStandalone.AssignLiteral("yes");
11009 } else {
11010 aStandalone.AssignLiteral("no");
11015 void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
11016 mColorSchemeMetaTags.Insert(aMeta);
11017 RecomputeColorScheme();
11020 void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
11021 mColorSchemeMetaTags.RemoveElement(aMeta);
11022 RecomputeColorScheme();
11025 void Document::RecomputeColorScheme() {
11026 auto oldColorScheme = mColorSchemeBits;
11027 mColorSchemeBits = 0;
11028 const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
11029 for (const HTMLMetaElement* el : elements) {
11030 nsAutoString content;
11031 if (!el->GetAttr(nsGkAtoms::content, content)) {
11032 continue;
11035 NS_ConvertUTF16toUTF8 contentU8(content);
11036 if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
11037 break;
11041 if (mColorSchemeBits == oldColorScheme) {
11042 return;
11045 if (nsPresContext* pc = GetPresContext()) {
11046 // This affects system colors, which are inherited, so we need to recascade.
11047 pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
11051 bool Document::IsScriptEnabled() const {
11052 // If this document is sandboxed without 'allow-scripts'
11053 // script is not enabled
11054 if (HasScriptsBlockedBySandbox()) {
11055 return false;
11058 nsCOMPtr<nsIScriptGlobalObject> globalObject =
11059 do_QueryInterface(GetInnerWindow());
11060 if (!globalObject || !globalObject->HasJSGlobal()) {
11061 return false;
11064 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
11065 .Allowed();
11068 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
11069 PRTime modDate = 0;
11070 nsresult rv;
11072 nsCOMPtr<nsIHttpChannel> httpChannel;
11073 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
11074 if (NS_WARN_IF(NS_FAILED(rv))) {
11075 return;
11078 if (httpChannel) {
11079 nsAutoCString tmp;
11080 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
11082 if (NS_SUCCEEDED(rv)) {
11083 PRTime time;
11084 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
11085 if (st == PR_SUCCESS) {
11086 modDate = time;
11090 static const char* const headers[] = {
11091 "default-style", "content-style-type", "content-language",
11092 "content-disposition", "refresh", "x-dns-prefetch-control",
11093 "x-frame-options", "origin-trial",
11094 // add more http headers if you need
11095 // XXXbz don't add content-location support without reading bug
11096 // 238654 and its dependencies/dups first.
11099 nsAutoCString headerVal;
11100 const char* const* name = headers;
11101 while (*name) {
11102 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
11103 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
11104 RefPtr<nsAtom> key = NS_Atomize(*name);
11105 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
11107 ++name;
11109 } else {
11110 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
11111 if (fileChannel) {
11112 nsCOMPtr<nsIFile> file;
11113 fileChannel->GetFile(getter_AddRefs(file));
11114 if (file) {
11115 PRTime msecs;
11116 rv = file->GetLastModifiedTime(&msecs);
11118 if (NS_SUCCEEDED(rv)) {
11119 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
11122 } else {
11123 nsAutoCString contentDisp;
11124 rv = aChannel->GetContentDispositionHeader(contentDisp);
11125 if (NS_SUCCEEDED(rv)) {
11126 SetHeaderData(nsGkAtoms::headerContentDisposition,
11127 NS_ConvertASCIItoUTF16(contentDisp));
11132 mLastModified.Truncate();
11133 if (modDate != 0) {
11134 GetFormattedTimeString(modDate,
11135 ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
11136 mLastModified);
11140 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
11141 // set any HTTP-EQUIV data into document's header data as well as url
11142 nsAutoString header;
11143 aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
11144 if (!header.IsEmpty()) {
11145 // Ignore META REFRESH when document is sandboxed from automatic features.
11146 nsContentUtils::ASCIIToLower(header);
11147 if (nsGkAtoms::refresh->Equals(header) &&
11148 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
11149 return;
11152 nsAutoString result;
11153 aMetaElement->GetAttr(nsGkAtoms::content, result);
11154 if (!result.IsEmpty()) {
11155 RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
11156 SetHeaderData(fieldAtom, result);
11160 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
11161 nsGkAtoms::handheldFriendly, eIgnoreCase)) {
11162 nsAutoString result;
11163 aMetaElement->GetAttr(nsGkAtoms::content, result);
11164 if (!result.IsEmpty()) {
11165 nsContentUtils::ASCIIToLower(result);
11166 SetHeaderData(nsGkAtoms::handheldFriendly, result);
11171 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
11172 nsAtom* aPrefix,
11173 int32_t aNamespaceID,
11174 const nsAString* aIs) {
11175 #ifdef DEBUG
11176 nsAutoString qName;
11177 if (aPrefix) {
11178 aPrefix->ToString(qName);
11179 qName.Append(':');
11181 qName.Append(aName);
11183 // Note: "a:b:c" is a valid name in non-namespaces XML, and
11184 // Document::CreateElement can call us with such a name and no prefix,
11185 // which would cause an error if we just used true here.
11186 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
11187 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
11188 "Don't pass invalid prefixes to Document::CreateElem, "
11189 "check caller.");
11190 #endif
11192 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
11193 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
11194 getter_AddRefs(nodeInfo));
11195 NS_ENSURE_TRUE(nodeInfo, nullptr);
11197 nsCOMPtr<Element> element;
11198 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
11199 NOT_FROM_PARSER, aIs);
11200 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
11203 bool Document::IsSafeToFlush() const {
11204 PresShell* presShell = GetPresShell();
11205 if (!presShell) {
11206 return true;
11208 return presShell->IsSafeToFlush();
11211 void Document::Sanitize() {
11212 // Sanitize the document by resetting all (current and former) password fields
11213 // and any form fields with autocomplete=off to their default values. We do
11214 // this now, instead of when the presentation is restored, to offer some
11215 // protection in case there is ever an exploit that allows a cached document
11216 // to be accessed from a different document.
11218 // First locate all input elements, regardless of whether they are
11219 // in a form, and reset the password and autocomplete=off elements.
11221 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
11223 nsAutoString value;
11225 uint32_t length = nodes->Length(true);
11226 for (uint32_t i = 0; i < length; ++i) {
11227 NS_ASSERTION(nodes->Item(i), "null item in node list!");
11229 RefPtr<HTMLInputElement> input =
11230 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
11231 if (!input) continue;
11233 input->GetAttr(nsGkAtoms::autocomplete, value);
11234 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
11235 input->Reset();
11239 // Now locate all _form_ elements that have autocomplete=off and reset them
11240 nodes = GetElementsByTagName(u"form"_ns);
11242 length = nodes->Length(true);
11243 for (uint32_t i = 0; i < length; ++i) {
11244 // Reset() may change the list dynamically.
11245 RefPtr<HTMLFormElement> form =
11246 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
11247 if (!form) continue;
11249 form->GetAttr(nsGkAtoms::autocomplete, value);
11250 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
11254 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
11255 if (!mSubDocuments) {
11256 return;
11259 // PLDHashTable::Iterator can't handle modifications while iterating so we
11260 // copy all entries to an array first before calling any callbacks.
11261 AutoTArray<RefPtr<Document>, 8> subdocs;
11262 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11263 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11264 if (Document* subdoc = entry->mSubDocument) {
11265 subdocs.AppendElement(subdoc);
11268 for (auto& subdoc : subdocs) {
11269 if (aCallback(*subdoc) == CallState::Stop) {
11270 break;
11275 void Document::CollectDescendantDocuments(
11276 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
11277 if (!mSubDocuments) {
11278 return;
11281 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11282 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11283 const Document* subdoc = entry->mSubDocument;
11284 if (subdoc) {
11285 if (aCallback(subdoc)) {
11286 aDescendants.AppendElement(entry->mSubDocument);
11288 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
11293 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
11294 uint32_t& aBFCacheCombo,
11295 bool aIncludeSubdocuments,
11296 bool aAllowUnloadListeners) {
11297 bool ret = true;
11299 if (!IsBFCachingAllowed()) {
11300 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
11301 ret = false;
11304 nsAutoCString uri;
11305 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11306 if (mDocumentURI) {
11307 mDocumentURI->GetSpec(uri);
11311 if (EventHandlingSuppressed()) {
11312 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11313 ("Save of %s blocked on event handling suppression", uri.get()));
11314 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
11315 ret = false;
11318 // Do not allow suspended windows to be placed in the
11319 // bfcache. This method is also used to verify a document
11320 // coming out of the bfcache is ok to restore, though. So
11321 // we only want to block suspend windows that aren't also
11322 // frozen.
11323 nsPIDOMWindowInner* win = GetInnerWindow();
11324 if (win && win->IsSuspended() && !win->IsFrozen()) {
11325 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11326 ("Save of %s blocked on suspended Window", uri.get()));
11327 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
11328 ret = false;
11331 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
11332 bool thirdParty = false;
11333 // Currently some other mobile browsers seem to bfcache only cross-domain
11334 // pages, but bfcache those also when there are unload event listeners, so
11335 // this is trying to match that behavior as much as possible.
11336 bool allowUnloadListeners =
11337 aAllowUnloadListeners &&
11338 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
11339 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
11340 channel, &thirdParty)) &&
11341 thirdParty));
11343 // Check our event listener manager for unload/beforeunload listeners.
11344 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
11345 if (!allowUnloadListeners && piTarget) {
11346 EventListenerManager* manager = piTarget->GetExistingListenerManager();
11347 if (manager) {
11348 if (manager->HasUnloadListeners()) {
11349 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11350 ("Save of %s blocked due to unload handlers", uri.get()));
11351 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
11352 ret = false;
11354 if (manager->HasBeforeUnloadListeners()) {
11355 if (!mozilla::SessionHistoryInParent() ||
11356 !StaticPrefs::
11357 docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
11358 MOZ_LOG(
11359 gPageCacheLog, mozilla::LogLevel::Verbose,
11360 ("Save of %s blocked due to beforeUnload handlers", uri.get()));
11361 aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
11362 ret = false;
11368 // Check if we have pending network requests
11369 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11370 if (loadGroup) {
11371 nsCOMPtr<nsISimpleEnumerator> requests;
11372 loadGroup->GetRequests(getter_AddRefs(requests));
11374 bool hasMore = false;
11376 // We want to bail out if we have any requests other than aNewRequest (or
11377 // in the case when aNewRequest is a part of a multipart response the base
11378 // channel the multipart response is coming in on).
11379 nsCOMPtr<nsIChannel> baseChannel;
11380 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
11381 if (part) {
11382 part->GetBaseChannel(getter_AddRefs(baseChannel));
11385 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11386 nsCOMPtr<nsISupports> elem;
11387 requests->GetNext(getter_AddRefs(elem));
11389 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11390 if (request && request != aNewRequest && request != baseChannel) {
11391 // Favicon loads don't need to block caching.
11392 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
11393 if (channel) {
11394 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
11395 if (li->InternalContentPolicyType() ==
11396 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
11397 continue;
11401 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11402 nsAutoCString requestName;
11403 request->GetName(requestName);
11404 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
11405 ("Save of %s blocked because document has request %s",
11406 uri.get(), requestName.get()));
11408 aBFCacheCombo |= BFCacheStatus::REQUEST;
11409 ret = false;
11414 // Check if we have active GetUserMedia use
11415 if (MediaManager::Exists() && win &&
11416 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
11417 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11418 ("Save of %s blocked due to GetUserMedia", uri.get()));
11419 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
11420 ret = false;
11423 #ifdef MOZ_WEBRTC
11424 // Check if we have active PeerConnections
11425 if (win && win->HasActivePeerConnections()) {
11426 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11427 ("Save of %s blocked due to PeerConnection", uri.get()));
11428 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
11429 ret = false;
11431 #endif // MOZ_WEBRTC
11433 // Don't save presentations for documents containing EME content, so that
11434 // CDMs reliably shutdown upon user navigation.
11435 if (ContainsEMEContent()) {
11436 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
11437 ret = false;
11440 // Don't save presentations for documents containing MSE content, to
11441 // reduce memory usage.
11442 if (ContainsMSEContent()) {
11443 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11444 ("Save of %s blocked due to MSE use", uri.get()));
11445 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
11446 ret = false;
11449 if (aIncludeSubdocuments && mSubDocuments) {
11450 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11451 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11452 Document* subdoc = entry->mSubDocument;
11454 uint32_t subDocBFCacheCombo = 0;
11455 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
11456 bool canCache =
11457 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
11458 true, allowUnloadListeners)
11459 : false;
11460 if (!canCache) {
11461 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11462 ("Save of %s blocked due to subdocument blocked", uri.get()));
11463 aBFCacheCombo |= subDocBFCacheCombo;
11464 ret = false;
11469 if (!mozilla::BFCacheInParent()) {
11470 // BFCache is currently not compatible with remote subframes (bug 1609324)
11471 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
11472 for (auto& child : browsingContext->Children()) {
11473 if (!child->IsInProcess()) {
11474 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
11475 ret = false;
11476 break;
11482 if (win) {
11483 auto* globalWindow = nsGlobalWindowInner::Cast(win);
11484 #ifdef MOZ_WEBSPEECH
11485 if (globalWindow->HasActiveSpeechSynthesis()) {
11486 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11487 ("Save of %s blocked due to Speech use", uri.get()));
11488 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
11489 ret = false;
11491 #endif
11492 if (globalWindow->HasUsedVR()) {
11493 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11494 ("Save of %s blocked due to having used VR", uri.get()));
11495 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
11496 ret = false;
11499 if (win->HasActiveLocks()) {
11500 MOZ_LOG(
11501 gPageCacheLog, mozilla::LogLevel::Verbose,
11502 ("Save of %s blocked due to having active lock requests", uri.get()));
11503 aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
11504 ret = false;
11507 if (win->HasActiveWebTransports()) {
11508 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11509 ("Save of %s blocked due to WebTransport", uri.get()));
11510 aBFCacheCombo |= BFCacheStatus::ACTIVE_WEBTRANSPORT;
11511 ret = false;
11515 return ret;
11518 void Document::Destroy() {
11519 // The ContentViewer wants to release the document now. So, tell our content
11520 // to drop any references to the document so that it can be destroyed.
11521 if (mIsGoingAway) {
11522 return;
11525 ReportDocumentUseCounters();
11526 ReportLCP();
11527 SetDevToolsWatchingDOMMutations(false);
11529 mIsGoingAway = true;
11531 ScriptLoader()->Destroy();
11532 SetScriptGlobalObject(nullptr);
11533 RemovedFromDocShell();
11535 bool oldVal = mInUnlinkOrDeletion;
11536 mInUnlinkOrDeletion = true;
11538 #ifdef DEBUG
11539 uint32_t oldChildCount = GetChildCount();
11540 #endif
11542 for (nsIContent* child = GetFirstChild(); child;
11543 child = child->GetNextSibling()) {
11544 child->DestroyContent();
11545 MOZ_ASSERT(child->GetParentNode() == this);
11547 MOZ_ASSERT(oldChildCount == GetChildCount());
11548 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
11550 mInUnlinkOrDeletion = oldVal;
11552 mLayoutHistoryState = nullptr;
11554 if (mOriginalDocument) {
11555 mOriginalDocument->mLatestStaticClone = nullptr;
11558 if (IsStaticDocument()) {
11559 RemoveProperty(nsGkAtoms::printisfocuseddoc);
11560 RemoveProperty(nsGkAtoms::printselectionranges);
11563 // Shut down our external resource map. We might not need this for
11564 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
11565 // tearing down all those frame trees right now is the right thing to do.
11566 mExternalResourceMap.Shutdown();
11568 // Manually break cycles via promise's global object pointer.
11569 mReadyForIdle = nullptr;
11570 mOrientationPendingPromise = nullptr;
11572 // To break cycles.
11573 mPreloadService.ClearAllPreloads();
11575 if (mDocumentL10n) {
11576 mDocumentL10n->Destroy();
11579 if (!mPresShell) {
11580 DropStyleSet();
11584 void Document::RemovedFromDocShell() {
11585 mEditingState = EditingState::eOff;
11587 if (mRemovedFromDocShell) return;
11589 mRemovedFromDocShell = true;
11590 NotifyActivityChanged();
11592 for (nsIContent* child = GetFirstChild(); child;
11593 child = child->GetNextSibling()) {
11594 child->SaveSubtreeState();
11597 nsIDocShell* docShell = GetDocShell();
11598 if (docShell) {
11599 docShell->SynchronizeLayoutHistoryState();
11603 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
11604 const {
11605 nsCOMPtr<nsILayoutHistoryState> state;
11606 if (!mScriptGlobalObject) {
11607 state = mLayoutHistoryState;
11608 } else {
11609 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
11610 if (docShell) {
11611 docShell->GetLayoutHistoryState(getter_AddRefs(state));
11615 return state.forget();
11618 void Document::EnsureOnloadBlocker() {
11619 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11620 // -- it's not ours.
11621 if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
11622 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11623 if (loadGroup) {
11624 // Check first to see if mOnloadBlocker is in the loadgroup.
11625 nsCOMPtr<nsISimpleEnumerator> requests;
11626 loadGroup->GetRequests(getter_AddRefs(requests));
11628 bool hasMore = false;
11629 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11630 nsCOMPtr<nsISupports> elem;
11631 requests->GetNext(getter_AddRefs(elem));
11632 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11633 if (request && request == mOnloadBlocker) {
11634 return;
11638 // Not in the loadgroup, so add it.
11639 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11644 void Document::BlockOnload() {
11645 if (mDisplayDocument) {
11646 mDisplayDocument->BlockOnload();
11647 return;
11650 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11651 // -- it's not ours.
11652 if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
11653 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11654 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11657 ++mOnloadBlockCount;
11660 void Document::UnblockOnload(bool aFireSync) {
11661 if (mDisplayDocument) {
11662 mDisplayDocument->UnblockOnload(aFireSync);
11663 return;
11666 --mOnloadBlockCount;
11668 if (mOnloadBlockCount == 0) {
11669 if (mScriptGlobalObject) {
11670 // Only manipulate the loadgroup in this case, because if
11671 // mScriptGlobalObject is null, it's not ours.
11672 if (aFireSync) {
11673 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
11674 ++mOnloadBlockCount;
11675 DoUnblockOnload();
11676 } else {
11677 PostUnblockOnloadEvent();
11679 } else if (mIsBeingUsedAsImage) {
11680 // To correctly unblock onload for a document that contains an SVG
11681 // image, we need to know when all of the SVG document's resources are
11682 // done loading, in a way comparable to |window.onload|. We fire this
11683 // event to indicate that the SVG should be considered fully loaded.
11684 // Because scripting is disabled on SVG-as-image documents, this event
11685 // is not accessible to content authors. (See bug 837315.)
11686 RefPtr<AsyncEventDispatcher> asyncDispatcher =
11687 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
11688 CanBubble::eNo, ChromeOnlyDispatch::eNo);
11689 asyncDispatcher->PostDOMEvent();
11694 class nsUnblockOnloadEvent : public Runnable {
11695 public:
11696 explicit nsUnblockOnloadEvent(Document* aDoc)
11697 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
11698 NS_IMETHOD Run() override {
11699 mDoc->DoUnblockOnload();
11700 return NS_OK;
11703 private:
11704 RefPtr<Document> mDoc;
11707 void Document::PostUnblockOnloadEvent() {
11708 MOZ_RELEASE_ASSERT(NS_IsMainThread());
11709 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
11710 nsresult rv = Dispatch(evt.forget());
11711 if (NS_SUCCEEDED(rv)) {
11712 // Stabilize block count so we don't post more events while this one is up
11713 ++mOnloadBlockCount;
11714 } else {
11715 NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
11719 void Document::DoUnblockOnload() {
11720 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
11721 MOZ_ASSERT(mOnloadBlockCount != 0,
11722 "Shouldn't have a count of zero here, since we stabilized in "
11723 "PostUnblockOnloadEvent");
11725 --mOnloadBlockCount;
11727 if (mOnloadBlockCount != 0) {
11728 // We blocked again after the last unblock. Nothing to do here. We'll
11729 // post a new event when we unblock again.
11730 return;
11733 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11734 // -- it's not ours.
11735 if (mScriptGlobalObject) {
11736 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11737 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
11742 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
11743 for (nsIFrame* f = aFrame; f;
11744 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
11745 nsIContent* content = f->GetContent();
11746 if (!content) {
11747 continue;
11750 if (content->OwnerDoc() == this) {
11751 return content;
11753 // We must be in a subdocument so jump directly to the root frame.
11754 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
11755 // the containing document.
11756 f = f->PresContext()->GetPresShell()->GetRootFrame();
11759 return nullptr;
11762 void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
11763 const nsAString& aType, bool aInFrameSwap,
11764 bool aPersisted, bool aOnlySystemGroup) {
11765 if (!aDispatchTarget) {
11766 return;
11769 PageTransitionEventInit init;
11770 init.mBubbles = true;
11771 init.mCancelable = true;
11772 init.mPersisted = aPersisted;
11773 init.mInFrameSwap = aInFrameSwap;
11775 RefPtr<PageTransitionEvent> event =
11776 PageTransitionEvent::Constructor(this, aType, init);
11778 event->SetTrusted(true);
11779 event->SetTarget(this);
11780 if (aOnlySystemGroup) {
11781 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
11783 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
11784 nullptr);
11787 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
11788 bool aOnlySystemGroup) {
11789 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11790 nsCString uri;
11791 if (GetDocumentURI()) {
11792 uri = GetDocumentURI()->GetSpecOrDefault();
11794 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11795 ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
11798 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11799 MOZ_DIAGNOSTIC_ASSERT(
11800 inFrameLoaderSwap ==
11801 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11803 Element* root = GetRootElement();
11804 if (aPersisted && root) {
11805 // Send out notifications that our <link> elements are attached.
11806 RefPtr<nsContentList> links =
11807 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11809 uint32_t linkCount = links->Length(true);
11810 for (uint32_t i = 0; i < linkCount; ++i) {
11811 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
11815 // See Document
11816 if (!inFrameLoaderSwap) {
11817 if (aPersisted) {
11818 ImageTracker()->SetAnimatingState(true);
11821 // Set mIsShowing before firing events, in case those event handlers
11822 // move us around.
11823 mIsShowing = true;
11824 mVisible = true;
11826 UpdateVisibilityState();
11829 NotifyActivityChanged();
11831 EnumerateExternalResources([aPersisted](Document& aExternalResource) {
11832 aExternalResource.OnPageShow(aPersisted, nullptr);
11833 return CallState::Continue;
11836 if (mAnimationController) {
11837 mAnimationController->OnPageShow();
11840 if (!mIsBeingUsedAsImage) {
11841 // Dispatch observer notification to notify observers page is shown.
11842 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11843 if (os) {
11844 nsIPrincipal* principal = NodePrincipal();
11845 os->NotifyObservers(ToSupports(this),
11846 principal->IsSystemPrincipal() ? "chrome-page-shown"
11847 : "content-page-shown",
11848 nullptr);
11851 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11852 if (!target) {
11853 target = do_QueryInterface(GetWindow());
11855 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
11856 aPersisted, aOnlySystemGroup);
11860 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
11861 if (nsPresContext* presContext = aDocument.GetPresContext()) {
11862 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
11863 FullscreenEventType::Change, &aDocument, aTarget);
11864 presContext->RefreshDriver()->ScheduleFullscreenEvent(
11865 std::move(pendingEvent));
11869 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
11870 bool aOnlySystemGroup) {
11871 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11872 nsCString uri;
11873 if (GetDocumentURI()) {
11874 uri = GetDocumentURI()->GetSpecOrDefault();
11876 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11877 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
11880 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11881 MOZ_DIAGNOSTIC_ASSERT(
11882 inFrameLoaderSwap ==
11883 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11885 if (mAnimationController) {
11886 mAnimationController->OnPageHide();
11889 if (!inFrameLoaderSwap) {
11890 if (aPersisted) {
11891 // We do not stop the animations (bug 1024343) when the page is refreshing
11892 // while being dragged out.
11893 ImageTracker()->SetAnimatingState(false);
11896 // Set mIsShowing before firing events, in case those event handlers
11897 // move us around.
11898 mIsShowing = false;
11899 mVisible = false;
11902 ExitPointerLock();
11904 if (!mIsBeingUsedAsImage) {
11905 // Dispatch observer notification to notify observers page is hidden.
11906 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11907 if (os) {
11908 nsIPrincipal* principal = NodePrincipal();
11909 os->NotifyObservers(ToSupports(this),
11910 principal->IsSystemPrincipal()
11911 ? "chrome-page-hidden"
11912 : "content-page-hidden",
11913 nullptr);
11916 // Now send out a PageHide event.
11917 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11918 if (!target) {
11919 target = do_QueryInterface(GetWindow());
11922 PageUnloadingEventTimeStamp timeStamp(this);
11923 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
11924 aPersisted, aOnlySystemGroup);
11928 if (!inFrameLoaderSwap) {
11929 UpdateVisibilityState();
11932 EnumerateExternalResources([aPersisted](Document& aExternalResource) {
11933 aExternalResource.OnPageHide(aPersisted, nullptr);
11934 return CallState::Continue;
11936 NotifyActivityChanged();
11938 ClearPendingFullscreenRequests(this);
11939 if (Fullscreen()) {
11940 // If this document was fullscreen, we should exit fullscreen in this
11941 // doctree branch. This ensures that if the user navigates while in
11942 // fullscreen mode we don't leave its still visible ancestor documents
11943 // in fullscreen mode. So exit fullscreen in the document's fullscreen
11944 // root document, as this will exit fullscreen in all the root's
11945 // descendant documents. Note that documents are removed from the
11946 // doctree by the time OnPageHide() is called, so we must store a
11947 // reference to the root (in Document::mFullscreenRoot) since we can't
11948 // just traverse the doctree to get the root.
11949 Document::ExitFullscreenInDocTree(this);
11951 // Since the document is removed from the doctree before OnPageHide() is
11952 // called, ExitFullscreen() can't traverse from the root down to *this*
11953 // document, so we must manually call CleanupFullscreenState() below too.
11954 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
11955 // so we *must* call it after ExitFullscreen(), not before.
11956 // OnPageHide() is called in every hidden (i.e. descendant) document,
11957 // so calling CleanupFullscreenState() here will ensure all hidden
11958 // documents have their fullscreen state reset.
11959 CleanupFullscreenState();
11961 // The fullscreenchange event is to be queued in the refresh driver,
11962 // however a hidden page wouldn't trigger that again, so it makes no
11963 // sense to dispatch such event here.
11967 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
11968 NS_ASSERTION(
11969 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
11970 "mSubtreeModifiedTargets not cleared after dispatching?");
11971 ++mSubtreeModifiedDepth;
11972 if (aTarget) {
11973 // MayDispatchMutationEvent is often called just before this method,
11974 // so it has already appended the node to mSubtreeModifiedTargets.
11975 int32_t count = mSubtreeModifiedTargets.Count();
11976 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
11977 mSubtreeModifiedTargets.AppendObject(aTarget);
11982 void Document::MutationEventDispatched(nsINode* aTarget) {
11983 if (--mSubtreeModifiedDepth) {
11984 return;
11987 int32_t count = mSubtreeModifiedTargets.Count();
11988 if (!count) {
11989 return;
11992 nsPIDOMWindowInner* window = GetInnerWindow();
11993 if (window &&
11994 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
11995 mSubtreeModifiedTargets.Clear();
11996 return;
11999 nsCOMArray<nsINode> realTargets;
12000 for (nsINode* possibleTarget : mSubtreeModifiedTargets) {
12001 if (possibleTarget->ChromeOnlyAccess()) {
12002 continue;
12005 nsINode* commonAncestor = nullptr;
12006 int32_t realTargetCount = realTargets.Count();
12007 for (int32_t j = 0; j < realTargetCount; ++j) {
12008 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
12009 possibleTarget, realTargets[j]);
12010 if (commonAncestor) {
12011 realTargets.ReplaceObjectAt(commonAncestor, j);
12012 break;
12015 if (!commonAncestor) {
12016 realTargets.AppendObject(possibleTarget);
12020 mSubtreeModifiedTargets.Clear();
12022 for (const nsCOMPtr<nsINode>& target : realTargets) {
12023 InternalMutationEvent mutation(true, eLegacySubtreeModified);
12024 // MOZ_KnownLive due to bug 1620312
12025 AsyncEventDispatcher::RunDOMEventWhenSafe(MOZ_KnownLive(*target), mutation);
12029 void Document::DestroyElementMaps() {
12030 #ifdef DEBUG
12031 mStyledLinksCleared = true;
12032 #endif
12033 mStyledLinks.Clear();
12034 // Notify ID change listeners before clearing the identifier map.
12035 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
12036 iter.Get()->ClearAndNotify();
12038 mIdentifierMap.Clear();
12039 mComposedShadowRoots.Clear();
12040 mResponsiveContent.Clear();
12041 IncrementExpandoGeneration(*this);
12044 void Document::RefreshLinkHrefs() {
12045 // Get a list of all links we know about. We will reset them, which will
12046 // remove them from the document, so we need a copy of what is in the
12047 // hashtable.
12048 const nsTArray<Link*> linksToNotify = ToArray(mStyledLinks);
12050 // Reset all of our styled links.
12051 nsAutoScriptBlocker scriptBlocker;
12052 for (Link* link : linksToNotify) {
12053 link->ResetLinkState(true);
12057 nsresult Document::CloneDocHelper(Document* clone) const {
12058 clone->mIsStaticDocument = mCreatingStaticClone;
12060 // Init document
12061 nsresult rv = clone->Init(NodePrincipal(), mPartitionedPrincipal);
12062 NS_ENSURE_SUCCESS(rv, rv);
12064 if (mCreatingStaticClone) {
12065 if (mOriginalDocument) {
12066 clone->mOriginalDocument = mOriginalDocument;
12067 } else {
12068 clone->mOriginalDocument = const_cast<Document*>(this);
12070 clone->mOriginalDocument->mLatestStaticClone = clone;
12071 clone->mOriginalDocument->mStaticCloneCount++;
12073 nsCOMPtr<nsILoadGroup> loadGroup;
12075 // |mDocumentContainer| is the container of the document that is being
12076 // created and not the original container. See CreateStaticClone function().
12077 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
12078 if (docLoader) {
12079 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
12081 nsCOMPtr<nsIChannel> channel = GetChannel();
12082 nsCOMPtr<nsIURI> uri;
12083 if (channel) {
12084 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
12085 } else {
12086 uri = Document::GetDocumentURI();
12088 clone->mChannel = channel;
12089 clone->mShouldResistFingerprinting = mShouldResistFingerprinting;
12090 if (uri) {
12091 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
12094 clone->mIsSrcdocDocument = mIsSrcdocDocument;
12095 clone->SetContainer(mDocumentContainer);
12097 // Setup the navigation time. This will be needed by any animations in the
12098 // document, even if they are only paused.
12099 MOZ_ASSERT(!clone->GetNavigationTiming(),
12100 "Navigation time was already set?");
12101 if (mTiming) {
12102 RefPtr<nsDOMNavigationTiming> timing =
12103 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
12104 clone->SetNavigationTiming(timing);
12106 clone->SetCsp(mCSP);
12109 // Now ensure that our clone has the same URI, base URI, and principal as us.
12110 // We do this after the mCreatingStaticClone block above, because that block
12111 // can set the base URI to an incorrect value in cases when base URI
12112 // information came from the channel. So we override explicitly, and do it
12113 // for all these properties, in case ResetToURI messes with any of the rest of
12114 // them.
12115 clone->SetDocumentURI(Document::GetDocumentURI());
12116 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
12117 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
12118 clone->mActiveCookiePrincipal = mActiveCookiePrincipal;
12119 // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
12120 // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
12121 // when printed standalone via window.print() (where there won't be a parent
12122 // document to grab the URI from).
12123 clone->mDocumentBaseURI = GetDocBaseURI();
12124 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
12125 clone->mReferrerInfo =
12126 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
12127 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
12129 bool hasHadScriptObject = true;
12130 nsIScriptGlobalObject* scriptObject =
12131 GetScriptHandlingObject(hasHadScriptObject);
12132 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
12133 if (mCreatingStaticClone) {
12134 // If we're doing a static clone (print, print preview), then we're going to
12135 // be setting a scope object after the clone. It's better to set it only
12136 // once, so we don't do that here. However, we do want to act as if there is
12137 // a script handling object. So we set mHasHadScriptHandlingObject.
12138 clone->mHasHadScriptHandlingObject = true;
12139 } else if (scriptObject) {
12140 clone->SetScriptHandlingObject(scriptObject);
12141 } else {
12142 clone->SetScopeObject(GetScopeObject());
12144 // Make the clone a data document
12145 clone->SetLoadedAsData(
12146 true,
12147 /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
12149 // Misc state
12151 // State from Document
12152 clone->mCharacterSet = mCharacterSet;
12153 clone->mCharacterSetSource = mCharacterSetSource;
12154 clone->SetCompatibilityMode(mCompatMode);
12155 clone->mBidiOptions = mBidiOptions;
12156 clone->mContentLanguage = mContentLanguage;
12157 clone->SetContentType(GetContentTypeInternal());
12158 clone->mSecurityInfo = mSecurityInfo;
12160 // State from Document
12161 clone->mType = mType;
12162 clone->mXMLDeclarationBits = mXMLDeclarationBits;
12163 clone->mBaseTarget = mBaseTarget;
12165 return NS_OK;
12168 void Document::NotifyLoading(bool aNewParentIsLoading,
12169 const ReadyState& aCurrentState,
12170 ReadyState aNewState) {
12171 // Mirror the top-level loading state down to all subdocuments
12172 bool was_loading = mAncestorIsLoading ||
12173 aCurrentState == READYSTATE_LOADING ||
12174 aCurrentState == READYSTATE_INTERACTIVE;
12175 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
12176 aNewState == READYSTATE_INTERACTIVE; // new value for state
12177 bool set_load_state = was_loading != is_loading;
12179 MOZ_LOG(
12180 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12181 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
12182 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
12183 "set_load_state: %d",
12184 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
12185 (int)aNewState, was_loading, is_loading, set_load_state));
12187 mAncestorIsLoading = aNewParentIsLoading;
12188 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
12189 // Tell our innerwindow (and thus TimeoutManager)
12190 nsPIDOMWindowInner* inner = GetInnerWindow();
12191 if (inner) {
12192 inner->SetActiveLoadingState(is_loading);
12194 BrowsingContext* context = GetBrowsingContext();
12195 if (context) {
12196 // Don't use PreOrderWalk to mirror this down; go down one level as a
12197 // time so we can set mAncestorIsLoading and take into account the
12198 // readystates of the subdocument. In the child process it will call
12199 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
12200 // iterate it's children
12201 for (auto& child : context->Children()) {
12202 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12203 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
12204 // Setting ancestor loading on a discarded browsing context has no
12205 // effect.
12206 Unused << child->SetAncestorLoading(is_loading);
12212 void Document::SetReadyStateInternal(ReadyState aReadyState,
12213 bool aUpdateTimingInformation) {
12214 if (aReadyState == READYSTATE_UNINITIALIZED) {
12215 // Transition back to uninitialized happens only to keep assertions happy
12216 // right before readyState transitions to something else. Make this
12217 // transition undetectable by Web content.
12218 mReadyState = aReadyState;
12219 return;
12222 if (IsTopLevelContentDocument()) {
12223 if (aReadyState == READYSTATE_LOADING) {
12224 AddToplevelLoadingDocument(this);
12225 } else if (aReadyState == READYSTATE_COMPLETE) {
12226 RemoveToplevelLoadingDocument(this);
12230 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
12231 SetLoadingOrRestoredFromBFCacheTimeStampToNow();
12233 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
12234 mReadyState = aReadyState;
12235 if (aUpdateTimingInformation && mTiming) {
12236 switch (aReadyState) {
12237 case READYSTATE_LOADING:
12238 mTiming->NotifyDOMLoading(GetDocumentURI());
12239 break;
12240 case READYSTATE_INTERACTIVE:
12241 mTiming->NotifyDOMInteractive(GetDocumentURI());
12242 break;
12243 case READYSTATE_COMPLETE:
12244 mTiming->NotifyDOMComplete(GetDocumentURI());
12245 break;
12246 default:
12247 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
12248 break;
12251 // At the time of loading start, we don't have timing object, record time.
12253 if (READYSTATE_INTERACTIVE == aReadyState &&
12254 NodePrincipal()->IsSystemPrincipal()) {
12255 if (!mXULPersist && XRE_IsParentProcess()) {
12256 mXULPersist = new XULPersist(this);
12257 mXULPersist->Init();
12259 if (!mChromeObserver) {
12260 mChromeObserver = new ChromeObserver(this);
12261 mChromeObserver->Init();
12265 if (aUpdateTimingInformation) {
12266 RecordNavigationTiming(aReadyState);
12269 AsyncEventDispatcher::RunDOMEventWhenSafe(
12270 *this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
12273 void Document::GetReadyState(nsAString& aReadyState) const {
12274 switch (mReadyState) {
12275 case READYSTATE_LOADING:
12276 aReadyState.AssignLiteral(u"loading");
12277 break;
12278 case READYSTATE_INTERACTIVE:
12279 aReadyState.AssignLiteral(u"interactive");
12280 break;
12281 case READYSTATE_COMPLETE:
12282 aReadyState.AssignLiteral(u"complete");
12283 break;
12284 default:
12285 aReadyState.AssignLiteral(u"uninitialized");
12289 void Document::SuppressEventHandling(uint32_t aIncrease) {
12290 mEventsSuppressed += aIncrease;
12291 if (mEventsSuppressed == aIncrease) {
12292 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
12293 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12296 UpdateFrameRequestCallbackSchedulingState();
12297 for (uint32_t i = 0; i < aIncrease; ++i) {
12298 ScriptLoader()->AddExecuteBlocker();
12301 EnumerateSubDocuments([aIncrease](Document& aSubDoc) {
12302 aSubDoc.SuppressEventHandling(aIncrease);
12303 return CallState::Continue;
12307 void Document::NotifyAbortedLoad() {
12308 // If we still have outstanding work blocking DOMContentLoaded,
12309 // then don't try to change the readystate now, but wait until
12310 // they finish and then do so.
12311 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
12312 mSetCompleteAfterDOMContentLoaded = true;
12313 return;
12316 // Otherwise we're fully done at this point, so set the
12317 // readystate to complete.
12318 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
12319 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
12323 MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents(
12324 nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) {
12325 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
12326 if (MOZ_UNLIKELY(!fm)) {
12327 return;
12330 nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments);
12331 for (uint32_t i = 0; i < documents.Length(); ++i) {
12332 nsCOMPtr<Document> document = std::move(documents[i]);
12333 // NB: Don't bother trying to fire delayed events on documents that were
12334 // closed before this event ran.
12335 if (!document->EventHandlingSuppressed()) {
12336 fm->FireDelayedEvents(document);
12337 RefPtr<PresShell> presShell = document->GetPresShell();
12338 if (presShell) {
12339 // Only fire events for active documents.
12340 bool fire = aFireEvents && document->GetInnerWindow() &&
12341 document->GetInnerWindow()->IsCurrentInnerWindow();
12342 presShell->FireOrClearDelayedEvents(fire);
12344 document->FireOrClearPostMessageEvents(aFireEvents);
12349 void Document::PreloadPictureClosed() {
12350 MOZ_ASSERT(mPreloadPictureDepth > 0);
12351 mPreloadPictureDepth--;
12352 if (mPreloadPictureDepth == 0) {
12353 mPreloadPictureFoundSource.SetIsVoid(true);
12357 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
12358 const nsAString& aSizesAttr,
12359 const nsAString& aTypeAttr,
12360 const nsAString& aMediaAttr) {
12361 // Nested pictures are not valid syntax, so while we'll eventually load them,
12362 // it's not worth tracking sources mixed between nesting levels to preload
12363 // them effectively.
12364 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
12365 // <picture> selects the first matching source, so if this returns a URI we
12366 // needn't consider new sources until a new <picture> is encountered.
12367 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
12368 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
12369 aMediaAttr, mPreloadPictureFoundSource);
12370 if (found && mPreloadPictureFoundSource.IsVoid()) {
12371 // Found an empty source, which counts
12372 mPreloadPictureFoundSource.SetIsVoid(false);
12377 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
12378 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
12379 const nsAString& aSizesAttr, bool* aIsImgSet) {
12380 nsString sourceURL;
12381 bool isImgSet;
12382 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
12383 // We're in a <picture> element and found a URI from a source previous to
12384 // this image, use it.
12385 sourceURL = mPreloadPictureFoundSource;
12386 isImgSet = true;
12387 } else {
12388 // Otherwise try to use this <img> as a source
12389 HTMLImageElement::SelectSourceForTagWithAttrs(
12390 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
12391 VoidString(), sourceURL);
12392 isImgSet = !aSrcsetAttr.IsEmpty();
12395 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
12396 if (sourceURL.IsEmpty()) {
12397 return nullptr;
12400 // Construct into URI using passed baseURI (the parser may know of base URI
12401 // changes that have not reached us)
12402 nsresult rv;
12403 nsCOMPtr<nsIURI> uri;
12404 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
12405 this, aBaseURI);
12406 if (NS_FAILED(rv)) {
12407 return nullptr;
12410 *aIsImgSet = isImgSet;
12412 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
12413 // this this <picture> share the same <sources> (though this is not valid per
12414 // spec)
12415 return uri.forget();
12418 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
12419 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
12420 bool aLinkPreload, uint64_t aEarlyHintPreloaderId,
12421 const nsAString& aFetchPriority) {
12422 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
12423 nsContentUtils::CORSModeToLoadImageFlags(
12424 Element::StringToCORSMode(aCrossOriginAttr));
12426 nsContentPolicyType policyType =
12427 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
12428 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
12430 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12431 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12433 RefPtr<imgRequestProxy> request;
12435 nsLiteralString initiator = aEarlyHintPreloaderId
12436 ? u"early-hints"_ns
12437 : (aLinkPreload ? u"link"_ns : u"img"_ns);
12439 nsresult rv = nsContentUtils::LoadImage(
12440 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
12441 nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request),
12442 policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId,
12443 nsGenericHTMLElement::ToFetchPriority(aFetchPriority));
12445 // Pin image-reference to avoid evicting it from the img-cache before
12446 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
12447 // unlink
12448 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
12449 mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
12453 void Document::MaybePreLoadImage(nsIURI* aUri,
12454 const nsAString& aCrossOriginAttr,
12455 ReferrerPolicyEnum aReferrerPolicy,
12456 bool aIsImgSet, bool aLinkPreload,
12457 const nsAString& aFetchPriority) {
12458 const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr);
12459 if (aLinkPreload) {
12460 // Check if the image was already preloaded in this document to avoid
12461 // duplicate preloading.
12462 PreloadHashKey key =
12463 PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode);
12464 if (!mPreloadService.PreloadExists(key)) {
12465 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12466 aLinkPreload, 0, aFetchPriority);
12468 return;
12471 // Early exit if the img is already present in the img-cache
12472 // which indicates that the "real" load has already started and
12473 // that we shouldn't preload it.
12474 if (nsContentUtils::IsImageAvailable(aUri, NodePrincipal(), corsMode, this)) {
12475 return;
12478 // Image not in cache - trigger preload
12479 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload,
12480 0, aFetchPriority);
12483 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
12484 if (!StaticPrefs::network_preconnect()) {
12485 return;
12488 NS_MutateURI mutator(aOrigURI);
12489 if (NS_FAILED(mutator.GetStatus())) {
12490 return;
12493 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
12494 // which ignores the path and uses only the origin. The other is for the
12495 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
12496 // non-Anonymous preconnects create different connections on the wire and
12497 // therefore should not be considred duplicates of each other and we
12498 // normalize the path before putting it in the hash to accomplish that.
12500 if (aCORSMode == CORS_ANONYMOUS) {
12501 mutator.SetPathQueryRef("/anonymous"_ns);
12502 } else {
12503 mutator.SetPathQueryRef("/"_ns);
12506 nsCOMPtr<nsIURI> uri;
12507 nsresult rv = mutator.Finalize(uri);
12508 if (NS_FAILED(rv)) {
12509 return;
12512 const bool existingEntryFound =
12513 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
12514 if (entry) {
12515 return true;
12517 entry.Insert(true);
12518 return false;
12520 if (existingEntryFound) {
12521 return;
12524 nsCOMPtr<nsISpeculativeConnect> speculator =
12525 mozilla::components::IO::Service();
12526 if (!speculator) {
12527 return;
12530 OriginAttributes oa;
12531 StoragePrincipalHelper::GetOriginAttributesForNetworkState(this, oa);
12532 speculator->SpeculativeConnectWithOriginAttributesNative(
12533 uri, std::move(oa), nullptr, aCORSMode == CORS_ANONYMOUS);
12536 void Document::ForgetImagePreload(nsIURI* aURI) {
12537 // Checking count is faster than hashing the URI in the common
12538 // case of empty table.
12539 if (mPreloadingImages.Count() != 0) {
12540 nsCOMPtr<imgIRequest> req;
12541 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
12542 if (req) {
12543 // Make sure to cancel the request so imagelib knows it's gone.
12544 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
12549 void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates,
12550 bool aNotify) {
12551 const DocumentState oldStates = mState;
12552 if (aMaybeChangedStates.HasAtLeastOneOfStates(
12553 DocumentState::ALL_LOCALEDIR_BITS)) {
12554 mState &= ~DocumentState::ALL_LOCALEDIR_BITS;
12555 if (IsDocumentRightToLeft()) {
12556 mState |= DocumentState::RTL_LOCALE;
12557 } else {
12558 mState |= DocumentState::LTR_LOCALE;
12562 if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) {
12563 BrowsingContext* bc = GetBrowsingContext();
12564 if (!bc || !bc->GetIsActiveBrowserWindow()) {
12565 mState |= DocumentState::WINDOW_INACTIVE;
12566 } else {
12567 mState &= ~DocumentState::WINDOW_INACTIVE;
12571 const DocumentState changedStates = oldStates ^ mState;
12572 if (aNotify && !changedStates.IsEmpty()) {
12573 if (PresShell* ps = GetObservingPresShell()) {
12574 ps->DocumentStatesChanged(changedStates);
12579 namespace {
12582 * Stub for LoadSheet(), since all we want is to get the sheet into
12583 * the CSSLoader's style cache
12585 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
12586 ~StubCSSLoaderObserver() = default;
12588 public:
12589 NS_IMETHOD
12590 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
12591 NS_DECL_ISUPPORTS
12593 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
12595 } // namespace
12597 SheetPreloadStatus Document::PreloadStyle(
12598 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
12599 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aNonce,
12600 const nsAString& aIntegrity, css::StylePreloadKind aKind,
12601 uint64_t aEarlyHintPreloaderId, const nsAString& aFetchPriority) {
12602 MOZ_ASSERT(aKind != css::StylePreloadKind::None);
12604 // The CSSLoader will retain this object after we return.
12605 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
12607 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12608 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12610 // Charset names are always ASCII.
12611 auto result = CSSLoader()->LoadSheet(
12612 uri, aKind, aEncoding, referrerInfo, obs, aEarlyHintPreloaderId,
12613 Element::StringToCORSMode(aCrossOriginAttr), aNonce, aIntegrity,
12614 nsGenericHTMLElement::ToFetchPriority(aFetchPriority));
12615 if (result.isErr()) {
12616 return SheetPreloadStatus::Errored;
12618 RefPtr<StyleSheet> sheet = result.unwrap();
12619 if (sheet->IsComplete()) {
12620 return SheetPreloadStatus::AlreadyComplete;
12622 return SheetPreloadStatus::InProgress;
12625 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
12626 return CSSLoader()
12627 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
12628 .unwrapOr(nullptr);
12631 void Document::ResetDocumentDirection() {
12632 if (!nsContentUtils::IsChromeDoc(this)) {
12633 return;
12635 UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true);
12638 bool Document::IsDocumentRightToLeft() {
12639 if (!nsContentUtils::IsChromeDoc(this)) {
12640 return false;
12642 // setting the localedir attribute on the root element forces a
12643 // specific direction for the document.
12644 Element* element = GetRootElement();
12645 if (element) {
12646 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
12647 nullptr};
12648 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
12649 strings, eCaseMatters)) {
12650 case 0:
12651 return false;
12652 case 1:
12653 return true;
12654 default:
12655 break; // otherwise, not a valid value, so fall through
12659 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
12660 !mDocumentURI->SchemeIs("resource")) {
12661 return false;
12664 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
12667 class nsDelayedEventDispatcher : public Runnable {
12668 public:
12669 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
12670 : mozilla::Runnable("nsDelayedEventDispatcher"),
12671 mDocuments(std::move(aDocuments)) {}
12672 virtual ~nsDelayedEventDispatcher() = default;
12674 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
12675 // bug 1535398.
12676 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
12677 FireOrClearDelayedEvents(std::move(mDocuments), true);
12678 return NS_OK;
12681 private:
12682 nsTArray<nsCOMPtr<Document>> mDocuments;
12685 static void GetAndUnsuppressSubDocuments(
12686 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
12687 if (aDocument.EventHandlingSuppressed() > 0) {
12688 aDocument.DecreaseEventSuppression();
12689 aDocument.ScriptLoader()->RemoveExecuteBlocker();
12691 aDocuments.AppendElement(&aDocument);
12692 aDocument.EnumerateSubDocuments([&aDocuments](Document& aSubDoc) {
12693 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
12694 return CallState::Continue;
12698 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
12699 nsTArray<nsCOMPtr<Document>> documents;
12700 GetAndUnsuppressSubDocuments(*this, documents);
12702 for (nsCOMPtr<Document>& doc : documents) {
12703 if (!doc->EventHandlingSuppressed()) {
12704 if (WindowGlobalChild* wgc = doc->GetWindowGlobalChild()) {
12705 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12708 MOZ_ASSERT(NS_IsMainThread());
12709 nsTArray<RefPtr<net::ChannelEventQueue>> queues =
12710 std::move(doc->mSuspendedQueues);
12711 for (net::ChannelEventQueue* queue : queues) {
12712 queue->Resume();
12715 // If there have been any events driven by the refresh driver which were
12716 // delayed due to events being suppressed in this document, make sure
12717 // there is a refresh scheduled soon so the events will run.
12718 if (doc->mHasDelayedRefreshEvent) {
12719 doc->mHasDelayedRefreshEvent = false;
12721 if (doc->mPresShell) {
12722 nsRefreshDriver* rd =
12723 doc->mPresShell->GetPresContext()->RefreshDriver();
12724 rd->RunDelayedEventsSoon();
12730 if (aFireEvents) {
12731 MOZ_RELEASE_ASSERT(NS_IsMainThread());
12732 nsCOMPtr<nsIRunnable> ded =
12733 new nsDelayedEventDispatcher(std::move(documents));
12734 Dispatch(ded.forget());
12735 } else {
12736 FireOrClearDelayedEvents(std::move(documents), false);
12740 bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
12741 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
12744 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
12745 MOZ_ASSERT(NS_IsMainThread());
12746 MOZ_ASSERT(EventHandlingSuppressed());
12747 mSuspendedQueues.AppendElement(aQueue);
12750 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
12751 MOZ_ASSERT(NS_IsMainThread());
12753 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
12754 mSuspendedPostMessageEvents.AppendElement(aEvent);
12755 return true;
12757 return false;
12760 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
12761 nsTArray<RefPtr<PostMessageEvent>> events =
12762 std::move(mSuspendedPostMessageEvents);
12764 if (aFireEvents) {
12765 for (PostMessageEvent* event : events) {
12766 event->Run();
12771 void Document::SetSuppressedEventListener(EventListener* aListener) {
12772 mSuppressedEventListener = aListener;
12773 EnumerateSubDocuments([&](Document& aDocument) {
12774 aDocument.SetSuppressedEventListener(aListener);
12775 return CallState::Continue;
12779 bool Document::IsActive() const {
12780 return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
12781 !GetBrowsingContext()->IsInBFCache();
12784 nsISupports* Document::GetCurrentContentSink() {
12785 return mParser ? mParser->GetContentSink() : nullptr;
12788 Document* Document::GetTemplateContentsOwner() {
12789 if (!mTemplateContentsOwner) {
12790 bool hasHadScriptObject = true;
12791 nsIScriptGlobalObject* scriptObject =
12792 GetScriptHandlingObject(hasHadScriptObject);
12794 nsCOMPtr<Document> document;
12795 nsresult rv = NS_NewDOMDocument(
12796 getter_AddRefs(document),
12797 u""_ns, // aNamespaceURI
12798 u""_ns, // aQualifiedName
12799 nullptr, // aDoctype
12800 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
12801 true, // aLoadedAsData
12802 scriptObject, // aEventObject
12803 IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
12804 NS_ENSURE_SUCCESS(rv, nullptr);
12806 mTemplateContentsOwner = document;
12807 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
12809 if (!scriptObject) {
12810 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
12813 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
12815 // Set |mTemplateContentsOwner| as the template contents owner of itself so
12816 // that it is the template contents owner of nested template elements.
12817 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
12820 MOZ_ASSERT(mTemplateContentsOwner->IsTemplateContentsOwner());
12821 return mTemplateContentsOwner;
12824 // https://html.spec.whatwg.org/#the-autofocus-attribute
12825 void Document::ElementWithAutoFocusInserted(Element* aAutoFocusCandidate) {
12826 BrowsingContext* bc = GetBrowsingContext();
12827 if (!bc) {
12828 return;
12831 // If target is not fully active, then return.
12832 if (!IsCurrentActiveDocument()) {
12833 return;
12836 // If target's active sandboxing flag set has the sandboxed automatic features
12837 // browsing context flag, then return.
12838 if (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES) {
12839 return;
12842 // For each ancestorBC of target's browsing context's ancestor browsing
12843 // contexts: if ancestorBC's active document's origin is not same origin with
12844 // target's origin, then return.
12845 while (bc) {
12846 BrowsingContext* parent = bc->GetParent();
12847 if (!parent) {
12848 break;
12850 // AncestorBC is not the same site
12851 if (!parent->IsInProcess()) {
12852 return;
12855 Document* currentDocument = bc->GetDocument();
12856 if (!currentDocument) {
12857 return;
12860 Document* parentDocument = parent->GetDocument();
12861 if (!parentDocument) {
12862 return;
12865 // Not same origin
12866 if (!currentDocument->NodePrincipal()->Equals(
12867 parentDocument->NodePrincipal())) {
12868 return;
12871 bc = parent;
12873 MOZ_ASSERT(bc->IsTop());
12875 Document* topDocument = bc->GetDocument();
12876 MOZ_ASSERT(topDocument);
12877 topDocument->AppendAutoFocusCandidateToTopDocument(aAutoFocusCandidate);
12880 void Document::ScheduleFlushAutoFocusCandidates() {
12881 MOZ_ASSERT(mPresShell && mPresShell->DidInitialize());
12882 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12883 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
12884 rd->ScheduleAutoFocusFlush(this);
12888 void Document::AppendAutoFocusCandidateToTopDocument(
12889 Element* aAutoFocusCandidate) {
12890 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12891 if (mAutoFocusFired) {
12892 return;
12895 if (!HasAutoFocusCandidates()) {
12896 // PresShell may be initialized later
12897 if (mPresShell && mPresShell->DidInitialize()) {
12898 ScheduleFlushAutoFocusCandidates();
12902 nsWeakPtr element = do_GetWeakReference(aAutoFocusCandidate);
12903 mAutoFocusCandidates.RemoveElement(element);
12904 mAutoFocusCandidates.AppendElement(element);
12907 void Document::SetAutoFocusFired() {
12908 mAutoFocusCandidates.Clear();
12909 mAutoFocusFired = true;
12912 // https://html.spec.whatwg.org/#flush-autofocus-candidates
12913 void Document::FlushAutoFocusCandidates() {
12914 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12915 if (mAutoFocusFired) {
12916 return;
12919 if (!mPresShell) {
12920 return;
12923 MOZ_ASSERT(HasAutoFocusCandidates());
12924 MOZ_ASSERT(mPresShell->DidInitialize());
12926 nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetWindow();
12927 // We should be the top document
12928 if (!topWindow) {
12929 return;
12932 #ifdef DEBUG
12934 // Trying to find the top window (equivalent to window.top).
12935 nsCOMPtr<nsPIDOMWindowOuter> top = topWindow->GetInProcessTop();
12936 MOZ_ASSERT(topWindow == top);
12938 #endif
12940 // Don't steal the focus from the user
12941 if (topWindow->GetFocusedElement()) {
12942 SetAutoFocusFired();
12943 return;
12946 MOZ_ASSERT(mDocumentURI);
12947 nsAutoCString ref;
12948 // GetRef never fails
12949 nsresult rv = mDocumentURI->GetRef(ref);
12950 if (NS_SUCCEEDED(rv) &&
12951 nsContentUtils::GetTargetElement(this, NS_ConvertUTF8toUTF16(ref))) {
12952 SetAutoFocusFired();
12953 return;
12956 nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mAutoFocusCandidates);
12957 while (iter.HasMore()) {
12958 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(iter.GetNext());
12959 if (!autoFocusElement) {
12960 continue;
12962 RefPtr<Document> autoFocusElementDoc = autoFocusElement->OwnerDoc();
12963 // Get the latest info about the frame and allow scripts
12964 // to run which might affect the focusability of this element.
12965 autoFocusElementDoc->FlushPendingNotifications(FlushType::Frames);
12967 // Above layout flush may cause the PresShell to disappear.
12968 if (!mPresShell) {
12969 return;
12972 // Re-get the element because the ownerDoc() might have changed
12973 autoFocusElementDoc = autoFocusElement->OwnerDoc();
12974 BrowsingContext* bc = autoFocusElementDoc->GetBrowsingContext();
12975 if (!bc) {
12976 continue;
12979 // If doc is not fully active, then remove element from candidates, and
12980 // continue.
12981 if (!autoFocusElementDoc->IsCurrentActiveDocument()) {
12982 iter.Remove();
12983 continue;
12986 nsCOMPtr<nsIContentSink> sink =
12987 do_QueryInterface(autoFocusElementDoc->GetCurrentContentSink());
12988 if (sink) {
12989 nsHtml5TreeOpExecutor* executor =
12990 static_cast<nsHtml5TreeOpExecutor*>(sink->AsExecutor());
12991 if (executor) {
12992 // This is a HTML5 document
12993 MOZ_ASSERT(autoFocusElementDoc->IsHTMLDocument());
12994 // If doc's script-blocking style sheet counter is greater than 0, th
12995 // return.
12996 if (executor->WaitForPendingSheets()) {
12997 // In this case, element is the currently-best candidate, but doc is
12998 // not ready for autofocusing. We'll try again next time flush
12999 // autofocus candidates is called.
13000 ScheduleFlushAutoFocusCandidates();
13001 return;
13006 // The autofocus element could be moved to a different
13007 // top level BC.
13008 if (bc->Top()->GetDocument() != this) {
13009 continue;
13012 iter.Remove();
13014 // Let inclusiveAncestorDocuments be a list consisting of doc, plus the
13015 // active documents of each of doc's browsing context's ancestor browsing
13016 // contexts.
13017 // If any Document in inclusiveAncestorDocuments has non-null target
13018 // element, then continue.
13019 bool shouldFocus = true;
13020 while (bc) {
13021 Document* doc = bc->GetDocument();
13022 if (!doc) {
13023 shouldFocus = false;
13024 break;
13027 nsIURI* uri = doc->GetDocumentURI();
13028 if (!uri) {
13029 shouldFocus = false;
13030 break;
13033 nsAutoCString ref;
13034 nsresult rv = uri->GetRef(ref);
13035 // If there is an element in the document tree that has an ID equal to
13036 // fragment
13037 if (NS_SUCCEEDED(rv) &&
13038 nsContentUtils::GetTargetElement(doc, NS_ConvertUTF8toUTF16(ref))) {
13039 shouldFocus = false;
13040 break;
13042 bc = bc->GetParent();
13045 if (!shouldFocus) {
13046 continue;
13049 MOZ_ASSERT(topWindow);
13050 if (TryAutoFocusCandidate(*autoFocusElement)) {
13051 // We've successfully autofocused an element, don't
13052 // need to try to focus the rest.
13053 SetAutoFocusFired();
13054 break;
13058 if (HasAutoFocusCandidates()) {
13059 ScheduleFlushAutoFocusCandidates();
13063 bool Document::TryAutoFocusCandidate(Element& aElement) {
13064 const FocusOptions options;
13065 if (RefPtr<Element> target = nsFocusManager::GetTheFocusableArea(
13066 &aElement, nsFocusManager::ProgrammaticFocusFlags(options))) {
13067 target->Focus(options, CallerType::NonSystem, IgnoreErrors());
13068 return true;
13071 return false;
13074 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
13075 if (!aDocumentURI) {
13076 return;
13079 nsAutoCString ref;
13081 // Since all URI's that pass through here aren't URL's we can't
13082 // rely on the nsIURI implementation for providing a way for
13083 // finding the 'ref' part of the URI, we'll haveto revert to
13084 // string routines for finding the data past '#'
13086 nsresult rv = aDocumentURI->GetSpec(ref);
13087 if (NS_FAILED(rv)) {
13088 Unused << aDocumentURI->GetRef(mScrollToRef);
13089 return;
13092 nsReadingIterator<char> start, end;
13094 ref.BeginReading(start);
13095 ref.EndReading(end);
13097 if (FindCharInReadable('#', start, end)) {
13098 ++start; // Skip over the '#'
13100 mScrollToRef = Substring(start, end);
13104 // https://html.spec.whatwg.org/#scrolling-to-a-fragment
13105 void Document::ScrollToRef() {
13106 RefPtr<PresShell> presShell = GetPresShell();
13107 if (!presShell) {
13108 return;
13110 if (mScrolledToRefAlready) {
13111 presShell->ScrollToAnchor();
13112 return;
13115 // If text directives is non-null, then highlight the text directives and
13116 // scroll to the last one.
13117 // XXX(:jjaschke): Document policy integration should happen here
13118 // as soon as https://bugzil.la/1860915 lands.
13119 // XXX(:jjaschke): Same goes for User Activation and security aspects,
13120 // tracked in https://bugzil.la/1888756.
13121 const bool didScrollToTextFragment =
13122 presShell->HighlightAndGoToTextFragment(true);
13124 // 2. If fragment is the empty string and no text directives have been
13125 // scrolled to, then return the special value top of the document.
13126 if (didScrollToTextFragment || mScrollToRef.IsEmpty()) {
13127 return;
13129 // 3. Let potentialIndicatedElement be the result of finding a potential
13130 // indicated element given document and fragment.
13131 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
13132 auto rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
13134 // 4. If potentialIndicatedElement is not null, then return
13135 // potentialIndicatedElement.
13136 if (NS_SUCCEEDED(rv)) {
13137 mScrolledToRefAlready = true;
13138 return;
13141 // 5. Let fragmentBytes be the result of percent-decoding fragment.
13142 nsAutoCString fragmentBytes;
13143 const bool unescaped =
13144 NS_UnescapeURL(mScrollToRef.Data(), mScrollToRef.Length(),
13145 /* aFlags = */ 0, fragmentBytes);
13147 if (!unescaped || fragmentBytes.IsEmpty()) {
13148 // Another attempt is only necessary if characters were unescaped.
13149 return;
13152 // 6. Let decodedFragment be the result of running UTF-8 decode without BOM on
13153 // fragmentBytes.
13154 nsAutoString decodedFragment;
13155 rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(fragmentBytes, decodedFragment);
13156 NS_ENSURE_SUCCESS_VOID(rv);
13158 // 7. Set potentialIndicatedElement to the result of finding a potential
13159 // indicated element given document and decodedFragment.
13160 rv = presShell->GoToAnchor(decodedFragment,
13161 mChangeScrollPosWhenScrollingToRef);
13162 if (NS_SUCCEEDED(rv)) {
13163 mScrolledToRefAlready = true;
13167 void Document::RegisterActivityObserver(nsISupports* aSupports) {
13168 if (!mActivityObservers) {
13169 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
13171 mActivityObservers->Insert(aSupports);
13174 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
13175 if (!mActivityObservers) {
13176 return false;
13178 return mActivityObservers->EnsureRemoved(aSupports);
13181 void Document::EnumerateActivityObservers(
13182 ActivityObserverEnumerator aEnumerator) {
13183 if (!mActivityObservers) {
13184 return;
13187 const auto keyArray =
13188 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
13189 for (auto& observer : keyArray) {
13190 aEnumerator(observer.get());
13194 void Document::RegisterPendingLinkUpdate(Link* aLink) {
13195 if (aLink->HasPendingLinkUpdate()) {
13196 return;
13199 aLink->SetHasPendingLinkUpdate();
13201 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
13202 nsCOMPtr<nsIRunnable> event =
13203 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
13204 &Document::FlushPendingLinkUpdates);
13205 // Do this work in a second in the worst case.
13206 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
13207 EventQueuePriority::Idle);
13208 if (NS_FAILED(rv)) {
13209 // If during shutdown posting a runnable doesn't succeed, we probably
13210 // don't need to update link states.
13211 return;
13213 mHasLinksToUpdateRunnable = true;
13216 mLinksToUpdate.InfallibleAppend(aLink);
13219 void Document::FlushPendingLinkUpdates() {
13220 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
13221 MOZ_ASSERT(mHasLinksToUpdateRunnable);
13222 mHasLinksToUpdateRunnable = false;
13224 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
13225 mFlushingPendingLinkUpdates = true;
13227 while (!mLinksToUpdate.IsEmpty()) {
13228 LinksToUpdateList links(std::move(mLinksToUpdate));
13229 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
13230 Link* link = iter.Get();
13231 Element* element = link->GetElement();
13232 if (element->OwnerDoc() == this) {
13233 link->ClearHasPendingLinkUpdate();
13234 if (element->IsInComposedDoc()) {
13235 link->TriggerLinkUpdate(/* aNotify = */ true);
13243 * Retrieves the node in a static-clone document that corresponds to aOrigNode,
13244 * which is a node in the original document from which aStaticClone was cloned.
13246 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
13247 Document& aStaticClone) {
13248 MOZ_ASSERT(aOrigNode);
13250 // Selections in anonymous subtrees aren't supported.
13251 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
13252 return nullptr;
13255 // If the node is disconnected, this is a bug in the selection code, but it
13256 // can happen with shadow DOM so handle it.
13257 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
13258 return nullptr;
13261 AutoTArray<Maybe<uint32_t>, 32> indexArray;
13262 const nsINode* current = aOrigNode;
13263 while (const nsINode* parent = current->GetParentNode()) {
13264 Maybe<uint32_t> index = parent->ComputeIndexOf(current);
13265 NS_ENSURE_TRUE(index.isSome(), nullptr);
13266 indexArray.AppendElement(std::move(index));
13267 current = parent;
13269 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
13270 nsINode* correspondingNode = [&]() -> nsINode* {
13271 if (current->IsDocument()) {
13272 return &aStaticClone;
13274 const auto* shadow = ShadowRoot::FromNode(*current);
13275 if (!shadow) {
13276 return nullptr;
13278 nsINode* correspondingHost =
13279 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
13280 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
13281 return nullptr;
13283 return correspondingHost->AsElement()->GetShadowRoot();
13284 }();
13286 if (NS_WARN_IF(!correspondingNode)) {
13287 return nullptr;
13289 for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
13290 correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
13291 NS_ENSURE_TRUE(correspondingNode, nullptr);
13293 return correspondingNode;
13297 * Caches the selection ranges from the source document onto the static clone in
13298 * case the "Print Selection Only" functionality is invoked.
13300 * Note that we cannot use the selection obtained from GetOriginalDocument()
13301 * since that selection may have mutated after the print was invoked.
13303 * Note also that because nsRange objects point into a specific document's
13304 * nodes, we cannot reuse an array of nsRange objects across multiple static
13305 * clone documents. For that reason we cache a new array of ranges on each
13306 * static clone that we create.
13308 * TODO(emilio): This can be simplified once we don't re-clone from static
13309 * documents.
13311 * @param aSourceDoc the document from which we are caching selection ranges
13312 * @param aStaticClone the document that will hold the cache
13313 * @return true if a selection range was cached
13315 static void CachePrintSelectionRanges(const Document& aSourceDoc,
13316 Document& aStaticClone) {
13317 MOZ_ASSERT(aStaticClone.IsStaticDocument());
13318 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc));
13319 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
13321 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
13323 // When the user opts to "Print Selection Only", the print code prefers any
13324 // selection in the static clone corresponding to the focused frame. If this
13325 // is that static clone, flag it for the printing code:
13326 const bool isFocusedDoc = [&] {
13327 if (sourceDocIsStatic) {
13328 return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc));
13330 nsPIDOMWindowOuter* window = aSourceDoc.GetWindow();
13331 if (!window) {
13332 return false;
13334 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
13335 if (!rootWindow) {
13336 return false;
13338 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
13339 nsFocusManager::GetFocusedDescendant(rootWindow,
13340 nsFocusManager::eIncludeAllDescendants,
13341 getter_AddRefs(focusedWindow));
13342 return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc;
13343 }();
13344 if (isFocusedDoc) {
13345 aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc,
13346 reinterpret_cast<void*>(true));
13349 const Selection* origSelection = nullptr;
13350 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
13352 if (sourceDocIsStatic) {
13353 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
13354 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
13355 } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
13356 origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
13359 if (!origSelection && !origRanges) {
13360 return;
13363 const uint32_t rangeCount =
13364 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
13365 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
13367 for (const uint32_t i : IntegerRange(rangeCount)) {
13368 MOZ_ASSERT_IF(!sourceDocIsStatic,
13369 origSelection->RangeCount() == rangeCount);
13370 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
13371 : origSelection->GetRangeAt(i);
13372 MOZ_ASSERT(range);
13373 nsINode* startContainer = range->GetStartContainer();
13374 nsINode* endContainer = range->GetEndContainer();
13376 if (!startContainer || !endContainer) {
13377 continue;
13380 nsINode* startNode =
13381 GetCorrespondingNodeInDocument(startContainer, aStaticClone);
13382 nsINode* endNode =
13383 GetCorrespondingNodeInDocument(endContainer, aStaticClone);
13385 if (NS_WARN_IF(!startNode || !endNode)) {
13386 continue;
13389 RefPtr<nsRange> clonedRange =
13390 nsRange::Create(startNode, range->StartOffset(), endNode,
13391 range->EndOffset(), IgnoreErrors());
13392 if (clonedRange && !clonedRange->Collapsed()) {
13393 printRanges->AppendElement(std::move(clonedRange));
13397 if (printRanges->IsEmpty()) {
13398 return;
13401 aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
13402 printRanges.release(),
13403 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
13406 already_AddRefed<Document> Document::CreateStaticClone(
13407 nsIDocShell* aCloneContainer, nsIDocumentViewer* aViewer,
13408 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
13409 MOZ_ASSERT(!mCreatingStaticClone);
13410 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
13411 MOZ_DIAGNOSTIC_ASSERT(aViewer);
13413 mCreatingStaticClone = true;
13414 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
13415 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
13417 auto raii = MakeScopeExit([&] {
13418 RemoveProperty(nsGkAtoms::adoptedsheetclones);
13419 mCreatingStaticClone = false;
13422 // Make document use different container during cloning.
13424 // FIXME(emilio): Why is this needed?
13425 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
13426 SetContainer(nsDocShell::Cast(aCloneContainer));
13427 IgnoredErrorResult rv;
13428 nsCOMPtr<nsINode> clonedNode = CloneNode(true, rv);
13429 SetContainer(originalShell);
13430 if (rv.Failed()) {
13431 return nullptr;
13434 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
13435 if (!clonedDoc) {
13436 return nullptr;
13439 size_t sheetsCount = SheetCount();
13440 for (size_t i = 0; i < sheetsCount; ++i) {
13441 RefPtr<StyleSheet> sheet = SheetAt(i);
13442 if (sheet) {
13443 if (sheet->IsApplicable()) {
13444 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13445 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13446 if (clonedSheet) {
13447 clonedDoc->AddStyleSheet(clonedSheet);
13452 clonedDoc->CloneAdoptedSheetsFrom(*this);
13454 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
13455 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
13456 for (StyleSheet* sheet : sheets) {
13457 if (sheet->IsApplicable()) {
13458 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13459 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13460 if (clonedSheet) {
13461 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
13462 clonedSheet);
13468 // Font faces created with the JS API will not be reflected in the
13469 // stylesheets and need to be copied over to the cloned document.
13470 if (const FontFaceSet* set = GetFonts()) {
13471 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
13474 clonedDoc->mReferrerInfo =
13475 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
13476 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
13477 CachePrintSelectionRanges(*this, *clonedDoc);
13479 // We're done with the clone, embed ourselves into the document viewer and
13480 // clone our children. The order here is pretty important, because our
13481 // document our document needs to have an owner global before we can create
13482 // the frame loaders for subdocuments.
13483 aViewer->SetDocument(clonedDoc);
13485 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
13487 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
13488 for (const auto& clone : pendingClones) {
13489 RefPtr<Element> element = do_QueryObject(clone.mElement);
13490 RefPtr<nsFrameLoader> frameLoader =
13491 nsFrameLoader::Create(element, /* aNetworkCreated */ false);
13493 if (NS_WARN_IF(!frameLoader)) {
13494 continue;
13497 clone.mElement->SetFrameLoader(frameLoader);
13499 nsresult rv = frameLoader->FinishStaticClone(
13500 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
13501 Unused << NS_WARN_IF(NS_FAILED(rv));
13504 return clonedDoc.forget();
13507 void Document::UnlinkOriginalDocumentIfStatic() {
13508 if (IsStaticDocument() && mOriginalDocument) {
13509 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
13510 mOriginalDocument->mStaticCloneCount--;
13511 mOriginalDocument = nullptr;
13513 MOZ_ASSERT(!mOriginalDocument);
13516 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
13517 int32_t* aHandle) {
13518 nsresult rv = mFrameRequestManager.Schedule(aCallback, aHandle);
13519 if (NS_FAILED(rv)) {
13520 return rv;
13523 UpdateFrameRequestCallbackSchedulingState();
13524 return NS_OK;
13527 void Document::CancelFrameRequestCallback(int32_t aHandle) {
13528 if (mFrameRequestManager.Cancel(aHandle)) {
13529 UpdateFrameRequestCallbackSchedulingState();
13533 bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
13534 return mFrameRequestManager.IsCanceled(aHandle);
13537 nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
13538 // Get the document's current state object. This is the object backing both
13539 // history.state and popStateEvent.state.
13541 // mStateObjectContainer may be null; this just means that there's no
13542 // current state object.
13544 if (!mCachedStateObjectValid) {
13545 if (mStateObjectContainer) {
13546 AutoJSAPI jsapi;
13547 // Init with null is "OK" in the sense that it will just fail.
13548 if (!jsapi.Init(GetScopeObject())) {
13549 return NS_ERROR_UNEXPECTED;
13551 JS::Rooted<JS::Value> value(jsapi.cx());
13552 nsresult rv =
13553 mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value);
13554 NS_ENSURE_SUCCESS(rv, rv);
13556 mCachedStateObject = value;
13557 if (!value.isNullOrUndefined()) {
13558 mozilla::HoldJSObjects(this);
13560 } else {
13561 mCachedStateObject = JS::NullValue();
13563 mCachedStateObjectValid = true;
13566 aState.set(mCachedStateObject);
13567 return NS_OK;
13570 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
13571 mTiming = aTiming;
13572 if (!mLoadingOrRestoredFromBFCacheTimeStamp.IsNull() && mTiming) {
13573 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(),
13574 mLoadingOrRestoredFromBFCacheTimeStamp);
13577 // If there's already the DocumentTimeline instance, tell it since the
13578 // DocumentTimeline is based on both the navigation start time stamp and the
13579 // refresh driver timestamp.
13580 if (mDocumentTimeline) {
13581 mDocumentTimeline->UpdateLastRefreshDriverTime();
13585 nsContentList* Document::ImageMapList() {
13586 if (!mImageMaps) {
13587 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
13588 nsGkAtoms::map);
13591 return mImageMaps;
13594 #define DEPRECATED_OPERATION(_op) #_op "Warning",
13595 static const char* kDeprecationWarnings[] = {
13596 #include "nsDeprecatedOperationList.h"
13597 nullptr};
13598 #undef DEPRECATED_OPERATION
13600 #define DOCUMENT_WARNING(_op) #_op "Warning",
13601 static const char* kDocumentWarnings[] = {
13602 #include "nsDocumentWarningList.h"
13603 nullptr};
13604 #undef DOCUMENT_WARNING
13606 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
13607 switch (aOperation) {
13608 #define DEPRECATED_OPERATION(_op) \
13609 case DeprecatedOperations::e##_op: \
13610 return eUseCounter_##_op;
13611 #include "nsDeprecatedOperationList.h"
13612 #undef DEPRECATED_OPERATION
13613 default:
13614 MOZ_CRASH();
13618 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
13619 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
13622 void Document::WarnOnceAbout(
13623 DeprecatedOperations aOperation, bool asError /* = false */,
13624 const nsTArray<nsString>& aParams /* = empty array */) const {
13625 MOZ_ASSERT(NS_IsMainThread());
13626 if (HasWarnedAbout(aOperation)) {
13627 return;
13629 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
13630 // Don't count deprecated operations for about pages since those pages
13631 // are almost in our control, and we always need to remove uses there
13632 // before we remove the operation itself anyway.
13633 if (!IsAboutPage()) {
13634 const_cast<Document*>(this)->SetUseCounter(
13635 OperationToUseCounter(aOperation));
13637 uint32_t flags =
13638 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13639 nsContentUtils::ReportToConsole(
13640 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
13641 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
13644 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
13645 return mDocWarningWarnedAbout[aWarning];
13648 void Document::WarnOnceAbout(
13649 DocumentWarnings aWarning, bool asError /* = false */,
13650 const nsTArray<nsString>& aParams /* = empty array */) const {
13651 MOZ_ASSERT(NS_IsMainThread());
13652 if (HasWarnedAbout(aWarning)) {
13653 return;
13655 mDocWarningWarnedAbout[aWarning] = true;
13656 uint32_t flags =
13657 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13658 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
13659 nsContentUtils::eDOM_PROPERTIES,
13660 kDocumentWarnings[aWarning], aParams);
13663 mozilla::dom::ImageTracker* Document::ImageTracker() {
13664 if (!mImageTracker) {
13665 mImageTracker = new mozilla::dom::ImageTracker;
13667 return mImageTracker;
13670 void Document::ScheduleSVGUseElementShadowTreeUpdate(
13671 SVGUseElement& aUseElement) {
13672 MOZ_ASSERT(aUseElement.IsInComposedDoc());
13674 if (MOZ_UNLIKELY(mIsStaticDocument)) {
13675 // Printing doesn't deal well with dynamic DOM mutations.
13676 return;
13679 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
13681 if (PresShell* presShell = GetPresShell()) {
13682 presShell->EnsureStyleFlush();
13686 void Document::DoUpdateSVGUseElementShadowTrees() {
13687 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13689 MOZ_ASSERT(!mCloningForSVGUse);
13690 mCloningForSVGUse = true;
13692 do {
13693 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
13694 mSVGUseElementsNeedingShadowTreeUpdate);
13695 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
13697 for (const auto& useElement : useElementsToUpdate) {
13698 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
13699 // The element was in another <use> shadow tree which we processed
13700 // already and also needed an update, and is removed from the document
13701 // now, so nothing to do here.
13702 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
13703 continue;
13705 useElement->UpdateShadowTree();
13707 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13709 mCloningForSVGUse = false;
13712 void Document::NotifyMediaFeatureValuesChanged() {
13713 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
13714 imageElement->MediaFeatureValuesChanged();
13718 already_AddRefed<Touch> Document::CreateTouch(
13719 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
13720 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
13721 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
13722 float aRotationAngle, float aForce) {
13723 RefPtr<Touch> touch =
13724 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
13725 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
13726 return touch.forget();
13729 already_AddRefed<TouchList> Document::CreateTouchList() {
13730 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13731 return retval.forget();
13734 already_AddRefed<TouchList> Document::CreateTouchList(
13735 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
13736 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13737 retval->Append(&aTouch);
13738 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13739 retval->Append(aTouches[i].get());
13741 return retval.forget();
13744 already_AddRefed<TouchList> Document::CreateTouchList(
13745 const Sequence<OwningNonNull<Touch>>& aTouches) {
13746 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13747 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13748 retval->Append(aTouches[i].get());
13750 return retval.forget();
13753 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
13754 float aX, float aY) {
13755 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
13757 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
13758 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
13759 nsPoint pt(x, y);
13761 FlushPendingNotifications(FlushType::Layout);
13763 PresShell* presShell = GetPresShell();
13764 if (!presShell) {
13765 return nullptr;
13768 nsIFrame* rootFrame = presShell->GetRootFrame();
13770 // XUL docs, unlike HTML, have no frame tree until everything's done loading
13771 if (!rootFrame) {
13772 return nullptr;
13775 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
13776 RelativeTo{rootFrame}, pt,
13777 {{FrameForPointOption::IgnorePaintSuppression,
13778 FrameForPointOption::IgnoreCrossDoc}});
13779 if (!ptFrame) {
13780 return nullptr;
13783 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
13784 nsPoint adjustedPoint = pt;
13785 if (nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
13786 adjustedPoint) !=
13787 nsLayoutUtils::TRANSFORM_SUCCEEDED) {
13788 return nullptr;
13791 nsIFrame::ContentOffsets offsets =
13792 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
13794 nsCOMPtr<nsIContent> node = offsets.content;
13795 uint32_t offset = offsets.offset;
13796 nsCOMPtr<nsIContent> anonNode = node;
13797 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
13798 if (nodeIsAnonymous) {
13799 node = ptFrame->GetContent();
13800 nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
13801 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
13802 nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
13803 if (textFrame) {
13804 // If the anonymous content node has a child, then we need to make sure
13805 // that we get the appropriate child, as otherwise the offset may not be
13806 // correct when we construct a range for it.
13807 nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
13808 if (firstChild) {
13809 anonNode = firstChild;
13812 if (textArea) {
13813 offset =
13814 nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
13817 node = nonanon;
13818 } else {
13819 node = nullptr;
13820 offset = 0;
13824 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
13825 if (nodeIsAnonymous) {
13826 aCaretPos->SetAnonymousContentNode(anonNode);
13828 return aCaretPos.forget();
13831 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
13832 // We rely on correct frame information here, so need to flush frames.
13833 FlushPendingNotifications(FlushType::Frames);
13835 // An element that is the HTML body element is potentially scrollable if all
13836 // of the following conditions are true:
13838 // The element has an associated CSS layout box.
13839 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
13840 if (!bodyFrame) {
13841 return false;
13844 // The element's parent element's computed value of the overflow-x and
13845 // overflow-y properties are visible.
13846 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
13847 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
13848 if (parentFrame &&
13849 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
13850 return false;
13853 // The element's computed value of the overflow-x or overflow-y properties is
13854 // not visible.
13855 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
13858 Element* Document::GetScrollingElement() {
13859 // Keep this in sync with IsScrollingElement.
13860 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
13861 RefPtr<HTMLBodyElement> body = GetBodyElement();
13862 if (body && !IsPotentiallyScrollable(body)) {
13863 return body;
13866 return nullptr;
13869 return GetRootElement();
13872 bool Document::IsScrollingElement(Element* aElement) {
13873 // Keep this in sync with GetScrollingElement.
13874 MOZ_ASSERT(aElement);
13876 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
13877 return aElement == GetRootElement();
13880 // In the common case when aElement != body, avoid refcounting.
13881 HTMLBodyElement* body = GetBodyElement();
13882 if (aElement != body) {
13883 return false;
13886 // Now we know body is non-null, since aElement is not null. It's the
13887 // scrolling element for the document if it itself is not potentially
13888 // scrollable.
13889 RefPtr<HTMLBodyElement> strongBody(body);
13890 return !IsPotentiallyScrollable(strongBody);
13893 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
13894 public:
13895 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
13896 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
13898 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
13899 const BlockParsingOptions& aOptions)
13900 : mPromise(aPromise) {
13901 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
13902 if (parser &&
13903 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
13904 parser->BlockParser();
13905 mParser = do_GetWeakReference(parser);
13906 mDocument = aDocument;
13907 mDocument->BlockOnload();
13908 mDocument->BlockDOMContentLoaded();
13912 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13913 ErrorResult& aRv) override {
13914 MaybeUnblockParser();
13916 mPromise->MaybeResolve(aValue);
13919 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13920 ErrorResult& aRv) override {
13921 MaybeUnblockParser();
13923 mPromise->MaybeReject(aValue);
13926 protected:
13927 virtual ~UnblockParsingPromiseHandler() {
13928 // If we're being cleaned up by the cycle collector, our mDocument reference
13929 // may have been unlinked while our mParser weak reference is still alive.
13930 if (mDocument) {
13931 MaybeUnblockParser();
13935 private:
13936 void MaybeUnblockParser() {
13937 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
13938 if (parser) {
13939 MOZ_DIAGNOSTIC_ASSERT(mDocument);
13940 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
13941 if (parser == docParser) {
13942 parser->UnblockParser();
13943 parser->ContinueInterruptedParsingAsync();
13946 if (mDocument) {
13947 // We blocked DOMContentLoaded and load events on this document. Unblock
13948 // them. Note that we want to do that no matter what's going on with the
13949 // parser state for this document. Maybe someone caused it to stop being
13950 // parsed, so CreatorParserOrNull() is returning null, but we still want
13951 // to unblock these.
13952 mDocument->UnblockDOMContentLoaded();
13953 mDocument->UnblockOnload(false);
13955 mParser = nullptr;
13956 mDocument = nullptr;
13959 nsWeakPtr mParser;
13960 RefPtr<Promise> mPromise;
13961 RefPtr<Document> mDocument;
13964 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
13966 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
13967 NS_INTERFACE_MAP_ENTRY(nsISupports)
13968 NS_INTERFACE_MAP_END
13970 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
13971 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
13973 already_AddRefed<Promise> Document::BlockParsing(
13974 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
13975 RefPtr<Promise> resultPromise =
13976 Promise::Create(aPromise.GetParentObject(), aRv);
13977 if (aRv.Failed()) {
13978 return nullptr;
13981 RefPtr<PromiseNativeHandler> promiseHandler =
13982 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
13983 aPromise.AppendNativeHandler(promiseHandler);
13985 return resultPromise.forget();
13988 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
13989 if (mFailedChannel) {
13990 nsCOMPtr<nsIURI> failedURI;
13991 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
13992 return failedURI.forget();
13996 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
13997 if (!uri) {
13998 return nullptr;
14001 return uri.forget();
14004 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
14005 if (mIsGoingAway) {
14006 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
14007 return nullptr;
14010 if (!mReadyForIdle) {
14011 nsIGlobalObject* global = GetScopeObject();
14012 if (!global) {
14013 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
14014 return nullptr;
14017 mReadyForIdle = Promise::Create(global, aRv);
14018 if (aRv.Failed()) {
14019 return nullptr;
14023 return mReadyForIdle;
14026 void Document::MaybeResolveReadyForIdle() {
14027 IgnoredErrorResult rv;
14028 Promise* readyPromise = GetDocumentReadyForIdle(rv);
14029 if (readyPromise) {
14030 readyPromise->MaybeResolveWithUndefined();
14034 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
14035 // The policy is created when the document is initialized. We _must_ have a
14036 // policy here even if the featurePolicy pref is off. If this assertion fails,
14037 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
14038 MOZ_ASSERT(mFeaturePolicy);
14039 return mFeaturePolicy;
14042 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
14043 // Only chrome documents are allowed to use command dispatcher.
14044 if (!nsContentUtils::IsChromeDoc(this)) {
14045 return nullptr;
14047 if (!mCommandDispatcher) {
14048 // Create our command dispatcher and hook it up.
14049 mCommandDispatcher = new nsXULCommandDispatcher(this);
14051 return mCommandDispatcher;
14054 void Document::InitializeXULBroadcastManager() {
14055 if (mXULBroadcastManager) {
14056 return;
14058 mXULBroadcastManager = new XULBroadcastManager(this);
14061 namespace {
14063 class DevToolsMutationObserver final : public nsStubMutationObserver {
14064 NS_DECL_ISUPPORTS
14065 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
14066 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
14067 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
14069 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
14070 // relies on the event firing _before_ the removal happens.
14071 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
14073 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
14074 // data changes right now (maybe intentionally?).
14075 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
14077 DevToolsMutationObserver() = default;
14079 private:
14080 void FireEvent(nsINode* aTarget, const nsAString& aType);
14082 ~DevToolsMutationObserver() = default;
14085 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
14087 void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
14088 const nsAString& aType) {
14089 AsyncEventDispatcher::RunDOMEventWhenSafe(*aTarget, aType, CanBubble::eNo,
14090 ChromeOnlyDispatch::eYes,
14091 Composed::eYes);
14094 void DevToolsMutationObserver::AttributeChanged(Element* aElement,
14095 int32_t aNamespaceID,
14096 nsAtom* aAttribute,
14097 int32_t aModType,
14098 const nsAttrValue* aOldValue) {
14099 FireEvent(aElement, u"devtoolsattrmodified"_ns);
14102 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
14103 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
14104 ContentInserted(c);
14108 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
14109 FireEvent(aChild, u"devtoolschildinserted"_ns);
14112 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
14114 } // namespace
14116 void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
14117 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
14118 return;
14120 mDevToolsWatchingDOMMutations = aValue;
14121 if (aValue) {
14122 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
14123 sDevToolsMutationObserver = new DevToolsMutationObserver();
14124 ClearOnShutdown(&sDevToolsMutationObserver);
14126 AddMutationObserver(sDevToolsMutationObserver);
14127 } else if (sDevToolsMutationObserver) {
14128 RemoveMutationObserver(sDevToolsMutationObserver);
14132 void EvaluateMediaQueryLists(nsTArray<RefPtr<MediaQueryList>>& aListsToNotify,
14133 Document& aDocument, bool aRecurse) {
14134 if (nsPresContext* pc = aDocument.GetPresContext()) {
14135 pc->FlushPendingMediaFeatureValuesChanged();
14138 for (MediaQueryList* mql : aDocument.MediaQueryLists()) {
14139 if (mql->EvaluateOnRenderingUpdate()) {
14140 aListsToNotify.AppendElement(mql);
14143 if (!aRecurse) {
14144 return;
14146 aDocument.EnumerateSubDocuments([&](Document& aSubDoc) {
14147 EvaluateMediaQueryLists(aListsToNotify, aSubDoc, true);
14148 return CallState::Continue;
14152 void Document::EvaluateMediaQueriesAndReportChanges(bool aRecurse) {
14153 AutoTArray<RefPtr<MediaQueryList>, 32> mqls;
14154 EvaluateMediaQueryLists(mqls, *this, aRecurse);
14155 for (auto& mql : mqls) {
14156 mql->FireChangeEvent();
14160 void Document::MaybeWarnAboutZoom() {
14161 if (mHasWarnedAboutZoom) {
14162 return;
14164 const bool usedZoom = Servo_IsPropertyIdRecordedInUseCounter(
14165 mStyleUseCounters.get(), eCSSProperty_zoom);
14166 if (!usedZoom) {
14167 return;
14170 mHasWarnedAboutZoom = true;
14171 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
14172 this, nsContentUtils::eLAYOUT_PROPERTIES,
14173 "ZoomPropertyWarning");
14176 nsIHTMLCollection* Document::Children() {
14177 if (!mChildrenCollection) {
14178 mChildrenCollection =
14179 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
14180 nsGkAtoms::_asterisk, false);
14183 return mChildrenCollection;
14186 uint32_t Document::ChildElementCount() { return Children()->Length(); }
14188 // Singleton class to manage the list of fullscreen documents which are the
14189 // root of a branch which contains fullscreen documents. We maintain this list
14190 // so that we can easily exit all windows from fullscreen when the user
14191 // presses the escape key.
14192 class FullscreenRoots {
14193 public:
14194 // Adds the root of given document to the manager. Calling this method
14195 // with a document whose root is already contained has no effect.
14196 static void Add(Document* aDoc);
14198 // Iterates over every root in the root list, and calls aFunction, passing
14199 // each root once to aFunction. It is safe to call Add() and Remove() while
14200 // iterating over the list (i.e. in aFunction). Documents that are removed
14201 // from the manager during traversal are not traversed, and documents that
14202 // are added to the manager during traversal are also not traversed.
14203 static void ForEach(void (*aFunction)(Document* aDoc));
14205 // Removes the root of a specific document from the manager.
14206 static void Remove(Document* aDoc);
14208 // Returns true if all roots added to the list have been removed.
14209 static bool IsEmpty();
14211 private:
14212 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
14213 MOZ_COUNTED_DTOR(FullscreenRoots)
14215 using RootsArray = nsTArray<WeakPtr<Document>>;
14217 // Returns true if aRoot is in the list of fullscreen roots.
14218 static bool Contains(Document* aRoot);
14220 // Singleton instance of the FullscreenRoots. This is instantiated when a
14221 // root is added, and it is deleted when the last root is removed.
14222 static FullscreenRoots* sInstance;
14224 // List of weak pointers to roots.
14225 RootsArray mRoots;
14228 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
14230 /* static */
14231 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
14232 if (!sInstance) {
14233 return;
14235 // Create a copy of the roots array, and iterate over the copy. This is so
14236 // that if an element is removed from mRoots we don't mess up our iteration.
14237 RootsArray roots(sInstance->mRoots.Clone());
14238 // Call aFunction on all entries.
14239 for (uint32_t i = 0; i < roots.Length(); i++) {
14240 nsCOMPtr<Document> root(roots[i]);
14241 // Check that the root isn't in the manager. This is so that new additions
14242 // while we were running don't get traversed.
14243 if (root && FullscreenRoots::Contains(root)) {
14244 aFunction(root);
14249 /* static */
14250 bool FullscreenRoots::Contains(Document* aRoot) {
14251 return sInstance && sInstance->mRoots.Contains(aRoot);
14254 /* static */
14255 void FullscreenRoots::Add(Document* aDoc) {
14256 nsCOMPtr<Document> root =
14257 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14258 if (!FullscreenRoots::Contains(root)) {
14259 if (!sInstance) {
14260 sInstance = new FullscreenRoots();
14262 sInstance->mRoots.AppendElement(root);
14266 /* static */
14267 void FullscreenRoots::Remove(Document* aDoc) {
14268 nsCOMPtr<Document> root =
14269 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14270 if (!sInstance || !sInstance->mRoots.RemoveElement(root)) {
14271 NS_ERROR("Should only try to remove roots which are still added!");
14272 return;
14274 if (sInstance->mRoots.IsEmpty()) {
14275 delete sInstance;
14276 sInstance = nullptr;
14280 /* static */
14281 bool FullscreenRoots::IsEmpty() { return !sInstance; }
14283 // Any fullscreen change waiting for the widget to finish transition
14284 // is queued here. This is declared static instead of a member of
14285 // Document because in the majority of time, there would be at most
14286 // one document requesting or exiting fullscreen. We shouldn't waste
14287 // the space to hold for it in every document.
14288 class PendingFullscreenChangeList {
14289 public:
14290 PendingFullscreenChangeList() = delete;
14292 template <typename T>
14293 static void Add(UniquePtr<T> aChange) {
14294 sList.insertBack(aChange.release());
14297 static const FullscreenChange* GetLast() { return sList.getLast(); }
14299 enum IteratorOption {
14300 // When we are committing fullscreen changes or preparing for
14301 // that, we generally want to iterate all requests in the same
14302 // window with eDocumentsWithSameRoot option.
14303 eDocumentsWithSameRoot,
14304 // If we are removing a document from the tree, we would only
14305 // want to remove the requests from the given document and its
14306 // descendants. For that case, use eInclusiveDescendants.
14307 eInclusiveDescendants
14310 template <typename T>
14311 class Iterator {
14312 public:
14313 explicit Iterator(Document* aDoc, IteratorOption aOption)
14314 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
14315 if (mCurrent) {
14316 if (aDoc->GetBrowsingContext()) {
14317 mRootBCForIteration = aDoc->GetBrowsingContext();
14318 if (aOption == eDocumentsWithSameRoot) {
14319 BrowsingContext* bc =
14320 GetParentIgnoreChromeBoundary(mRootBCForIteration);
14321 while (bc) {
14322 mRootBCForIteration = bc;
14323 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
14327 SkipToNextMatch();
14331 UniquePtr<T> TakeAndNext() {
14332 auto thisChange = TakeAndNextInternal();
14333 SkipToNextMatch();
14334 return thisChange;
14336 bool AtEnd() const { return mCurrent == nullptr; }
14338 private:
14339 static BrowsingContext* GetParentIgnoreChromeBoundary(
14340 BrowsingContext* aBC) {
14341 // Chrome BrowsingContexts are only available in the parent process, so if
14342 // we're in a content process, we only worry about the context tree.
14343 if (XRE_IsParentProcess()) {
14344 return aBC->Canonical()->GetParentCrossChromeBoundary();
14346 return aBC->GetParent();
14349 UniquePtr<T> TakeAndNextInternal() {
14350 FullscreenChange* thisChange = mCurrent;
14351 MOZ_ASSERT(thisChange->Type() == T::kType);
14352 mCurrent = mCurrent->removeAndGetNext();
14353 return WrapUnique(static_cast<T*>(thisChange));
14355 void SkipToNextMatch() {
14356 while (mCurrent) {
14357 if (mCurrent->Type() == T::kType) {
14358 BrowsingContext* bc = mCurrent->Document()->GetBrowsingContext();
14359 if (!bc) {
14360 // Always automatically drop fullscreen changes which are
14361 // from a document detached from the doc shell.
14362 UniquePtr<T> change = TakeAndNextInternal();
14363 change->MayRejectPromise("Document is not active");
14364 continue;
14366 while (bc && bc != mRootBCForIteration) {
14367 bc = GetParentIgnoreChromeBoundary(bc);
14369 if (bc) {
14370 break;
14373 // The current one either don't have matched type, or isn't
14374 // inside the given subtree, so skip this item.
14375 mCurrent = mCurrent->getNext();
14379 FullscreenChange* mCurrent;
14380 RefPtr<BrowsingContext> mRootBCForIteration;
14383 private:
14384 static LinkedList<FullscreenChange> sList;
14387 /* static */
14388 LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
14390 size_t Document::CountFullscreenElements() const {
14391 size_t count = 0;
14392 for (const nsWeakPtr& ptr : mTopLayer) {
14393 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14394 if (elem->State().HasState(ElementState::FULLSCREEN)) {
14395 count++;
14399 return count;
14402 // https://github.com/whatwg/html/issues/9143
14403 // We need to consider the precedence between active modal dialog, topmost auto
14404 // popover and fullscreen element once it's specified.
14405 void Document::HandleEscKey() {
14406 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14407 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14408 if (RefPtr popoverHTMLEl = nsGenericHTMLElement::FromNodeOrNull(element)) {
14409 if (element->IsAutoPopover() && element->IsPopoverOpen()) {
14410 popoverHTMLEl->HidePopover(IgnoreErrors());
14411 break;
14414 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14415 dialog->QueueCancelDialog();
14416 break;
14421 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
14422 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
14423 RefPtr<Promise> promise = exit->GetPromise();
14424 RestorePreviousFullscreenState(std::move(exit));
14425 return promise.forget();
14428 static void AskWindowToExitFullscreen(Document* aDoc) {
14429 if (XRE_GetProcessType() == GeckoProcessType_Content) {
14430 nsContentUtils::DispatchEventOnlyToChrome(
14431 aDoc, aDoc, u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
14432 Cancelable::eNo, /* DefaultAction */ nullptr);
14433 } else {
14434 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
14435 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
14440 class nsCallExitFullscreen : public Runnable {
14441 public:
14442 explicit nsCallExitFullscreen(Document* aDoc)
14443 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
14445 NS_IMETHOD Run() final {
14446 if (!mDoc) {
14447 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
14448 } else {
14449 AskWindowToExitFullscreen(mDoc);
14451 return NS_OK;
14454 private:
14455 nsCOMPtr<Document> mDoc;
14458 /* static */
14459 void Document::AsyncExitFullscreen(Document* aDoc) {
14460 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14461 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
14462 NS_DispatchToCurrentThread(exit.forget());
14465 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
14466 uint32_t count = 0;
14467 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
14468 aDoc.EnumerateSubDocuments([&count](Document& aSubDoc) {
14469 if (aSubDoc.Fullscreen()) {
14470 count++;
14472 return CallState::Continue;
14474 return count;
14477 bool Document::IsFullscreenLeaf() {
14478 // A fullscreen leaf document is fullscreen, and has no fullscreen
14479 // subdocuments.
14481 // FIXME(emilio): This doesn't seem to account for fission iframes, is that
14482 // ok?
14483 return Fullscreen() && CountFullscreenSubDocuments(*this) == 0;
14486 static Document* GetFullscreenLeaf(Document& aDoc) {
14487 if (aDoc.IsFullscreenLeaf()) {
14488 return &aDoc;
14490 if (!aDoc.Fullscreen()) {
14491 return nullptr;
14493 Document* leaf = nullptr;
14494 aDoc.EnumerateSubDocuments([&leaf](Document& aSubDoc) {
14495 leaf = GetFullscreenLeaf(aSubDoc);
14496 return leaf ? CallState::Stop : CallState::Continue;
14498 return leaf;
14501 static Document* GetFullscreenLeaf(Document* aDoc) {
14502 if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
14503 return leaf;
14505 // Otherwise we could be either in a non-fullscreen doc tree, or we're
14506 // below the fullscreen doc. Start the search from the root.
14507 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14508 return GetFullscreenLeaf(*root);
14511 static CallState ResetFullscreen(Document& aDocument) {
14512 if (Element* fsElement = aDocument.GetUnretargetedFullscreenElement()) {
14513 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
14514 "Should have at most 1 fullscreen subdocument.");
14515 aDocument.CleanupFullscreenState();
14516 NS_ASSERTION(!aDocument.Fullscreen(), "Should reset fullscreen");
14517 DispatchFullscreenChange(aDocument, fsElement);
14518 aDocument.EnumerateSubDocuments(ResetFullscreen);
14520 return CallState::Continue;
14523 // Since Document::ExitFullscreenInDocTree() could be called from
14524 // Element::UnbindFromTree() where it is not safe to synchronously run
14525 // script. This runnable is the script part of that function.
14526 class ExitFullscreenScriptRunnable : public Runnable {
14527 public:
14528 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
14529 : mozilla::Runnable("ExitFullscreenScriptRunnable"),
14530 mRoot(aRoot),
14531 mLeaf(aLeaf) {}
14533 NS_IMETHOD Run() override {
14534 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
14535 // document since we want this event to follow the same path that
14536 // MozDOMFullscreen:Entered was dispatched.
14537 nsContentUtils::DispatchEventOnlyToChrome(
14538 mLeaf, mLeaf, u"MozDOMFullscreen:Exited"_ns, CanBubble::eYes,
14539 Cancelable::eNo, /* DefaultAction */ nullptr);
14540 // Ensure the window exits fullscreen, as long as we don't have
14541 // pending fullscreen requests.
14542 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
14543 if (!mRoot->HasPendingFullscreenRequests()) {
14544 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
14545 false);
14548 return NS_OK;
14551 private:
14552 nsCOMPtr<Document> mRoot;
14553 nsCOMPtr<Document> mLeaf;
14556 /* static */
14557 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
14558 MOZ_ASSERT(aMaybeNotARootDoc);
14560 // Unlock the pointer
14561 PointerLockManager::Unlock();
14563 // Resolve all promises which waiting for exit fullscreen.
14564 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
14565 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14566 while (!iter.AtEnd()) {
14567 UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
14568 exit->MayResolvePromise();
14571 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
14572 if (!root || !root->Fullscreen()) {
14573 // If a document was detached before exiting from fullscreen, it is
14574 // possible that the root had left fullscreen state. In this case,
14575 // we would not get anything from the ResetFullscreen() call. Root's
14576 // not being a fullscreen doc also means the widget should have
14577 // exited fullscreen state. It means even if we do not return here,
14578 // we would actually do nothing below except crashing ourselves via
14579 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
14580 // document.
14581 return;
14584 // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
14585 // See ExitFullscreenScriptRunnable::Run for details. We have to
14586 // record it here because we don't have such information after we
14587 // reset the fullscreen state below.
14588 Document* fullscreenLeaf = GetFullscreenLeaf(root);
14590 // Walk the tree of fullscreen documents, and reset their fullscreen state.
14591 ResetFullscreen(*root);
14593 NS_ASSERTION(!root->Fullscreen(),
14594 "Fullscreen root should no longer be a fullscreen doc...");
14596 // Move the top-level window out of fullscreen mode.
14597 FullscreenRoots::Remove(root);
14599 nsContentUtils::AddScriptRunner(
14600 new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
14603 static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
14604 RefPtr<AsyncEventDispatcher> asyncDispatcher =
14605 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
14606 CanBubble::eYes, ChromeOnlyDispatch::eYes);
14607 asyncDispatcher->PostDOMEvent();
14610 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
14611 NS_ASSERTION(!Fullscreen() || !FullscreenRoots::IsEmpty(),
14612 "Should have at least 1 fullscreen root when fullscreen!");
14614 if (!GetWindow()) {
14615 aExit->MayRejectPromise("No active window");
14616 return;
14618 if (!Fullscreen() || FullscreenRoots::IsEmpty()) {
14619 aExit->MayRejectPromise("Not in fullscreen mode");
14620 return;
14623 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
14624 AutoTArray<Element*, 8> exitElements;
14626 Document* doc = fullScreenDoc;
14627 // Collect all subdocuments.
14628 for (; doc != this; doc = doc->GetInProcessParentDocument()) {
14629 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14630 MOZ_ASSERT(fsElement,
14631 "Parent document of "
14632 "a fullscreen document without fullscreen element?");
14633 exitElements.AppendElement(fsElement);
14635 MOZ_ASSERT(doc == this, "Must have reached this doc");
14636 // Collect all ancestor documents which we are going to change.
14637 for (; doc; doc = doc->GetInProcessParentDocument()) {
14638 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14639 MOZ_ASSERT(fsElement,
14640 "Ancestor of fullscreen document must also be in fullscreen");
14641 if (doc != this) {
14642 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
14643 if (iframe->FullscreenFlag()) {
14644 // If this is an iframe, and it explicitly requested
14645 // fullscreen, don't rollback it automatically.
14646 break;
14650 exitElements.AppendElement(fsElement);
14651 if (doc->CountFullscreenElements() > 1) {
14652 break;
14656 Document* lastDoc = exitElements.LastElement()->OwnerDoc();
14657 size_t fullscreenCount = lastDoc->CountFullscreenElements();
14658 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
14659 // If we are fully exiting fullscreen, don't touch anything here,
14660 // just wait for the window to get out from fullscreen first.
14661 PendingFullscreenChangeList::Add(std::move(aExit));
14662 AskWindowToExitFullscreen(this);
14663 return;
14666 // If fullscreen mode is updated the pointer should be unlocked
14667 PointerLockManager::Unlock();
14668 // All documents listed in the array except the last one are going to
14669 // completely exit from the fullscreen state.
14670 for (auto i : IntegerRange(exitElements.Length() - 1)) {
14671 exitElements[i]->OwnerDoc()->CleanupFullscreenState();
14673 // The last document will either rollback one fullscreen element, or
14674 // completely exit from the fullscreen state as well.
14675 Document* newFullscreenDoc;
14676 if (fullscreenCount > 1) {
14677 DebugOnly<bool> removedFullscreenElement = lastDoc->PopFullscreenElement();
14678 MOZ_ASSERT(removedFullscreenElement);
14679 newFullscreenDoc = lastDoc;
14680 } else {
14681 lastDoc->CleanupFullscreenState();
14682 newFullscreenDoc = lastDoc->GetInProcessParentDocument();
14684 // Dispatch the fullscreenchange event to all document listed. Note
14685 // that the loop order is reversed so that events are dispatched in
14686 // the tree order as indicated in the spec.
14687 for (Element* e : Reversed(exitElements)) {
14688 DispatchFullscreenChange(*e->OwnerDoc(), e);
14690 aExit->MayResolvePromise();
14692 MOZ_ASSERT(newFullscreenDoc,
14693 "If we were going to exit from fullscreen on "
14694 "all documents in this doctree, we should've asked the window to "
14695 "exit first instead of reaching here.");
14696 if (fullScreenDoc != newFullscreenDoc &&
14697 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
14698 // We've popped so enough off the stack that we've rolled back to
14699 // a fullscreen element in a parent document. If this document is
14700 // cross origin, dispatch an event to chrome so it knows to show
14701 // the warning UI.
14702 DispatchFullscreenNewOriginEvent(newFullscreenDoc);
14706 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
14707 if (nsPresContext* presContext = aDoc->GetPresContext()) {
14708 presContext->UpdateViewportScrollStylesOverride();
14712 static void NotifyFullScreenChangedForMediaElement(Element& aElement) {
14713 // When a media element enters the fullscreen, we would like to notify that
14714 // to the media controller in order to update its status.
14715 if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) {
14716 mediaElem->NotifyFullScreenChanged();
14720 void Document::CleanupFullscreenState() {
14721 while (PopFullscreenElement(UpdateViewport::No)) {
14722 // Remove the next one if appropriate
14725 UpdateViewportScrollbarOverrideForFullscreen(this);
14726 mFullscreenRoot = nullptr;
14728 // Restore the zoom level that was in place prior to entering fullscreen.
14729 if (PresShell* presShell = GetPresShell()) {
14730 if (presShell->GetMobileViewportManager()) {
14731 presShell->SetResolutionAndScaleTo(
14732 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
14737 bool Document::PopFullscreenElement(UpdateViewport aUpdateViewport) {
14738 Element* removedElement = TopLayerPop([](Element* element) -> bool {
14739 return element->State().HasState(ElementState::FULLSCREEN);
14742 if (!removedElement) {
14743 return false;
14746 MOZ_ASSERT(removedElement->State().HasState(ElementState::FULLSCREEN));
14747 removedElement->RemoveStates(ElementState::FULLSCREEN | ElementState::MODAL);
14748 NotifyFullScreenChangedForMediaElement(*removedElement);
14749 // Reset iframe fullscreen flag.
14750 if (auto* iframe = HTMLIFrameElement::FromNode(removedElement)) {
14751 iframe->SetFullscreenFlag(false);
14753 if (aUpdateViewport == UpdateViewport::Yes) {
14754 UpdateViewportScrollbarOverrideForFullscreen(this);
14756 return true;
14759 void Document::SetFullscreenElement(Element& aElement) {
14760 ElementState statesToAdd = ElementState::FULLSCREEN;
14761 if (!IsInChromeDocShell()) {
14762 // Don't make the document modal in chrome documents, since we don't want
14763 // the browser UI like the context menu / etc to be inert.
14764 statesToAdd |= ElementState::MODAL;
14766 aElement.AddStates(statesToAdd);
14767 TopLayerPush(aElement);
14768 NotifyFullScreenChangedForMediaElement(aElement);
14769 UpdateViewportScrollbarOverrideForFullscreen(this);
14772 void Document::TopLayerPush(Element& aElement) {
14773 const bool modal = aElement.State().HasState(ElementState::MODAL);
14775 TopLayerPop(aElement);
14776 if (nsIFrame* f = aElement.GetPrimaryFrame()) {
14777 f->MarkNeedsDisplayItemRebuild();
14780 mTopLayer.AppendElement(do_GetWeakReference(&aElement));
14781 NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
14783 if (modal) {
14784 aElement.AddStates(ElementState::TOPMOST_MODAL);
14786 bool foundExistingModalElement = false;
14787 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14788 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14789 if (element && element != &aElement &&
14790 element->State().HasState(ElementState::TOPMOST_MODAL)) {
14791 element->RemoveStates(ElementState::TOPMOST_MODAL);
14792 foundExistingModalElement = true;
14793 break;
14797 if (!foundExistingModalElement) {
14798 Element* root = GetRootElement();
14799 MOZ_RELEASE_ASSERT(root, "top layer element outside of document?");
14800 if (&aElement != root) {
14801 // Add inert to the root element so that the inertness is applied to the
14802 // entire document.
14803 root->AddStates(ElementState::INERT);
14809 void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
14810 aDialogElement.AddStates(ElementState::MODAL);
14811 TopLayerPush(aDialogElement);
14814 void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
14815 DebugOnly<Element*> removedElement = TopLayerPop(aDialogElement);
14816 MOZ_ASSERT(removedElement == &aDialogElement);
14817 aDialogElement.RemoveStates(ElementState::MODAL);
14820 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
14821 if (mTopLayer.IsEmpty()) {
14822 return nullptr;
14825 // Remove the topmost element that qualifies aPredicate; This
14826 // is required is because the top layer contains not only
14827 // fullscreen elements, but also dialog elements.
14828 Element* removedElement = nullptr;
14829 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
14830 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
14831 if (element && aPredicate(element)) {
14832 removedElement = element;
14833 if (nsIFrame* f = element->GetPrimaryFrame()) {
14834 f->MarkNeedsDisplayItemRebuild();
14836 mTopLayer.RemoveElementAt(i);
14837 break;
14841 // Pop from the stack null elements (references to elements which have
14842 // been GC'd since they were added to the stack) and elements which are
14843 // no longer in this document.
14845 // FIXME(emilio): If this loop does something, it'd violate the assertions
14846 // from GetTopLayerTop()... What gives?
14847 while (!mTopLayer.IsEmpty()) {
14848 Element* element = GetTopLayerTop();
14849 if (!element || element->GetComposedDoc() != this) {
14850 if (element) {
14851 if (nsIFrame* f = element->GetPrimaryFrame()) {
14852 f->MarkNeedsDisplayItemRebuild();
14856 mTopLayer.RemoveLastElement();
14857 } else {
14858 // The top element of the stack is now an in-doc element. Return here.
14859 break;
14863 if (!removedElement) {
14864 return nullptr;
14867 const bool modal = removedElement->State().HasState(ElementState::MODAL);
14869 if (modal) {
14870 removedElement->RemoveStates(ElementState::TOPMOST_MODAL);
14871 bool foundExistingModalElement = false;
14872 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14873 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14874 if (element && element->State().HasState(ElementState::MODAL)) {
14875 element->AddStates(ElementState::TOPMOST_MODAL);
14876 foundExistingModalElement = true;
14877 break;
14880 // No more modal elements, make the document not inert anymore.
14881 if (!foundExistingModalElement) {
14882 Element* root = GetRootElement();
14883 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
14884 root->RemoveStates(ElementState::INERT);
14889 return removedElement;
14892 Element* Document::TopLayerPop(Element& aElement) {
14893 auto predictFunc = [&aElement](Element* element) {
14894 return element == &aElement;
14896 return TopLayerPop(predictFunc);
14899 void Document::GetWireframe(bool aIncludeNodes,
14900 Nullable<Wireframe>& aWireframe) {
14901 FlushPendingNotifications(FlushType::Layout);
14902 GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
14905 void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
14906 Nullable<Wireframe>& aWireframe) {
14907 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
14908 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
14910 PresShell* shell = GetPresShell();
14911 if (!shell) {
14912 return;
14915 nsPresContext* pc = shell->GetPresContext();
14916 if (!pc) {
14917 return;
14920 nsIFrame* rootFrame = shell->GetRootFrame();
14921 if (!rootFrame) {
14922 return;
14925 auto& wireframe = aWireframe.SetValue();
14926 wireframe.mCanvasBackground = shell->ComputeCanvasBackground().mViewportColor;
14928 FrameForPointOptions options;
14929 options.mBits += FrameForPointOption::IgnoreCrossDoc;
14930 options.mBits += FrameForPointOption::IgnorePaintSuppression;
14931 options.mBits += FrameForPointOption::OnlyVisible;
14933 AutoTArray<nsIFrame*, 32> frames;
14934 const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
14935 nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
14936 options);
14938 // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
14939 // something perhaps, but seems hard / like it'd involve at least some extra
14940 // copying around, since they don't outlive GetFramesForArea.
14941 auto& rects = wireframe.mRects.Construct();
14942 if (!rects.SetCapacity(frames.Length(), fallible)) {
14943 return;
14945 for (nsIFrame* frame : Reversed(frames)) {
14946 auto [rectColor,
14947 rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> {
14948 if (frame->IsTextFrame()) {
14949 return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
14950 WireframeRectType::Text};
14952 if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
14953 return {0, WireframeRectType::Image};
14955 if (frame->IsThemed()) {
14956 return {0, WireframeRectType::Background};
14958 bool drawImage = false;
14959 bool drawColor = false;
14960 if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) {
14961 const nscolor color = nsCSSRendering::DetermineBackgroundColor(
14962 pc, bgStyle, frame, drawImage, drawColor);
14963 if (drawImage &&
14964 !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
14965 return {color, WireframeRectType::Image};
14967 if (drawColor && !frame->IsCanvasFrame()) {
14968 // Canvas frame background already accounted for in mCanvasBackground.
14969 return {color, WireframeRectType::Background};
14972 return {0, WireframeRectType::Unknown};
14973 }();
14975 if (rectType == WireframeRectType::Unknown) {
14976 continue;
14979 const auto r =
14980 CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
14981 frame, frame->GetRectRelativeToSelf(), relativeTo));
14982 if ((uint32_t)r.Area() <
14983 StaticPrefs::browser_history_wireframeAreaThreshold()) {
14984 continue;
14987 // Can't really fail because SetCapacity succeeded.
14988 auto& taggedRect = *rects.AppendElement(fallible);
14990 if (aIncludeNodes) {
14991 if (nsIContent* c = frame->GetContent()) {
14992 taggedRect.mNode.Construct(c);
14995 taggedRect.mX = r.x;
14996 taggedRect.mY = r.y;
14997 taggedRect.mWidth = r.width;
14998 taggedRect.mHeight = r.height;
14999 taggedRect.mColor = rectColor;
15000 taggedRect.mType.Construct(rectType);
15004 Element* Document::GetTopLayerTop() {
15005 if (mTopLayer.IsEmpty()) {
15006 return nullptr;
15008 uint32_t last = mTopLayer.Length() - 1;
15009 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
15010 NS_ASSERTION(element, "Should have a top layer element!");
15011 NS_ASSERTION(element->IsInComposedDoc(),
15012 "Top layer element should be in doc");
15013 NS_ASSERTION(element->OwnerDoc() == this,
15014 "Top layer element should be in this doc");
15015 return element;
15018 Element* Document::GetUnretargetedFullscreenElement() const {
15019 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15020 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15021 // Per spec, the fullscreen element is the topmost element in the document’s
15022 // top layer whose fullscreen flag is set, if any, and null otherwise.
15023 if (element && element->State().HasState(ElementState::FULLSCREEN)) {
15024 return element;
15027 return nullptr;
15030 nsTArray<Element*> Document::GetTopLayer() const {
15031 nsTArray<Element*> elements;
15032 for (const nsWeakPtr& ptr : mTopLayer) {
15033 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
15034 elements.AppendElement(elem);
15037 return elements;
15040 bool Document::TopLayerContains(Element& aElement) const {
15041 if (mTopLayer.IsEmpty()) {
15042 return false;
15044 nsWeakPtr weakElement = do_GetWeakReference(&aElement);
15045 return mTopLayer.Contains(weakElement);
15048 void Document::HideAllPopoversUntil(nsINode& aEndpoint,
15049 bool aFocusPreviousElement,
15050 bool aFireEvents) {
15051 auto closeAllOpenPopovers = [&aFocusPreviousElement, &aFireEvents,
15052 this]() MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
15053 while (RefPtr<Element> topmost = GetTopmostAutoPopover()) {
15054 HidePopover(*topmost, aFocusPreviousElement, aFireEvents, IgnoreErrors());
15058 if (aEndpoint.IsElement() && !aEndpoint.AsElement()->IsPopoverOpen()) {
15059 return;
15062 if (&aEndpoint == this) {
15063 closeAllOpenPopovers();
15064 return;
15067 // https://github.com/whatwg/html/pull/9198
15068 auto needRepeatingHide = [&]() {
15069 auto autoList = AutoPopoverList();
15070 return autoList.Contains(&aEndpoint) &&
15071 &aEndpoint != autoList.LastElement();
15074 MOZ_ASSERT((&aEndpoint)->IsElement() &&
15075 (&aEndpoint)->AsElement()->IsAutoPopover());
15076 bool repeatingHide = false;
15077 bool fireEvents = aFireEvents;
15078 do {
15079 RefPtr<const Element> lastToHide = nullptr;
15080 bool foundEndpoint = false;
15081 for (const Element* popover : AutoPopoverList()) {
15082 if (popover == &aEndpoint) {
15083 foundEndpoint = true;
15084 } else if (foundEndpoint) {
15085 lastToHide = popover;
15086 break;
15090 if (!foundEndpoint) {
15091 closeAllOpenPopovers();
15092 return;
15095 while (lastToHide && lastToHide->IsPopoverOpen()) {
15096 RefPtr<Element> topmost = GetTopmostAutoPopover();
15097 if (!topmost) {
15098 break;
15100 HidePopover(*topmost, aFocusPreviousElement, fireEvents, IgnoreErrors());
15103 repeatingHide = needRepeatingHide();
15104 if (repeatingHide) {
15105 fireEvents = false;
15107 } while (repeatingHide);
15110 void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement,
15111 bool aFireEvents, ErrorResult& aRv) {
15112 RefPtr<nsGenericHTMLElement> popoverHTMLEl =
15113 nsGenericHTMLElement::FromNode(aPopover);
15114 NS_ASSERTION(popoverHTMLEl, "Not a HTML element");
15116 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15117 nullptr, aRv)) {
15118 return;
15121 bool wasShowingOrHiding =
15122 popoverHTMLEl->GetPopoverData()->IsShowingOrHiding();
15123 popoverHTMLEl->GetPopoverData()->SetIsShowingOrHiding(true);
15124 const bool fireEvents = aFireEvents && !wasShowingOrHiding;
15125 auto cleanupHidingFlag = MakeScopeExit([&]() {
15126 if (auto* popoverData = popoverHTMLEl->GetPopoverData()) {
15127 popoverData->SetIsShowingOrHiding(wasShowingOrHiding);
15131 if (popoverHTMLEl->IsAutoPopover()) {
15132 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, fireEvents);
15133 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15134 nullptr, aRv)) {
15135 return;
15137 // TODO: we can't always guarantee:
15138 // The last item in document's auto popover list is popoverHTMLEl.
15139 // See, https://github.com/whatwg/html/issues/9197
15140 // If popoverHTMLEl is not on top, hide popovers again without firing
15141 // events.
15142 if (NS_WARN_IF(GetTopmostAutoPopover() != popoverHTMLEl)) {
15143 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
15144 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15145 nullptr, aRv)) {
15146 return;
15148 MOZ_ASSERT(GetTopmostAutoPopover() == popoverHTMLEl,
15149 "popoverHTMLEl should be on top of auto popover list");
15153 auto* data = popoverHTMLEl->GetPopoverData();
15154 MOZ_ASSERT(data, "Should have popover data");
15155 data->SetInvoker(nullptr);
15157 // Fire beforetoggle event and re-check popover validity.
15158 if (fireEvents) {
15159 // Intentionally ignore the return value here as only on open event for
15160 // beforetoggle the cancelable attribute is initialized to true.
15161 popoverHTMLEl->FireToggleEvent(PopoverVisibilityState::Showing,
15162 PopoverVisibilityState::Hidden,
15163 u"beforetoggle"_ns);
15165 // https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm
15166 // step 10.2.
15167 // Hide all popovers when beforetoggle shows a popover.
15168 if (popoverHTMLEl->IsAutoPopover() &&
15169 GetTopmostAutoPopover() != popoverHTMLEl &&
15170 popoverHTMLEl->PopoverOpen()) {
15171 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
15174 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15175 nullptr, aRv)) {
15176 return;
15180 RemovePopoverFromTopLayer(aPopover);
15182 popoverHTMLEl->PopoverPseudoStateUpdate(false, true);
15183 popoverHTMLEl->GetPopoverData()->SetPopoverVisibilityState(
15184 PopoverVisibilityState::Hidden);
15186 // Queue popover toggle event task.
15187 if (fireEvents) {
15188 popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing);
15191 if (aFocusPreviousElement) {
15192 popoverHTMLEl->FocusPreviousElementAfterHidingPopover();
15193 } else {
15194 popoverHTMLEl->ForgetPreviouslyFocusedElementAfterHidingPopover();
15198 nsTArray<Element*> Document::AutoPopoverList() const {
15199 nsTArray<Element*> elements;
15200 for (const nsWeakPtr& ptr : mTopLayer) {
15201 if (nsCOMPtr<Element> element = do_QueryReferent(ptr)) {
15202 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15203 elements.AppendElement(element);
15207 return elements;
15210 Element* Document::GetTopmostAutoPopover() const {
15211 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15212 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15213 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15214 return element;
15217 return nullptr;
15220 void Document::AddToAutoPopoverList(Element& aElement) {
15221 MOZ_ASSERT(aElement.IsAutoPopover());
15222 TopLayerPush(aElement);
15225 void Document::RemoveFromAutoPopoverList(Element& aElement) {
15226 MOZ_ASSERT(aElement.IsAutoPopover());
15227 TopLayerPop(aElement);
15230 void Document::AddPopoverToTopLayer(Element& aElement) {
15231 MOZ_ASSERT(aElement.GetPopoverData());
15232 TopLayerPush(aElement);
15235 void Document::RemovePopoverFromTopLayer(Element& aElement) {
15236 MOZ_ASSERT(aElement.GetPopoverData());
15237 TopLayerPop(aElement);
15240 // Returns true if aDoc browsing context is focused.
15241 bool IsInFocusedTab(Document* aDoc) {
15242 BrowsingContext* bc = aDoc->GetBrowsingContext();
15243 if (!bc) {
15244 return false;
15247 nsFocusManager* fm = nsFocusManager::GetFocusManager();
15248 if (!fm) {
15249 return false;
15252 if (XRE_IsParentProcess()) {
15253 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
15254 // by retaining the old code path for the parent process.
15255 nsIDocShell* docshell = aDoc->GetDocShell();
15256 if (!docshell) {
15257 return false;
15259 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15260 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15261 if (!rootItem) {
15262 return false;
15264 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
15265 if (!rootWin) {
15266 return false;
15269 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
15270 activeWindow = fm->GetActiveWindow();
15271 if (!activeWindow) {
15272 return false;
15275 return activeWindow == rootWin;
15278 return fm->GetActiveBrowsingContext() == bc->Top();
15281 // Returns true if aDoc browsing context is focused and is also active.
15282 bool IsInActiveTab(Document* aDoc) {
15283 if (!IsInFocusedTab(aDoc)) {
15284 return false;
15287 BrowsingContext* bc = aDoc->GetBrowsingContext();
15288 MOZ_ASSERT(bc, "With no BrowsingContext, we should have failed earlier.");
15289 return bc->IsActive();
15292 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
15293 // Ensure the frame element is the fullscreen element in this document.
15294 // If the frame element is already the fullscreen element in this document,
15295 // this has no effect.
15296 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
15297 RequestFullscreen(std::move(request), XRE_IsContentProcess());
15300 void Document::RemoteFrameFullscreenReverted() {
15301 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
15302 RestorePreviousFullscreenState(std::move(exit));
15305 static bool HasFullscreenSubDocument(Document& aDoc) {
15306 uint32_t count = CountFullscreenSubDocuments(aDoc);
15307 NS_ASSERTION(count <= 1,
15308 "Fullscreen docs should have at most 1 fullscreen child!");
15309 return count >= 1;
15312 // Returns nullptr if a request for Fullscreen API is currently enabled
15313 // in the given document. Returns a static string indicates the reason
15314 // why it is not enabled otherwise.
15315 const char* Document::GetFullscreenError(CallerType aCallerType) {
15316 if (!StaticPrefs::full_screen_api_enabled()) {
15317 return "FullscreenDeniedDisabled";
15320 if (aCallerType == CallerType::System) {
15321 // Chrome code can always use the fullscreen API, provided it's not
15322 // explicitly disabled.
15323 return nullptr;
15326 if (!IsVisible()) {
15327 return "FullscreenDeniedHidden";
15330 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
15331 return "FullscreenDeniedFeaturePolicy";
15334 // Ensure that all containing elements are <iframe> and have allowfullscreen
15335 // attribute set.
15336 BrowsingContext* bc = GetBrowsingContext();
15337 if (!bc || !bc->FullscreenAllowed()) {
15338 return "FullscreenDeniedContainerNotAllowed";
15341 return nullptr;
15344 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
15345 Element* elem = aRequest.Element();
15346 // Strictly speaking, this isn't part of the fullscreen element ready
15347 // check in the spec, but per steps in the spec, when an element which
15348 // is already the fullscreen element requests fullscreen, nothing
15349 // should change and no event should be dispatched, but we still need
15350 // to resolve the returned promise.
15351 Element* fullscreenElement = GetUnretargetedFullscreenElement();
15352 if (elem == fullscreenElement) {
15353 aRequest.MayResolvePromise();
15354 return false;
15356 if (!elem->IsInComposedDoc()) {
15357 aRequest.Reject("FullscreenDeniedNotInDocument");
15358 return false;
15360 if (elem->IsPopoverOpen()) {
15361 aRequest.Reject("FullscreenDeniedPopoverOpen");
15362 return false;
15364 if (elem->OwnerDoc() != this) {
15365 aRequest.Reject("FullscreenDeniedMovedDocument");
15366 return false;
15368 if (!GetWindow()) {
15369 aRequest.Reject("FullscreenDeniedLostWindow");
15370 return false;
15372 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
15373 aRequest.Reject(msg);
15374 return false;
15376 if (HasFullscreenSubDocument(*this)) {
15377 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
15378 return false;
15380 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
15381 aRequest.Reject("FullscreenDeniedHTMLDialog");
15382 return false;
15384 if (!nsContentUtils::IsChromeDoc(this) && !IsInFocusedTab(this)) {
15385 aRequest.Reject("FullscreenDeniedNotFocusedTab");
15386 return false;
15388 return true;
15391 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
15392 MOZ_ASSERT(XRE_IsParentProcess());
15393 nsIDocShell* docShell = aDoc->GetDocShell();
15394 if (!docShell) {
15395 return nullptr;
15397 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15398 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15399 return rootItem ? rootItem->GetWindow() : nullptr;
15402 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
15403 nsPIDOMWindowOuter* aRootWin) {
15404 MOZ_ASSERT(XRE_IsParentProcess());
15405 // If we are in the chrome process, and the window has not been in
15406 // fullscreen, we certainly need to make that fullscreen first.
15407 if (!aRootWin->GetFullScreen()) {
15408 return false;
15410 // The iterator not being at end indicates there is still some
15411 // pending fullscreen request relates to this document. We have to
15412 // push the request to the pending queue so requests are handled
15413 // in the correct order.
15414 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15415 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15416 if (!iter.AtEnd()) {
15417 return false;
15420 // Same thing for exits. If we have any pending, we have to push
15421 // to the pending queue.
15422 PendingFullscreenChangeList::Iterator<FullscreenExit> iterExit(
15423 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15424 if (!iterExit.AtEnd()) {
15425 return false;
15428 // We have to apply the fullscreen state directly in this case,
15429 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
15430 // if it is already in fullscreen. If we do not apply the state but
15431 // instead add it to the queue and wait for the window as normal,
15432 // we would get stuck.
15433 return true;
15436 static bool CheckFullscreenAllowedElementType(const Element* elem) {
15437 // Per spec only HTML, <svg>, and <math> should be allowed, but
15438 // we also need to allow XUL elements right now.
15439 return elem->IsHTMLElement() || elem->IsXULElement() ||
15440 elem->IsSVGElement(nsGkAtoms::svg) ||
15441 elem->IsMathMLElement(nsGkAtoms::math);
15444 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
15445 bool aApplyFullscreenDirectly) {
15446 if (XRE_IsContentProcess()) {
15447 RequestFullscreenInContentProcess(std::move(aRequest),
15448 aApplyFullscreenDirectly);
15449 } else {
15450 RequestFullscreenInParentProcess(std::move(aRequest),
15451 aApplyFullscreenDirectly);
15455 void Document::RequestFullscreenInContentProcess(
15456 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15457 MOZ_ASSERT(XRE_IsContentProcess());
15459 // If we are in the content process, we can apply the fullscreen
15460 // state directly only if we have been in DOM fullscreen, because
15461 // otherwise we always need to notify the chrome.
15462 if (aApplyFullscreenDirectly ||
15463 nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) {
15464 ApplyFullscreen(std::move(aRequest));
15465 return;
15468 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15469 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15470 return;
15473 // We don't need to check element ready before this point, because
15474 // if we called ApplyFullscreen, it would check that for us.
15475 if (!FullscreenElementReadyCheck(*aRequest)) {
15476 return;
15479 PendingFullscreenChangeList::Add(std::move(aRequest));
15480 // If we are not the top level process, dispatch an event to make
15481 // our parent process go fullscreen first.
15482 Dispatch(NS_NewRunnableFunction(
15483 "Document::RequestFullscreenInContentProcess", [self = RefPtr{this}] {
15484 if (!self->HasPendingFullscreenRequests()) {
15485 return;
15487 nsContentUtils::DispatchEventOnlyToChrome(
15488 self, self, u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
15489 Cancelable::eNo, /* DefaultAction */ nullptr);
15490 }));
15493 void Document::RequestFullscreenInParentProcess(
15494 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15495 MOZ_ASSERT(XRE_IsParentProcess());
15496 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
15497 if (!rootWin) {
15498 aRequest->MayRejectPromise("No active window");
15499 return;
15502 if (aApplyFullscreenDirectly ||
15503 ShouldApplyFullscreenDirectly(this, rootWin)) {
15504 ApplyFullscreen(std::move(aRequest));
15505 return;
15508 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15509 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15510 return;
15513 // See if we're waiting on an exit. If so, just make this one pending.
15514 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
15515 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15516 if (!iter.AtEnd()) {
15517 PendingFullscreenChangeList::Add(std::move(aRequest));
15518 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15519 return;
15522 // We don't need to check element ready before this point, because
15523 // if we called ApplyFullscreen, it would check that for us.
15524 if (!FullscreenElementReadyCheck(*aRequest)) {
15525 return;
15528 PendingFullscreenChangeList::Add(std::move(aRequest));
15529 // Make the window fullscreen.
15530 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15533 /* static */
15534 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
15535 bool handled = false;
15536 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15537 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15538 while (!iter.AtEnd()) {
15539 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15540 Document* doc = request->Document();
15541 if (doc->ApplyFullscreen(std::move(request))) {
15542 handled = true;
15545 return handled;
15548 /* static */
15549 void Document::ClearPendingFullscreenRequests(Document* aDoc) {
15550 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15551 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
15552 while (!iter.AtEnd()) {
15553 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15554 request->MayRejectPromise("Fullscreen request aborted");
15558 bool Document::HasPendingFullscreenRequests() {
15559 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15560 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15561 return !iter.AtEnd();
15564 MOZ_CAN_RUN_SCRIPT_BOUNDARY
15565 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
15566 if (!FullscreenElementReadyCheck(*aRequest)) {
15567 return false;
15570 Element* elem = aRequest->Element();
15572 RefPtr<nsINode> hideUntil = elem->GetTopmostPopoverAncestor(nullptr, false);
15573 if (!hideUntil) {
15574 hideUntil = OwnerDoc();
15577 RefPtr<Document> doc = aRequest->Document();
15578 doc->HideAllPopoversUntil(*hideUntil, false, true);
15580 // Stash a reference to any existing fullscreen doc, we'll use this later
15581 // to detect if the origin which is fullscreen has changed.
15582 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
15584 // Stores a list of documents which we must dispatch "fullscreenchange"
15585 // too. We're required by the spec to dispatch the events in root-to-leaf
15586 // order, but we traverse the doctree in a leaf-to-root order, so we save
15587 // references to the documents we must dispatch to so that we get the order
15588 // as specified.
15589 AutoTArray<Document*, 8> changed;
15591 // Remember the root document, so that if a fullscreen document is hidden
15592 // we can reset fullscreen state in the remaining visible fullscreen
15593 // documents.
15594 Document* fullScreenRootDoc =
15595 nsContentUtils::GetInProcessSubtreeRootDocument(this);
15597 // If a document is already in fullscreen, then unlock the mouse pointer
15598 // before setting a new document to fullscreen
15599 PointerLockManager::Unlock();
15601 // Set the fullscreen element. This sets the fullscreen style on the
15602 // element, and the fullscreen-ancestor styles on ancestors of the element
15603 // in this document.
15604 SetFullscreenElement(*elem);
15605 // Set the iframe fullscreen flag.
15606 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
15607 iframe->SetFullscreenFlag(true);
15609 changed.AppendElement(this);
15611 // Propagate up the document hierarchy, setting the fullscreen element as
15612 // the element's container in ancestor documents. This also sets the
15613 // appropriate css styles as well. Note we don't propagate down the
15614 // document hierarchy, the fullscreen element (or its container) is not
15615 // visible there. Stop when we reach the root document.
15616 Document* child = this;
15617 while (true) {
15618 child->SetFullscreenRoot(fullScreenRootDoc);
15620 // When entering fullscreen, reset the RCD's resolution to the intrinsic
15621 // resolution, otherwise the fullscreen content could be sized larger than
15622 // the screen (since fullscreen is implemented using position:fixed and
15623 // fixed elements are sized to the layout viewport).
15624 // This also ensures that things like video controls aren't zoomed in
15625 // when in fullscreen mode.
15626 if (PresShell* presShell = child->GetPresShell()) {
15627 if (RefPtr<MobileViewportManager> manager =
15628 presShell->GetMobileViewportManager()) {
15629 // Save the previous resolution so it can be restored.
15630 child->mSavedResolution = presShell->GetResolution();
15631 presShell->SetResolutionAndScaleTo(
15632 manager->ComputeIntrinsicResolution(),
15633 ResolutionChangeOrigin::MainThreadRestore);
15637 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
15638 "Fullscreen root should be set!");
15639 if (child == fullScreenRootDoc) {
15640 break;
15643 Element* element = child->GetEmbedderElement();
15644 if (!element) {
15645 // We've reached the root.No more changes need to be made
15646 // to the top layer stacks of documents further up the tree.
15647 break;
15650 Document* parent = child->GetInProcessParentDocument();
15651 parent->SetFullscreenElement(*element);
15652 changed.AppendElement(parent);
15653 child = parent;
15656 FullscreenRoots::Add(this);
15658 // If it is the first entry of the fullscreen, trigger an event so
15659 // that the UI can response to this change, e.g. hide chrome, or
15660 // notifying parent process to enter fullscreen. Note that chrome
15661 // code may also want to listen to MozDOMFullscreen:NewOrigin event
15662 // to pop up warning UI.
15663 if (!previousFullscreenDoc) {
15664 nsContentUtils::DispatchEventOnlyToChrome(
15665 this, elem, u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
15666 Cancelable::eNo, /* DefaultAction */ nullptr);
15669 // The origin which is fullscreen gets changed. Trigger an event so
15670 // that the chrome knows to pop up a warning UI. Note that
15671 // previousFullscreenDoc == nullptr upon first entry, we show the warning UI
15672 // directly as soon as chrome document goes into fullscreen state. Also note
15673 // that, in a multi-process browser, the code in content process is
15674 // responsible for sending message with the origin to its parent, and the
15675 // parent shouldn't rely on this event itself.
15676 if (aRequest->mShouldNotifyNewOrigin && previousFullscreenDoc &&
15677 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
15678 DispatchFullscreenNewOriginEvent(this);
15681 // Dispatch "fullscreenchange" events. Note that the loop order is
15682 // reversed so that events are dispatched in the tree order as
15683 // indicated in the spec.
15684 for (Document* d : Reversed(changed)) {
15685 DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement());
15687 aRequest->MayResolvePromise();
15688 return true;
15691 void Document::ClearOrientationPendingPromise() {
15692 mOrientationPendingPromise = nullptr;
15695 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
15696 if (mIsGoingAway) {
15697 return false;
15700 mOrientationPendingPromise = aPromise;
15701 return true;
15704 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
15705 dom::VisibilityState oldState = mVisibilityState;
15706 mVisibilityState = ComputeVisibilityState();
15707 if (oldState != mVisibilityState) {
15708 if (aDispatchEvent == DispatchVisibilityChange::Yes) {
15709 nsContentUtils::DispatchTrustedEvent(this, this, u"visibilitychange"_ns,
15710 CanBubble::eYes, Cancelable::eNo);
15712 NotifyActivityChanged();
15713 if (mVisibilityState == dom::VisibilityState::Visible) {
15714 MaybeActiveMediaComponents();
15717 bool visible = !Hidden();
15718 for (auto* listener : mWorkerListeners) {
15719 listener->OnVisible(visible);
15722 // https://w3c.github.io/screen-wake-lock/#handling-document-loss-of-visibility
15723 if (!visible) {
15724 UnlockAllWakeLocks(WakeLockType::Screen);
15729 void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
15730 mWorkerListeners.Insert(aListener);
15731 aListener->OnVisible(!Hidden());
15734 void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
15735 mWorkerListeners.Remove(aListener);
15738 VisibilityState Document::ComputeVisibilityState() const {
15739 // We have to check a few pieces of information here:
15740 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
15741 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
15742 // want to use GetWindow here because it does weird groveling for windows
15743 // in some cases.
15744 // 3) Is our outer window background? If so, we're hidden.
15745 // Otherwise, we're visible.
15746 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
15747 mWindow->GetOuterWindow()->IsBackground()) {
15748 return dom::VisibilityState::Hidden;
15751 return dom::VisibilityState::Visible;
15754 void Document::PostVisibilityUpdateEvent() {
15755 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
15756 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
15757 DispatchVisibilityChange::Yes);
15758 Dispatch(event.forget());
15761 void Document::MaybeActiveMediaComponents() {
15762 auto* window = GetWindow();
15763 if (!window || !window->ShouldDelayMediaFromStart()) {
15764 return;
15766 window->ActivateMediaComponents();
15769 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
15770 nsINode::AddSizeOfExcludingThis(aWindowSizes,
15771 &aWindowSizes.mDOMSizes.mDOMOtherSize);
15773 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
15774 AddSizeOfNodeTree(*kid, aWindowSizes);
15777 // IMPORTANT: for our ComputedValues measurements, we want to measure
15778 // ComputedValues accessible from DOM elements before ComputedValues not
15779 // accessible from DOM elements (i.e. accessible only from the frame tree).
15781 // Therefore, the measurement of the Document superclass must happen after
15782 // the measurement of DOM nodes (above), because Document contains the
15783 // PresShell, which contains the frame tree.
15784 if (mPresShell) {
15785 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
15788 if (mStyleSet) {
15789 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
15792 aWindowSizes.mPropertyTablesSize +=
15793 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15795 if (EventListenerManager* elm = GetExistingListenerManager()) {
15796 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15799 if (mNodeInfoManager) {
15800 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
15803 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15804 mDOMMediaQueryLists.sizeOfExcludingThis(
15805 aWindowSizes.mState.mMallocSizeOf);
15807 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
15808 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15809 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15812 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
15814 for (auto& sheetArray : mAdditionalSheets) {
15815 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
15817 // Lumping in the loader with the style-sheets size is not ideal,
15818 // but most of the things in there are in fact stylesheets, so it
15819 // doesn't seem worthwhile to separate it out.
15820 // This can be null if we've already been unlinked.
15821 if (mCSSLoader) {
15822 aWindowSizes.mLayoutStyleSheetsSize +=
15823 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
15826 aWindowSizes.mDOMSizes.mDOMResizeObserverControllerSize +=
15827 mResizeObservers.ShallowSizeOfExcludingThis(
15828 aWindowSizes.mState.mMallocSizeOf);
15830 if (mAttributeStyles) {
15831 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15832 mAttributeStyles->DOMSizeOfIncludingThis(
15833 aWindowSizes.mState.mMallocSizeOf);
15836 if (mRadioGroupContainer) {
15837 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15838 mRadioGroupContainer->SizeOfIncludingThis(
15839 aWindowSizes.mState.mMallocSizeOf);
15842 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15843 mStyledLinks.ShallowSizeOfExcludingThis(
15844 aWindowSizes.mState.mMallocSizeOf);
15846 // Measurement of the following members may be added later if DMD finds it
15847 // is worthwhile:
15848 // - mMidasCommandManager
15849 // - many!
15852 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
15853 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15854 aWindowSizes.mState.mMallocSizeOf(this);
15855 DocAddSizeOfExcludingThis(aWindowSizes);
15858 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
15859 size_t* aNodeSize) const {
15860 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
15861 // nsDocuments can only appear at the top of the DOM tree, and we use the
15862 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
15863 // be called.
15864 MOZ_CRASH();
15867 /* static */
15868 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
15869 size_t nodeSize = 0;
15870 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
15872 // This is where we transfer the nodeSize obtained from
15873 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
15874 switch (aNode.NodeType()) {
15875 case nsINode::ELEMENT_NODE:
15876 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
15877 break;
15878 case nsINode::TEXT_NODE:
15879 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
15880 break;
15881 case nsINode::CDATA_SECTION_NODE:
15882 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
15883 break;
15884 case nsINode::COMMENT_NODE:
15885 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
15886 break;
15887 default:
15888 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
15889 break;
15892 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
15893 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15896 if (aNode.IsContent()) {
15897 nsTArray<nsIContent*> anonKids;
15898 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
15899 nsIContent::eAllChildren);
15900 for (nsIContent* anonKid : anonKids) {
15901 AddSizeOfNodeTree(*anonKid, aWindowSizes);
15904 if (auto* element = Element::FromNode(aNode)) {
15905 if (ShadowRoot* shadow = element->GetShadowRoot()) {
15906 AddSizeOfNodeTree(*shadow, aWindowSizes);
15911 // NOTE(emilio): If you feel smart and want to change this function to use
15912 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
15913 // sane way, and kids of <content> won't point to the parent, so we'd never
15914 // find the root node where we should stop at.
15915 for (nsIContent* kid = aNode.GetFirstChild(); kid;
15916 kid = kid->GetNextSibling()) {
15917 AddSizeOfNodeTree(*kid, aWindowSizes);
15921 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
15922 ErrorResult& rv) {
15923 nsCOMPtr<nsIScriptGlobalObject> global =
15924 do_QueryInterface(aGlobal.GetAsSupports());
15925 if (!global) {
15926 rv.Throw(NS_ERROR_UNEXPECTED);
15927 return nullptr;
15930 nsCOMPtr<nsIScriptObjectPrincipal> prin =
15931 do_QueryInterface(aGlobal.GetAsSupports());
15932 if (!prin) {
15933 rv.Throw(NS_ERROR_UNEXPECTED);
15934 return nullptr;
15937 nsCOMPtr<nsIURI> uri;
15938 NS_NewURI(getter_AddRefs(uri), "about:blank");
15939 if (!uri) {
15940 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
15941 return nullptr;
15944 nsCOMPtr<Document> doc;
15945 nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
15946 nullptr, uri, uri, prin->GetPrincipal(),
15947 true, global, DocumentFlavorPlain);
15948 if (NS_FAILED(res)) {
15949 rv.Throw(res);
15950 return nullptr;
15953 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
15955 return doc.forget();
15958 UniquePtr<XPathExpression> Document::CreateExpression(
15959 const nsAString& aExpression, XPathNSResolver* aResolver, ErrorResult& rv) {
15960 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
15963 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
15964 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
15967 already_AddRefed<XPathResult> Document::Evaluate(
15968 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
15969 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
15970 ErrorResult& rv) {
15971 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
15972 aType, aResult, rv);
15975 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
15976 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
15977 if (!item) {
15978 return nullptr;
15980 nsCOMPtr<nsIDocShellTreeOwner> owner;
15981 item->GetTreeOwner(getter_AddRefs(owner));
15982 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
15983 if (!appWin) {
15984 return nullptr;
15986 nsCOMPtr<nsIDocShell> appWinShell;
15987 appWin->GetDocShell(getter_AddRefs(appWinShell));
15988 if (!SameCOMIdentity(appWinShell, item)) {
15989 return nullptr;
15991 return appWin.forget();
15994 WindowContext* Document::GetTopLevelWindowContext() const {
15995 WindowContext* windowContext = GetWindowContext();
15996 return windowContext ? windowContext->TopWindowContext() : nullptr;
15999 Document* Document::GetTopLevelContentDocumentIfSameProcess() {
16000 Document* parent;
16002 if (!mLoadedAsData) {
16003 parent = this;
16004 } else {
16005 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
16006 if (!window) {
16007 return nullptr;
16010 parent = window->GetExtantDoc();
16011 if (!parent) {
16012 return nullptr;
16016 do {
16017 if (parent->IsTopLevelContentDocument()) {
16018 break;
16021 // If we ever have a non-content parent before we hit a toplevel content
16022 // parent, then we're never going to find one. Just bail.
16023 if (!parent->IsContentDocument()) {
16024 return nullptr;
16027 parent = parent->GetInProcessParentDocument();
16028 } while (parent);
16030 return parent;
16033 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
16034 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
16037 void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
16038 MOZ_ASSERT(IsBeingUsedAsImage());
16039 MOZ_ASSERT(aReferencingDocument);
16041 if (!aReferencingDocument->mShouldReportUseCounters) {
16042 // No need to propagate use counters to a document that itself won't report
16043 // use counters.
16044 return;
16047 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16048 ("PropagateImageUseCounters from %s to %s",
16049 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
16050 nsContentUtils::TruncatedURLForDisplay(
16051 aReferencingDocument->mDocumentURI)
16052 .get()));
16054 if (aReferencingDocument->IsBeingUsedAsImage()) {
16055 NS_WARNING(
16056 "Page use counters from nested image documents may not "
16057 "propagate to the top-level document (bug 1657805)");
16060 SetCssUseCounterBits();
16061 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
16062 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
16065 bool Document::HasScriptsBlockedBySandbox() const {
16066 return mSandboxFlags & SANDBOXED_SCRIPTS;
16069 void Document::SetCssUseCounterBits() {
16070 if (StaticPrefs::layout_css_use_counters_enabled()) {
16071 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
16072 auto id = nsCSSPropertyID(i);
16073 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
16074 SetUseCounter(nsCSSProps::UseCounterFor(id));
16079 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
16080 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
16081 auto id = CountedUnknownProperty(i);
16082 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
16083 id)) {
16084 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
16090 void Document::InitUseCounters() {
16091 // We can be called more than once, e.g. when session history navigation shows
16092 // us a second time.
16093 if (mUseCountersInitialized) {
16094 return;
16096 mUseCountersInitialized = true;
16098 if (!ShouldIncludeInTelemetry()) {
16099 return;
16102 // Now we know for sure that we should report use counters from this document.
16103 mShouldReportUseCounters = true;
16105 WindowContext* top = GetWindowContextForPageUseCounters();
16106 if (!top) {
16107 // This is the case for SVG image documents. They are not displayed in a
16108 // window, but we still do want to record document use counters for them.
16110 // Page use counter propagation is handled in PropagateImageUseCounters,
16111 // so there is no need to use the cross-process machinery to send them.
16112 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16113 ("InitUseCounters for a non-displayed document [%s]",
16114 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16115 return;
16118 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16119 if (!wgc) {
16120 return;
16123 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16124 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
16125 " [from %s]",
16126 wgc->InnerWindowId(), top->Id(),
16127 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16129 // Inform the parent process that we will send it page use counters later on.
16130 wgc->SendExpectPageUseCounters(top);
16131 mShouldSendPageUseCounters = true;
16134 // We keep separate counts for individual documents and top-level
16135 // pages to more accurately track how many web pages might break if
16136 // certain features were removed. Consider the case of a single
16137 // HTML document with several SVG images and/or iframes with
16138 // sub-documents of their own. If we maintained a single set of use
16139 // counters and all the sub-documents use a particular feature, then
16140 // telemetry would indicate that we would be breaking N documents if
16141 // that feature were removed. Whereas with a document/top-level
16142 // page split, we can see that N documents would be affected, but
16143 // only a single web page would be affected.
16145 // The difference between the values of these two counts and the
16146 // related use counters below tell us how many pages did *not* use
16147 // the feature in question. For instance, if we see that a given
16148 // session has destroyed 30 content documents, but a particular use
16149 // counter shows only a count of 5, we can infer that the use
16150 // counter was *not* used in 25 of those 30 documents.
16152 // We do things this way, rather than accumulating a boolean flag
16153 // for each use counter, to avoid sending data for features
16154 // that don't get widely used. Doing things in this fashion means
16155 // smaller telemetry payloads and faster processing on the server
16156 // side.
16157 void Document::ReportDocumentUseCounters() {
16158 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
16159 return;
16162 mReportedDocumentUseCounters = true;
16164 // Note that a document is being destroyed. See the comment above for how
16165 // use counter data are interpreted relative to this measurement.
16166 glean::use_counter::content_documents_destroyed.Add();
16168 // Ask all of our resource documents to report their own document use
16169 // counters.
16170 EnumerateExternalResources([](Document& aDoc) {
16171 aDoc.ReportDocumentUseCounters();
16172 return CallState::Continue;
16175 // Copy StyleUseCounters into our document use counters.
16176 SetCssUseCounterBits();
16178 Maybe<nsCString> urlForLogging;
16179 const bool dumpCounters = StaticPrefs::dom_use_counters_dump_document();
16180 if (dumpCounters) {
16181 urlForLogging.emplace(
16182 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()));
16185 // Report our per-document use counters.
16186 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
16187 auto uc = static_cast<UseCounter>(c);
16188 if (!mUseCounters[uc]) {
16189 continue;
16192 const char* metricName = IncrementUseCounter(uc, /* aIsPage = */ false);
16193 if (dumpCounters) {
16194 printf_stderr("USE_COUNTER_DOCUMENT: %s - %s\n", metricName,
16195 urlForLogging->get());
16200 void Document::ReportLCP() {
16201 const nsDOMNavigationTiming* timing = GetNavigationTiming();
16203 if (!timing) {
16204 return;
16207 TimeStamp lcpTime = timing->GetLargestContentfulRenderTimeStamp();
16209 if (!lcpTime) {
16210 return;
16213 mozilla::glean::perf::largest_contentful_paint.AccumulateRawDuration(
16214 lcpTime - timing->GetNavigationStartTimeStamp());
16216 if (!GetChannel()) {
16217 return;
16220 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
16221 if (!timedChannel) {
16222 return;
16225 TimeStamp responseStart;
16226 timedChannel->GetResponseStart(&responseStart);
16228 if (!responseStart) {
16229 return;
16232 mozilla::glean::perf::largest_contentful_paint_from_response_start
16233 .AccumulateRawDuration(lcpTime - responseStart);
16235 if (profiler_thread_is_being_profiled_for_markers()) {
16236 MarkerInnerWindowId innerWindowID =
16237 MarkerInnerWindowIdFromDocShell(GetDocShell());
16238 GetNavigationTiming()->MaybeAddLCPProfilerMarker(innerWindowID);
16242 void Document::SendPageUseCounters() {
16243 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
16244 return;
16247 // Ask all of our resource documents to send their own document use
16248 // counters to the parent process to be counted as page use counters.
16249 EnumerateExternalResources([](Document& aDoc) {
16250 aDoc.SendPageUseCounters();
16251 return CallState::Continue;
16254 // Send our use counters to the parent process to accumulate them towards the
16255 // page use counters for the top-level document.
16257 // We take our own document use counters (those in mUseCounters) and any child
16258 // document use counters (those in mChildDocumentUseCounters) that have been
16259 // explicitly propagated up to us, which includes resource documents, static
16260 // clones, and SVG images.
16261 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16262 if (!wgc) {
16263 MOZ_ASSERT_UNREACHABLE(
16264 "SendPageUseCounters should be called while we still have access "
16265 "to our WindowContext");
16266 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16267 (" > too late to send page use counters"));
16268 return;
16271 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16272 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
16273 wgc->WindowContext()->Id(),
16274 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
16276 // Copy StyleUseCounters into our document use counters.
16277 SetCssUseCounterBits();
16279 UseCounters counters = mUseCounters | mChildDocumentUseCounters;
16280 wgc->SendAccumulatePageUseCounters(counters);
16283 bool Document::RecomputeResistFingerprinting() {
16284 mOverriddenFingerprintingSettings.reset();
16285 const bool previous = mShouldResistFingerprinting;
16287 RefPtr<BrowsingContext> opener =
16288 GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
16289 // If we have a parent or opener document, defer to it only when we have a
16290 // null principal (e.g. a sandboxed iframe or a data: uri) or when the
16291 // document's principal matches. This means we will defer about:blank,
16292 // about:srcdoc, blob and same-origin iframes/popups to the parent/opener,
16293 // but not cross-origin ones. Cross-origin iframes/popups may inherit a
16294 // CookieJarSettings.mShouldRFP = false bit however, which will be respected.
16295 auto shouldInheritFrom = [this](Document* aDoc) {
16296 return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
16297 this->NodePrincipal()->GetIsNullPrincipal());
16300 if (shouldInheritFrom(mParentDocument)) {
16301 MOZ_LOG(
16302 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16303 ("Inside RecomputeResistFingerprinting with URI %s and deferring "
16304 "to parent document %s",
16305 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16306 mParentDocument->GetDocumentURI()->GetSpecOrDefault().get()));
16307 mShouldResistFingerprinting = mParentDocument->ShouldResistFingerprinting(
16308 RFPTarget::IsAlwaysEnabledForPrecompute);
16309 mOverriddenFingerprintingSettings =
16310 mParentDocument->mOverriddenFingerprintingSettings;
16311 } else if (opener && shouldInheritFrom(opener->GetDocument())) {
16312 MOZ_LOG(
16313 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16314 ("Inside RecomputeResistFingerprinting with URI %s and deferring to "
16315 "opener document %s",
16316 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16317 opener->GetDocument()->GetDocumentURI()->GetSpecOrDefault().get()));
16318 mShouldResistFingerprinting =
16319 opener->GetDocument()->ShouldResistFingerprinting(
16320 RFPTarget::IsAlwaysEnabledForPrecompute);
16321 mOverriddenFingerprintingSettings =
16322 opener->GetDocument()->mOverriddenFingerprintingSettings;
16323 } else if (nsContentUtils::IsChromeDoc(this)) {
16324 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16325 ("Inside RecomputeResistFingerprinting with a ChromeDoc"));
16327 mShouldResistFingerprinting = false;
16328 mOverriddenFingerprintingSettings.reset();
16329 } else if (mChannel) {
16330 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16331 ("Inside RecomputeResistFingerprinting with URI %s",
16332 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get()
16333 : "null"));
16334 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
16335 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16337 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16338 mOverriddenFingerprintingSettings =
16339 loadInfo->GetOverriddenFingerprintingSettings();
16340 } else {
16341 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16342 ("Inside RecomputeResistFingerprinting fallback case."));
16343 // We still preserve the behavior in the fallback case. But, it means there
16344 // might be some cases we haven't considered yet and we need to investigate
16345 // them.
16346 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
16347 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16348 mOverriddenFingerprintingSettings.reset();
16351 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16352 ("Finished RecomputeResistFingerprinting with result %x",
16353 mShouldResistFingerprinting));
16355 bool changed = previous != mShouldResistFingerprinting;
16356 if (changed) {
16357 if (auto win = nsGlobalWindowInner::Cast(GetInnerWindow())) {
16358 win->RefreshReduceTimerPrecisionCallerType();
16361 return changed;
16364 bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const {
16365 return mShouldResistFingerprinting &&
16366 nsRFPService::IsRFPEnabledFor(this->IsInPrivateBrowsing(), aTarget,
16367 mOverriddenFingerprintingSettings);
16370 void Document::RecordCanvasUsage(CanvasUsage& aUsage) {
16371 // Limit the number of recent canvas extraction uses that are tracked.
16372 const size_t kTrackedCanvasLimit = 8;
16373 // Timeout between different canvas extractions.
16374 const uint64_t kTimeoutUsec = 3000 * 1000;
16376 uint64_t now = PR_Now();
16377 if ((mCanvasUsage.Length() > kTrackedCanvasLimit) ||
16378 ((now - mLastCanvasUsage) > kTimeoutUsec)) {
16379 mCanvasUsage.ClearAndRetainStorage();
16382 mCanvasUsage.AppendElement(aUsage);
16383 mLastCanvasUsage = now;
16385 nsCString originNoSuffix;
16386 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
16387 return;
16390 nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsage, GetChannel(),
16391 originNoSuffix);
16394 void Document::RecordFontFingerprinting() {
16395 nsCString originNoSuffix;
16396 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
16397 return;
16400 nsRFPService::MaybeReportFontFingerprinter(GetChannel(), originNoSuffix);
16403 bool Document::IsInPrivateBrowsing() const { return mIsInPrivateBrowsing; }
16405 WindowContext* Document::GetWindowContextForPageUseCounters() const {
16406 if (mDisplayDocument) {
16407 // If we are a resource document, then go through it to find the
16408 // top-level document.
16409 return mDisplayDocument->GetWindowContextForPageUseCounters();
16412 if (mOriginalDocument) {
16413 // For static clones (print preview documents), contribute page use counters
16414 // towards the original document.
16415 return mOriginalDocument->GetWindowContextForPageUseCounters();
16418 WindowContext* wc = GetTopLevelWindowContext();
16419 if (!wc || !wc->GetBrowsingContext()->IsContent()) {
16420 return nullptr;
16423 return wc;
16426 void Document::UpdateIntersections(TimeStamp aNowTime) {
16427 if (!mIntersectionObservers.IsEmpty()) {
16428 DOMHighResTimeStamp time = 0;
16429 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
16430 if (Performance* perf = win->GetPerformance()) {
16431 time = perf->TimeStampToDOMHighResForRendering(aNowTime);
16434 for (DOMIntersectionObserver* observer : mIntersectionObservers) {
16435 observer->Update(*this, time);
16437 Dispatch(NewRunnableMethod("Document::NotifyIntersectionObservers", this,
16438 &Document::NotifyIntersectionObservers));
16440 EnumerateSubDocuments([aNowTime](Document& aDoc) {
16441 aDoc.UpdateIntersections(aNowTime);
16442 return CallState::Continue;
16446 void Document::NotifyIntersectionObservers() {
16447 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16448 mIntersectionObservers);
16449 for (const auto& observer : observers) {
16450 // MOZ_KnownLive because the 'observers' array guarantees to keep it
16451 // alive.
16452 MOZ_KnownLive(observer)->Notify();
16456 DOMIntersectionObserver& Document::EnsureLazyLoadObserver() {
16457 if (!mLazyLoadObserver) {
16458 mLazyLoadObserver = DOMIntersectionObserver::CreateLazyLoadObserver(*this);
16460 return *mLazyLoadObserver;
16463 void Document::ObserveForLastRememberedSize(Element& aElement) {
16464 if (NS_WARN_IF(!IsActive())) {
16465 return;
16467 mElementsObservedForLastRememberedSize.Insert(&aElement);
16470 void Document::UnobserveForLastRememberedSize(Element& aElement) {
16471 mElementsObservedForLastRememberedSize.Remove(&aElement);
16474 void Document::UpdateLastRememberedSizes() {
16475 auto shouldRemoveElement = [&](auto* element) {
16476 if (element->GetComposedDoc() != this) {
16477 element->RemoveLastRememberedBSize();
16478 element->RemoveLastRememberedISize();
16479 return true;
16481 return !element->GetPrimaryFrame();
16484 for (auto it = mElementsObservedForLastRememberedSize.begin(),
16485 end = mElementsObservedForLastRememberedSize.end();
16486 it != end; ++it) {
16487 if (shouldRemoveElement(*it)) {
16488 mElementsObservedForLastRememberedSize.Remove(it);
16489 continue;
16491 const auto element = *it;
16492 MOZ_ASSERT(element->GetComposedDoc() == this);
16493 nsIFrame* frame = element->GetPrimaryFrame();
16494 MOZ_ASSERT(frame);
16496 // As for ResizeObserver, skip nodes hidden `content-visibility`.
16497 if (frame->IsHiddenByContentVisibilityOnAnyAncestor()) {
16498 continue;
16501 MOZ_ASSERT(!frame->IsLineParticipant() || frame->IsReplaced(),
16502 "Should have unobserved non-replaced inline.");
16503 MOZ_ASSERT(!frame->HidesContent(),
16504 "Should have unobserved element skipping its contents.");
16505 const nsStylePosition* stylePos = frame->StylePosition();
16506 const WritingMode wm = frame->GetWritingMode();
16507 bool canUpdateBSize = stylePos->ContainIntrinsicBSize(wm).HasAuto();
16508 bool canUpdateISize = stylePos->ContainIntrinsicISize(wm).HasAuto();
16509 MOZ_ASSERT(canUpdateBSize || !element->HasLastRememberedBSize(),
16510 "Should have removed the last remembered block size.");
16511 MOZ_ASSERT(canUpdateISize || !element->HasLastRememberedISize(),
16512 "Should have removed the last remembered inline size.");
16513 MOZ_ASSERT(canUpdateBSize || canUpdateISize,
16514 "Should have unobserved if we can't update any size.");
16516 AutoTArray<LogicalPixelSize, 1> contentSizeList =
16517 ResizeObserver::CalculateBoxSize(element,
16518 ResizeObserverBoxOptions::Content_box,
16519 /* aForceFragmentHandling */ true);
16520 MOZ_ASSERT(!contentSizeList.IsEmpty());
16522 if (canUpdateBSize) {
16523 float bSize = 0;
16524 for (const auto& current : contentSizeList) {
16525 bSize += current.BSize();
16527 element->SetLastRememberedBSize(bSize);
16529 if (canUpdateISize) {
16530 float iSize = 0;
16531 for (const auto& current : contentSizeList) {
16532 iSize = std::max(iSize, current.ISize());
16534 element->SetLastRememberedISize(iSize);
16539 void Document::NotifyLayerManagerRecreated() {
16540 NotifyActivityChanged();
16541 EnumerateSubDocuments([](Document& aSubDoc) {
16542 aSubDoc.NotifyLayerManagerRecreated();
16543 return CallState::Continue;
16547 XPathEvaluator* Document::XPathEvaluator() {
16548 if (!mXPathEvaluator) {
16549 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
16551 return mXPathEvaluator.get();
16554 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
16555 return mCachedEncoder.forget();
16558 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
16559 mCachedEncoder = aEncoder;
16562 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
16564 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
16566 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
16567 mStateObjectContainer = scContainer;
16568 mCachedStateObject = JS::UndefinedValue();
16569 mCachedStateObjectValid = false;
16572 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
16573 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
16574 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
16575 ELEMENT_NODE);
16576 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
16578 nsCOMPtr<Element> element;
16579 DebugOnly<nsresult> rv =
16580 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
16581 mozilla::dom::NOT_FROM_PARSER);
16583 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
16584 return element.forget();
16587 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
16588 BrowsingContext* aContext) {
16589 aContext->PreOrderWalk([&](BrowsingContext* aBC) {
16590 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
16591 if (RefPtr<Document> doc = win->GetExtantDoc()) {
16592 SuppressDocument(doc);
16593 mDocuments.AppendElement(doc);
16599 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
16600 BrowsingContextGroup* aGroup) {
16601 for (const auto& bc : aGroup->Toplevels()) {
16602 SuppressBrowsingContext(bc);
16606 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
16607 SyncOperationBehavior aSyncBehavior)
16608 : mSyncBehavior(aSyncBehavior) {
16609 mMicroTaskLevel = 0;
16610 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16611 mMicroTaskLevel = ccjs->MicroTaskLevel();
16612 ccjs->SetMicroTaskLevel(0);
16614 if (aDoc) {
16615 mBrowsingContext = aDoc->GetBrowsingContext();
16616 if (InputTaskManager::CanSuspendInputEvent()) {
16617 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
16618 SuppressBrowsingContextGroup(bcg);
16620 } else if (mBrowsingContext) {
16621 SuppressBrowsingContext(mBrowsingContext->Top());
16623 if (mBrowsingContext &&
16624 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16625 InputTaskManager::CanSuspendInputEvent()) {
16626 mBrowsingContext->Group()->IncInputEventSuspensionLevel();
16631 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
16632 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16633 win->TimeoutManager().BeginSyncOperation();
16635 aDoc->SetIsInSyncOperation(true);
16638 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
16639 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16640 win->TimeoutManager().EndSyncOperation();
16642 aDoc->SetIsInSyncOperation(false);
16645 nsAutoSyncOperation::~nsAutoSyncOperation() {
16646 UnsuppressDocuments();
16647 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
16648 if (ccjs) {
16649 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
16651 if (mBrowsingContext &&
16652 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16653 InputTaskManager::CanSuspendInputEvent()) {
16654 mBrowsingContext->Group()->DecInputEventSuspensionLevel();
16658 void Document::SetIsInSyncOperation(bool aSync) {
16659 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16660 ccjs->UpdateMicroTaskSuppressionGeneration();
16663 if (aSync) {
16664 ++mInSyncOperationCount;
16665 } else {
16666 --mInSyncOperationCount;
16670 gfxUserFontSet* Document::GetUserFontSet() {
16671 if (!mFontFaceSet) {
16672 return nullptr;
16675 return mFontFaceSet->GetImpl();
16678 void Document::FlushUserFontSet() {
16679 if (!mFontFaceSetDirty) {
16680 return;
16683 mFontFaceSetDirty = false;
16685 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
16686 nsTArray<nsFontFaceRuleContainer> rules;
16687 RefPtr<PresShell> presShell = GetPresShell();
16688 if (presShell) {
16689 MOZ_ASSERT(mStyleSetFilled);
16690 EnsureStyleSet().AppendFontFaceRules(rules);
16693 if (!mFontFaceSet && !rules.IsEmpty()) {
16694 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16697 bool changed = false;
16698 if (mFontFaceSet) {
16699 changed = mFontFaceSet->UpdateRules(rules);
16702 // We need to enqueue a style change reflow (for later) to
16703 // reflect that we're modifying @font-face rules. (However,
16704 // without a reflow, nothing will happen to start any downloads
16705 // that are needed.)
16706 if (changed && presShell) {
16707 if (nsPresContext* presContext = presShell->GetPresContext()) {
16708 presContext->UserFontSetUpdated();
16714 void Document::MarkUserFontSetDirty() {
16715 if (mFontFaceSetDirty) {
16716 return;
16718 mFontFaceSetDirty = true;
16719 if (PresShell* presShell = GetPresShell()) {
16720 presShell->EnsureStyleFlush();
16724 FontFaceSet* Document::Fonts() {
16725 if (!mFontFaceSet) {
16726 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16727 FlushUserFontSet();
16729 return mFontFaceSet;
16732 void Document::ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp) {
16733 MOZ_ASSERT(!aTimeStamp.IsNull());
16735 if (!mLastScrollLinkedEffectDetectionTime.IsNull() &&
16736 mLastScrollLinkedEffectDetectionTime >= aTimeStamp) {
16737 return;
16740 if (mLastScrollLinkedEffectDetectionTime.IsNull()) {
16741 // Report to console just once.
16742 nsContentUtils::ReportToConsole(
16743 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
16744 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
16747 mLastScrollLinkedEffectDetectionTime = aTimeStamp;
16750 bool Document::HasScrollLinkedEffect() const {
16751 if (nsPresContext* pc = GetPresContext()) {
16752 return mLastScrollLinkedEffectDetectionTime ==
16753 pc->RefreshDriver()->MostRecentRefresh();
16756 return false;
16759 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
16760 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16761 // Setting has user interction on a discarded browsing context has
16762 // no effect.
16763 Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
16767 bool Document::GetSHEntryHasUserInteraction() {
16768 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16769 return topWc->GetSHEntryHasUserInteraction();
16771 return false;
16774 void Document::SetUserHasInteracted() {
16775 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
16776 ("Document %p has been interacted by user.", this));
16778 // We maybe need to update the user-interaction permission.
16779 MaybeStoreUserInteractionAsPermission();
16781 // For purposes of reducing irrelevant session history entries on
16782 // the back button, we annotate entries with whether they had user
16783 // interaction. This is gated on its own flag on the WindowContext
16784 // (instead of mUserHasInteracted) to account for the fact that multiple
16785 // top-level SH entries can be associated with the same document.
16786 // Thus, whenever we create a new SH entry for this document,
16787 // this flag is reset.
16788 if (!GetSHEntryHasUserInteraction()) {
16789 nsIDocShell* docShell = GetDocShell();
16790 if (docShell) {
16791 nsCOMPtr<nsISHEntry> currentEntry;
16792 bool oshe;
16793 nsresult rv =
16794 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
16795 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
16796 currentEntry->SetHasUserInteraction(true);
16799 SetSHEntryHasUserInteraction(true);
16802 if (mUserHasInteracted) {
16803 return;
16806 mUserHasInteracted = true;
16808 if (mChannel) {
16809 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16810 loadInfo->SetDocumentHasUserInteracted(true);
16812 // Tell the parent process about user interaction
16813 if (auto* wgc = GetWindowGlobalChild()) {
16814 wgc->SendUpdateDocumentHasUserInteracted(true);
16817 MaybeAllowStorageForOpenerAfterUserInteraction();
16820 BrowsingContext* Document::GetBrowsingContext() const {
16821 return mDocumentContainer ? mDocumentContainer->GetBrowsingContext()
16822 : nullptr;
16825 void Document::NotifyUserGestureActivation(
16826 UserActivation::Modifiers
16827 aModifiers /* = UserActivation::Modifiers::None() */) {
16828 // https://html.spec.whatwg.org/multipage/interaction.html#activation-notification
16829 // 1. "Assert: document is fully active."
16830 RefPtr<BrowsingContext> currentBC = GetBrowsingContext();
16831 if (!currentBC) {
16832 return;
16835 RefPtr<WindowContext> currentWC = GetWindowContext();
16836 if (!currentWC) {
16837 return;
16840 // 2. "Let windows be « document's relevant global object"
16841 // Instead of assembling a list, we just call notify for wanted windows as we
16842 // find them
16843 currentWC->NotifyUserGestureActivation(aModifiers);
16845 // 3. "...windows with the active window of each of document's ancestor
16846 // navigables."
16847 for (WindowContext* wc = currentWC; wc; wc = wc->GetParentWindowContext()) {
16848 wc->NotifyUserGestureActivation(aModifiers);
16851 // 4. "windows with the active window of each of document's descendant
16852 // navigables, filtered to include only those navigables whose active
16853 // document's origin is same origin with document's origin"
16854 currentBC->PreOrderWalk([&](BrowsingContext* bc) {
16855 WindowContext* wc = bc->GetCurrentWindowContext();
16856 if (!wc) {
16857 return;
16860 // Check same-origin as current document
16861 WindowGlobalChild* wgc = wc->GetWindowGlobalChild();
16862 if (!wgc || !wgc->IsSameOriginWith(currentWC)) {
16863 return;
16866 wc->NotifyUserGestureActivation(aModifiers);
16870 bool Document::HasBeenUserGestureActivated() {
16871 RefPtr<WindowContext> wc = GetWindowContext();
16872 return wc && wc->HasBeenUserGestureActivated();
16875 DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
16876 if (RefPtr<WindowContext> wc = GetWindowContext()) {
16877 if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) {
16878 if (Performance* perf = innerWindow->GetPerformance()) {
16879 return perf->GetDOMTiming()->TimeStampToDOMHighRes(
16880 wc->GetUserGestureStart());
16885 NS_WARNING(
16886 "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
16887 return 0;
16890 void Document::ClearUserGestureActivation() {
16891 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16892 bc = bc->Top();
16893 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16894 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
16895 windowContext->NotifyResetUserGestureActivation();
16901 bool Document::HasValidTransientUserGestureActivation() const {
16902 RefPtr<WindowContext> wc = GetWindowContext();
16903 return wc && wc->HasValidTransientUserGestureActivation();
16906 bool Document::ConsumeTransientUserGestureActivation() {
16907 RefPtr<WindowContext> wc = GetWindowContext();
16908 return wc && wc->ConsumeTransientUserGestureActivation();
16911 bool Document::GetTransientUserGestureActivationModifiers(
16912 UserActivation::Modifiers* aModifiers) {
16913 RefPtr<WindowContext> wc = GetWindowContext();
16914 return wc && wc->GetTransientUserGestureActivationModifiers(aModifiers);
16917 void Document::SetDocTreeHadMedia() {
16918 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
16919 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
16920 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
16924 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
16925 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
16926 return;
16929 // This will probably change for project fission, but currently this document
16930 // and the opener are on the same process. In the future, we should make this
16931 // part async.
16932 nsPIDOMWindowInner* inner = GetInnerWindow();
16933 if (NS_WARN_IF(!inner)) {
16934 return;
16937 // We care about first-party tracking resources only.
16938 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
16939 return;
16942 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16943 if (NS_WARN_IF(!outer)) {
16944 return;
16947 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
16948 if (!openerBC) {
16949 // No opener.
16950 return;
16953 // We want to ensure the following check works for both fission mode and
16954 // non-fission mode:
16955 // "If the opener is not a 3rd party and if this window is not a 3rd party
16956 // with respect to the opener, we should not continue."
16958 // In non-fission mode, the opener and the opened window are in the same
16959 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
16960 // In fission mode, if this window is not a 3rd party with respect to the
16961 // opener, they must be in the same process, so we can still use
16962 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
16963 // party.
16964 if (openerBC->IsInProcess()) {
16965 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
16966 if (NS_WARN_IF(!outerOpener)) {
16967 return;
16970 nsCOMPtr<nsPIDOMWindowInner> openerInner =
16971 outerOpener->GetCurrentInnerWindow();
16972 if (NS_WARN_IF(!openerInner)) {
16973 return;
16976 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
16977 if (NS_WARN_IF(!openerDocument)) {
16978 return;
16981 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
16982 if (NS_WARN_IF(!openerURI)) {
16983 return;
16986 // If the opener is not a 3rd party and if this window is not
16987 // a 3rd party with respect to the opener, we should not continue.
16988 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
16989 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
16990 return;
16994 // We don't care when the asynchronous work finishes here.
16995 Unused << StorageAccessAPIHelper::AllowAccessForOnChildProcess(
16996 NodePrincipal(), openerBC,
16997 ContentBlockingNotifier::eOpenerAfterUserInteraction);
17000 namespace {
17002 // Documents can stay alive for days. We don't want to update the permission
17003 // value at any user-interaction, and, using a timer triggered any X seconds
17004 // should be good enough. 'X' is taken from
17005 // privacy.userInteraction.document.interval pref.
17006 // We also want to store the user-interaction before shutting down, and, for
17007 // this reason, this class implements nsIAsyncShutdownBlocker interface.
17008 class UserInteractionTimer final : public Runnable,
17009 public nsITimerCallback,
17010 public nsIAsyncShutdownBlocker {
17011 public:
17012 NS_DECL_ISUPPORTS_INHERITED
17014 explicit UserInteractionTimer(Document* aDocument)
17015 : Runnable("UserInteractionTimer"),
17016 mPrincipal(aDocument->NodePrincipal()),
17017 mDocument(aDocument) {
17018 static int32_t userInteractionTimerId = 0;
17019 // Blocker names must be unique. Let's create it now because when needed,
17020 // the document could be already gone.
17021 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
17022 ++userInteractionTimerId, aDocument);
17025 // Runnable interface
17027 NS_IMETHOD
17028 Run() override {
17029 uint32_t interval =
17030 StaticPrefs::privacy_userInteraction_document_interval();
17031 if (!interval) {
17032 return NS_OK;
17035 RefPtr<UserInteractionTimer> self = this;
17036 auto raii =
17037 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
17039 nsresult rv = NS_NewTimerWithCallback(
17040 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
17041 NS_ENSURE_SUCCESS(rv, NS_OK);
17043 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
17044 NS_ENSURE_TRUE(!!phase, NS_OK);
17046 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
17047 __LINE__, u"UserInteractionTimer shutdown"_ns);
17048 NS_ENSURE_SUCCESS(rv, NS_OK);
17050 raii.release();
17051 return NS_OK;
17054 // nsITimerCallback interface
17056 NS_IMETHOD
17057 Notify(nsITimer* aTimer) override {
17058 StoreUserInteraction();
17059 return NS_OK;
17062 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
17063 using nsINamed::GetName;
17064 #endif
17066 // nsIAsyncShutdownBlocker interface
17068 NS_IMETHOD
17069 GetName(nsAString& aName) override {
17070 aName = mBlockerName;
17071 return NS_OK;
17074 NS_IMETHOD
17075 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
17076 CancelTimerAndStoreUserInteraction();
17077 return NS_OK;
17080 NS_IMETHOD
17081 GetState(nsIPropertyBag**) override { return NS_OK; }
17083 private:
17084 ~UserInteractionTimer() = default;
17086 void StoreUserInteraction() {
17087 // Remove the shutting down blocker
17088 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
17089 if (phase) {
17090 phase->RemoveBlocker(this);
17093 // If the document is not gone, let's reset its timer flag.
17094 nsCOMPtr<Document> document(mDocument);
17095 if (document) {
17096 ContentBlockingUserInteraction::Observe(mPrincipal);
17097 document->ResetUserInteractionTimer();
17101 void CancelTimerAndStoreUserInteraction() {
17102 if (mTimer) {
17103 mTimer->Cancel();
17104 mTimer = nullptr;
17107 StoreUserInteraction();
17110 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
17111 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
17112 NS_ENSURE_TRUE(!!svc, nullptr);
17114 nsCOMPtr<nsIAsyncShutdownClient> phase;
17115 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
17116 NS_ENSURE_SUCCESS(rv, nullptr);
17118 return phase.forget();
17121 nsCOMPtr<nsIPrincipal> mPrincipal;
17122 WeakPtr<Document> mDocument;
17124 nsCOMPtr<nsITimer> mTimer;
17126 nsString mBlockerName;
17129 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
17130 nsIAsyncShutdownBlocker)
17132 } // namespace
17134 void Document::MaybeStoreUserInteractionAsPermission() {
17135 // We care about user-interaction stored only for top-level documents
17136 // and documents with access to the Storage Access API
17137 if (!IsTopLevelContentDocument()) {
17138 bool hasSA;
17139 nsresult rv = HasStorageAccessSync(hasSA);
17140 if (NS_FAILED(rv) || !hasSA) {
17141 return;
17145 if (!mUserHasInteracted) {
17146 // First interaction, let's store this info now.
17147 ContentBlockingUserInteraction::Observe(NodePrincipal());
17148 return;
17151 if (mHasUserInteractionTimerScheduled) {
17152 return;
17155 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
17156 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
17157 EventQueuePriority::Idle);
17158 if (NS_WARN_IF(NS_FAILED(rv))) {
17159 return;
17162 // This value will be reset by the timer.
17163 mHasUserInteractionTimerScheduled = true;
17166 void Document::ResetUserInteractionTimer() {
17167 mHasUserInteractionTimerScheduled = false;
17170 bool Document::IsExtensionPage() const {
17171 return BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
17174 void Document::AddResizeObserver(ResizeObserver& aObserver) {
17175 MOZ_ASSERT(!mResizeObservers.Contains(&aObserver));
17176 mResizeObservers.AppendElement(&aObserver);
17179 void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
17180 MOZ_ASSERT(mResizeObservers.Contains(&aObserver));
17181 mResizeObservers.RemoveElement(&aObserver);
17184 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
17185 if (!mPermissionDelegateHandler) {
17186 mPermissionDelegateHandler = MakeAndAddRef<PermissionDelegateHandler>(this);
17189 if (!mPermissionDelegateHandler->Initialize()) {
17190 mPermissionDelegateHandler = nullptr;
17193 return mPermissionDelegateHandler;
17196 void Document::ScheduleResizeObserversNotification() const {
17197 if (!mPresShell) {
17198 return;
17200 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
17201 rd->EnsureResizeObserverUpdateHappens();
17205 static void FlushLayoutForWholeBrowsingContextTree(Document& aDoc) {
17206 const ChangesToFlush ctf(FlushType::Layout, /* aFlushAnimations = */ false);
17207 BrowsingContext* bc = aDoc.GetBrowsingContext();
17208 if (bc && bc->GetExtantDocument() == &aDoc) {
17209 RefPtr<BrowsingContext> top = bc->Top();
17210 top->PreOrderWalk([ctf](BrowsingContext* aCur) {
17211 if (Document* doc = aCur->GetExtantDocument()) {
17212 doc->FlushPendingNotifications(ctf);
17215 } else {
17216 // If there is no browsing context, or we're not the current document of the
17217 // browsing context, then we just flush this document itself.
17218 aDoc.FlushPendingNotifications(ctf);
17222 void Document::DetermineProximityToViewportAndNotifyResizeObservers() {
17223 uint32_t shallowestTargetDepth = 0;
17224 bool initialResetOfScrolledIntoViewFlagsDone = false;
17225 while (true) {
17226 // Flush layout, so that any callback functions' style changes / resizes
17227 // get a chance to take effect. The callback functions may do changes in its
17228 // sub-documents or ancestors, so flushing layout for the whole browsing
17229 // context tree makes sure we don't miss anyone.
17230 FlushLayoutForWholeBrowsingContextTree(*this);
17232 // Last remembered sizes are recorded "at the time that ResizeObserver
17233 // events are determined and delivered".
17234 // https://drafts.csswg.org/css-sizing-4/#last-remembered
17236 // We do it right after layout to make sure sizes are up-to-date. If we do
17237 // it after determining the proximities to viewport of
17238 // 'content-visibility: auto' nodes, and if one of such node ever becomes
17239 // relevant to the user, then we would be incorrectly recording the size
17240 // of its rendering when it was skipping its content.
17241 UpdateLastRememberedSizes();
17243 if (PresShell* presShell = GetPresShell()) {
17244 auto result = presShell->DetermineProximityToViewport();
17245 if (result.mHadInitialDetermination) {
17246 continue;
17248 if (result.mAnyScrollIntoViewFlag) {
17249 // Not defined in the spec: It's possible that some elements with
17250 // content-visibility: auto were forced to be visible in order to
17251 // perform scrollIntoView() so clear their flags now and restart the
17252 // loop.
17253 // See https://github.com/w3c/csswg-drafts/issues/9337
17254 presShell->ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags();
17255 presShell->ScheduleContentRelevancyUpdate(
17256 ContentRelevancyReason::Visible);
17257 if (!initialResetOfScrolledIntoViewFlagsDone) {
17258 initialResetOfScrolledIntoViewFlagsDone = true;
17259 continue;
17264 // To avoid infinite resize loop, we only gather all active observations
17265 // that have the depth of observed target element more than current
17266 // shallowestTargetDepth.
17267 GatherAllActiveResizeObservations(shallowestTargetDepth);
17269 if (!HasAnyActiveResizeObservations()) {
17270 break;
17273 DebugOnly<uint32_t> oldShallowestTargetDepth = shallowestTargetDepth;
17274 shallowestTargetDepth = BroadcastAllActiveResizeObservations();
17275 NS_ASSERTION(oldShallowestTargetDepth < shallowestTargetDepth,
17276 "shallowestTargetDepth should be getting strictly deeper");
17279 if (HasAnySkippedResizeObservations()) {
17280 if (nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow()) {
17281 // Per spec, we deliver an error if the document has any skipped
17282 // observations. Also, we re-register via ScheduleNotification().
17283 RootedDictionary<ErrorEventInit> init(RootingCx());
17284 init.mMessage.AssignLiteral(
17285 "ResizeObserver loop completed with undelivered notifications.");
17286 init.mBubbles = false;
17287 init.mCancelable = false;
17289 nsEventStatus status = nsEventStatus_eIgnore;
17290 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
17291 MOZ_ASSERT(sgo);
17292 if (NS_WARN_IF(sgo->HandleScriptError(init, &status))) {
17293 status = nsEventStatus_eIgnore;
17295 } else {
17296 // We don't fire error events at any global for non-window JS on the main
17297 // thread.
17300 // We need to deliver pending notifications in next cycle.
17301 ScheduleResizeObserversNotification();
17305 void Document::GatherAllActiveResizeObservations(uint32_t aDepth) {
17306 for (ResizeObserver* observer : mResizeObservers) {
17307 observer->GatherActiveObservations(aDepth);
17311 uint32_t Document::BroadcastAllActiveResizeObservations() {
17312 uint32_t shallowestTargetDepth = std::numeric_limits<uint32_t>::max();
17314 // Copy the observers as this invokes the callbacks and could register and
17315 // unregister observers at will.
17316 const auto observers =
17317 ToTArray<nsTArray<RefPtr<ResizeObserver>>>(mResizeObservers);
17318 for (const auto& observer : observers) {
17319 // MOZ_KnownLive because 'observers' is guaranteed to keep it
17320 // alive.
17322 // This can go away once
17323 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
17324 uint32_t targetDepth =
17325 MOZ_KnownLive(observer)->BroadcastActiveObservations();
17326 if (targetDepth < shallowestTargetDepth) {
17327 shallowestTargetDepth = targetDepth;
17331 return shallowestTargetDepth;
17334 bool Document::HasAnySkippedResizeObservations() const {
17335 for (const auto& observer : mResizeObservers) {
17336 if (observer->HasSkippedObservations()) {
17337 return true;
17340 return false;
17343 bool Document::HasAnyActiveResizeObservations() const {
17344 for (const auto& observer : mResizeObservers) {
17345 if (observer->HasActiveObservations()) {
17346 return true;
17349 return false;
17352 void Document::ClearStaleServoData() {
17353 DocumentStyleRootIterator iter(this);
17354 while (Element* root = iter.GetNextStyleRoot()) {
17355 RestyleManager::ClearServoDataFromSubtree(root);
17359 Selection* Document::GetSelection(ErrorResult& aRv) {
17360 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
17361 if (!window) {
17362 return nullptr;
17365 if (!window->IsCurrentInnerWindow()) {
17366 return nullptr;
17369 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
17372 void Document::MakeBrowsingContextNonSynthetic() {
17373 if (BrowsingContext* bc = GetBrowsingContext()) {
17374 if (bc->GetSyntheticDocumentContainer()) {
17375 Unused << bc->SetSyntheticDocumentContainer(false);
17380 nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
17381 // Step 1: check if cookie permissions are available or denied to this
17382 // document's principal
17383 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17384 if (!inner) {
17385 aHasStorageAccess = false;
17386 return NS_OK;
17388 Maybe<bool> resultBecauseCookiesApproved =
17389 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17390 CookieJarSettings(), NodePrincipal());
17391 if (resultBecauseCookiesApproved.isSome()) {
17392 if (resultBecauseCookiesApproved.value()) {
17393 aHasStorageAccess = true;
17394 return NS_OK;
17395 } else {
17396 aHasStorageAccess = false;
17397 return NS_OK;
17401 // Step 2: Check if the browser settings determine whether or not this
17402 // document has access to its unpartitioned cookies.
17403 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17404 bool isOnThirdPartySkipList = false;
17405 if (mChannel) {
17406 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17407 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17408 nsILoadInfo::StoragePermissionAllowListed;
17410 bool isThirdPartyTracker =
17411 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17412 Maybe<bool> resultBecauseBrowserSettings =
17413 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17414 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
17415 isThirdPartyTracker);
17416 if (resultBecauseBrowserSettings.isSome()) {
17417 if (resultBecauseBrowserSettings.value()) {
17418 aHasStorageAccess = true;
17419 return NS_OK;
17420 } else {
17421 aHasStorageAccess = false;
17422 return NS_OK;
17426 // Step 3: Check if the location of this call (embedded, top level, same-site)
17427 // determines if cookies are permitted or not.
17428 Maybe<bool> resultBecauseCallContext =
17429 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17430 false);
17431 if (resultBecauseCallContext.isSome()) {
17432 if (resultBecauseCallContext.value()) {
17433 aHasStorageAccess = true;
17434 return NS_OK;
17435 } else {
17436 aHasStorageAccess = false;
17437 return NS_OK;
17441 // Step 4: Check if the permissions for this document determine if if has
17442 // access or is denied cookies.
17443 Maybe<bool> resultBecausePreviousPermission =
17444 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17445 this, false);
17446 if (resultBecausePreviousPermission.isSome()) {
17447 if (resultBecausePreviousPermission.value()) {
17448 aHasStorageAccess = true;
17449 return NS_OK;
17450 } else {
17451 aHasStorageAccess = false;
17452 return NS_OK;
17455 // If you get here, we default to not giving you permission.
17456 aHasStorageAccess = false;
17457 return NS_OK;
17460 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
17461 mozilla::ErrorResult& aRv) {
17462 nsIGlobalObject* global = GetScopeObject();
17463 if (!global) {
17464 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17465 return nullptr;
17468 RefPtr<Promise> promise =
17469 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
17470 if (aRv.Failed()) {
17471 return nullptr;
17474 if (!IsCurrentActiveDocument()) {
17475 promise->MaybeRejectWithInvalidStateError(
17476 "hasStorageAccess requires an active document");
17477 return promise.forget();
17480 bool hasStorageAccess;
17481 nsresult rv = HasStorageAccessSync(hasStorageAccess);
17482 if (NS_FAILED(rv)) {
17483 promise->MaybeRejectWithUndefined();
17484 } else {
17485 promise->MaybeResolve(hasStorageAccess);
17488 return promise.forget();
17491 RefPtr<Document::GetContentBlockingEventsPromise>
17492 Document::GetContentBlockingEvents() {
17493 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
17494 if (!wgc) {
17495 return nullptr;
17498 return wgc->SendGetContentBlockingEvents()->Then(
17499 GetCurrentSerialEventTarget(), __func__,
17500 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
17501 ResolveOrRejectValue& aValue) {
17502 if (aValue.IsResolve()) {
17503 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
17504 aValue.ResolveValue(), __func__);
17507 return Document::GetContentBlockingEventsPromise::CreateAndReject(
17508 false, __func__);
17512 StorageAccessAPIHelper::PerformPermissionGrant
17513 Document::CreatePermissionGrantPromise(
17514 nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal,
17515 bool aHasUserInteraction, bool aRequireUserInteraction,
17516 const Maybe<nsCString>& aTopLevelBaseDomain, bool aFrameOnly) {
17517 MOZ_ASSERT(aInnerWindow);
17518 MOZ_ASSERT(aPrincipal);
17519 RefPtr<Document> self(this);
17520 RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
17521 RefPtr<nsIPrincipal> principal(aPrincipal);
17523 return [inner, self, principal, aHasUserInteraction, aRequireUserInteraction,
17524 aTopLevelBaseDomain, aFrameOnly]() {
17525 RefPtr<StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::Private>
17526 p = new StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
17527 Private(__func__);
17529 // Before we prompt, see if we are same-site
17530 if (aFrameOnly) {
17531 nsIChannel* channel = self->GetChannel();
17532 if (channel) {
17533 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
17534 if (!loadInfo->GetIsThirdPartyContextToTopWindow()) {
17535 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17536 return p;
17541 RefPtr<PWindowGlobalChild::GetStorageAccessPermissionPromise> promise;
17542 // Test the permission
17543 MOZ_ASSERT(XRE_IsContentProcess());
17545 WindowGlobalChild* wgc = inner->GetWindowGlobalChild();
17546 MOZ_ASSERT(wgc);
17548 promise = wgc->SendGetStorageAccessPermission();
17549 MOZ_ASSERT(promise);
17550 promise->Then(
17551 GetCurrentSerialEventTarget(), __func__,
17552 [self, p, inner, principal, aHasUserInteraction,
17553 aRequireUserInteraction, aTopLevelBaseDomain,
17554 aFrameOnly](uint32_t aAction) {
17555 if (aAction == nsIPermissionManager::ALLOW_ACTION) {
17556 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17557 return;
17559 if (aAction == nsIPermissionManager::DENY_ACTION) {
17560 p->Reject(false, __func__);
17561 return;
17564 // We require user activation before conducting a permission request
17565 // See
17566 // https://privacycg.github.io/storage-access/#dom-document-requeststorageaccess
17567 // where we "If has transient activation is false: ..." immediately
17568 // before we "Let permissionState be the result of requesting
17569 // permission to use "storage-access"" from in parallel.
17570 if (!aHasUserInteraction && aRequireUserInteraction) {
17571 // Report an error to the console for this case
17572 nsContentUtils::ReportToConsole(
17573 nsIScriptError::errorFlag,
17574 nsLiteralCString("requestStorageAccess"), self,
17575 nsContentUtils::eDOM_PROPERTIES,
17576 "RequestStorageAccessUserGesture");
17577 p->Reject(false, __func__);
17578 return;
17581 // Create the user prompt
17582 RefPtr<StorageAccessPermissionRequest> sapr =
17583 StorageAccessPermissionRequest::Create(
17584 inner, principal, aTopLevelBaseDomain, aFrameOnly,
17585 // Allow
17586 [p] {
17587 Telemetry::AccumulateCategorical(
17588 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
17589 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17591 // Block
17592 [p] {
17593 Telemetry::AccumulateCategorical(
17594 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
17595 p->Reject(false, __func__);
17598 using PromptResult = ContentPermissionRequestBase::PromptResult;
17599 PromptResult pr = sapr->CheckPromptPrefs();
17601 if (pr == PromptResult::Pending) {
17602 // We're about to show a prompt, record the request attempt
17603 Telemetry::AccumulateCategorical(
17604 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
17607 // Try to auto-grant the storage access so the user doesn't see the
17608 // prompt.
17609 self->AutomaticStorageAccessPermissionCanBeGranted(
17610 aHasUserInteraction)
17611 ->Then(
17612 GetCurrentSerialEventTarget(), __func__,
17613 // If the autogrant check didn't fail, call this function
17614 [p, pr, sapr,
17615 inner](const Document::
17616 AutomaticStorageAccessPermissionGrantPromise::
17617 ResolveOrRejectValue& aValue) -> void {
17618 // Make a copy because we can't modified copy-captured
17619 // lambda variables.
17620 PromptResult pr2 = pr;
17622 // If the user didn't already click "allow" and we can
17623 // autogrant, do that!
17624 bool storageAccessCanBeGrantedAutomatically =
17625 aValue.IsResolve() && aValue.ResolveValue();
17626 bool autoGrant = false;
17627 if (pr2 == PromptResult::Pending &&
17628 storageAccessCanBeGrantedAutomatically) {
17629 pr2 = PromptResult::Granted;
17630 autoGrant = true;
17632 Telemetry::AccumulateCategorical(
17633 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
17634 AllowAutomatically);
17637 // If we can complete the permission request, do so.
17638 if (pr2 != PromptResult::Pending) {
17639 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
17640 pr2 == PromptResult::Denied);
17641 if (pr2 == PromptResult::Granted) {
17642 StorageAccessAPIHelper::StorageAccessPromptChoices
17643 choice = StorageAccessAPIHelper::eAllow;
17644 if (autoGrant) {
17645 choice = StorageAccessAPIHelper::eAllowAutoGrant;
17647 if (!autoGrant) {
17648 p->Resolve(choice, __func__);
17649 } else {
17650 // We capture sapr here to prevent it from destructing
17651 // before the callbacks complete.
17652 sapr->MaybeDelayAutomaticGrants()->Then(
17653 GetCurrentSerialEventTarget(), __func__,
17654 [p, sapr, choice] {
17655 p->Resolve(choice, __func__);
17657 [p, sapr] { p->Reject(false, __func__); });
17659 return;
17661 p->Reject(false, __func__);
17662 return;
17665 // If we get here, the auto-decision failed and we need to
17666 // wait for the user prompt to complete.
17667 sapr->RequestDelayedTask(
17668 GetMainThreadSerialEventTarget(),
17669 ContentPermissionRequestBase::DelayedTaskType::Request);
17672 [p](mozilla::ipc::ResponseRejectReason aError) {
17673 p->Reject(false, __func__);
17674 return p;
17677 return p;
17681 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
17682 mozilla::ErrorResult& aRv) {
17683 nsIGlobalObject* global = GetScopeObject();
17684 if (!global) {
17685 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17686 return nullptr;
17689 RefPtr<Promise> promise = Promise::Create(global, aRv);
17690 if (aRv.Failed()) {
17691 return nullptr;
17694 if (!IsCurrentActiveDocument()) {
17695 promise->MaybeRejectWithInvalidStateError(
17696 "requestStorageAccess requires an active document");
17697 return promise.forget();
17700 // Get a pointer to the inner window- We need this for convenience sake
17701 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17702 if (!inner) {
17703 ConsumeTransientUserGestureActivation();
17704 promise->MaybeRejectWithNotAllowedError(
17705 "requestStorageAccess not allowed"_ns);
17706 return promise.forget();
17709 // Step 1: Check if the principal calling this has a permission that lets
17710 // them use cookies or forbids them from using cookies.
17711 // This is outside of the spec of the StorageAccess API, but makes the return
17712 // values to have proper semantics.
17713 Maybe<bool> resultBecauseCookiesApproved =
17714 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17715 CookieJarSettings(), NodePrincipal());
17716 if (resultBecauseCookiesApproved.isSome()) {
17717 if (resultBecauseCookiesApproved.value()) {
17718 promise->MaybeResolveWithUndefined();
17719 return promise.forget();
17720 } else {
17721 ConsumeTransientUserGestureActivation();
17722 promise->MaybeRejectWithNotAllowedError(
17723 "requestStorageAccess not allowed"_ns);
17724 return promise.forget();
17728 // Step 2: Check if the browser settings always allow or deny cookies.
17729 // We should always return a resolved promise if the cookieBehavior is ACCEPT.
17730 // This is outside of the spec of the StorageAccess API, but makes the return
17731 // values to have proper semantics.
17732 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17733 bool isOnThirdPartySkipList = false;
17734 if (mChannel) {
17735 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17736 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17737 nsILoadInfo::StoragePermissionAllowListed;
17739 bool isThirdPartyTracker =
17740 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17741 Maybe<bool> resultBecauseBrowserSettings =
17742 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17743 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
17744 isThirdPartyTracker);
17745 if (resultBecauseBrowserSettings.isSome()) {
17746 if (resultBecauseBrowserSettings.value()) {
17747 promise->MaybeResolveWithUndefined();
17748 return promise.forget();
17749 } else {
17750 ConsumeTransientUserGestureActivation();
17751 promise->MaybeRejectWithNotAllowedError(
17752 "requestStorageAccess not allowed"_ns);
17753 return promise.forget();
17757 // Step 3: Check if the Document calling requestStorageAccess has anything to
17758 // gain from storage access. It should be embedded, non-null, etc.
17759 Maybe<bool> resultBecauseCallContext =
17760 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17761 true);
17762 if (resultBecauseCallContext.isSome()) {
17763 if (resultBecauseCallContext.value()) {
17764 promise->MaybeResolveWithUndefined();
17765 return promise.forget();
17766 } else {
17767 ConsumeTransientUserGestureActivation();
17768 promise->MaybeRejectWithNotAllowedError(
17769 "requestStorageAccess not allowed"_ns);
17770 return promise.forget();
17774 // Step 4: Check if we already allowed or denied storage access for this
17775 // document's storage key.
17776 Maybe<bool> resultBecausePreviousPermission =
17777 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17778 this, true);
17779 if (resultBecausePreviousPermission.isSome()) {
17780 if (resultBecausePreviousPermission.value()) {
17781 promise->MaybeResolveWithUndefined();
17782 return promise.forget();
17783 } else {
17784 ConsumeTransientUserGestureActivation();
17785 promise->MaybeRejectWithNotAllowedError(
17786 "requestStorageAccess not allowed"_ns);
17787 return promise.forget();
17791 // Get pointers to some objects that will be used in the async portion
17792 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17793 RefPtr<nsGlobalWindowOuter> outer =
17794 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17795 if (!outer) {
17796 ConsumeTransientUserGestureActivation();
17797 promise->MaybeRejectWithNotAllowedError(
17798 "requestStorageAccess not allowed"_ns);
17799 return promise.forget();
17801 RefPtr<Document> self(this);
17803 // Step 5. Start an async call to request storage access. This will either
17804 // perform an automatic decision or notify the user, then perform some follow
17805 // on work changing state to reflect the result of the API. If it resolves,
17806 // the request was granted. If it rejects it was denied.
17807 StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17808 this, inner, bc, NodePrincipal(),
17809 self->HasValidTransientUserGestureActivation(), true, true,
17810 ContentBlockingNotifier::eStorageAccessAPI, true)
17811 ->Then(
17812 GetCurrentSerialEventTarget(), __func__,
17813 [inner] { return inner->SaveStorageAccessPermissionGranted(); },
17814 [] {
17815 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
17817 ->Then(
17818 GetCurrentSerialEventTarget(), __func__,
17819 [promise] { promise->MaybeResolveWithUndefined(); },
17820 [promise, self] {
17821 self->ConsumeTransientUserGestureActivation();
17822 promise->MaybeRejectWithNotAllowedError(
17823 "requestStorageAccess not allowed"_ns);
17826 return promise.forget();
17829 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
17830 const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
17831 mozilla::ErrorResult& aRv) {
17832 nsIGlobalObject* global = GetScopeObject();
17833 if (!global) {
17834 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17835 return nullptr;
17837 RefPtr<Promise> promise = Promise::Create(global, aRv);
17838 if (aRv.Failed()) {
17839 return nullptr;
17842 // Step 0: Check that we have user activation before proceeding to prevent
17843 // rapid calls to the API to leak information.
17844 if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) {
17845 // Report an error to the console for this case
17846 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17847 nsLiteralCString("requestStorageAccess"),
17848 this, nsContentUtils::eDOM_PROPERTIES,
17849 "RequestStorageAccessUserGesture");
17850 ConsumeTransientUserGestureActivation();
17851 promise->MaybeRejectWithNotAllowedError(
17852 "requestStorageAccess not allowed"_ns);
17853 return promise.forget();
17856 // Step 1: Check if the provided URI is different-site to this Document
17857 nsCOMPtr<nsIURI> thirdPartyURI;
17858 nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
17859 if (NS_WARN_IF(NS_FAILED(rv))) {
17860 aRv.Throw(rv);
17861 return nullptr;
17863 bool isThirdPartyDocument;
17864 rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument);
17865 if (NS_WARN_IF(NS_FAILED(rv))) {
17866 aRv.Throw(rv);
17867 return nullptr;
17869 Maybe<bool> resultBecauseBrowserSettings =
17870 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17871 CookieJarSettings(), isThirdPartyDocument, false, true);
17872 if (resultBecauseBrowserSettings.isSome()) {
17873 if (resultBecauseBrowserSettings.value()) {
17874 promise->MaybeResolveWithUndefined();
17875 return promise.forget();
17877 ConsumeTransientUserGestureActivation();
17878 promise->MaybeRejectWithNotAllowedError(
17879 "requestStorageAccess not allowed"_ns);
17880 return promise.forget();
17883 // Step 2: Check that this Document is same-site to the top, and check that
17884 // we have user activation if we require it.
17885 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17886 CheckSameSiteCallingContextDecidesStorageAccessAPI(
17887 this, aRequireUserActivation);
17888 if (resultBecauseCallContext.isSome()) {
17889 if (resultBecauseCallContext.value()) {
17890 promise->MaybeResolveWithUndefined();
17891 return promise.forget();
17893 ConsumeTransientUserGestureActivation();
17894 promise->MaybeRejectWithNotAllowedError(
17895 "requestStorageAccess not allowed"_ns);
17896 return promise.forget();
17899 // Step 3: Get some useful variables that can be captured by the lambda for
17900 // the asynchronous portion
17901 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17902 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17903 if (!inner) {
17904 ConsumeTransientUserGestureActivation();
17905 promise->MaybeRejectWithNotAllowedError(
17906 "requestStorageAccess not allowed"_ns);
17907 return promise.forget();
17909 RefPtr<nsGlobalWindowOuter> outer =
17910 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17911 if (!outer) {
17912 ConsumeTransientUserGestureActivation();
17913 promise->MaybeRejectWithNotAllowedError(
17914 "requestStorageAccess not allowed"_ns);
17915 return promise.forget();
17917 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17918 thirdPartyURI, NodePrincipal()->OriginAttributesRef());
17919 if (!principal) {
17920 ConsumeTransientUserGestureActivation();
17921 promise->MaybeRejectWithNotAllowedError(
17922 "requestStorageAccess not allowed"_ns);
17923 return promise.forget();
17926 RefPtr<Document> self(this);
17927 bool hasUserActivation = HasValidTransientUserGestureActivation();
17929 // Consume user activation before entering the async part of this method.
17930 // This prevents usage of other transient activation-gated APIs.
17931 ConsumeTransientUserGestureActivation();
17933 // Step 4a: Start the async part of this function. Check the cookie
17934 // permission, but this can't be done in this process. We needs the cookie
17935 // permission of the URL as if it were embedded on this page, so we need to
17936 // make this check in the ContentParent.
17937 StorageAccessAPIHelper::
17938 AsyncCheckCookiesPermittedDecidesStorageAccessAPIOnChildProcess(
17939 GetBrowsingContext(), principal)
17940 ->Then(
17941 GetCurrentSerialEventTarget(), __func__,
17942 [inner, thirdPartyURI, bc, principal, hasUserActivation,
17943 aRequireUserActivation, self,
17944 promise](Maybe<bool> cookieResult) {
17945 // Handle the result of the cookie permission check that took
17946 // place in the ContentParent.
17947 if (cookieResult.isSome()) {
17948 if (cookieResult.value()) {
17949 return MozPromise<int, bool, true>::CreateAndResolve(
17950 true, __func__);
17952 return MozPromise<int, bool, true>::CreateAndReject(false,
17953 __func__);
17956 // Step 4b: Check for the existing storage access permission
17957 nsAutoCString type;
17958 bool ok = AntiTrackingUtils::CreateStoragePermissionKey(
17959 principal, type);
17960 if (!ok) {
17961 return MozPromise<int, bool, true>::CreateAndReject(false,
17962 __func__);
17964 if (AntiTrackingUtils::CheckStoragePermission(
17965 self->NodePrincipal(), type,
17966 nsContentUtils::IsInPrivateBrowsing(self), nullptr,
17967 0)) {
17968 return MozPromise<int, bool, true>::CreateAndResolve(
17969 true, __func__);
17972 // Step 4c: Try to request storage access, either automatically
17973 // or with a user-prompt. This is the part that is async in the
17974 // typical requestStorageAccess function.
17975 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17976 self, inner, bc, principal, hasUserActivation,
17977 aRequireUserActivation, false,
17978 ContentBlockingNotifier::
17979 ePrivilegeStorageAccessForOriginAPI,
17980 true);
17982 // If the IPC rejects, we should reject our promise here which
17983 // will cause a rejection of the promise we already returned
17984 [promise]() {
17985 return MozPromise<int, bool, true>::CreateAndReject(false,
17986 __func__);
17988 ->Then(
17989 GetCurrentSerialEventTarget(), __func__,
17990 // If the previous handlers resolved, we should reinstate user
17991 // activation and resolve the promise we returned in Step 5.
17992 [self, inner, promise] {
17993 inner->SaveStorageAccessPermissionGranted();
17994 self->NotifyUserGestureActivation();
17995 promise->MaybeResolveWithUndefined();
17997 // If the previous handler rejected, we should reject the promise
17998 // returned by this function.
17999 [promise] {
18000 promise->MaybeRejectWithNotAllowedError(
18001 "requestStorageAccess not allowed"_ns);
18004 // Step 5: While the async stuff is happening, we should return the promise so
18005 // our caller can continue executing.
18006 return promise.forget();
18009 already_AddRefed<Promise> Document::RequestStorageAccessUnderSite(
18010 const nsAString& aSerializedSite, ErrorResult& aRv) {
18011 nsIGlobalObject* global = GetScopeObject();
18012 if (!global) {
18013 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
18014 return nullptr;
18016 RefPtr<Promise> promise = Promise::Create(global, aRv);
18017 if (aRv.Failed()) {
18018 return nullptr;
18021 // Check that we have user activation before proceeding to prevent
18022 // rapid calls to the API to leak information.
18023 if (!ConsumeTransientUserGestureActivation()) {
18024 // Report an error to the console for this case
18025 nsContentUtils::ReportToConsole(
18026 nsIScriptError::errorFlag, "requestStorageAccess"_ns, this,
18027 nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture");
18028 promise->MaybeRejectWithUndefined();
18029 return promise.forget();
18032 // Check if the provided URI is different-site to this Document
18033 nsCOMPtr<nsIURI> siteURI;
18034 nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite);
18035 if (NS_WARN_IF(NS_FAILED(rv))) {
18036 promise->MaybeRejectWithUndefined();
18037 return promise.forget();
18039 bool isCrossSiteArgument;
18040 rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument);
18041 if (NS_WARN_IF(NS_FAILED(rv))) {
18042 aRv.Throw(rv);
18043 return nullptr;
18045 if (!isCrossSiteArgument) {
18046 promise->MaybeRejectWithUndefined();
18047 return promise.forget();
18050 // Check if this party has broad cookie permissions.
18051 Maybe<bool> resultBecauseCookiesApproved =
18052 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
18053 CookieJarSettings(), NodePrincipal());
18054 if (resultBecauseCookiesApproved.isSome()) {
18055 if (resultBecauseCookiesApproved.value()) {
18056 promise->MaybeResolveWithUndefined();
18057 return promise.forget();
18059 promise->MaybeRejectWithUndefined();
18060 return promise.forget();
18063 // Check if browser settings preclude this document getting storage
18064 // access under the provided site
18065 Maybe<bool> resultBecauseBrowserSettings =
18066 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
18067 CookieJarSettings(), true, false, true);
18068 if (resultBecauseBrowserSettings.isSome()) {
18069 if (resultBecauseBrowserSettings.value()) {
18070 promise->MaybeResolveWithUndefined();
18071 return promise.forget();
18073 promise->MaybeRejectWithUndefined();
18074 return promise.forget();
18077 // Check that this Document is same-site to the top
18078 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
18079 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
18080 if (resultBecauseCallContext.isSome()) {
18081 if (resultBecauseCallContext.value()) {
18082 promise->MaybeResolveWithUndefined();
18083 return promise.forget();
18085 promise->MaybeRejectWithUndefined();
18086 return promise.forget();
18089 nsCOMPtr<nsIPrincipal> principal(NodePrincipal());
18091 // Test if the permission this is requesting is already set
18092 nsCOMPtr<nsIPrincipal> argumentPrincipal =
18093 BasePrincipal::CreateContentPrincipal(
18094 siteURI, NodePrincipal()->OriginAttributesRef());
18095 if (!argumentPrincipal) {
18096 ConsumeTransientUserGestureActivation();
18097 promise->MaybeRejectWithUndefined();
18098 return promise.forget();
18100 nsCString originNoSuffix;
18101 rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix);
18102 if (NS_WARN_IF(NS_FAILED(rv))) {
18103 promise->MaybeRejectWithUndefined();
18104 return promise.forget();
18107 ContentChild* cc = ContentChild::GetSingleton();
18108 MOZ_ASSERT(cc);
18109 RefPtr<Document> self(this);
18110 cc->SendTestStorageAccessPermission(argumentPrincipal, originNoSuffix)
18111 ->Then(
18112 GetCurrentSerialEventTarget(), __func__,
18113 [promise, siteURI,
18114 self](const ContentChild::TestStorageAccessPermissionPromise::
18115 ResolveValueType& aResult) {
18116 if (aResult) {
18117 return StorageAccessAPIHelper::
18118 StorageAccessPermissionGrantPromise::CreateAndResolve(
18119 StorageAccessAPIHelper::eAllow, __func__);
18121 // Get a grant for the storage access permission that will be set
18122 // when this is completed in the embedding context
18123 nsCString serializedSite;
18124 RefPtr<nsEffectiveTLDService> etld =
18125 nsEffectiveTLDService::GetInstance();
18126 if (!etld) {
18127 return StorageAccessAPIHelper::
18128 StorageAccessPermissionGrantPromise::CreateAndReject(
18129 false, __func__);
18131 nsresult rv = etld->GetSite(siteURI, serializedSite);
18132 if (NS_FAILED(rv)) {
18133 return StorageAccessAPIHelper::
18134 StorageAccessPermissionGrantPromise::CreateAndReject(
18135 false, __func__);
18137 return self->CreatePermissionGrantPromise(
18138 self->GetInnerWindow(), self->NodePrincipal(), true, true,
18139 Some(serializedSite), false)();
18141 [](const ContentChild::TestStorageAccessPermissionPromise::
18142 RejectValueType& aResult) {
18143 return StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
18144 CreateAndReject(false, __func__);
18146 ->Then(
18147 GetCurrentSerialEventTarget(), __func__,
18148 [promise, principal, siteURI](int result) {
18149 ContentChild* cc = ContentChild::GetSingleton();
18150 if (!cc) {
18151 // TODO(bug 1778561): Make this work in non-content processes.
18152 promise->MaybeRejectWithUndefined();
18153 return;
18155 // Set a permission in the parent process that this document wants
18156 // storage access under the argument's site, resolving our returned
18157 // promise on success
18158 cc->SendSetAllowStorageAccessRequestFlag(principal, siteURI)
18159 ->Then(
18160 GetCurrentSerialEventTarget(), __func__,
18161 [promise](bool success) {
18162 if (success) {
18163 promise->MaybeResolveWithUndefined();
18164 } else {
18165 promise->MaybeRejectWithUndefined();
18168 [promise](mozilla::ipc::ResponseRejectReason reason) {
18169 promise->MaybeRejectWithUndefined();
18172 [promise](bool result) { promise->MaybeRejectWithUndefined(); });
18174 // Return the promise that is resolved in the async handler above
18175 return promise.forget();
18178 already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite(
18179 const nsAString& aSerializedOrigin, ErrorResult& aRv) {
18180 nsIGlobalObject* global = GetScopeObject();
18181 if (!global) {
18182 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
18183 return nullptr;
18185 RefPtr<Promise> promise = Promise::Create(global, aRv);
18186 if (aRv.Failed()) {
18187 return nullptr;
18190 // Check that the provided URI is different-site to this Document
18191 nsCOMPtr<nsIURI> argumentURI;
18192 nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin);
18193 if (NS_WARN_IF(NS_FAILED(rv))) {
18194 promise->MaybeRejectWithUndefined();
18195 return promise.forget();
18197 bool isCrossSiteArgument;
18198 rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument);
18199 if (NS_WARN_IF(NS_FAILED(rv))) {
18200 aRv.Throw(rv);
18201 return nullptr;
18203 if (!isCrossSiteArgument) {
18204 promise->MaybeRejectWithUndefined();
18205 return promise.forget();
18208 // Check if browser settings preclude this document getting storage
18209 // access under the provided site
18210 Maybe<bool> resultBecauseBrowserSettings =
18211 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
18212 CookieJarSettings(), true, false, true);
18213 if (resultBecauseBrowserSettings.isSome()) {
18214 if (resultBecauseBrowserSettings.value()) {
18215 promise->MaybeResolveWithUndefined();
18216 return promise.forget();
18218 promise->MaybeRejectWithUndefined();
18219 return promise.forget();
18222 // Check that this Document is same-site to the top
18223 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
18224 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
18225 if (resultBecauseCallContext.isSome()) {
18226 if (resultBecauseCallContext.value()) {
18227 promise->MaybeResolveWithUndefined();
18228 return promise.forget();
18230 promise->MaybeRejectWithUndefined();
18231 return promise.forget();
18234 // Create principal of the embedded site requesting storage access
18235 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
18236 argumentURI, NodePrincipal()->OriginAttributesRef());
18237 if (!principal) {
18238 promise->MaybeRejectWithUndefined();
18239 return promise.forget();
18242 // Get versions of these objects that we can use in lambdas for callbacks
18243 RefPtr<Document> self(this);
18244 RefPtr<BrowsingContext> bc = GetBrowsingContext();
18245 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
18247 // Test that the permission was set by a call to RequestStorageAccessUnderSite
18248 // from a top level document that is same-site with the argument
18249 ContentChild* cc = ContentChild::GetSingleton();
18250 if (!cc) {
18251 // TODO(bug 1778561): Make this work in non-content processes.
18252 promise->MaybeRejectWithUndefined();
18253 return promise.forget();
18255 cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI)
18256 ->Then(
18257 GetCurrentSerialEventTarget(), __func__,
18258 [inner, bc, self, principal](bool success) {
18259 if (success) {
18260 // If that resolved with true, check that we don't already have a
18261 // permission that gives cookie access.
18262 return StorageAccessAPIHelper::
18263 AsyncCheckCookiesPermittedDecidesStorageAccessAPIOnChildProcess(
18264 bc, principal);
18266 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18267 NS_ERROR_FAILURE, __func__);
18269 [](mozilla::ipc::ResponseRejectReason reason) {
18270 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18271 NS_ERROR_FAILURE, __func__);
18273 ->Then(
18274 GetCurrentSerialEventTarget(), __func__,
18275 [inner, bc, principal, self, promise](Maybe<bool> cookieResult) {
18276 // Handle the result of the cookie permission check that took place
18277 // in the ContentParent.
18278 if (cookieResult.isSome()) {
18279 if (cookieResult.value()) {
18280 return StorageAccessAPIHelper::
18281 StorageAccessPermissionGrantPromise::CreateAndResolve(
18282 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18284 return StorageAccessAPIHelper::
18285 StorageAccessPermissionGrantPromise::CreateAndReject(
18286 false, __func__);
18289 // Check for the existing storage access permission
18290 nsAutoCString type;
18291 bool ok =
18292 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
18293 if (!ok) {
18294 return StorageAccessAPIHelper::
18295 StorageAccessPermissionGrantPromise::CreateAndReject(
18296 false, __func__);
18298 if (AntiTrackingUtils::CheckStoragePermission(
18299 self->NodePrincipal(), type,
18300 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
18301 return StorageAccessAPIHelper::
18302 StorageAccessPermissionGrantPromise::CreateAndResolve(
18303 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18306 // Try to request storage access, ignoring the final checks.
18307 // We ignore the final checks because this is where the "grant"
18308 // either by prompt doorhanger or autogrant takes place. We already
18309 // gathered an equivalent grant in requestStorageAccessUnderSite.
18310 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
18311 self, inner, bc, principal, true, true, false,
18312 ContentBlockingNotifier::eStorageAccessAPI, false);
18314 // If the IPC rejects, we should reject our promise here which will
18315 // cause a rejection of the promise we already returned
18316 [promise]() {
18317 return MozPromise<int, bool, true>::CreateAndReject(false,
18318 __func__);
18320 ->Then(
18321 GetCurrentSerialEventTarget(), __func__,
18322 // If the previous handlers resolved, we should reinstate user
18323 // activation and resolve the promise we returned in Step 5.
18324 [self, inner, promise] {
18325 inner->SaveStorageAccessPermissionGranted();
18326 promise->MaybeResolveWithUndefined();
18328 // If the previous handler rejected, we should reject the promise
18329 // returned by this function.
18330 [promise] { promise->MaybeRejectWithUndefined(); });
18332 return promise.forget();
18335 nsTHashSet<RefPtr<WakeLockSentinel>>& Document::ActiveWakeLocks(
18336 WakeLockType aType) {
18337 return mActiveLocks.LookupOrInsert(aType);
18340 class UnlockAllWakeLockRunnable final : public Runnable {
18341 public:
18342 UnlockAllWakeLockRunnable(WakeLockType aType, Document* aDoc)
18343 : Runnable("UnlockAllWakeLocks"), mType(aType), mDoc(aDoc) {}
18345 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
18346 // bug 1535398.
18347 MOZ_CAN_RUN_SCRIPT_BOUNDARY
18348 NS_IMETHOD Run() override {
18349 // Move, as ReleaseWakeLock will try to remove from and possibly allow
18350 // scripts via onrelease to add to document.[[ActiveLocks]]["screen"]
18351 nsCOMPtr<Document> doc = mDoc;
18352 nsTHashSet<RefPtr<WakeLockSentinel>> locks =
18353 std::move(doc->ActiveWakeLocks(mType));
18354 for (const auto& lock : locks) {
18355 // ReleaseWakeLock runs script, which could release other locks
18356 if (!lock->Released()) {
18357 ReleaseWakeLock(doc, MOZ_KnownLive(lock), mType);
18360 return NS_OK;
18363 protected:
18364 ~UnlockAllWakeLockRunnable() = default;
18366 private:
18367 WakeLockType mType;
18368 nsCOMPtr<Document> mDoc;
18371 void Document::UnlockAllWakeLocks(WakeLockType aType) {
18372 // Perform unlock in a runnable to prevent UnlockAll being MOZ_CAN_RUN_SCRIPT
18373 if (!ActiveWakeLocks(aType).IsEmpty()) {
18374 RefPtr<UnlockAllWakeLockRunnable> runnable =
18375 MakeRefPtr<UnlockAllWakeLockRunnable>(aType, this);
18376 nsresult rv = NS_DispatchToMainThread(runnable);
18377 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
18378 Unused << rv;
18382 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
18383 Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
18384 // requestStorageAccessForOrigin may not require user activation. If we don't
18385 // have user activation at this point we should always show the prompt.
18386 if (!hasUserActivation ||
18387 !StaticPrefs::privacy_antitracking_enableWebcompat()) {
18388 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18389 false, __func__);
18391 if (XRE_IsContentProcess()) {
18392 // In the content process, we need to ask the parent process to compute
18393 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
18394 // isn't accessible in the content process.
18395 ContentChild* cc = ContentChild::GetSingleton();
18396 MOZ_ASSERT(cc);
18398 return cc->SendAutomaticStorageAccessPermissionCanBeGranted(NodePrincipal())
18399 ->Then(GetCurrentSerialEventTarget(), __func__,
18400 [](const ContentChild::
18401 AutomaticStorageAccessPermissionCanBeGrantedPromise::
18402 ResolveOrRejectValue& aValue) {
18403 if (aValue.IsResolve()) {
18404 return AutomaticStorageAccessPermissionGrantPromise::
18405 CreateAndResolve(aValue.ResolveValue(), __func__);
18408 return AutomaticStorageAccessPermissionGrantPromise::
18409 CreateAndReject(false, __func__);
18413 if (XRE_IsParentProcess()) {
18414 // In the parent process, we can directly compute this.
18415 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18416 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
18417 __func__);
18420 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
18421 false, __func__);
18424 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
18425 nsIPrincipal* aPrincipal) {
18426 if (!StaticPrefs::dom_storage_access_auto_grants()) {
18427 return false;
18430 if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
18431 return false;
18434 nsCOMPtr<nsIBrowserUsage> bu = do_ImportESModule(
18435 "resource:///modules/BrowserUsageTelemetry.sys.mjs", fallible);
18436 if (NS_WARN_IF(!bu)) {
18437 return false;
18440 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
18441 nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours(
18442 &uniqueDomainsVisitedInPast24Hours);
18443 if (NS_WARN_IF(NS_FAILED(rv))) {
18444 return false;
18447 Maybe<size_t> maybeOriginsThirdPartyHasAccessTo =
18448 AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal);
18449 if (maybeOriginsThirdPartyHasAccessTo.isNothing()) {
18450 return false;
18452 size_t originsThirdPartyHasAccessTo =
18453 maybeOriginsThirdPartyHasAccessTo.value();
18455 // one percent of the number of top-levels origins visited in the current
18456 // session (but not to exceed 24 hours), or the value of the
18457 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
18458 // higher.
18459 size_t maxConcurrentAutomaticGrants = std::max(
18460 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
18461 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
18464 return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
18467 void Document::RecordNavigationTiming(ReadyState aReadyState) {
18468 if (!XRE_IsContentProcess()) {
18469 return;
18471 if (!IsTopLevelContentDocument()) {
18472 return;
18474 // If we dont have the timing yet (mostly because the doc is still loading),
18475 // get it from docshell.
18476 RefPtr<nsDOMNavigationTiming> timing = mTiming;
18477 if (!timing) {
18478 if (!mDocumentContainer) {
18479 return;
18481 timing = mDocumentContainer->GetNavigationTiming();
18482 if (!timing) {
18483 return;
18486 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
18487 switch (aReadyState) {
18488 case READYSTATE_LOADING:
18489 if (!mDOMLoadingSet) {
18490 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
18491 startTime);
18492 mDOMLoadingSet = true;
18494 break;
18495 case READYSTATE_INTERACTIVE:
18496 if (!mDOMInteractiveSet) {
18497 glean::performance_time::dom_interactive.AccumulateRawDuration(
18498 TimeStamp::Now() - startTime);
18499 mDOMInteractiveSet = true;
18501 break;
18502 case READYSTATE_COMPLETE:
18503 if (!mDOMCompleteSet) {
18504 glean::performance_time::dom_complete.AccumulateRawDuration(
18505 TimeStamp::Now() - startTime);
18506 mDOMCompleteSet = true;
18508 break;
18509 default:
18510 NS_WARNING("Unexpected ReadyState value");
18511 break;
18515 void Document::ReportShadowDOMUsage() {
18516 nsPIDOMWindowInner* inner = GetInnerWindow();
18517 if (NS_WARN_IF(!inner)) {
18518 return;
18521 WindowContext* wc = inner->GetWindowContext();
18522 if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
18523 return;
18526 WindowContext* topWc = wc->TopWindowContext();
18527 if (topWc->GetHasReportedShadowDOMUsage()) {
18528 return;
18531 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
18534 // static
18535 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
18536 return StaticPrefs::dom_storage_access_enabled() &&
18537 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
18540 bool Document::StorageAccessSandboxed() const {
18541 return Document::StorageAccessSandboxed(GetSandboxFlags());
18544 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
18545 if (mCachedTabSizeGeneration == 0 ||
18546 GetGeneration() != mCachedTabSizeGeneration) {
18547 return false;
18549 aSizes->mDom += mCachedTabSizes.mDom;
18550 aSizes->mStyle += mCachedTabSizes.mStyle;
18551 aSizes->mOther += mCachedTabSizes.mOther;
18552 return true;
18555 void Document::SetCachedSizes(nsTabSizes* aSizes) {
18556 mCachedTabSizes.mDom = aSizes->mDom;
18557 mCachedTabSizes.mStyle = aSizes->mStyle;
18558 mCachedTabSizes.mOther = aSizes->mOther;
18559 mCachedTabSizeGeneration = GetGeneration();
18562 nsAtom* Document::GetContentLanguageAsAtomForStyle() const {
18563 // Content-Language may be a comma-separated list of language codes,
18564 // in which case the HTML5 spec says to treat it as unknown
18565 if (mContentLanguage &&
18566 !nsDependentAtomString(mContentLanguage).Contains(char16_t(','))) {
18567 return GetContentLanguage();
18570 return nullptr;
18573 nsAtom* Document::GetLanguageForStyle() const {
18574 if (nsAtom* lang = GetContentLanguageAsAtomForStyle()) {
18575 return lang;
18577 return mLanguageFromCharset.get();
18580 void Document::GetContentLanguageForBindings(DOMString& aString) const {
18581 aString.SetKnownLiveAtom(mContentLanguage, DOMString::eTreatNullAsEmpty);
18584 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
18585 nsAtom* aLanguage, bool* aNeedsToCache) const {
18586 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
18587 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
18590 void Document::DoCacheAllKnownLangPrefs() {
18591 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
18592 RefPtr<nsAtom> lang = GetLanguageForStyle();
18593 StaticPresData* data = StaticPresData::Get();
18594 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
18595 data->GetFontPrefsForLang(nsGkAtoms::x_math);
18596 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
18597 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
18598 for (const auto& key : mLanguagesUsed) {
18599 data->GetFontPrefsForLang(key);
18601 mMayNeedFontPrefsUpdate = false;
18604 void Document::RecomputeLanguageFromCharset() {
18605 RefPtr<nsAtom> language;
18606 // Optimize the default character sets.
18607 if (mCharacterSet == WINDOWS_1252_ENCODING) {
18608 language = nsGkAtoms::x_western;
18609 } else {
18610 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
18611 if (mCharacterSet == UTF_8_ENCODING) {
18612 language = nsGkAtoms::Unicode;
18613 } else {
18614 language = service->LookupCharSet(mCharacterSet);
18617 if (language == nsGkAtoms::Unicode) {
18618 language = service->GetLocaleLanguage();
18622 if (language == mLanguageFromCharset) {
18623 return;
18626 mMayNeedFontPrefsUpdate = true;
18627 mLanguageFromCharset = std::move(language);
18630 nsICookieJarSettings* Document::CookieJarSettings() {
18631 // If we are here, this is probably a javascript: URL document. In any case,
18632 // we must have a nsCookieJarSettings. Let's create it.
18633 if (!mCookieJarSettings) {
18634 Document* inProcessParent = GetInProcessParentDocument();
18636 if (inProcessParent) {
18637 mCookieJarSettings = net::CookieJarSettings::Create(
18638 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
18639 mozilla::net::CookieJarSettings::Cast(
18640 inProcessParent->CookieJarSettings())
18641 ->GetPartitionKey(),
18642 inProcessParent->CookieJarSettings()->GetIsFirstPartyIsolated(),
18643 inProcessParent->CookieJarSettings()
18644 ->GetIsOnContentBlockingAllowList(),
18645 inProcessParent->CookieJarSettings()
18646 ->GetShouldResistFingerprinting());
18648 // Inherit the fingerprinting random key from the parent.
18649 nsTArray<uint8_t> randomKey;
18650 nsresult rv = inProcessParent->CookieJarSettings()
18651 ->GetFingerprintingRandomizationKey(randomKey);
18653 if (NS_SUCCEEDED(rv)) {
18654 net::CookieJarSettings::Cast(mCookieJarSettings)
18655 ->SetFingerprintingRandomizationKey(randomKey);
18658 // Inerit the top level windowContext id from the parent.
18659 net::CookieJarSettings::Cast(mCookieJarSettings)
18660 ->SetTopLevelWindowContextId(
18661 net::CookieJarSettings::Cast(inProcessParent->CookieJarSettings())
18662 ->GetTopLevelWindowContextId());
18663 } else {
18664 mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
18666 if (IsTopLevelContentDocument()) {
18667 net::CookieJarSettings::Cast(mCookieJarSettings)
18668 ->SetTopLevelWindowContextId(InnerWindowID());
18672 if (auto* wgc = GetWindowGlobalChild()) {
18673 net::CookieJarSettingsArgs csArgs;
18675 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
18676 // Update cookie settings in the parent process
18677 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
18678 NS_WARNING(
18679 "Failed to update document's cookie jar settings on the "
18680 "WindowGlobalParent");
18685 return mCookieJarSettings;
18688 bool Document::UsingStorageAccess() {
18689 if (WindowContext* wc = GetWindowContext()) {
18690 return wc->GetUsingStorageAccess();
18693 // If we don't yet have a window context, we have to use the decision
18694 // from the Document's Channel's LoadInfo directly.
18695 if (!mChannel) {
18696 return false;
18699 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18700 return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
18703 bool Document::HasStorageAccessPermissionGrantedByAllowList() {
18704 // We only care about if the document gets the storage permission via the
18705 // allow list here. So we don't check the storage access cache in the inner
18706 // window.
18708 if (!mChannel) {
18709 return false;
18712 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18713 return loadInfo->GetStoragePermission() ==
18714 nsILoadInfo::StoragePermissionAllowListed;
18717 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
18718 if (!StaticPrefs::
18719 privacy_partition_always_partition_third_party_non_cookie_storage()) {
18720 return EffectiveCookiePrincipal();
18723 nsPIDOMWindowInner* inner = GetInnerWindow();
18724 if (!inner) {
18725 return NodePrincipal();
18728 // Return our cached storage principal if one exists.
18729 if (mActiveStoragePrincipal) {
18730 return mActiveStoragePrincipal;
18733 // Calling StorageAllowedForDocument will notify the ContentBlockLog. This
18734 // loads TrackingDBService.sys.mjs, making us potentially
18735 // fail // browser/base/content/test/performance/browser_startup.js. To avoid
18736 // that, we short-circuit the check here by allowing storage access to system
18737 // and addon principles, avoiding the test-failure.
18738 nsIPrincipal* principal = NodePrincipal();
18739 if (principal && (principal->IsSystemPrincipal() ||
18740 principal->GetIsAddonOrExpandedAddonPrincipal())) {
18741 return mActiveStoragePrincipal = NodePrincipal();
18744 auto cookieJarSettings = const_cast<Document*>(this)->CookieJarSettings();
18745 if (cookieJarSettings->GetIsOnContentBlockingAllowList()) {
18746 return mActiveStoragePrincipal = NodePrincipal();
18749 StorageAccess storageAccess = StorageAllowedForDocument(this);
18750 if (!ShouldPartitionStorage(storageAccess) ||
18751 !StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
18752 return mActiveStoragePrincipal = NodePrincipal();
18755 Unused << NS_WARN_IF(NS_FAILED(StoragePrincipalHelper::GetPrincipal(
18756 nsGlobalWindowInner::Cast(inner),
18757 StoragePrincipalHelper::eForeignPartitionedPrincipal,
18758 getter_AddRefs(mActiveStoragePrincipal))));
18759 return mActiveStoragePrincipal;
18762 nsIPrincipal* Document::EffectiveCookiePrincipal() const {
18763 nsPIDOMWindowInner* inner = GetInnerWindow();
18764 if (!inner) {
18765 return NodePrincipal();
18768 // Return our cached storage principal if one exists.
18769 if (mActiveCookiePrincipal) {
18770 return mActiveCookiePrincipal;
18773 // We use the lower-level ContentBlocking API here to ensure this
18774 // check doesn't send notifications.
18775 uint32_t rejectedReason = 0;
18776 if (ShouldAllowAccessFor(inner, GetDocumentURI(), &rejectedReason)) {
18777 return mActiveCookiePrincipal = NodePrincipal();
18780 // Let's use the storage principal only if we need to partition the cookie
18781 // jar. When the permission is granted, access will be different and the
18782 // normal principal will be used.
18783 if (ShouldPartitionStorage(rejectedReason) &&
18784 !StoragePartitioningEnabled(
18785 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
18786 return mActiveCookiePrincipal = NodePrincipal();
18789 return mActiveCookiePrincipal = mPartitionedPrincipal;
18792 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
18793 // If the document is sandboxed document or data: document, we should
18794 // get URI of the parent document.
18795 for (const Document* document = this;
18796 document && document->IsContentDocument();
18797 document = document->GetInProcessParentDocument()) {
18798 // The document URI may be about:blank even if it comes from actual web
18799 // site. Therefore, we need to check the URI of its principal.
18800 nsIPrincipal* principal = document->NodePrincipal();
18801 if (principal->GetIsNullPrincipal()) {
18802 continue;
18804 return principal;
18806 return nullptr;
18809 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
18810 mIsInitialDocumentInWindow = aIsInitialDocument;
18812 if (aIsInitialDocument && !mIsEverInitialDocumentInWindow) {
18813 mIsEverInitialDocumentInWindow = aIsInitialDocument;
18816 // Asynchronously tell the parent process that we are, or are no longer, the
18817 // initial document. This happens async.
18818 if (auto* wgc = GetWindowGlobalChild()) {
18819 wgc->SendSetIsInitialDocument(aIsInitialDocument);
18823 // static
18824 void Document::AddToplevelLoadingDocument(Document* aDoc) {
18825 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18826 // Currently we're interested in foreground documents only, so bail out early.
18827 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
18828 return;
18831 if (!sLoadingForegroundTopLevelContentDocument) {
18832 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
18833 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18834 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18835 if (idleScheduler) {
18836 idleScheduler->SendRunningPrioritizedOperation();
18839 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
18840 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
18844 // static
18845 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
18846 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18847 if (sLoadingForegroundTopLevelContentDocument) {
18848 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
18849 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
18850 delete sLoadingForegroundTopLevelContentDocument;
18851 sLoadingForegroundTopLevelContentDocument = nullptr;
18853 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18854 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18855 if (idleScheduler) {
18856 idleScheduler->SendPrioritizedOperationDone();
18862 ColorScheme Document::DefaultColorScheme() const {
18863 return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
18866 ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
18867 if (ShouldResistFingerprinting(RFPTarget::CSSPrefersColorScheme) &&
18868 aIgnoreRFP == IgnoreRFP::No) {
18869 return ColorScheme::Light;
18872 if (nsPresContext* pc = GetPresContext()) {
18873 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
18874 return *scheme;
18878 return PreferenceSheet::PrefsFor(*this).mColorScheme;
18881 bool Document::HasRecentlyStartedForegroundLoads() {
18882 if (!sLoadingForegroundTopLevelContentDocument) {
18883 return false;
18886 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
18887 ++i) {
18888 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
18889 // A page loaded in foreground could be in background now.
18890 if (!doc->IsInBackgroundWindow()) {
18891 nsPIDOMWindowInner* win = doc->GetInnerWindow();
18892 if (win) {
18893 Performance* perf = win->GetPerformance();
18894 if (perf &&
18895 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
18896 return true;
18902 // Didn't find any loading foreground documents, just clear the array.
18903 delete sLoadingForegroundTopLevelContentDocument;
18904 sLoadingForegroundTopLevelContentDocument = nullptr;
18906 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18907 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18908 if (idleScheduler) {
18909 idleScheduler->SendPrioritizedOperationDone();
18911 return false;
18914 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
18915 nsFrameLoader* aStaticCloneOf) {
18916 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
18917 clone->mElement = aElement;
18918 clone->mStaticCloneOf = aStaticCloneOf;
18921 bool Document::ShouldAvoidNativeTheme() const {
18922 return StaticPrefs::widget_non_native_theme_enabled() &&
18923 (!IsInChromeDocShell() || XRE_IsContentProcess());
18926 bool Document::UseRegularPrincipal() const {
18927 return EffectiveStoragePrincipal() == NodePrincipal();
18930 bool Document::HasThirdPartyChannel() {
18931 nsCOMPtr<nsIChannel> channel = GetChannel();
18932 if (channel) {
18933 // We assume that the channel is a third-party by default.
18934 bool thirdParty = true;
18936 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
18937 components::ThirdPartyUtil::Service();
18938 if (!thirdPartyUtil) {
18939 return thirdParty;
18942 // Check that if the channel is a third-party to its parent.
18943 nsresult rv =
18944 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
18945 if (NS_FAILED(rv)) {
18946 // Assume third-party in case of failure
18947 thirdParty = true;
18950 return thirdParty;
18953 if (mParentDocument) {
18954 return mParentDocument->HasThirdPartyChannel();
18957 return false;
18960 bool Document::IsLikelyContentInaccessibleTopLevelAboutBlank() const {
18961 if (!mDocumentURI || !NS_IsAboutBlank(mDocumentURI)) {
18962 return false;
18964 // FIXME(emilio): This is not quite edge-case free. See bug 1860098.
18966 // For stuff in frames, that makes our per-document telemetry probes not
18967 // really reliable but doesn't affect the correctness of our page probes, so
18968 // it's not too terrible.
18969 BrowsingContext* bc = GetBrowsingContext();
18970 return bc && bc->IsTop() && !bc->HadOriginalOpener();
18973 bool Document::ShouldIncludeInTelemetry() const {
18974 if (!IsContentDocument() && !IsResourceDoc()) {
18975 return false;
18978 if (IsLikelyContentInaccessibleTopLevelAboutBlank()) {
18979 return false;
18982 nsIPrincipal* prin = NodePrincipal();
18983 // TODO(emilio): Should this use GetIsContentPrincipal() +
18984 // GetPrecursorPrincipal() instead (accounting for add-ons separately)?
18985 return !(prin->GetIsAddonOrExpandedAddonPrincipal() ||
18986 prin->IsSystemPrincipal() || prin->SchemeIs("about") ||
18987 prin->SchemeIs("chrome") || prin->SchemeIs("resource"));
18990 void Document::GetConnectedShadowRoots(
18991 nsTArray<RefPtr<ShadowRoot>>& aOut) const {
18992 AppendToArray(aOut, mComposedShadowRoots);
18995 void Document::AddMediaElementWithMSE() {
18996 if (mMediaElementWithMSECount++ == 0) {
18997 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18998 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
19003 void Document::RemoveMediaElementWithMSE() {
19004 MOZ_ASSERT(mMediaElementWithMSECount > 0);
19005 if (--mMediaElementWithMSECount == 0) {
19006 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
19007 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
19012 void Document::UnregisterFromMemoryReportingForDataDocument() {
19013 if (!mAddedToMemoryReportingAsDataDocument) {
19014 return;
19016 mAddedToMemoryReportingAsDataDocument = false;
19017 nsIGlobalObject* global = GetScopeObject();
19018 if (global) {
19019 if (nsPIDOMWindowInner* win = global->GetAsInnerWindow()) {
19020 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
19021 this);
19025 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
19026 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
19027 mOOPChildrenLoading.AppendElement(aChild);
19028 if (mOOPChildrenLoading.Length() == 1) {
19029 // Let's block unload so that we're blocked from going into the BFCache
19030 // until the child has actually notified us that it has done loading.
19031 BlockOnload();
19035 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
19036 // aChild will not be in the list if nsDocLoader::Stop() was called, since
19037 // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
19038 // so we don't need to call DocLoaderIsEmpty in that case.
19039 if (mOOPChildrenLoading.RemoveElement(aChild)) {
19040 if (mOOPChildrenLoading.IsEmpty()) {
19041 UnblockOnload(false);
19043 RefPtr<nsDocLoader> docLoader(mDocumentContainer);
19044 if (docLoader) {
19045 docLoader->OOPChildrenLoadingIsEmpty();
19050 void Document::ClearOOPChildrenLoading() {
19051 nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
19052 mOOPChildrenLoading.SwapElements(oopChildrenLoading);
19053 if (!oopChildrenLoading.IsEmpty()) {
19054 UnblockOnload(false);
19058 bool Document::MayHaveDOMActivateListeners() const {
19059 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
19060 return inner->HasDOMActivateEventListeners();
19063 // If we can't get information from the window object, default to true.
19064 return true;
19067 HighlightRegistry& Document::HighlightRegistry() {
19068 if (!mHighlightRegistry) {
19069 mHighlightRegistry = MakeRefPtr<class HighlightRegistry>(this);
19071 return *mHighlightRegistry;
19074 FragmentDirective* Document::FragmentDirective() {
19075 if (!mFragmentDirective) {
19076 mFragmentDirective = MakeRefPtr<class FragmentDirective>(this);
19078 return mFragmentDirective;
19081 RadioGroupContainer& Document::OwnedRadioGroupContainer() {
19082 if (!mRadioGroupContainer) {
19083 mRadioGroupContainer = MakeUnique<RadioGroupContainer>();
19085 return *mRadioGroupContainer;
19088 void Document::UpdateHiddenByContentVisibilityForAnimations() {
19089 for (AnimationTimeline* timeline : Timelines()) {
19090 timeline->UpdateHiddenByContentVisibility();
19094 void Document::SetAllowDeclarativeShadowRoots(
19095 bool aAllowDeclarativeShadowRoots) {
19096 mAllowDeclarativeShadowRoots = aAllowDeclarativeShadowRoots;
19099 bool Document::AllowsDeclarativeShadowRoots() const {
19100 return mAllowDeclarativeShadowRoots;
19103 /* static */
19104 already_AddRefed<Document> Document::ParseHTMLUnsafe(GlobalObject& aGlobal,
19105 const nsAString& aHTML) {
19106 nsCOMPtr<nsIURI> uri;
19107 NS_NewURI(getter_AddRefs(uri), "about:blank");
19108 if (!uri) {
19109 return nullptr;
19112 nsCOMPtr<Document> doc;
19113 nsresult rv =
19114 NS_NewHTMLDocument(getter_AddRefs(doc), aGlobal.GetSubjectPrincipal(),
19115 aGlobal.GetSubjectPrincipal());
19116 if (NS_WARN_IF(NS_FAILED(rv))) {
19117 return nullptr;
19120 doc->SetAllowDeclarativeShadowRoots(true);
19121 doc->SetDocumentURI(uri);
19122 rv = nsContentUtils::ParseDocumentHTML(aHTML, doc, false);
19123 if (NS_WARN_IF(NS_FAILED(rv))) {
19124 return nullptr;
19127 return doc.forget();
19130 } // namespace mozilla::dom