Bug 1842773 - Part 5: Add ArrayBuffer.prototype.{maxByteLength,resizable} getters...
[gecko.git] / dom / base / Document.cpp
blobe9230db93d47a0c74bd70ccabc83fcdc1b1c7ecf
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * Base class for all our document implementations.
9 */
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/DocumentInlines.h"
14 #include <inttypes.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <algorithm>
18 #include <cstddef>
19 #include <cstdint>
20 #include <initializer_list>
21 #include <iterator>
22 #include <limits>
23 #include <type_traits>
24 #include "Attr.h"
25 #include "ErrorList.h"
26 #include "ExpandedPrincipal.h"
27 #include "MainThreadUtils.h"
28 #include "MobileViewportManager.h"
29 #include "NodeUbiReporting.h"
30 #include "PLDHashTable.h"
31 #include "StorageAccessPermissionRequest.h"
32 #include "ThirdPartyUtil.h"
33 #include "domstubs.h"
34 #include "gfxPlatform.h"
35 #include "imgIContainer.h"
36 #include "imgLoader.h"
37 #include "imgRequestProxy.h"
38 #include "js/Value.h"
39 #include "jsapi.h"
40 #include "mozAutoDocUpdate.h"
41 #include "mozIDOMWindow.h"
42 #include "mozIThirdPartyUtil.h"
43 #include "mozilla/AntiTrackingUtils.h"
44 #include "mozilla/ArrayIterator.h"
45 #include "mozilla/ArrayUtils.h"
46 #include "mozilla/AsyncEventDispatcher.h"
47 #include "mozilla/Base64.h"
48 #include "mozilla/BasePrincipal.h"
49 #include "mozilla/CSSEnabledState.h"
50 #include "mozilla/ContentBlockingAllowList.h"
51 #include "mozilla/ContentBlockingNotifier.h"
52 #include "mozilla/ContentBlockingUserInteraction.h"
53 #include "mozilla/ContentPrincipal.h"
54 #include "mozilla/CycleCollectedJSContext.h"
55 #include "mozilla/DebugOnly.h"
56 #include "mozilla/ProfilerMarkers.h"
57 #include "mozilla/AttributeStyles.h"
58 #include "mozilla/DocumentStyleRootIterator.h"
59 #include "mozilla/EditorBase.h"
60 #include "mozilla/EditorCommands.h"
61 #include "mozilla/Encoding.h"
62 #include "mozilla/ErrorResult.h"
63 #include "mozilla/EventDispatcher.h"
64 #include "mozilla/EventListenerManager.h"
65 #include "mozilla/EventQueue.h"
66 #include "mozilla/EventStateManager.h"
67 #include "mozilla/ExtensionPolicyService.h"
68 #include "mozilla/FullscreenChange.h"
69 #include "mozilla/GlobalStyleSheetCache.h"
70 #include "mozilla/MappedDeclarationsBuilder.h"
71 #include "mozilla/HTMLEditor.h"
72 #include "mozilla/HoldDropJSObjects.h"
73 #include "mozilla/IdentifierMapEntry.h"
74 #include "mozilla/InputTaskManager.h"
75 #include "mozilla/IntegerRange.h"
76 #include "mozilla/InternalMutationEvent.h"
77 #include "mozilla/Likely.h"
78 #include "mozilla/Logging.h"
79 #include "mozilla/LookAndFeel.h"
80 #include "mozilla/MacroForEach.h"
81 #include "mozilla/Maybe.h"
82 #include "mozilla/MediaFeatureChange.h"
83 #include "mozilla/MediaManager.h"
84 #include "mozilla/MemoryReporting.h"
85 #include "mozilla/NullPrincipal.h"
86 #include "mozilla/OriginAttributes.h"
87 #include "mozilla/OwningNonNull.h"
88 #include "mozilla/PendingFullscreenEvent.h"
89 #include "mozilla/PermissionDelegateHandler.h"
90 #include "mozilla/PermissionManager.h"
91 #include "mozilla/Preferences.h"
92 #include "mozilla/PreloadHashKey.h"
93 #include "mozilla/PresShell.h"
94 #include "mozilla/PresShellForwards.h"
95 #include "mozilla/PresShellInlines.h"
96 #include "mozilla/PseudoStyleType.h"
97 #include "mozilla/RefCountType.h"
98 #include "mozilla/RelativeTo.h"
99 #include "mozilla/RestyleManager.h"
100 #include "mozilla/ReverseIterator.h"
101 #include "mozilla/SchedulerGroup.h"
102 #include "mozilla/ScrollTimelineAnimationTracker.h"
103 #include "mozilla/SMILAnimationController.h"
104 #include "mozilla/SMILTimeContainer.h"
105 #include "mozilla/ScopeExit.h"
106 #include "mozilla/Components.h"
107 #include "mozilla/ServoStyleConsts.h"
108 #include "mozilla/ServoTypes.h"
109 #include "mozilla/SizeOfState.h"
110 #include "mozilla/Span.h"
111 #include "mozilla/Sprintf.h"
112 #include "mozilla/StaticAnalysisFunctions.h"
113 #include "mozilla/StaticPrefs_apz.h"
114 #include "mozilla/StaticPrefs_browser.h"
115 #include "mozilla/StaticPrefs_docshell.h"
116 #include "mozilla/StaticPrefs_dom.h"
117 #include "mozilla/StaticPrefs_fission.h"
118 #include "mozilla/StaticPrefs_full_screen_api.h"
119 #include "mozilla/StaticPrefs_layout.h"
120 #include "mozilla/StaticPrefs_network.h"
121 #include "mozilla/StaticPrefs_page_load.h"
122 #include "mozilla/StaticPrefs_privacy.h"
123 #include "mozilla/StaticPrefs_security.h"
124 #include "mozilla/StaticPrefs_widget.h"
125 #include "mozilla/StaticPresData.h"
126 #include "mozilla/StorageAccess.h"
127 #include "mozilla/StoragePrincipalHelper.h"
128 #include "mozilla/StyleSheet.h"
129 #include "mozilla/Telemetry.h"
130 #include "mozilla/TelemetryHistogramEnums.h"
131 #include "mozilla/TelemetryScalarEnums.h"
132 #include "mozilla/TextControlElement.h"
133 #include "mozilla/TextEditor.h"
134 #include "mozilla/TypedEnumBits.h"
135 #include "mozilla/URLDecorationStripper.h"
136 #include "mozilla/URLExtraData.h"
137 #include "mozilla/Unused.h"
138 #include "mozilla/css/ImageLoader.h"
139 #include "mozilla/css/Loader.h"
140 #include "mozilla/css/Rule.h"
141 #include "mozilla/css/SheetParsingMode.h"
142 #include "mozilla/dom/AnonymousContent.h"
143 #include "mozilla/dom/BlobURLProtocolHandler.h"
144 #include "mozilla/dom/BrowserChild.h"
145 #include "mozilla/dom/BrowsingContext.h"
146 #include "mozilla/dom/BrowsingContextGroup.h"
147 #include "mozilla/dom/CanonicalBrowsingContext.h"
148 #include "mozilla/dom/CanvasRenderingContextHelper.h"
149 #include "mozilla/dom/CDATASection.h"
150 #include "mozilla/dom/CSPDictionariesBinding.h"
151 #include "mozilla/dom/ChromeObserver.h"
152 #include "mozilla/dom/ClientInfo.h"
153 #include "mozilla/dom/ClientState.h"
154 #include "mozilla/dom/Comment.h"
155 #include "mozilla/dom/ContentChild.h"
156 #include "mozilla/dom/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/FromParser.h"
176 #include "mozilla/dom/HighlightRegistry.h"
177 #include "mozilla/dom/HTMLAllCollection.h"
178 #include "mozilla/dom/HTMLBodyElement.h"
179 #include "mozilla/dom/HTMLCollectionBinding.h"
180 #include "mozilla/dom/HTMLDialogElement.h"
181 #include "mozilla/dom/HTMLFormElement.h"
182 #include "mozilla/dom/HTMLIFrameElement.h"
183 #include "mozilla/dom/HTMLImageElement.h"
184 #include "mozilla/dom/HTMLInputElement.h"
185 #include "mozilla/dom/HTMLLinkElement.h"
186 #include "mozilla/dom/HTMLMediaElement.h"
187 #include "mozilla/dom/HTMLMetaElement.h"
188 #include "mozilla/dom/HTMLSharedElement.h"
189 #include "mozilla/dom/HTMLTextAreaElement.h"
190 #include "mozilla/dom/ImageTracker.h"
191 #include "mozilla/dom/InspectorUtils.h"
192 #include "mozilla/dom/Link.h"
193 #include "mozilla/dom/MediaQueryList.h"
194 #include "mozilla/dom/MediaSource.h"
195 #include "mozilla/dom/MutationObservers.h"
196 #include "mozilla/dom/NameSpaceConstants.h"
197 #include "mozilla/dom/Navigator.h"
198 #include "mozilla/dom/NetErrorInfoBinding.h"
199 #include "mozilla/dom/NodeInfo.h"
200 #include "mozilla/dom/NodeIterator.h"
201 #include "mozilla/dom/PContentChild.h"
202 #include "mozilla/dom/PWindowGlobalChild.h"
203 #include "mozilla/dom/PageTransitionEvent.h"
204 #include "mozilla/dom/PageTransitionEventBinding.h"
205 #include "mozilla/dom/Performance.h"
206 #include "mozilla/dom/PermissionMessageUtils.h"
207 #include "mozilla/dom/PostMessageEvent.h"
208 #include "mozilla/dom/ProcessingInstruction.h"
209 #include "mozilla/dom/Promise.h"
210 #include "mozilla/dom/PromiseNativeHandler.h"
211 #include "mozilla/dom/ResizeObserver.h"
212 #include "mozilla/dom/RustTypes.h"
213 #include "mozilla/dom/SVGElement.h"
214 #include "mozilla/dom/SVGDocument.h"
215 #include "mozilla/dom/SVGSVGElement.h"
216 #include "mozilla/dom/SVGUseElement.h"
217 #include "mozilla/dom/ScriptLoader.h"
218 #include "mozilla/dom/ScriptSettings.h"
219 #include "mozilla/dom/Selection.h"
220 #include "mozilla/dom/ServiceWorkerContainer.h"
221 #include "mozilla/dom/ServiceWorkerDescriptor.h"
222 #include "mozilla/dom/ServiceWorkerManager.h"
223 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
224 #include "mozilla/dom/ShadowRoot.h"
225 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
226 #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
227 #include "mozilla/dom/StyleSheetList.h"
228 #include "mozilla/dom/StyleSheetRemovedEvent.h"
229 #include "mozilla/dom/StyleSheetRemovedEventBinding.h"
230 #include "mozilla/dom/TimeoutManager.h"
231 #include "mozilla/dom/ToggleEvent.h"
232 #include "mozilla/dom/Touch.h"
233 #include "mozilla/dom/TouchEvent.h"
234 #include "mozilla/dom/TreeOrderedArrayInlines.h"
235 #include "mozilla/dom/TreeWalker.h"
236 #include "mozilla/dom/URL.h"
237 #include "mozilla/dom/UseCounterMetrics.h"
238 #include "mozilla/dom/UserActivation.h"
239 #include "mozilla/dom/WakeLockJS.h"
240 #include "mozilla/dom/WakeLockSentinel.h"
241 #include "mozilla/dom/WindowBinding.h"
242 #include "mozilla/dom/WindowContext.h"
243 #include "mozilla/dom/WindowGlobalChild.h"
244 #include "mozilla/dom/WindowProxyHolder.h"
245 #include "mozilla/dom/WorkerDocumentListener.h"
246 #include "mozilla/dom/XPathEvaluator.h"
247 #include "mozilla/dom/XPathExpression.h"
248 #include "mozilla/dom/nsCSPContext.h"
249 #include "mozilla/dom/nsCSPUtils.h"
250 #include "mozilla/extensions/WebExtensionPolicy.h"
251 #include "mozilla/fallible.h"
252 #include "mozilla/gfx/BaseCoord.h"
253 #include "mozilla/gfx/BaseSize.h"
254 #include "mozilla/gfx/Coord.h"
255 #include "mozilla/gfx/Point.h"
256 #include "mozilla/gfx/ScaleFactor.h"
257 #include "mozilla/glean/GleanMetrics.h"
258 #include "mozilla/intl/LocaleService.h"
259 #include "mozilla/ipc/IdleSchedulerChild.h"
260 #include "mozilla/ipc/MessageChannel.h"
261 #include "mozilla/net/ChannelEventQueue.h"
262 #include "mozilla/net/CookieJarSettings.h"
263 #include "mozilla/net/NeckoChannelParams.h"
264 #include "mozilla/net/RequestContextService.h"
265 #include "nsAboutProtocolUtils.h"
266 #include "nsAlgorithm.h"
267 #include "nsAttrValue.h"
268 #include "nsAttrValueInlines.h"
269 #include "nsBaseHashtable.h"
270 #include "nsBidiUtils.h"
271 #include "nsCRT.h"
272 #include "nsCSSPropertyID.h"
273 #include "nsCSSProps.h"
274 #include "nsCSSPseudoElements.h"
275 #include "nsCSSRendering.h"
276 #include "nsCanvasFrame.h"
277 #include "nsCaseTreatment.h"
278 #include "nsCharsetSource.h"
279 #include "nsCommandManager.h"
280 #include "nsCommandParams.h"
281 #include "nsComponentManagerUtils.h"
282 #include "nsContentCreatorFunctions.h"
283 #include "nsContentList.h"
284 #include "nsContentPermissionHelper.h"
285 #include "nsContentSecurityUtils.h"
286 #include "nsContentUtils.h"
287 #include "nsCoord.h"
288 #include "nsCycleCollectionNoteChild.h"
289 #include "nsCycleCollectionTraversalCallback.h"
290 #include "nsDOMAttributeMap.h"
291 #include "nsDOMCaretPosition.h"
292 #include "nsDOMNavigationTiming.h"
293 #include "nsDOMString.h"
294 #include "nsDeviceContext.h"
295 #include "nsDocShell.h"
296 #include "nsDocShellLoadTypes.h"
297 #include "nsEffectiveTLDService.h"
298 #include "nsError.h"
299 #include "nsEscape.h"
300 #include "nsFocusManager.h"
301 #include "nsFrameLoader.h"
302 #include "nsFrameLoaderOwner.h"
303 #include "nsGenericHTMLElement.h"
304 #include "nsGlobalWindowInner.h"
305 #include "nsGlobalWindowOuter.h"
306 #include "nsHTMLDocument.h"
307 #include "nsHtml5Module.h"
308 #include "nsHtml5Parser.h"
309 #include "nsHtml5TreeOpExecutor.h"
310 #include "nsIAsyncShutdown.h"
311 #include "nsIAuthPrompt.h"
312 #include "nsIAuthPrompt2.h"
313 #include "nsIBFCacheEntry.h"
314 #include "nsIBaseWindow.h"
315 #include "nsIBrowserChild.h"
316 #include "nsIBrowserUsage.h"
317 #include "nsICSSLoaderObserver.h"
318 #include "nsICategoryManager.h"
319 #include "nsICertOverrideService.h"
320 #include "nsIContent.h"
321 #include "nsIContentInlines.h"
322 #include "nsIContentPolicy.h"
323 #include "nsIContentSecurityPolicy.h"
324 #include "nsIContentSink.h"
325 #include "nsICookieJarSettings.h"
326 #include "nsICookieService.h"
327 #include "nsIDOMXULCommandDispatcher.h"
328 #include "nsIDocShell.h"
329 #include "nsIDocShellTreeItem.h"
330 #include "nsIDocumentActivity.h"
331 #include "nsIDocumentEncoder.h"
332 #include "nsIDocumentLoader.h"
333 #include "nsIDocumentLoaderFactory.h"
334 #include "nsIDocumentObserver.h"
335 #include "nsIDNSService.h"
336 #include "nsIEditingSession.h"
337 #include "nsIEditor.h"
338 #include "nsIEffectiveTLDService.h"
339 #include "nsIFile.h"
340 #include "nsIFileChannel.h"
341 #include "nsIFrame.h"
342 #include "nsIGlobalObject.h"
343 #include "nsIHTMLCollection.h"
344 #include "nsIHttpChannel.h"
345 #include "nsIHttpChannelInternal.h"
346 #include "nsIIOService.h"
347 #include "nsIImageLoadingContent.h"
348 #include "nsIInlineSpellChecker.h"
349 #include "nsIInputStreamChannel.h"
350 #include "nsIInterfaceRequestorUtils.h"
351 #include "nsILayoutHistoryState.h"
352 #include "nsIMultiPartChannel.h"
353 #include "nsIMutationObserver.h"
354 #include "nsINSSErrorsService.h"
355 #include "nsINamed.h"
356 #include "nsINodeList.h"
357 #include "nsIObjectLoadingContent.h"
358 #include "nsIObserverService.h"
359 #include "nsIPermission.h"
360 #include "nsIPrompt.h"
361 #include "nsIPropertyBag2.h"
362 #include "nsIPublicKeyPinningService.h"
363 #include "nsIReferrerInfo.h"
364 #include "nsIRefreshURI.h"
365 #include "nsIRequest.h"
366 #include "nsIRequestContext.h"
367 #include "nsIRunnable.h"
368 #include "nsISHEntry.h"
369 #include "nsIScriptElement.h"
370 #include "nsIScriptError.h"
371 #include "nsIScriptGlobalObject.h"
372 #include "nsIScriptSecurityManager.h"
373 #include "nsISecurityConsoleMessage.h"
374 #include "nsISelectionController.h"
375 #include "nsISerialEventTarget.h"
376 #include "nsISimpleEnumerator.h"
377 #include "nsISiteSecurityService.h"
378 #include "nsISocketProvider.h"
379 #include "nsISpeculativeConnect.h"
380 #include "nsIStructuredCloneContainer.h"
381 #include "nsIThread.h"
382 #include "nsITimedChannel.h"
383 #include "nsITimer.h"
384 #include "nsITransportSecurityInfo.h"
385 #include "nsIURIMutator.h"
386 #include "nsIVariant.h"
387 #include "nsIWeakReference.h"
388 #include "nsIWebNavigation.h"
389 #include "nsIWidget.h"
390 #include "nsIX509Cert.h"
391 #include "nsIX509CertValidity.h"
392 #include "nsIXMLContentSink.h"
393 #include "nsIHTMLContentSink.h"
394 #include "nsIXULRuntime.h"
395 #include "nsImageLoadingContent.h"
396 #include "nsImportModule.h"
397 #include "nsLanguageAtomService.h"
398 #include "nsLayoutUtils.h"
399 #include "nsMimeTypes.h"
400 #include "nsNetCID.h"
401 #include "nsNetUtil.h"
402 #include "nsNodeInfoManager.h"
403 #include "nsObjectLoadingContent.h"
404 #include "nsPIDOMWindowInlines.h"
405 #include "nsPIWindowRoot.h"
406 #include "nsPoint.h"
407 #include "nsPointerHashKeys.h"
408 #include "nsPresContext.h"
409 #include "nsQueryFrame.h"
410 #include "nsQueryObject.h"
411 #include "nsRange.h"
412 #include "nsRect.h"
413 #include "nsRefreshDriver.h"
414 #include "nsSandboxFlags.h"
415 #include "nsSerializationHelper.h"
416 #include "nsServiceManagerUtils.h"
417 #include "nsStringFlags.h"
418 #include "nsStyleUtil.h"
419 #include "nsStringIterator.h"
420 #include "nsStyleSheetService.h"
421 #include "nsStyleStruct.h"
422 #include "nsTextNode.h"
423 #include "nsUnicharUtils.h"
424 #include "nsWrapperCache.h"
425 #include "nsWrapperCacheInlines.h"
426 #include "nsXPCOMCID.h"
427 #include "nsXULAppAPI.h"
428 #include "prthread.h"
429 #include "prtime.h"
430 #include "prtypes.h"
431 #include "xpcpublic.h"
433 // XXX Must be included after mozilla/Encoding.h
434 #include "encoding_rs.h"
436 #include "mozilla/dom/XULBroadcastManager.h"
437 #include "mozilla/dom/XULPersist.h"
438 #include "nsIAppWindow.h"
439 #include "nsXULPrototypeDocument.h"
440 #include "nsXULCommandDispatcher.h"
441 #include "nsXULPopupManager.h"
442 #include "nsIDocShellTreeOwner.h"
444 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
445 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
446 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
447 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
449 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
451 mozilla::LazyLogModule gPageCacheLog("PageCache");
452 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
453 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
454 mozilla::LazyLogModule gUseCountersLog("UseCounters");
456 namespace mozilla {
457 namespace dom {
459 class Document::HeaderData {
460 public:
461 HeaderData(nsAtom* aField, const nsAString& aData)
462 : mField(aField), mData(aData) {}
464 ~HeaderData() {
465 // Delete iteratively to avoid blowing up the stack, though it shouldn't
466 // happen in practice.
467 UniquePtr<HeaderData> next = std::move(mNext);
468 while (next) {
469 next = std::move(next->mNext);
473 RefPtr<nsAtom> mField;
474 nsString mData;
475 UniquePtr<HeaderData> mNext;
478 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
479 nullptr;
481 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
482 static LazyLogModule gCspPRLog("CSP");
483 LazyLogModule gUserInteractionPRLog("UserInteraction");
485 static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
486 nsIHttpChannel** aHttpChannel) {
487 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
488 if (httpChannel) {
489 httpChannel.forget(aHttpChannel);
490 return NS_OK;
493 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
494 if (!multipart) {
495 *aHttpChannel = nullptr;
496 return NS_OK;
499 nsCOMPtr<nsIChannel> baseChannel;
500 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
501 if (NS_WARN_IF(NS_FAILED(rv))) {
502 return rv;
505 httpChannel = do_QueryInterface(baseChannel);
506 httpChannel.forget(aHttpChannel);
508 return NS_OK;
511 } // namespace dom
513 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
515 IdentifierMapEntry::IdentifierMapEntry(
516 const IdentifierMapEntry::DependentAtomOrString* aKey)
517 : mKey(aKey ? *aKey : nullptr) {}
519 void IdentifierMapEntry::Traverse(
520 nsCycleCollectionTraversalCallback* aCallback) {
521 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
522 "mIdentifierMap mNameContentList");
523 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
525 if (mImageElement) {
526 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
527 "mIdentifierMap mImageElement element");
528 nsIContent* imageElement = mImageElement;
529 aCallback->NoteXPCOMChild(imageElement);
533 bool IdentifierMapEntry::IsEmpty() {
534 return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
535 !mImageElement;
538 bool IdentifierMapEntry::HasNameElement() const {
539 return mNameContentList && mNameContentList->Length() != 0;
542 void IdentifierMapEntry::AddContentChangeCallback(
543 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
544 if (!mChangeCallbacks) {
545 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
548 ChangeCallback cc = {aCallback, aData, aForImage};
549 mChangeCallbacks->PutEntry(cc);
552 void IdentifierMapEntry::RemoveContentChangeCallback(
553 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
554 if (!mChangeCallbacks) return;
555 ChangeCallback cc = {aCallback, aData, aForImage};
556 mChangeCallbacks->RemoveEntry(cc);
557 if (mChangeCallbacks->Count() == 0) {
558 mChangeCallbacks = nullptr;
562 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
563 Element* aNewElement,
564 bool aImageOnly) {
565 if (!mChangeCallbacks) return;
567 for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) {
568 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
569 // Don't fire image changes for non-image observers, and don't fire element
570 // changes for image observers when an image override is active.
571 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
572 continue;
575 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
576 iter.Remove();
581 void IdentifierMapEntry::AddIdElement(Element* aElement) {
582 MOZ_ASSERT(aElement, "Must have element");
583 MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
585 size_t index = mIdContentList.Insert(*aElement);
586 if (index == 0) {
587 Element* oldElement = mIdContentList->SafeElementAt(1);
588 FireChangeCallbacks(oldElement, aElement);
592 void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
593 MOZ_ASSERT(aElement, "Missing element");
595 // This should only be called while the document is in an update.
596 // Assertions near the call to this method guarantee this.
598 // This could fire in OOM situations
599 // Only assert this in HTML documents for now as XUL does all sorts of weird
600 // crap.
601 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
602 mIdContentList->Contains(aElement),
603 "Removing id entry that doesn't exist");
605 // XXXbz should this ever Compact() I guess when all the content is gone
606 // we'll just get cleaned up in the natural order of things...
607 Element* currentElement = mIdContentList->SafeElementAt(0);
608 mIdContentList.RemoveElement(*aElement);
609 if (currentElement == aElement) {
610 FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
614 void IdentifierMapEntry::SetImageElement(Element* aElement) {
615 Element* oldElement = GetImageIdElement();
616 mImageElement = aElement;
617 Element* newElement = GetImageIdElement();
618 if (oldElement != newElement) {
619 FireChangeCallbacks(oldElement, newElement, true);
623 void IdentifierMapEntry::ClearAndNotify() {
624 Element* currentElement = mIdContentList->SafeElementAt(0);
625 mIdContentList.Clear();
626 if (currentElement) {
627 FireChangeCallbacks(currentElement, nullptr);
629 mNameContentList = nullptr;
630 if (mImageElement) {
631 SetImageElement(nullptr);
633 mChangeCallbacks = nullptr;
636 namespace dom {
638 class SimpleHTMLCollection final : public nsSimpleContentList,
639 public nsIHTMLCollection {
640 public:
641 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
643 NS_DECL_ISUPPORTS_INHERITED
645 virtual nsINode* GetParentObject() override {
646 return nsSimpleContentList::GetParentObject();
648 virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
649 virtual Element* GetElementAt(uint32_t aIndex) override {
650 return mElements.SafeElementAt(aIndex)->AsElement();
653 virtual Element* GetFirstNamedElement(const nsAString& aName,
654 bool& aFound) override {
655 aFound = false;
656 RefPtr<nsAtom> name = NS_Atomize(aName);
657 for (uint32_t i = 0; i < mElements.Length(); i++) {
658 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
659 Element* element = mElements[i]->AsElement();
660 if (element->GetID() == name ||
661 (element->HasName() &&
662 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
663 aFound = true;
664 return element;
667 return nullptr;
670 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
671 AutoTArray<nsAtom*, 8> atoms;
672 for (uint32_t i = 0; i < mElements.Length(); i++) {
673 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
674 Element* element = mElements[i]->AsElement();
676 nsAtom* id = element->GetID();
677 MOZ_ASSERT(id != nsGkAtoms::_empty);
678 if (id && !atoms.Contains(id)) {
679 atoms.AppendElement(id);
682 if (element->HasName()) {
683 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
684 MOZ_ASSERT(name && name != nsGkAtoms::_empty);
685 if (name && !atoms.Contains(name)) {
686 atoms.AppendElement(name);
691 nsString* names = aNames.AppendElements(atoms.Length());
692 for (uint32_t i = 0; i < atoms.Length(); i++) {
693 atoms[i]->ToString(names[i]);
697 virtual JSObject* GetWrapperPreserveColorInternal() override {
698 return nsWrapperCache::GetWrapperPreserveColor();
700 virtual void PreserveWrapperInternal(
701 nsISupports* aScriptObjectHolder) override {
702 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
704 virtual JSObject* WrapObject(JSContext* aCx,
705 JS::Handle<JSObject*> aGivenProto) override {
706 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
709 using nsBaseContentList::Item;
711 private:
712 virtual ~SimpleHTMLCollection() = default;
715 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
716 nsIHTMLCollection)
718 } // namespace dom
720 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
721 if (!mNameContentList) {
722 mNameContentList = new dom::SimpleHTMLCollection(aNode);
725 mNameContentList->AppendElement(aElement);
728 void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
729 if (mNameContentList) {
730 mNameContentList->RemoveElement(aElement);
734 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const {
735 Element* idElement = GetIdElement();
736 return idElement &&
737 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
740 size_t IdentifierMapEntry::SizeOfExcludingThis(
741 MallocSizeOf aMallocSizeOf) const {
742 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
745 // Helper structs for the content->subdoc map
747 class SubDocMapEntry : public PLDHashEntryHdr {
748 public:
749 // Both of these are strong references
750 dom::Element* mKey; // must be first, to look like PLDHashEntryStub
751 dom::Document* mSubDocument;
754 class OnloadBlocker final : public nsIRequest {
755 public:
756 OnloadBlocker() = default;
758 NS_DECL_ISUPPORTS
759 NS_DECL_NSIREQUEST
761 private:
762 ~OnloadBlocker() = default;
765 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
767 NS_IMETHODIMP
768 OnloadBlocker::GetName(nsACString& aResult) {
769 aResult.AssignLiteral("about:document-onload-blocker");
770 return NS_OK;
773 NS_IMETHODIMP
774 OnloadBlocker::IsPending(bool* _retval) {
775 *_retval = true;
776 return NS_OK;
779 NS_IMETHODIMP
780 OnloadBlocker::GetStatus(nsresult* status) {
781 *status = NS_OK;
782 return NS_OK;
785 NS_IMETHODIMP OnloadBlocker::SetCanceledReason(const nsACString& aReason) {
786 return SetCanceledReasonImpl(aReason);
789 NS_IMETHODIMP OnloadBlocker::GetCanceledReason(nsACString& aReason) {
790 return GetCanceledReasonImpl(aReason);
793 NS_IMETHODIMP OnloadBlocker::CancelWithReason(nsresult aStatus,
794 const nsACString& aReason) {
795 return CancelWithReasonImpl(aStatus, aReason);
797 NS_IMETHODIMP
798 OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
799 NS_IMETHODIMP
800 OnloadBlocker::Suspend(void) { return NS_OK; }
801 NS_IMETHODIMP
802 OnloadBlocker::Resume(void) { return NS_OK; }
804 NS_IMETHODIMP
805 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
806 *aLoadGroup = nullptr;
807 return NS_OK;
810 NS_IMETHODIMP
811 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
813 NS_IMETHODIMP
814 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
815 *aLoadFlags = nsIRequest::LOAD_NORMAL;
816 return NS_OK;
819 NS_IMETHODIMP
820 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
821 return GetTRRModeImpl(aTRRMode);
824 NS_IMETHODIMP
825 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
826 return SetTRRModeImpl(aTRRMode);
829 NS_IMETHODIMP
830 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
832 // ==================================================================
834 namespace dom {
836 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
838 Document* ExternalResourceMap::RequestResource(
839 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
840 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
841 // If we ever start allowing non-same-origin loads here, we might need to do
842 // something interesting with aRequestingPrincipal even for the hashtable
843 // gets.
844 MOZ_ASSERT(aURI, "Must have a URI");
845 MOZ_ASSERT(aRequestingNode, "Must have a node");
846 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
847 *aPendingLoad = nullptr;
848 if (mHaveShutDown) {
849 return nullptr;
852 // First, make sure we strip the ref from aURI.
853 nsCOMPtr<nsIURI> clone;
854 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
855 if (NS_FAILED(rv) || !clone) {
856 return nullptr;
859 ExternalResource* resource;
860 mMap.Get(clone, &resource);
861 if (resource) {
862 return resource->mDocument;
865 bool loadStartSucceeded =
866 mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
867 if (!loadEntry) {
868 loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
870 if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
871 aRequestingNode))) {
872 return false;
876 RefPtr<PendingLoad> load(loadEntry.Data());
877 load.forget(aPendingLoad);
878 return true;
880 if (!loadStartSucceeded) {
881 // Make sure we don't thrash things by trying this load again, since
882 // chances are it failed for good reasons (security check, etc).
883 // This must be done outside the WithEntryHandle functor, as it accesses
884 // mPendingLoads.
885 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
888 return nullptr;
891 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
892 nsTArray<RefPtr<Document>> docs(mMap.Count());
893 for (const auto& entry : mMap.Values()) {
894 if (Document* doc = entry->mDocument) {
895 docs.AppendElement(doc);
899 for (auto& doc : docs) {
900 if (aCallback(*doc) == CallState::Stop) {
901 return;
906 void ExternalResourceMap::Traverse(
907 nsCycleCollectionTraversalCallback* aCallback) const {
908 // mPendingLoads will get cleared out as the requests complete, so
909 // no need to worry about those here.
910 for (const auto& entry : mMap) {
911 ExternalResourceMap::ExternalResource* resource = entry.GetWeak();
913 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
914 "mExternalResourceMap.mMap entry"
915 "->mDocument");
916 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
918 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
919 "mExternalResourceMap.mMap entry"
920 "->mViewer");
921 aCallback->NoteXPCOMChild(resource->mViewer);
923 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
924 "mExternalResourceMap.mMap entry"
925 "->mLoadGroup");
926 aCallback->NoteXPCOMChild(resource->mLoadGroup);
930 void ExternalResourceMap::HideViewers() {
931 for (const auto& entry : mMap) {
932 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
933 if (viewer) {
934 viewer->Hide();
939 void ExternalResourceMap::ShowViewers() {
940 for (const auto& entry : mMap) {
941 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer;
942 if (viewer) {
943 viewer->Show();
948 void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
949 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
951 if (aFromDoc->IsShowing()) {
952 aToDoc->OnPageShow(true, nullptr);
956 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
957 nsIDocumentViewer* aViewer,
958 nsILoadGroup* aLoadGroup,
959 Document* aDisplayDocument) {
960 MOZ_ASSERT(aURI, "Unexpected call");
961 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
962 "Must have both or neither");
964 RefPtr<PendingLoad> load;
965 mPendingLoads.Remove(aURI, getter_AddRefs(load));
967 nsresult rv = NS_OK;
969 nsCOMPtr<Document> doc;
970 if (aViewer) {
971 doc = aViewer->GetDocument();
972 NS_ASSERTION(doc, "Must have a document");
974 doc->SetDisplayDocument(aDisplayDocument);
976 // Make sure that hiding our viewer will tear down its presentation.
977 aViewer->SetSticky(false);
979 rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
980 if (NS_SUCCEEDED(rv)) {
981 rv = aViewer->Open(nullptr, nullptr);
984 if (NS_FAILED(rv)) {
985 doc = nullptr;
986 aViewer = nullptr;
987 aLoadGroup = nullptr;
991 ExternalResource* newResource =
992 mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get();
994 newResource->mDocument = doc;
995 newResource->mViewer = aViewer;
996 newResource->mLoadGroup = aLoadGroup;
997 if (doc) {
998 if (nsPresContext* pc = doc->GetPresContext()) {
999 pc->RecomputeBrowsingContextDependentData();
1001 TransferShowingState(aDisplayDocument, doc);
1004 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
1005 for (uint32_t i = 0; i < obs.Length(); ++i) {
1006 obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
1007 nullptr);
1010 return rv;
1013 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
1014 nsIRequestObserver)
1016 NS_IMETHODIMP
1017 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
1018 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
1019 if (map.HaveShutDown()) {
1020 return NS_BINDING_ABORTED;
1023 nsCOMPtr<nsIDocumentViewer> viewer;
1024 nsCOMPtr<nsILoadGroup> loadGroup;
1025 nsresult rv =
1026 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
1028 // Make sure to do this no matter what
1029 nsresult rv2 =
1030 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
1031 if (NS_FAILED(rv)) {
1032 return rv;
1034 if (NS_FAILED(rv2)) {
1035 mTargetListener = nullptr;
1036 return rv2;
1039 return mTargetListener->OnStartRequest(aRequest);
1042 nsresult ExternalResourceMap::PendingLoad::SetupViewer(
1043 nsIRequest* aRequest, nsIDocumentViewer** aViewer,
1044 nsILoadGroup** aLoadGroup) {
1045 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
1046 *aViewer = nullptr;
1047 *aLoadGroup = nullptr;
1049 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
1050 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
1052 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
1053 if (httpChannel) {
1054 bool requestSucceeded;
1055 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
1056 !requestSucceeded) {
1057 // Bail out on this load, since it looks like we have an HTTP error page
1058 return NS_BINDING_ABORTED;
1062 nsAutoCString type;
1063 chan->GetContentType(type);
1065 nsCOMPtr<nsILoadGroup> loadGroup;
1066 chan->GetLoadGroup(getter_AddRefs(loadGroup));
1068 // Give this document its own loadgroup
1069 nsCOMPtr<nsILoadGroup> newLoadGroup =
1070 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1071 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
1072 newLoadGroup->SetLoadGroup(loadGroup);
1074 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1075 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1077 nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
1078 new LoadgroupCallbacks(callbacks);
1079 newLoadGroup->SetNotificationCallbacks(newCallbacks);
1081 // This is some serious hackery cribbed from docshell
1082 nsCOMPtr<nsICategoryManager> catMan =
1083 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
1084 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
1085 nsCString contractId;
1086 nsresult rv =
1087 catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
1088 NS_ENSURE_SUCCESS(rv, rv);
1089 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
1090 do_GetService(contractId.get());
1091 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
1093 nsCOMPtr<nsIDocumentViewer> viewer;
1094 nsCOMPtr<nsIStreamListener> listener;
1095 rv = docLoaderFactory->CreateInstance(
1096 "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
1097 getter_AddRefs(listener), getter_AddRefs(viewer));
1098 NS_ENSURE_SUCCESS(rv, rv);
1099 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
1101 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
1102 if (!parser) {
1103 /// We don't want to deal with the various fake documents yet
1104 return NS_ERROR_NOT_IMPLEMENTED;
1107 // We can't handle HTML and other weird things here yet.
1108 nsIContentSink* sink = parser->GetContentSink();
1109 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
1110 if (!xmlSink) {
1111 return NS_ERROR_NOT_IMPLEMENTED;
1114 listener.swap(mTargetListener);
1115 viewer.forget(aViewer);
1116 newLoadGroup.forget(aLoadGroup);
1117 return NS_OK;
1120 NS_IMETHODIMP
1121 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
1122 nsIInputStream* aStream,
1123 uint64_t aOffset,
1124 uint32_t aCount) {
1125 // mTargetListener might be null if SetupViewer or AddExternalResource failed.
1126 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
1127 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
1128 return NS_BINDING_ABORTED;
1130 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1133 NS_IMETHODIMP
1134 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
1135 nsresult aStatus) {
1136 // mTargetListener might be null if SetupViewer or AddExternalResource failed
1137 if (mTargetListener) {
1138 nsCOMPtr<nsIStreamListener> listener;
1139 mTargetListener.swap(listener);
1140 return listener->OnStopRequest(aRequest, aStatus);
1143 return NS_OK;
1146 nsresult ExternalResourceMap::PendingLoad::StartLoad(
1147 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
1148 MOZ_ASSERT(aURI, "Must have a URI");
1149 MOZ_ASSERT(aRequestingNode, "Must have a node");
1150 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
1152 nsCOMPtr<nsILoadGroup> loadGroup =
1153 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
1155 nsresult rv = NS_OK;
1156 nsCOMPtr<nsIChannel> channel;
1157 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
1158 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1159 nsIContentPolicy::TYPE_OTHER,
1160 nullptr, // aPerformanceStorage
1161 loadGroup);
1162 NS_ENSURE_SUCCESS(rv, rv);
1164 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1165 if (httpChannel) {
1166 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
1167 Unused << NS_WARN_IF(NS_FAILED(rv));
1170 mURI = aURI;
1172 return channel->AsyncOpen(this);
1175 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
1176 nsIInterfaceRequestor)
1178 #define IMPL_SHIM(_i) \
1179 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
1181 IMPL_SHIM(nsILoadContext)
1182 IMPL_SHIM(nsIProgressEventSink)
1183 IMPL_SHIM(nsIChannelEventSink)
1185 #undef IMPL_SHIM
1187 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
1189 #define TRY_SHIM(_i) \
1190 PR_BEGIN_MACRO \
1191 if (IID_IS(_i)) { \
1192 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
1193 if (!real) { \
1194 return NS_NOINTERFACE; \
1196 nsCOMPtr<_i> shim = new _i##Shim(this, real); \
1197 shim.forget(aSink); \
1198 return NS_OK; \
1200 PR_END_MACRO
1202 NS_IMETHODIMP
1203 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
1204 void** aSink) {
1205 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
1206 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
1207 return mCallbacks->GetInterface(aIID, aSink);
1210 *aSink = nullptr;
1212 TRY_SHIM(nsILoadContext);
1213 TRY_SHIM(nsIProgressEventSink);
1214 TRY_SHIM(nsIChannelEventSink);
1216 return NS_NOINTERFACE;
1219 #undef TRY_SHIM
1220 #undef IID_IS
1222 ExternalResourceMap::ExternalResource::~ExternalResource() {
1223 if (mViewer) {
1224 mViewer->Close(nullptr);
1225 mViewer->Destroy();
1229 // ==================================================================
1230 // =
1231 // ==================================================================
1233 // If we ever have an nsIDocumentObserver notification for stylesheet title
1234 // changes we should update the list from that instead of overriding
1235 // EnsureFresh.
1236 class DOMStyleSheetSetList final : public DOMStringList {
1237 public:
1238 explicit DOMStyleSheetSetList(Document* aDocument);
1240 void Disconnect() { mDocument = nullptr; }
1242 virtual void EnsureFresh() override;
1244 protected:
1245 Document* mDocument; // Our document; weak ref. It'll let us know if it
1246 // dies.
1249 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
1250 : mDocument(aDocument) {
1251 NS_ASSERTION(mDocument, "Must have document!");
1254 void DOMStyleSheetSetList::EnsureFresh() {
1255 MOZ_ASSERT(NS_IsMainThread());
1257 mNames.Clear();
1259 if (!mDocument) {
1260 return; // Spec says "no exceptions", and we have no style sets if we have
1261 // no document, for sure
1264 size_t count = mDocument->SheetCount();
1265 nsAutoString title;
1266 for (size_t index = 0; index < count; index++) {
1267 StyleSheet* sheet = mDocument->SheetAt(index);
1268 NS_ASSERTION(sheet, "Null sheet in sheet list!");
1269 sheet->GetTitle(title);
1270 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
1271 return;
1276 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1278 // ==================================================================
1279 // =
1280 // ==================================================================
1282 Document::InternalCommandDataHashtable*
1283 Document::sInternalCommandDataHashtable = nullptr;
1285 // static
1286 void Document::Shutdown() {
1287 if (sInternalCommandDataHashtable) {
1288 sInternalCommandDataHashtable->Clear();
1289 delete sInternalCommandDataHashtable;
1290 sInternalCommandDataHashtable = nullptr;
1294 Document::Document(const char* aContentType)
1295 : nsINode(nullptr),
1296 DocumentOrShadowRoot(this),
1297 mCharacterSet(WINDOWS_1252_ENCODING),
1298 mCharacterSetSource(0),
1299 mParentDocument(nullptr),
1300 mCachedRootElement(nullptr),
1301 mNodeInfoManager(nullptr),
1302 #ifdef DEBUG
1303 mStyledLinksCleared(false),
1304 #endif
1305 mCachedStateObjectValid(false),
1306 mBlockAllMixedContent(false),
1307 mBlockAllMixedContentPreloads(false),
1308 mUpgradeInsecureRequests(false),
1309 mUpgradeInsecurePreloads(false),
1310 mDevToolsWatchingDOMMutations(false),
1311 mBidiEnabled(false),
1312 mMayNeedFontPrefsUpdate(true),
1313 mMathMLEnabled(false),
1314 mIsInitialDocumentInWindow(false),
1315 mIsEverInitialDocumentInWindow(false),
1316 mIgnoreDocGroupMismatches(false),
1317 mLoadedAsData(false),
1318 mAddedToMemoryReportingAsDataDocument(false),
1319 mMayStartLayout(true),
1320 mHaveFiredTitleChange(false),
1321 mIsShowing(false),
1322 mVisible(true),
1323 mRemovedFromDocShell(false),
1324 // mAllowDNSPrefetch starts true, so that we can always reliably && it
1325 // with various values that might disable it. Since we never prefetch
1326 // unless we get a window, and in that case the docshell value will get
1327 // &&-ed in, this is safe.
1328 mAllowDNSPrefetch(true),
1329 mIsStaticDocument(false),
1330 mCreatingStaticClone(false),
1331 mHasPrintCallbacks(false),
1332 mInUnlinkOrDeletion(false),
1333 mHasHadScriptHandlingObject(false),
1334 mIsBeingUsedAsImage(false),
1335 mChromeRulesEnabled(false),
1336 mInChromeDocShell(false),
1337 mIsSyntheticDocument(false),
1338 mHasLinksToUpdateRunnable(false),
1339 mFlushingPendingLinkUpdates(false),
1340 mMayHaveDOMMutationObservers(false),
1341 mMayHaveAnimationObservers(false),
1342 mHasCSPDeliveredThroughHeader(false),
1343 mBFCacheDisallowed(false),
1344 mHasHadDefaultView(false),
1345 mStyleSheetChangeEventsEnabled(false),
1346 mDevToolsAnonymousAndShadowEventsEnabled(false),
1347 mIsSrcdocDocument(false),
1348 mHasDisplayDocument(false),
1349 mFontFaceSetDirty(true),
1350 mDidFireDOMContentLoaded(true),
1351 mFrameRequestCallbacksScheduled(false),
1352 mIsTopLevelContentDocument(false),
1353 mIsContentDocument(false),
1354 mDidCallBeginLoad(false),
1355 mEncodingMenuDisabled(false),
1356 mLinksEnabled(true),
1357 mIsSVGGlyphsDocument(false),
1358 mInDestructor(false),
1359 mIsGoingAway(false),
1360 mStyleSetFilled(false),
1361 mQuirkSheetAdded(false),
1362 mContentEditableSheetAdded(false),
1363 mDesignModeSheetAdded(false),
1364 mMayHaveTitleElement(false),
1365 mDOMLoadingSet(false),
1366 mDOMInteractiveSet(false),
1367 mDOMCompleteSet(false),
1368 mAutoFocusFired(false),
1369 mScrolledToRefAlready(false),
1370 mChangeScrollPosWhenScrollingToRef(false),
1371 mDelayFrameLoaderInitialization(false),
1372 mSynchronousDOMContentLoaded(false),
1373 mMaybeServiceWorkerControlled(false),
1374 mAllowZoom(false),
1375 mValidScaleFloat(false),
1376 mValidMinScale(false),
1377 mValidMaxScale(false),
1378 mWidthStrEmpty(false),
1379 mParserAborted(false),
1380 mReportedDocumentUseCounters(false),
1381 mHasReportedShadowDOMUsage(false),
1382 mHasDelayedRefreshEvent(false),
1383 mLoadEventFiring(false),
1384 mSkipLoadEventAfterClose(false),
1385 mDisableCookieAccess(false),
1386 mDisableDocWrite(false),
1387 mTooDeepWriteRecursion(false),
1388 mPendingMaybeEditingStateChanged(false),
1389 mHasBeenEditable(false),
1390 mHasWarnedAboutZoom(false),
1391 mIsRunningExecCommandByContent(false),
1392 mIsRunningExecCommandByChromeOrAddon(false),
1393 mSetCompleteAfterDOMContentLoaded(false),
1394 mDidHitCompleteSheetCache(false),
1395 mUseCountersInitialized(false),
1396 mShouldReportUseCounters(false),
1397 mShouldSendPageUseCounters(false),
1398 mUserHasInteracted(false),
1399 mHasUserInteractionTimerScheduled(false),
1400 mShouldResistFingerprinting(false),
1401 mCloningForSVGUse(false),
1402 mAllowDeclarativeShadowRoots(false),
1403 mXMLDeclarationBits(0),
1404 mOnloadBlockCount(0),
1405 mWriteLevel(0),
1406 mContentEditableCount(0),
1407 mEditingState(EditingState::eOff),
1408 mCompatMode(eCompatibility_FullStandards),
1409 mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
1410 mAncestorIsLoading(false),
1411 mVisibilityState(dom::VisibilityState::Hidden),
1412 mType(eUnknown),
1413 mDefaultElementType(0),
1414 mAllowXULXBL(eTriUnset),
1415 mSkipDTDSecurityChecks(false),
1416 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
1417 mSandboxFlags(0),
1418 mPartID(0),
1419 mMarkedCCGeneration(0),
1420 mPresShell(nullptr),
1421 mSubtreeModifiedDepth(0),
1422 mPreloadPictureDepth(0),
1423 mEventsSuppressed(0),
1424 mIgnoreDestructiveWritesCounter(0),
1425 mStaticCloneCount(0),
1426 mWindow(nullptr),
1427 mBFCacheEntry(nullptr),
1428 mInSyncOperationCount(0),
1429 mBlockDOMContentLoaded(0),
1430 mUpdateNestLevel(0),
1431 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
1432 mViewportType(Unknown),
1433 mViewportFit(ViewportFitType::Auto),
1434 mSubDocuments(nullptr),
1435 mHeaderData(nullptr),
1436 mServoRestyleRootDirtyBits(0),
1437 mThrowOnDynamicMarkupInsertionCounter(0),
1438 mIgnoreOpensDuringUnloadCounter(0),
1439 mSavedResolution(1.0f),
1440 mSavedResolutionBeforeMVM(1.0f),
1441 mGeneration(0),
1442 mCachedTabSizeGeneration(0),
1443 mNextFormNumber(0),
1444 mNextControlNumber(0),
1445 mPreloadService(this),
1446 mShouldNotifyFetchSuccess(false),
1447 mShouldNotifyFormOrPasswordRemoved(false) {
1448 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
1450 SetIsInDocument();
1451 SetIsConnected(true);
1453 // Create these unconditionally, they will be used to warn about the `zoom`
1454 // property, even if use counters are disabled.
1455 mStyleUseCounters.reset(Servo_UseCounters_Create());
1457 SetContentType(nsDependentCString(aContentType));
1459 // Start out mLastStyleSheetSet as null, per spec
1460 SetDOMStringToNull(mLastStyleSheetSet);
1462 // void state used to differentiate an empty source from an unselected source
1463 mPreloadPictureFoundSource.SetIsVoid(true);
1465 RecomputeLanguageFromCharset();
1467 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
1468 mReferrerInfo = new dom::ReferrerInfo(nullptr);
1471 #ifndef ANDROID
1472 // unused by GeckoView
1473 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
1474 if (NS_WARN_IF(!aWin)) {
1475 return false;
1478 nsIURI* uri = aWin->GetDocumentURI();
1479 if (NS_WARN_IF(!uri)) {
1480 return false;
1482 // getSpec is an expensive operation, hence we first check the scheme
1483 // to see if the caller is actually an about: page.
1484 if (!uri->SchemeIs("about")) {
1485 return false;
1488 nsAutoCString aboutSpec;
1489 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
1490 NS_ENSURE_SUCCESS(rv, false);
1492 return aboutSpec.EqualsASCII(aSpec);
1494 #endif
1496 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
1497 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1498 #ifdef ANDROID
1499 // GeckoView uses data URLs for error pages, so for now just check for any
1500 // error page
1501 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1502 #else
1503 return win && IsAboutErrorPage(win, "neterror");
1504 #endif
1507 bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
1508 JSObject* aObject) {
1509 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1510 #ifdef ANDROID
1511 // GeckoView uses data URLs for error pages, so for now just check for any
1512 // error page
1513 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1514 #else
1515 return win && IsAboutErrorPage(win, "httpsonlyerror");
1516 #endif
1519 already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
1520 bool aIsTemporary, ErrorResult& aError) {
1521 RefPtr<Promise> promise = Promise::Create(GetScopeObject(), aError,
1522 Promise::ePropagateUserInteraction);
1523 if (aError.Failed()) {
1524 return nullptr;
1527 nsresult rv = NS_OK;
1528 if (NS_WARN_IF(!mFailedChannel)) {
1529 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1530 return promise.forget();
1533 nsCOMPtr<nsIURI> failedChannelURI;
1534 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
1535 if (!failedChannelURI) {
1536 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1537 return promise.forget();
1540 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI);
1541 if (!innerURI) {
1542 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1543 return promise.forget();
1546 nsAutoCString host;
1547 innerURI->GetAsciiHost(host);
1548 int32_t port;
1549 innerURI->GetPort(&port);
1551 nsCOMPtr<nsITransportSecurityInfo> tsi;
1552 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1553 if (NS_WARN_IF(NS_FAILED(rv))) {
1554 promise->MaybeReject(rv);
1555 return promise.forget();
1557 if (NS_WARN_IF(!tsi)) {
1558 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1559 return promise.forget();
1562 nsCOMPtr<nsIX509Cert> cert;
1563 rv = tsi->GetServerCert(getter_AddRefs(cert));
1564 if (NS_WARN_IF(NS_FAILED(rv))) {
1565 promise->MaybeReject(rv);
1566 return promise.forget();
1568 if (NS_WARN_IF(!cert)) {
1569 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1570 return promise.forget();
1573 if (XRE_IsContentProcess()) {
1574 ContentChild* cc = ContentChild::GetSingleton();
1575 MOZ_ASSERT(cc);
1576 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1577 cc->SendAddCertException(cert, host, port, attrs, aIsTemporary)
1578 ->Then(GetCurrentSerialEventTarget(), __func__,
1579 [promise](const mozilla::MozPromise<
1580 nsresult, mozilla::ipc::ResponseRejectReason,
1581 true>::ResolveOrRejectValue& aValue) {
1582 if (aValue.IsResolve()) {
1583 promise->MaybeResolve(aValue.ResolveValue());
1584 } else {
1585 promise->MaybeRejectWithUndefined();
1588 return promise.forget();
1591 if (XRE_IsParentProcess()) {
1592 nsCOMPtr<nsICertOverrideService> overrideService =
1593 do_GetService(NS_CERTOVERRIDE_CONTRACTID);
1594 if (!overrideService) {
1595 promise->MaybeReject(NS_ERROR_FAILURE);
1596 return promise.forget();
1599 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1600 rv = overrideService->RememberValidityOverride(host, port, attrs, cert,
1601 aIsTemporary);
1602 if (NS_WARN_IF(NS_FAILED(rv))) {
1603 promise->MaybeReject(rv);
1604 return promise.forget();
1607 promise->MaybeResolveWithUndefined();
1608 return promise.forget();
1611 promise->MaybeReject(NS_ERROR_FAILURE);
1612 return promise.forget();
1615 void Document::ReloadWithHttpsOnlyException() {
1616 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
1617 wgc->SendReloadWithHttpsOnlyException();
1621 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
1622 nsresult rv = NS_OK;
1623 if (NS_WARN_IF(!mFailedChannel)) {
1624 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1625 return;
1628 nsCOMPtr<nsITransportSecurityInfo> tsi;
1629 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1630 if (NS_WARN_IF(NS_FAILED(rv))) {
1631 aRv.Throw(rv);
1632 return;
1634 if (NS_WARN_IF(!tsi)) {
1635 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1636 return;
1639 nsAutoString errorCodeString;
1640 rv = tsi->GetErrorCodeString(errorCodeString);
1641 if (NS_WARN_IF(NS_FAILED(rv))) {
1642 aRv.Throw(rv);
1643 return;
1645 aInfo.mErrorCodeString.Assign(errorCodeString);
1648 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
1649 JSObject* aObject) {
1650 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1651 #ifdef ANDROID
1652 // GeckoView uses data URLs for error pages, so for now just check for any
1653 // error page
1654 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1655 #else
1656 return win && IsAboutErrorPage(win, "certerror");
1657 #endif
1660 bool Document::CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject) {
1661 RefPtr<BasePrincipal> principal =
1662 BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx));
1664 if (!principal) {
1665 return false;
1668 // We allow the privilege SSA to be called from system principal.
1669 if (principal->IsSystemPrincipal()) {
1670 return true;
1673 // We only allow calling the privilege SSA from the content script of the
1674 // webcompat extension.
1675 if (auto* policy = principal->ContentScriptAddonPolicy()) {
1676 nsAutoString addonID;
1677 policy->GetId(addonID);
1679 return addonID.EqualsLiteral("webcompat@mozilla.org");
1682 return false;
1685 bool Document::IsErrorPage() const {
1686 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
1687 return loadInfo && loadInfo->GetLoadErrorPage();
1690 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
1691 ErrorResult& aRv) {
1692 nsresult rv = NS_OK;
1693 if (NS_WARN_IF(!mFailedChannel)) {
1694 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1695 return;
1698 nsCOMPtr<nsITransportSecurityInfo> tsi;
1699 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
1700 if (NS_WARN_IF(NS_FAILED(rv))) {
1701 aRv.Throw(rv);
1702 return;
1704 if (NS_WARN_IF(!tsi)) {
1705 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1706 return;
1709 nsAutoString errorCodeString;
1710 rv = tsi->GetErrorCodeString(errorCodeString);
1711 if (NS_WARN_IF(NS_FAILED(rv))) {
1712 aRv.Throw(rv);
1713 return;
1715 aInfo.mErrorCodeString.Assign(errorCodeString);
1717 nsITransportSecurityInfo::OverridableErrorCategory errorCategory;
1718 rv = tsi->GetOverridableErrorCategory(&errorCategory);
1719 if (NS_WARN_IF(NS_FAILED(rv))) {
1720 aRv.Throw(rv);
1721 return;
1723 switch (errorCategory) {
1724 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST:
1725 aInfo.mOverridableErrorCategory =
1726 dom::OverridableErrorCategory::Trust_error;
1727 break;
1728 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN:
1729 aInfo.mOverridableErrorCategory =
1730 dom::OverridableErrorCategory::Domain_mismatch;
1731 break;
1732 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME:
1733 aInfo.mOverridableErrorCategory =
1734 dom::OverridableErrorCategory::Expired_or_not_yet_valid;
1735 break;
1736 default:
1737 aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Unset;
1738 break;
1741 nsCOMPtr<nsIX509Cert> cert;
1742 nsCOMPtr<nsIX509CertValidity> validity;
1743 rv = tsi->GetServerCert(getter_AddRefs(cert));
1744 if (NS_WARN_IF(NS_FAILED(rv))) {
1745 aRv.Throw(rv);
1746 return;
1748 if (NS_WARN_IF(!cert)) {
1749 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1750 return;
1753 rv = cert->GetValidity(getter_AddRefs(validity));
1754 if (NS_WARN_IF(NS_FAILED(rv))) {
1755 aRv.Throw(rv);
1756 return;
1758 if (NS_WARN_IF(!validity)) {
1759 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1760 return;
1763 PRTime validityResult;
1764 rv = validity->GetNotBefore(&validityResult);
1765 if (NS_WARN_IF(NS_FAILED(rv))) {
1766 aRv.Throw(rv);
1767 return;
1769 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1771 rv = validity->GetNotAfter(&validityResult);
1772 if (NS_WARN_IF(NS_FAILED(rv))) {
1773 aRv.Throw(rv);
1774 return;
1776 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1778 nsAutoString issuerCommonName;
1779 nsAutoString certChainPEMString;
1780 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
1781 int64_t maxValidity = std::numeric_limits<int64_t>::max();
1782 int64_t minValidity = 0;
1783 PRTime notBefore, notAfter;
1784 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
1785 rv = tsi->GetFailedCertChain(failedCertArray);
1786 if (NS_WARN_IF(NS_FAILED(rv))) {
1787 aRv.Throw(rv);
1788 return;
1791 if (NS_WARN_IF(failedCertArray.IsEmpty())) {
1792 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1793 return;
1796 for (const auto& certificate : failedCertArray) {
1797 rv = certificate->GetIssuerCommonName(issuerCommonName);
1798 if (NS_WARN_IF(NS_FAILED(rv))) {
1799 aRv.Throw(rv);
1800 return;
1803 rv = certificate->GetValidity(getter_AddRefs(validity));
1804 if (NS_WARN_IF(NS_FAILED(rv))) {
1805 aRv.Throw(rv);
1806 return;
1808 if (NS_WARN_IF(!validity)) {
1809 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1810 return;
1813 rv = validity->GetNotBefore(&notBefore);
1814 if (NS_WARN_IF(NS_FAILED(rv))) {
1815 aRv.Throw(rv);
1816 return;
1819 rv = validity->GetNotAfter(&notAfter);
1820 if (NS_WARN_IF(NS_FAILED(rv))) {
1821 aRv.Throw(rv);
1822 return;
1825 notBefore = std::max(minValidity, notBefore);
1826 notAfter = std::min(maxValidity, notAfter);
1827 nsTArray<uint8_t> certArray;
1828 rv = certificate->GetRawDER(certArray);
1829 if (NS_WARN_IF(NS_FAILED(rv))) {
1830 aRv.Throw(rv);
1831 return;
1834 nsAutoString der64;
1835 rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()),
1836 certArray.Length(), der64);
1837 if (NS_WARN_IF(NS_FAILED(rv))) {
1838 aRv.Throw(rv);
1839 return;
1841 if (!certChainStrings.AppendElement(der64, fallible)) {
1842 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1843 return;
1847 aInfo.mIssuerCommonName.Assign(issuerCommonName);
1848 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
1849 aInfo.mCertValidityRangeNotBefore =
1850 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
1852 int32_t errorCode;
1853 rv = tsi->GetErrorCode(&errorCode);
1854 if (NS_WARN_IF(NS_FAILED(rv))) {
1855 aRv.Throw(rv);
1856 return;
1859 nsCOMPtr<nsINSSErrorsService> nsserr =
1860 do_GetService("@mozilla.org/nss_errors_service;1");
1861 if (NS_WARN_IF(!nsserr)) {
1862 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1863 return;
1865 nsresult res;
1866 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
1867 if (NS_WARN_IF(NS_FAILED(rv))) {
1868 aRv.Throw(rv);
1869 return;
1871 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
1872 if (NS_WARN_IF(NS_FAILED(rv))) {
1873 aRv.Throw(rv);
1874 return;
1877 OriginAttributes attrs;
1878 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1879 nsCOMPtr<nsIURI> aURI;
1880 mFailedChannel->GetURI(getter_AddRefs(aURI));
1881 if (XRE_IsContentProcess()) {
1882 ContentChild* cc = ContentChild::GetSingleton();
1883 MOZ_ASSERT(cc);
1884 cc->SendIsSecureURI(aURI, attrs, &aInfo.mHasHSTS);
1885 } else {
1886 nsCOMPtr<nsISiteSecurityService> sss =
1887 do_GetService(NS_SSSERVICE_CONTRACTID);
1888 if (NS_WARN_IF(!sss)) {
1889 return;
1891 Unused << NS_WARN_IF(
1892 NS_FAILED(sss->IsSecureURI(aURI, attrs, &aInfo.mHasHSTS)));
1894 nsCOMPtr<nsIPublicKeyPinningService> pkps =
1895 do_GetService(NS_PKPSERVICE_CONTRACTID);
1896 if (NS_WARN_IF(!pkps)) {
1897 return;
1899 Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP)));
1902 bool Document::IsAboutPage() const {
1903 return NodePrincipal()->SchemeIs("about");
1906 void Document::ConstructUbiNode(void* storage) {
1907 JS::ubi::Concrete<Document>::construct(storage, this);
1910 void Document::LoadEventFired() {
1911 // Object used to collect some telemetry data so we don't need to query for it
1912 // twice.
1913 glean::perf::PageLoadExtra pageLoadEventData;
1915 // Accumulate timing data located in each document's realm and report to
1916 // telemetry.
1917 AccumulateJSTelemetry(pageLoadEventData);
1919 // Collect page load timings
1920 AccumulatePageLoadTelemetry(pageLoadEventData);
1922 // Record page load event
1923 RecordPageLoadEventTelemetry(pageLoadEventData);
1925 // Release the JS bytecode cache from its wait on the load event, and
1926 // potentially dispatch the encoding of the bytecode.
1927 if (ScriptLoader()) {
1928 ScriptLoader()->LoadEventFired();
1932 static uint32_t ConvertToUnsignedFromDouble(double aNumber) {
1933 return aNumber < 0 ? 0 : static_cast<uint32_t>(aNumber);
1936 void Document::RecordPageLoadEventTelemetry(
1937 glean::perf::PageLoadExtra& aEventTelemetryData) {
1938 // If the page load time is empty, then the content wasn't something we want
1939 // to report (i.e. not a top level document).
1940 if (!aEventTelemetryData.loadTime) {
1941 return;
1943 MOZ_ASSERT(IsTopLevelContentDocument());
1945 nsPIDOMWindowOuter* window = GetWindow();
1946 if (!window) {
1947 return;
1950 nsIDocShell* docshell = window->GetDocShell();
1951 if (!docshell) {
1952 return;
1955 nsAutoCString loadTypeStr;
1956 switch (docshell->GetLoadType()) {
1957 case LOAD_NORMAL:
1958 case LOAD_NORMAL_REPLACE:
1959 case LOAD_NORMAL_BYPASS_CACHE:
1960 case LOAD_NORMAL_BYPASS_PROXY:
1961 case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
1962 loadTypeStr.Append("NORMAL");
1963 break;
1964 case LOAD_HISTORY:
1965 loadTypeStr.Append("HISTORY");
1966 break;
1967 case LOAD_RELOAD_NORMAL:
1968 case LOAD_RELOAD_BYPASS_CACHE:
1969 case LOAD_RELOAD_BYPASS_PROXY:
1970 case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
1971 case LOAD_REFRESH:
1972 case LOAD_REFRESH_REPLACE:
1973 case LOAD_RELOAD_CHARSET_CHANGE:
1974 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
1975 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
1976 loadTypeStr.Append("RELOAD");
1977 break;
1978 case LOAD_LINK:
1979 loadTypeStr.Append("LINK");
1980 break;
1981 case LOAD_STOP_CONTENT:
1982 case LOAD_STOP_CONTENT_AND_REPLACE:
1983 loadTypeStr.Append("STOP");
1984 break;
1985 case LOAD_ERROR_PAGE:
1986 loadTypeStr.Append("ERROR");
1987 break;
1988 default:
1989 loadTypeStr.Append("OTHER");
1990 break;
1993 nsCOMPtr<nsIEffectiveTLDService> tldService =
1994 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
1995 if (tldService && mReferrerInfo &&
1996 (docshell->GetLoadType() & nsIDocShell::LOAD_CMD_NORMAL)) {
1997 nsAutoCString currentBaseDomain, referrerBaseDomain;
1998 nsCOMPtr<nsIURI> referrerURI = mReferrerInfo->GetComputedReferrer();
1999 if (referrerURI) {
2000 auto result = NS_SUCCEEDED(
2001 tldService->GetBaseDomain(referrerURI, 0, referrerBaseDomain));
2002 if (result) {
2003 bool sameOrigin = false;
2004 NodePrincipal()->IsSameOrigin(referrerURI, &sameOrigin);
2005 aEventTelemetryData.sameOriginNav = mozilla::Some(sameOrigin);
2010 aEventTelemetryData.loadType = mozilla::Some(loadTypeStr);
2012 // Sending a glean ping must be done on the parent process.
2013 if (ContentChild* cc = ContentChild::GetSingleton()) {
2014 cc->SendRecordPageLoadEvent(aEventTelemetryData);
2018 void Document::AccumulatePageLoadTelemetry(
2019 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2020 // Interested only in top level documents for real websites that are in the
2021 // foreground.
2022 if (!ShouldIncludeInTelemetry() || !IsTopLevelContentDocument() ||
2023 !GetNavigationTiming() ||
2024 !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
2025 return;
2028 if (!GetChannel()) {
2029 return;
2032 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
2033 if (!timedChannel) {
2034 return;
2037 // Default duration is 0, use this to check for bogus negative values.
2038 const TimeDuration zeroDuration;
2040 TimeStamp responseStart;
2041 timedChannel->GetResponseStart(&responseStart);
2043 TimeStamp redirectStart, redirectEnd;
2044 timedChannel->GetRedirectStart(&redirectStart);
2045 timedChannel->GetRedirectEnd(&redirectEnd);
2047 uint8_t redirectCount;
2048 timedChannel->GetRedirectCount(&redirectCount);
2049 if (redirectCount) {
2050 aEventTelemetryDataOut.redirectCount =
2051 mozilla::Some(static_cast<uint32_t>(redirectCount));
2054 if (!redirectStart.IsNull() && !redirectEnd.IsNull()) {
2055 TimeDuration redirectTime = redirectEnd - redirectStart;
2056 if (redirectTime > zeroDuration) {
2057 aEventTelemetryDataOut.redirectTime =
2058 mozilla::Some(static_cast<uint32_t>(redirectTime.ToMilliseconds()));
2062 TimeStamp dnsLookupStart, dnsLookupEnd;
2063 timedChannel->GetDomainLookupStart(&dnsLookupStart);
2064 timedChannel->GetDomainLookupEnd(&dnsLookupEnd);
2066 if (!dnsLookupStart.IsNull() && !dnsLookupEnd.IsNull()) {
2067 TimeDuration dnsLookupTime = dnsLookupEnd - dnsLookupStart;
2068 if (dnsLookupTime > zeroDuration) {
2069 aEventTelemetryDataOut.dnsLookupTime =
2070 mozilla::Some(static_cast<uint32_t>(dnsLookupTime.ToMilliseconds()));
2074 TimeStamp navigationStart =
2075 GetNavigationTiming()->GetNavigationStartTimeStamp();
2077 if (!responseStart || !navigationStart) {
2078 return;
2081 nsAutoCString dnsKey("Native");
2082 nsAutoCString http3Key;
2083 nsAutoCString http3WithPriorityKey;
2084 nsAutoCString earlyHintKey;
2085 nsCOMPtr<nsIHttpChannelInternal> httpChannel =
2086 do_QueryInterface(GetChannel());
2087 if (httpChannel) {
2088 bool resolvedByTRR = false;
2089 Unused << httpChannel->GetIsResolvedByTRR(&resolvedByTRR);
2090 if (resolvedByTRR) {
2091 if (nsCOMPtr<nsIDNSService> dns =
2092 do_GetService(NS_DNSSERVICE_CONTRACTID)) {
2093 dns->GetTRRDomainKey(dnsKey);
2094 } else {
2095 // Failed to get the DNS service.
2096 dnsKey = "(fail)"_ns;
2098 aEventTelemetryDataOut.trrDomain = mozilla::Some(dnsKey);
2101 uint32_t major;
2102 uint32_t minor;
2103 if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
2104 if (major == 3) {
2105 http3Key = "http3"_ns;
2106 nsCOMPtr<nsIHttpChannel> httpChannel2 = do_QueryInterface(GetChannel());
2107 nsCString header;
2108 if (httpChannel2 &&
2109 NS_SUCCEEDED(
2110 httpChannel2->GetResponseHeader("priority"_ns, header)) &&
2111 !header.IsEmpty()) {
2112 http3WithPriorityKey = "with_priority"_ns;
2113 } else {
2114 http3WithPriorityKey = "without_priority"_ns;
2116 } else if (major == 2) {
2117 bool supportHttp3 = false;
2118 if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
2119 supportHttp3 = false;
2121 if (supportHttp3) {
2122 http3Key = "supports_http3"_ns;
2126 aEventTelemetryDataOut.httpVer = mozilla::Some(major);
2129 uint32_t earlyHintType = 0;
2130 Unused << httpChannel->GetEarlyHintLinkType(&earlyHintType);
2131 if (earlyHintType & LinkStyle::ePRECONNECT) {
2132 earlyHintKey.Append("preconnect_"_ns);
2134 if (earlyHintType & LinkStyle::ePRELOAD) {
2135 earlyHintKey.Append("preload_"_ns);
2136 earlyHintKey.Append(mPreloadService.GetEarlyHintUsed() ? "1"_ns : "0"_ns);
2140 TimeStamp asyncOpen;
2141 timedChannel->GetAsyncOpen(&asyncOpen);
2142 if (asyncOpen) {
2143 Telemetry::AccumulateTimeDelta(Telemetry::DNS_PERF_FIRST_BYTE_MS, dnsKey,
2144 asyncOpen, responseStart);
2147 // First Contentful Composite
2148 if (TimeStamp firstContentfulComposite =
2149 GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) {
2150 Telemetry::AccumulateTimeDelta(Telemetry::PERF_FIRST_CONTENTFUL_PAINT_MS,
2151 navigationStart, firstContentfulComposite);
2153 if (!http3Key.IsEmpty()) {
2154 Telemetry::AccumulateTimeDelta(
2155 Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key,
2156 navigationStart, firstContentfulComposite);
2159 if (!http3WithPriorityKey.IsEmpty()) {
2160 Telemetry::AccumulateTimeDelta(
2161 Telemetry::H3P_PERF_FIRST_CONTENTFUL_PAINT_MS, http3WithPriorityKey,
2162 navigationStart, firstContentfulComposite);
2165 if (!earlyHintKey.IsEmpty()) {
2166 Telemetry::AccumulateTimeDelta(
2167 Telemetry::EH_PERF_FIRST_CONTENTFUL_PAINT_MS, earlyHintKey,
2168 navigationStart, firstContentfulComposite);
2171 Telemetry::AccumulateTimeDelta(
2172 Telemetry::DNS_PERF_FIRST_CONTENTFUL_PAINT_MS, dnsKey, navigationStart,
2173 firstContentfulComposite);
2175 Telemetry::AccumulateTimeDelta(
2176 Telemetry::PERF_FIRST_CONTENTFUL_PAINT_FROM_RESPONSESTART_MS,
2177 responseStart, firstContentfulComposite);
2179 TimeDuration fcpTime = firstContentfulComposite - navigationStart;
2180 if (fcpTime > zeroDuration) {
2181 aEventTelemetryDataOut.fcpTime =
2182 mozilla::Some(static_cast<uint32_t>(fcpTime.ToMilliseconds()));
2186 // Report the most up to date LCP time. For our histogram we actually report
2187 // this on page unload.
2188 if (TimeStamp lcpTime =
2189 GetNavigationTiming()->GetLargestContentfulRenderTimeStamp()) {
2190 aEventTelemetryDataOut.lcpTime = mozilla::Some(
2191 static_cast<uint32_t>((lcpTime - navigationStart).ToMilliseconds()));
2194 // DOM Content Loaded event
2195 if (TimeStamp dclEventStart =
2196 GetNavigationTiming()->GetDOMContentLoadedEventStartTimeStamp()) {
2197 Telemetry::AccumulateTimeDelta(Telemetry::PERF_DOM_CONTENT_LOADED_TIME_MS,
2198 navigationStart, dclEventStart);
2199 Telemetry::AccumulateTimeDelta(
2200 Telemetry::PERF_DOM_CONTENT_LOADED_TIME_FROM_RESPONSESTART_MS,
2201 responseStart, dclEventStart);
2204 // Load event
2205 if (TimeStamp loadEventStart =
2206 GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
2207 Telemetry::AccumulateTimeDelta(Telemetry::PERF_PAGE_LOAD_TIME_MS,
2208 navigationStart, loadEventStart);
2209 if (!http3Key.IsEmpty()) {
2210 Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
2211 http3Key, navigationStart, loadEventStart);
2214 if (!http3WithPriorityKey.IsEmpty()) {
2215 Telemetry::AccumulateTimeDelta(Telemetry::H3P_PERF_PAGE_LOAD_TIME_MS,
2216 http3WithPriorityKey, navigationStart,
2217 loadEventStart);
2220 if (!earlyHintKey.IsEmpty()) {
2221 Telemetry::AccumulateTimeDelta(Telemetry::EH_PERF_PAGE_LOAD_TIME_MS,
2222 earlyHintKey, navigationStart,
2223 loadEventStart);
2226 Telemetry::AccumulateTimeDelta(
2227 Telemetry::PERF_PAGE_LOAD_TIME_FROM_RESPONSESTART_MS, responseStart,
2228 loadEventStart);
2230 TimeDuration responseTime = responseStart - navigationStart;
2231 if (responseTime > zeroDuration) {
2232 aEventTelemetryDataOut.responseTime =
2233 mozilla::Some(static_cast<uint32_t>(responseTime.ToMilliseconds()));
2236 TimeDuration loadTime = loadEventStart - navigationStart;
2237 if (loadTime > zeroDuration) {
2238 aEventTelemetryDataOut.loadTime =
2239 mozilla::Some(static_cast<uint32_t>(loadTime.ToMilliseconds()));
2244 void Document::AccumulateJSTelemetry(
2245 glean::perf::PageLoadExtra& aEventTelemetryDataOut) {
2246 if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry()) {
2247 return;
2250 if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
2251 return;
2254 AutoJSContext cx;
2255 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
2256 JSAutoRealm ar(cx, globalObject);
2257 JS::JSTimers timers = JS::GetJSTimers(cx);
2259 if (!timers.executionTime.IsZero()) {
2260 Telemetry::Accumulate(
2261 Telemetry::JS_PAGELOAD_EXECUTION_MS,
2262 ConvertToUnsignedFromDouble(timers.executionTime.ToMilliseconds()));
2263 aEventTelemetryDataOut.jsExecTime = mozilla::Some(
2264 static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
2267 if (!timers.delazificationTime.IsZero()) {
2268 Telemetry::Accumulate(Telemetry::JS_PAGELOAD_DELAZIFICATION_MS,
2269 ConvertToUnsignedFromDouble(
2270 timers.delazificationTime.ToMilliseconds()));
2273 if (!timers.xdrEncodingTime.IsZero()) {
2274 Telemetry::Accumulate(
2275 Telemetry::JS_PAGELOAD_XDR_ENCODING_MS,
2276 ConvertToUnsignedFromDouble(timers.xdrEncodingTime.ToMilliseconds()));
2279 if (!timers.baselineCompileTime.IsZero()) {
2280 Telemetry::Accumulate(Telemetry::JS_PAGELOAD_BASELINE_COMPILE_MS,
2281 ConvertToUnsignedFromDouble(
2282 timers.baselineCompileTime.ToMilliseconds()));
2285 if (!timers.gcTime.IsZero()) {
2286 Telemetry::Accumulate(
2287 Telemetry::JS_PAGELOAD_GC_MS,
2288 ConvertToUnsignedFromDouble(timers.gcTime.ToMilliseconds()));
2291 if (!timers.protectTime.IsZero()) {
2292 Telemetry::Accumulate(
2293 Telemetry::JS_PAGELOAD_PROTECT_MS,
2294 ConvertToUnsignedFromDouble(timers.protectTime.ToMilliseconds()));
2298 Document::~Document() {
2299 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
2300 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
2301 "Can't be top-level and a resource doc at the same time");
2303 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
2305 if (IsTopLevelContentDocument()) {
2306 RemoveToplevelLoadingDocument(this);
2308 // don't report for about: pages
2309 if (!IsAboutPage()) {
2310 if (MOZ_UNLIKELY(mMathMLEnabled)) {
2311 ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
2314 if (IsHTMLDocument()) {
2315 switch (GetCompatibilityMode()) {
2316 case eCompatibility_FullStandards:
2317 Telemetry::AccumulateCategorical(
2318 Telemetry::LABELS_QUIRKS_MODE::FullStandards);
2319 break;
2320 case eCompatibility_AlmostStandards:
2321 Telemetry::AccumulateCategorical(
2322 Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
2323 break;
2324 case eCompatibility_NavQuirks:
2325 Telemetry::AccumulateCategorical(
2326 Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
2327 break;
2328 default:
2329 MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
2330 break;
2336 mInDestructor = true;
2337 mInUnlinkOrDeletion = true;
2339 mozilla::DropJSObjects(this);
2341 // Clear mObservers to keep it in sync with the mutationobserver list
2342 mObservers.Clear();
2344 mIntersectionObservers.Clear();
2346 if (mStyleSheetSetList) {
2347 mStyleSheetSetList->Disconnect();
2350 if (mAnimationController) {
2351 mAnimationController->Disconnect();
2354 MOZ_ASSERT(mTimelines.isEmpty());
2356 mParentDocument = nullptr;
2358 // Kill the subdocument map, doing this will release its strong
2359 // references, if any.
2360 delete mSubDocuments;
2361 mSubDocuments = nullptr;
2363 nsAutoScriptBlocker scriptBlocker;
2365 // Destroy link map now so we don't waste time removing
2366 // links one by one
2367 DestroyElementMaps();
2369 // Invalidate cached array of child nodes
2370 InvalidateChildNodes();
2372 // We should not have child nodes when destructor is called,
2373 // since child nodes keep their owner document alive.
2374 MOZ_ASSERT(!HasChildren());
2376 mCachedRootElement = nullptr;
2378 for (auto& sheets : mAdditionalSheets) {
2379 UnlinkStyleSheets(sheets);
2382 if (mAttributeStyles) {
2383 mAttributeStyles->SetOwningDocument(nullptr);
2386 if (mListenerManager) {
2387 mListenerManager->Disconnect();
2388 UnsetFlags(NODE_HAS_LISTENERMANAGER);
2391 if (mScriptLoader) {
2392 mScriptLoader->DropDocumentReference();
2395 if (mCSSLoader) {
2396 // Could be null here if Init() failed or if we have been unlinked.
2397 mCSSLoader->DropDocumentReference();
2400 if (mStyleImageLoader) {
2401 mStyleImageLoader->DropDocumentReference();
2404 if (mXULBroadcastManager) {
2405 mXULBroadcastManager->DropDocumentReference();
2408 if (mXULPersist) {
2409 mXULPersist->DropDocumentReference();
2412 if (mPermissionDelegateHandler) {
2413 mPermissionDelegateHandler->DropDocumentReference();
2416 mHeaderData = nullptr;
2418 mPendingTitleChangeEvent.Revoke();
2420 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
2421 "must not have media query lists left");
2423 if (mNodeInfoManager) {
2424 mNodeInfoManager->DropDocumentReference();
2427 if (mDocGroup) {
2428 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
2429 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
2432 UnlinkOriginalDocumentIfStatic();
2434 UnregisterFromMemoryReportingForDataDocument();
2437 void Document::DropStyleSet() { mStyleSet = nullptr; }
2439 NS_INTERFACE_TABLE_HEAD(Document)
2440 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
2441 NS_INTERFACE_TABLE_BEGIN
2442 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
2443 NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
2444 NS_INTERFACE_TABLE_ENTRY(Document, Document)
2445 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
2446 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
2447 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
2448 NS_INTERFACE_TABLE_END
2449 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
2450 NS_INTERFACE_MAP_END
2452 NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
2453 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Document, LastRelease())
2455 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2456 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2457 EventListenerManager* elm = tmp->GetExistingListenerManager();
2458 if (elm) {
2459 elm->MarkForCC();
2461 return true;
2463 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2465 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2466 return Element::CanSkipInCC(tmp);
2467 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2469 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2470 return Element::CanSkipThis(tmp);
2471 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2473 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2474 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2475 char name[512];
2476 nsAutoCString loadedAsData;
2477 if (tmp->IsLoadedAsData()) {
2478 loadedAsData.AssignLiteral("data");
2479 } else {
2480 loadedAsData.AssignLiteral("normal");
2482 uint32_t nsid = tmp->GetDefaultNamespaceID();
2483 nsAutoCString uri;
2484 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2485 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2486 "(xhtml)", "(XLink)", "(XSLT)",
2487 "(MathML)", "(RDF)", "(XUL)"};
2488 if (nsid < ArrayLength(kNSURIs)) {
2489 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2490 kNSURIs[nsid], uri.get());
2491 } else {
2492 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2494 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2495 } else {
2496 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2499 if (!nsINode::Traverse(tmp, cb)) {
2500 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2503 tmp->mExternalResourceMap.Traverse(&cb);
2505 // Traverse all Document pointer members.
2506 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2507 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2508 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2509 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2510 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2511 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry)
2513 // Traverse all Document nsCOMPtrs.
2514 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2515 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2516 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2517 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2518 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2520 DocumentOrShadowRoot::Traverse(tmp, cb);
2522 if (tmp->mRadioGroupContainer) {
2523 RadioGroupContainer::Traverse(tmp->mRadioGroupContainer.get(), cb);
2526 for (auto& sheets : tmp->mAdditionalSheets) {
2527 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2530 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2531 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadObserver)
2532 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRememberedSizeObserver)
2533 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2534 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2535 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2536 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2537 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2538 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2539 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker)
2540 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2541 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2542 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2543 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2544 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2545 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2546 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2547 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2548 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2549 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2550 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2551 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2552 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2553 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2554 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2555 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2556 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2557 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
2559 // Traverse all our nsCOMArrays.
2560 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2562 // Traverse animation components
2563 if (tmp->mAnimationController) {
2564 tmp->mAnimationController->Traverse(&cb);
2567 if (tmp->mSubDocuments) {
2568 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2569 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2571 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2572 cb.NoteXPCOMChild(entry->mKey);
2573 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2574 "mSubDocuments entry->mSubDocument");
2575 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2579 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2581 // We own only the items in mDOMMediaQueryLists that have listeners;
2582 // this reference is managed by their AddListener and RemoveListener
2583 // methods.
2584 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2585 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2586 if (mql->HasListeners() &&
2587 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2588 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2589 cb.NoteXPCOMChild(static_cast<EventTarget*>(mql));
2593 // XXX: This should be not needed once bug 1569185 lands.
2594 for (const auto& entry : tmp->mL10nProtoElements) {
2595 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2596 cb.NoteXPCOMChild(entry.GetKey());
2597 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
2600 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2601 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2602 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2603 mPendingFrameStaticClones[i].mStaticCloneOf);
2606 for (auto& tableEntry : tmp->mActiveLocks) {
2607 ImplCycleCollectionTraverse(cb, *tableEntry.GetModifiableData(),
2608 "mActiveLocks entry", 0);
2610 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2612 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2614 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document)
2615 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
2616 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject)
2617 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2619 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2620 tmp->mInUnlinkOrDeletion = true;
2622 tmp->SetStateObject(nullptr);
2624 // Clear out our external resources
2625 tmp->mExternalResourceMap.Shutdown();
2627 nsAutoScriptBlocker scriptBlocker;
2629 nsINode::Unlink(tmp);
2631 while (tmp->HasChildren()) {
2632 // Hold a strong ref to the node when we remove it, because we may be
2633 // the last reference to it.
2634 // If this code changes, change the corresponding code in Document's
2635 // unlink impl and ContentUnbinder::UnbindSubtree.
2636 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2637 tmp->DisconnectChild(child);
2638 child->UnbindFromTree();
2641 tmp->UnlinkOriginalDocumentIfStatic();
2643 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2645 tmp->SetScriptGlobalObject(nullptr);
2647 for (auto& sheets : tmp->mAdditionalSheets) {
2648 tmp->UnlinkStyleSheets(sheets);
2651 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2652 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2653 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadObserver)
2654 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastRememberedSizeObserver)
2655 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2656 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2657 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2658 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry)
2659 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2660 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2661 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2662 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2663 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2664 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2665 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2666 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2667 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker)
2668 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2669 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2670 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2671 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2672 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2673 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2674 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2675 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2676 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2677 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2678 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2679 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2680 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2681 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2682 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2683 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2684 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2685 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2687 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
2688 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
2689 tmp->mDocGroup);
2691 tmp->mDocGroup = nullptr;
2693 if (tmp->IsTopLevelContentDocument()) {
2694 RemoveToplevelLoadingDocument(tmp);
2697 tmp->mParentDocument = nullptr;
2699 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2701 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2703 if (tmp->mListenerManager) {
2704 tmp->mListenerManager->Disconnect();
2705 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2706 tmp->mListenerManager = nullptr;
2709 if (tmp->mStyleSheetSetList) {
2710 tmp->mStyleSheetSetList->Disconnect();
2711 tmp->mStyleSheetSetList = nullptr;
2714 delete tmp->mSubDocuments;
2715 tmp->mSubDocuments = nullptr;
2717 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
2718 MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
2719 "How did we get here without our presshell going away "
2720 "first?");
2722 DocumentOrShadowRoot::Unlink(tmp);
2724 tmp->mRadioGroupContainer = nullptr;
2726 // Document has a pretty complex destructor, so we're going to
2727 // assume that *most* cycles you actually want to break somewhere
2728 // else, and not unlink an awful lot here.
2730 tmp->mExpandoAndGeneration.OwnerUnlinked();
2732 if (tmp->mAnimationController) {
2733 tmp->mAnimationController->Unlink();
2736 tmp->mPendingTitleChangeEvent.Revoke();
2738 if (tmp->mCSSLoader) {
2739 tmp->mCSSLoader->DropDocumentReference();
2740 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2743 // We own only the items in mDOMMediaQueryLists that have listeners;
2744 // this reference is managed by their AddListener and RemoveListener
2745 // methods.
2746 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2747 MediaQueryList* next =
2748 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2749 mql->Disconnect();
2750 mql = next;
2753 tmp->mPendingFrameStaticClones.Clear();
2755 tmp->mActiveLocks.Clear();
2757 tmp->mInUnlinkOrDeletion = false;
2759 tmp->UnregisterFromMemoryReportingForDataDocument();
2761 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2762 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2763 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2764 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2766 nsresult Document::Init(nsIPrincipal* aPrincipal,
2767 nsIPrincipal* aPartitionedPrincipal) {
2768 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2769 return NS_ERROR_ALREADY_INITIALIZED;
2772 // Force initialization.
2773 mOnloadBlocker = new OnloadBlocker();
2774 mStyleImageLoader = new css::ImageLoader(this);
2776 mNodeInfoManager = new nsNodeInfoManager(this, aPrincipal);
2778 // mNodeInfo keeps NodeInfoManager alive!
2779 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2780 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2781 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2782 "Bad NodeType in aNodeInfo");
2784 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2786 mCSSLoader = new css::Loader(this);
2787 // Assume we're not quirky, until we know otherwise
2788 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2790 // If after creation the owner js global is not set for a document
2791 // we use the default compartment for this document, instead of creating
2792 // wrapper in some random compartment when the document is exposed to js
2793 // via some events.
2794 nsCOMPtr<nsIGlobalObject> global =
2795 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2796 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2797 mScopeObject = do_GetWeakReference(global);
2798 MOZ_ASSERT(mScopeObject);
2800 mScriptLoader = new dom::ScriptLoader(this);
2802 // we need to create a policy here so getting the policy within
2803 // ::Policy() can *always* return a non null policy
2804 mFeaturePolicy = new dom::FeaturePolicy(this);
2805 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2807 if (aPrincipal) {
2808 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2809 } else {
2810 RecomputeResistFingerprinting();
2813 return NS_OK;
2816 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2818 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2819 PropertyTable().RemoveAllPropertiesFor(aNode);
2822 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2823 nsCOMPtr<nsIURI> uri;
2824 nsCOMPtr<nsIPrincipal> principal;
2825 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2826 if (aChannel) {
2827 mIsInPrivateBrowsing = NS_UsePrivateBrowsing(aChannel);
2829 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2830 // nsScriptSecurityManager::GetChannelResultPrincipals.
2831 // Note: this should match the uri used for the OnNewURI call in
2832 // nsDocShell::CreateDocumentViewer.
2833 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2835 nsIScriptSecurityManager* securityManager =
2836 nsContentUtils::GetSecurityManager();
2837 if (securityManager) {
2838 securityManager->GetChannelResultPrincipals(
2839 aChannel, getter_AddRefs(principal),
2840 getter_AddRefs(partitionedPrincipal));
2844 bool equal = principal->Equals(partitionedPrincipal);
2846 principal = MaybeDowngradePrincipal(principal);
2847 if (equal) {
2848 partitionedPrincipal = principal;
2849 } else {
2850 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2853 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2855 // Note that, since mTiming does not change during a reset, the
2856 // navigationStart time remains unchanged and therefore any future new
2857 // timeline will have the same global clock time as the old one.
2858 mDocumentTimeline = nullptr;
2860 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
2861 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
2862 mDocumentBaseURI = baseURI.forget();
2863 mChromeXHRDocBaseURI = nullptr;
2867 mChannel = aChannel;
2868 RecomputeResistFingerprinting();
2871 void Document::DisconnectNodeTree() {
2872 // Delete references to sub-documents and kill the subdocument map,
2873 // if any. This is not strictly needed, but makes the node tree
2874 // teardown a bit faster.
2875 delete mSubDocuments;
2876 mSubDocuments = nullptr;
2878 bool oldVal = mInUnlinkOrDeletion;
2879 mInUnlinkOrDeletion = true;
2880 { // Scope for update
2881 MOZ_AUTO_DOC_UPDATE(this, true);
2883 // Destroy link map now so we don't waste time removing
2884 // links one by one
2885 DestroyElementMaps();
2887 // Invalidate cached array of child nodes
2888 InvalidateChildNodes();
2890 while (HasChildren()) {
2891 nsMutationGuard::DidMutate();
2892 nsCOMPtr<nsIContent> content = GetLastChild();
2893 nsIContent* previousSibling = content->GetPreviousSibling();
2894 DisconnectChild(content);
2895 if (content == mCachedRootElement) {
2896 // Immediately clear mCachedRootElement, now that it's been removed
2897 // from mChildren, so that GetRootElement() will stop returning this
2898 // now-stale value.
2899 mCachedRootElement = nullptr;
2901 MutationObservers::NotifyContentRemoved(this, content, previousSibling);
2902 content->UnbindFromTree();
2904 MOZ_ASSERT(!mCachedRootElement,
2905 "After removing all children, there should be no root elem");
2907 mInUnlinkOrDeletion = oldVal;
2910 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2911 nsIPrincipal* aPrincipal,
2912 nsIPrincipal* aPartitionedPrincipal) {
2913 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2914 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2916 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2917 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2919 mSecurityInfo = nullptr;
2921 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2922 if (!aLoadGroup || group != aLoadGroup) {
2923 mDocumentLoadGroup = nullptr;
2926 DisconnectNodeTree();
2928 // Reset our stylesheets
2929 ResetStylesheetsToURI(aURI);
2931 // Release the listener manager
2932 if (mListenerManager) {
2933 mListenerManager->Disconnect();
2934 mListenerManager = nullptr;
2937 // Release the stylesheets list.
2938 mDOMStyleSheets = nullptr;
2940 // Release our principal after tearing down the document, rather than before.
2941 // This ensures that, during teardown, the document and the dying window
2942 // (which already nulled out its document pointer and cached the principal)
2943 // have matching principals.
2944 SetPrincipals(nullptr, nullptr);
2946 // Clear the original URI so SetDocumentURI sets it.
2947 mOriginalURI = nullptr;
2949 SetDocumentURI(aURI);
2950 mChromeXHRDocURI = nullptr;
2951 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
2952 // mDocumentURI.
2953 mDocumentBaseURI = nullptr;
2954 mChromeXHRDocBaseURI = nullptr;
2956 if (aLoadGroup) {
2957 nsCOMPtr<nsIInterfaceRequestor> callbacks;
2958 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
2959 if (callbacks) {
2960 nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
2961 if (loadContext) {
2962 // This is asserting that if we previously set mIsInPrivateBrowsing
2963 // to true from the channel in Document::Reset, that the loadContext
2964 // also believes it to be true.
2965 // MOZ_ASSERT(!mIsInPrivateBrowsing ||
2966 // mIsInPrivateBrowsing == loadContext->UsePrivateBrowsing());
2967 mIsInPrivateBrowsing = loadContext->UsePrivateBrowsing();
2971 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
2972 // there was an assertion here that aLoadGroup was not null. This
2973 // is no longer valid: nsDocShell::SetDocument does not create a
2974 // load group, and it works just fine
2976 // XXXbz what does "just fine" mean exactly? And given that there
2977 // is no nsDocShell::SetDocument, what is this talking about?
2979 if (IsContentDocument()) {
2980 // Inform the associated request context about this load start so
2981 // any of its internal load progress flags gets reset.
2982 nsCOMPtr<nsIRequestContextService> rcsvc =
2983 net::RequestContextService::GetOrCreate();
2984 if (rcsvc) {
2985 nsCOMPtr<nsIRequestContext> rc;
2986 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
2987 if (rc) {
2988 rc->BeginLoad();
2994 mLastModified.Truncate();
2995 // XXXbz I guess we're assuming that the caller will either pass in
2996 // a channel with a useful type or call SetContentType?
2997 SetContentType(""_ns);
2998 mContentLanguage = nullptr;
2999 mBaseTarget.Truncate();
3001 mXMLDeclarationBits = 0;
3003 // Now get our new principal
3004 if (aPrincipal) {
3005 SetPrincipals(aPrincipal, aPartitionedPrincipal);
3006 } else {
3007 nsIScriptSecurityManager* securityManager =
3008 nsContentUtils::GetSecurityManager();
3009 if (securityManager) {
3010 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
3012 if (!loadContext && aLoadGroup) {
3013 nsCOMPtr<nsIInterfaceRequestor> cbs;
3014 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
3015 loadContext = do_GetInterface(cbs);
3018 MOZ_ASSERT(loadContext,
3019 "must have a load context or pass in an explicit principal");
3021 nsCOMPtr<nsIPrincipal> principal;
3022 nsresult rv = securityManager->GetLoadContextContentPrincipal(
3023 mDocumentURI, loadContext, getter_AddRefs(principal));
3024 if (NS_SUCCEEDED(rv)) {
3025 SetPrincipals(principal, principal);
3030 if (mFontFaceSet) {
3031 mFontFaceSet->RefreshStandardFontLoadPrincipal();
3034 // Refresh the principal on the realm.
3035 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
3036 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
3040 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
3041 nsIPrincipal* aPrincipal) {
3042 if (!aPrincipal) {
3043 return nullptr;
3046 // We can't load a document with an expanded principal. If we're given one,
3047 // automatically downgrade it to the last principal it subsumes (which is the
3048 // extension principal, in the case of extension content scripts).
3049 auto* basePrin = BasePrincipal::Cast(aPrincipal);
3050 if (basePrin->Is<ExpandedPrincipal>()) {
3051 MOZ_DIAGNOSTIC_ASSERT(false,
3052 "Should never try to create a document with "
3053 "an expanded principal");
3055 auto* expanded = basePrin->As<ExpandedPrincipal>();
3056 return do_AddRef(expanded->AllowList().LastElement());
3059 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
3060 // We basically want the parent document here, but because this is very
3061 // early in the load, GetInProcessParentDocument() returns null, so we use
3062 // the docshell hierarchy to get this information instead.
3063 if (RefPtr<BrowsingContext> parent =
3064 mDocumentContainer->GetBrowsingContext()->GetParent()) {
3065 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
3066 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
3067 nsCOMPtr<nsIPrincipal> nullPrincipal =
3068 NullPrincipal::CreateWithoutOriginAttributes();
3069 return nullPrincipal.forget();
3073 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
3074 return principal.forget();
3077 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
3078 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3079 ServoStyleSet& styleSet = EnsureStyleSet();
3081 // lowest index first
3082 int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
3084 size_t count = styleSet.SheetCount(StyleOrigin::Author);
3085 size_t index = 0;
3086 for (; index < count; index++) {
3087 auto* sheet = styleSet.SheetAt(StyleOrigin::Author, index);
3088 MOZ_ASSERT(sheet);
3089 int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
3090 if (sheetDocIndex > newDocIndex) {
3091 break;
3094 // If the sheet is not owned by the document it can be an author
3095 // sheet registered at nsStyleSheetService or an additional author
3096 // sheet on the document, which means the new
3097 // doc sheet should end up before it.
3098 if (sheetDocIndex < 0) {
3099 if (sheetService) {
3100 auto& authorSheets = *sheetService->AuthorStyleSheets();
3101 if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
3102 break;
3105 if (sheet == GetFirstAdditionalAuthorSheet()) {
3106 break;
3111 return index;
3114 void Document::ResetStylesheetsToURI(nsIURI* aURI) {
3115 MOZ_ASSERT(aURI);
3117 ClearAdoptedStyleSheets();
3118 ServoStyleSet& styleSet = EnsureStyleSet();
3120 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
3121 for (auto& sheet : Reversed(aSheetList)) {
3122 sheet->ClearAssociatedDocumentOrShadowRoot();
3123 if (mStyleSetFilled) {
3124 styleSet.RemoveStyleSheet(*sheet);
3127 aSheetList.Clear();
3129 ClearSheetList(mStyleSheets);
3130 for (auto& sheets : mAdditionalSheets) {
3131 ClearSheetList(sheets);
3133 if (mStyleSetFilled) {
3134 if (auto* ss = nsStyleSheetService::GetInstance()) {
3135 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
3136 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
3137 if (sheet->IsApplicable()) {
3138 styleSet.RemoveStyleSheet(*sheet);
3144 // Now reset our inline style and attribute sheets.
3145 if (mAttributeStyles) {
3146 mAttributeStyles->Reset();
3147 mAttributeStyles->SetOwningDocument(this);
3148 } else {
3149 mAttributeStyles = new AttributeStyles(this);
3152 if (mStyleSetFilled) {
3153 FillStyleSetDocumentSheets();
3155 if (styleSet.StyleSheetsHaveChanged()) {
3156 ApplicableStylesChanged();
3161 static void AppendSheetsToStyleSet(
3162 ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
3163 for (StyleSheet* sheet : Reversed(aSheets)) {
3164 aStyleSet->AppendStyleSheet(*sheet);
3168 void Document::FillStyleSetUserAndUASheets() {
3169 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
3170 // ordering.
3172 // The document will fill in the document sheets when we create the presshell
3173 auto* cache = GlobalStyleSheetCache::Singleton();
3175 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3176 MOZ_ASSERT(sheetService,
3177 "should never be creating a StyleSet after the style sheet "
3178 "service has gone");
3180 ServoStyleSet& styleSet = EnsureStyleSet();
3181 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
3182 styleSet.AppendStyleSheet(*sheet);
3185 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
3186 : cache->GetUserContentSheet();
3187 if (sheet) {
3188 styleSet.AppendStyleSheet(*sheet);
3191 styleSet.AppendStyleSheet(*cache->UASheet());
3193 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
3194 styleSet.AppendStyleSheet(*cache->MathMLSheet());
3197 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
3198 styleSet.AppendStyleSheet(*cache->SVGSheet());
3201 styleSet.AppendStyleSheet(*cache->HTMLSheet());
3203 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
3204 styleSet.AppendStyleSheet(*cache->NoFramesSheet());
3207 styleSet.AppendStyleSheet(*cache->CounterStylesSheet());
3209 // Only load the full XUL sheet if we'll need it.
3210 if (LoadsFullXULStyleSheetUpFront()) {
3211 styleSet.AppendStyleSheet(*cache->XULSheet());
3214 styleSet.AppendStyleSheet(*cache->FormsSheet());
3215 styleSet.AppendStyleSheet(*cache->ScrollbarsSheet());
3217 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
3218 styleSet.AppendStyleSheet(*sheet);
3221 MOZ_ASSERT(!mQuirkSheetAdded);
3222 if (NeedsQuirksSheet()) {
3223 styleSet.AppendStyleSheet(*cache->QuirkSheet());
3224 mQuirkSheetAdded = true;
3228 void Document::FillStyleSet() {
3229 MOZ_ASSERT(!mStyleSetFilled);
3230 FillStyleSetUserAndUASheets();
3231 FillStyleSetDocumentSheets();
3232 mStyleSetFilled = true;
3235 void Document::RemoveContentEditableStyleSheets() {
3236 MOZ_ASSERT(IsHTMLOrXHTML());
3238 ServoStyleSet& styleSet = EnsureStyleSet();
3239 auto* cache = GlobalStyleSheetCache::Singleton();
3240 bool changed = false;
3241 if (mDesignModeSheetAdded) {
3242 styleSet.RemoveStyleSheet(*cache->DesignModeSheet());
3243 mDesignModeSheetAdded = false;
3244 changed = true;
3246 if (mContentEditableSheetAdded) {
3247 styleSet.RemoveStyleSheet(*cache->ContentEditableSheet());
3248 mContentEditableSheetAdded = false;
3249 changed = true;
3251 if (changed) {
3252 MOZ_ASSERT(mStyleSetFilled);
3253 ApplicableStylesChanged();
3257 void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
3258 MOZ_ASSERT(IsHTMLOrXHTML());
3259 MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
3260 "Caller should ensure we're being rendered");
3262 ServoStyleSet& styleSet = EnsureStyleSet();
3263 auto* cache = GlobalStyleSheetCache::Singleton();
3264 bool changed = false;
3265 if (!mContentEditableSheetAdded) {
3266 styleSet.AppendStyleSheet(*cache->ContentEditableSheet());
3267 mContentEditableSheetAdded = true;
3268 changed = true;
3270 if (mDesignModeSheetAdded != aDesignMode) {
3271 if (mDesignModeSheetAdded) {
3272 styleSet.RemoveStyleSheet(*cache->DesignModeSheet());
3273 } else {
3274 styleSet.AppendStyleSheet(*cache->DesignModeSheet());
3276 mDesignModeSheetAdded = !mDesignModeSheetAdded;
3277 changed = true;
3279 if (changed) {
3280 ApplicableStylesChanged();
3284 void Document::FillStyleSetDocumentSheets() {
3285 ServoStyleSet& styleSet = EnsureStyleSet();
3286 MOZ_ASSERT(styleSet.SheetCount(StyleOrigin::Author) == 0,
3287 "Style set already has document sheets?");
3289 // Sheets are added in reverse order to avoid worst-case time complexity when
3290 // looking up the index of a sheet.
3292 // Note that usually appending is faster (rebuilds less stuff in the
3293 // styleset), but in this case it doesn't matter since we're filling the
3294 // styleset from scratch anyway.
3295 for (StyleSheet* sheet : Reversed(mStyleSheets)) {
3296 if (sheet->IsApplicable()) {
3297 styleSet.AddDocStyleSheet(*sheet);
3301 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
3302 if (aSheet.IsApplicable()) {
3303 styleSet.AddDocStyleSheet(aSheet);
3307 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3308 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
3309 styleSet.AppendStyleSheet(*sheet);
3312 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eAgentSheet]);
3313 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eUserSheet]);
3314 AppendSheetsToStyleSet(&styleSet, mAdditionalSheets[eAuthorSheet]);
3317 void Document::CompatibilityModeChanged() {
3318 MOZ_ASSERT(IsHTMLOrXHTML());
3319 CSSLoader()->SetCompatibilityMode(mCompatMode);
3321 if (mStyleSet) {
3322 mStyleSet->CompatibilityModeChanged();
3324 if (!mStyleSetFilled) {
3325 MOZ_ASSERT(!mQuirkSheetAdded);
3326 return;
3329 MOZ_ASSERT(mStyleSet);
3330 if (PresShell* presShell = GetPresShell()) {
3331 // Selectors may have become case-sensitive / case-insensitive, the stylist
3332 // has already performed the relevant invalidation.
3333 presShell->EnsureStyleFlush();
3335 if (mQuirkSheetAdded == NeedsQuirksSheet()) {
3336 return;
3338 auto* cache = GlobalStyleSheetCache::Singleton();
3339 StyleSheet* sheet = cache->QuirkSheet();
3340 if (mQuirkSheetAdded) {
3341 mStyleSet->RemoveStyleSheet(*sheet);
3342 } else {
3343 mStyleSet->AppendStyleSheet(*sheet);
3345 mQuirkSheetAdded = !mQuirkSheetAdded;
3346 ApplicableStylesChanged();
3349 void Document::SetCompatibilityMode(nsCompatibility aMode) {
3350 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
3351 "Bad compat mode for XHTML document!");
3353 if (mCompatMode == aMode) {
3354 return;
3356 mCompatMode = aMode;
3357 CompatibilityModeChanged();
3358 // Trigger recomputation of the nsViewportInfo the next time it's queried.
3359 mViewportType = Unknown;
3362 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
3363 uint32_t aSandboxFlags,
3364 nsIChannel* aChannel) {
3365 // If the document permits allow-top-navigation and
3366 // allow-top-navigation-by-user-activation this will permit all top
3367 // navigation.
3368 if (aSandboxFlags != SANDBOXED_NONE &&
3369 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
3370 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
3371 nsContentUtils::ReportToConsole(
3372 nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
3373 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
3374 "BothAllowTopNavigationAndUserActivationPresent");
3376 // If the document is sandboxed (via the HTML5 iframe sandbox
3377 // attribute) and both the allow-scripts and allow-same-origin
3378 // keywords are supplied, the sandboxed document can call into its
3379 // parent document and remove its sandboxing entirely - we print a
3380 // warning to the web console in this case.
3381 if (aSandboxFlags & SANDBOXED_NAVIGATION &&
3382 !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
3383 !(aSandboxFlags & SANDBOXED_ORIGIN)) {
3384 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
3385 MOZ_ASSERT(bc->IsInProcess());
3387 RefPtr<BrowsingContext> parentBC = bc->GetParent();
3388 if (!parentBC || !parentBC->IsInProcess()) {
3389 // If parent document is not in process, then by construction it
3390 // cannot be same origin.
3391 return;
3394 // Don't warn if our parent is not the top-level document.
3395 if (!parentBC->IsTopContent()) {
3396 return;
3399 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
3400 MOZ_ASSERT(parentDocShell);
3402 nsCOMPtr<nsIChannel> parentChannel;
3403 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
3404 if (!parentChannel) {
3405 return;
3407 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
3408 if (NS_FAILED(rv)) {
3409 return;
3412 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
3413 nsCOMPtr<nsIURI> iframeUri;
3414 parentChannel->GetURI(getter_AddRefs(iframeUri));
3415 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3416 "Iframe Sandbox"_ns, parentDocument,
3417 nsContentUtils::eSECURITY_PROPERTIES,
3418 "BothAllowScriptsAndSameOriginPresent",
3419 nsTArray<nsString>(), iframeUri);
3423 bool Document::IsSynthesized() {
3424 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
3425 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
3428 // static
3429 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
3430 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
3431 return principal && (principal->IsSystemPrincipal() ||
3432 principal->GetIsAddonOrExpandedAddonPrincipal());
3435 static void CheckIsBadPolicy(nsILoadInfo::CrossOriginOpenerPolicy aPolicy,
3436 BrowsingContext* aContext, nsIChannel* aChannel) {
3437 #if defined(EARLY_BETA_OR_EARLIER)
3438 auto requireCORP =
3439 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
3441 if (aContext->GetOpenerPolicy() == aPolicy ||
3442 (aContext->GetOpenerPolicy() != requireCORP && aPolicy != requireCORP)) {
3443 return;
3446 nsCOMPtr<nsIURI> uri;
3447 bool hasURI = NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(uri)));
3449 bool isViewSource = hasURI && uri->SchemeIs("view-source");
3451 nsCString contentType;
3452 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3453 bool isPDFJS = bag &&
3454 NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3455 contentType)) &&
3456 contentType.EqualsLiteral(APPLICATION_PDF);
3458 MOZ_DIAGNOSTIC_ASSERT(!isViewSource,
3459 "Bug 1834864: Assert due to view-source.");
3460 MOZ_DIAGNOSTIC_ASSERT(!isPDFJS, "Bug 1834864: Assert due to pdfjs.");
3461 MOZ_DIAGNOSTIC_ASSERT(aPolicy == requireCORP,
3462 "Assert due to clearing REQUIRE_CORP.");
3463 MOZ_DIAGNOSTIC_ASSERT(aContext->GetOpenerPolicy() == requireCORP,
3464 "Assert due to setting REQUIRE_CORP.");
3465 #endif // defined(EARLY_BETA_OR_EARLIER)
3468 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
3469 nsILoadGroup* aLoadGroup,
3470 nsISupports* aContainer,
3471 nsIStreamListener** aDocListener,
3472 bool aReset) {
3473 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
3474 nsCOMPtr<nsIURI> uri;
3475 aChannel->GetURI(getter_AddRefs(uri));
3476 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
3477 ("DOCUMENT %p StartDocumentLoad %s", this,
3478 uri ? uri->GetSpecOrDefault().get() : ""));
3481 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
3482 "Bad readyState");
3483 SetReadyStateInternal(READYSTATE_LOADING);
3485 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
3486 mLoadedAsData = true;
3487 SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true);
3488 // We need to disable script & style loading in this case.
3489 // We leave them disabled even in EndLoad(), and let anyone
3490 // who puts the document on display to worry about enabling.
3492 // Do not load/process scripts when loading as data
3493 ScriptLoader()->SetEnabled(false);
3495 // styles
3496 CSSLoader()->SetEnabled(
3497 false); // Do not load/process styles when loading as data
3498 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
3499 // Allow CSS, but not scripts
3500 ScriptLoader()->SetEnabled(false);
3503 mMayStartLayout = false;
3504 MOZ_ASSERT(!mReadyForIdle,
3505 "We should never hit DOMContentLoaded before this point");
3507 if (aReset) {
3508 Reset(aChannel, aLoadGroup);
3511 nsAutoCString contentType;
3512 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3513 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3514 contentType))) ||
3515 NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
3516 // XXX this is only necessary for viewsource:
3517 nsACString::const_iterator start, end, semicolon;
3518 contentType.BeginReading(start);
3519 contentType.EndReading(end);
3520 semicolon = start;
3521 FindCharInReadable(';', semicolon, end);
3522 SetContentType(Substring(start, semicolon));
3525 RetrieveRelevantHeaders(aChannel);
3527 mChannel = aChannel;
3528 RecomputeResistFingerprinting();
3529 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
3530 if (inStrmChan) {
3531 bool isSrcdocChannel;
3532 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
3533 if (isSrcdocChannel) {
3534 mIsSrcdocDocument = true;
3538 if (mChannel) {
3539 nsLoadFlags loadFlags;
3540 mChannel->GetLoadFlags(&loadFlags);
3541 bool isDocument = false;
3542 mChannel->GetIsDocument(&isDocument);
3543 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
3544 IsSynthesized() && XRE_IsContentProcess()) {
3545 ContentChild::UpdateCookieStatus(mChannel);
3548 // Store the security info for future use.
3549 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
3552 // If this document is being loaded by a docshell, copy its sandbox flags
3553 // to the document, and store the fullscreen enabled flag. These are
3554 // immutable after being set here.
3555 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
3557 // If this is an error page, don't inherit sandbox flags
3558 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3559 if (docShell && !loadInfo->GetLoadErrorPage()) {
3560 mSandboxFlags = loadInfo->GetSandboxFlags();
3561 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
3564 // Set the opener policy for the top level content document.
3565 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
3566 nsILoadInfo::CrossOriginOpenerPolicy policy =
3567 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
3568 if (IsTopLevelContentDocument() && httpChan &&
3569 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
3570 docShell->GetBrowsingContext()) {
3571 CheckIsBadPolicy(policy, docShell->GetBrowsingContext(), aChannel);
3573 // Setting the opener policy on a discarded context has no effect.
3574 Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
3577 // The CSP directives upgrade-insecure-requests as well as
3578 // block-all-mixed-content not only apply to the toplevel document,
3579 // but also to nested documents. The loadInfo of a subdocument
3580 // load already holds the correct flag, so let's just set it here
3581 // on the document. Please note that we set the appropriate preload
3582 // bits just for the sake of completeness here, because the preloader
3583 // does not reach into subdocuments.
3584 mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
3585 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3586 mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
3587 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3589 // HTTPS-Only Mode flags
3590 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
3591 // sub-resources and sub-documents.
3592 mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
3594 nsresult rv = InitReferrerInfo(aChannel);
3595 NS_ENSURE_SUCCESS(rv, rv);
3597 rv = InitCOEP(aChannel);
3598 NS_ENSURE_SUCCESS(rv, rv);
3600 // HACK: Calling EnsureIPCPoliciesRead() here will parse the CSP using the
3601 // context's current mSelfURI (which is still the previous mSelfURI),
3602 // bypassing some internal bugs with 'self' and iframe inheritance.
3603 // Not calling it here results in the mSelfURI being the current mSelfURI and
3604 // not the previous which breaks said inheritance.
3605 // https://bugzilla.mozilla.org/show_bug.cgi?id=1793560#ch-8
3606 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3607 if (cspToInherit) {
3608 cspToInherit->EnsureIPCPoliciesRead();
3611 rv = InitCSP(aChannel);
3612 NS_ENSURE_SUCCESS(rv, rv);
3614 // Initialize FeaturePolicy
3615 rv = InitFeaturePolicy(aChannel);
3616 NS_ENSURE_SUCCESS(rv, rv);
3618 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3619 NS_ENSURE_SUCCESS(rv, rv);
3621 // Generally XFO and CSP frame-ancestors is handled within
3622 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3623 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3624 nsContentPolicyType internalContentType =
3625 loadInfo->InternalContentPolicyType();
3626 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3627 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3628 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3630 nsresult status;
3631 aChannel->GetStatus(&status);
3632 if (status == NS_ERROR_XFO_VIOLATION) {
3633 // stop! ERROR page!
3634 // But before we have to reset the principal of the document
3635 // because the onload() event fires before the error page
3636 // is displayed and we do not want the enclosing document
3637 // to access the contentDocument.
3638 RefPtr<NullPrincipal> nullPrincipal =
3639 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3640 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3641 // and also GetInnerWindow() is still null at this point, before
3642 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3643 // within Document.cpp
3644 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3645 SetPrincipals(nullPrincipal, nullPrincipal);
3649 return NS_OK;
3652 void Document::SetLoadedAsData(bool aLoadedAsData,
3653 bool aConsiderForMemoryReporting) {
3654 mLoadedAsData = aLoadedAsData;
3655 if (aConsiderForMemoryReporting) {
3656 nsIGlobalObject* global = GetScopeObject();
3657 if (global) {
3658 if (nsPIDOMWindowInner* window = global->GetAsInnerWindow()) {
3659 nsGlobalWindowInner::Cast(window)
3660 ->RegisterDataDocumentForMemoryReporting(this);
3666 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3668 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
3670 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3671 return mPreloadCSP;
3674 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3675 mPreloadCSP = aPreloadCSP;
3678 void Document::GetCspJSON(nsString& aJSON) {
3679 aJSON.Truncate();
3681 if (!mCSP) {
3682 dom::CSPPolicies jsonPolicies;
3683 jsonPolicies.ToJSON(aJSON);
3684 return;
3686 mCSP->ToJSON(aJSON);
3689 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3690 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3691 nsAutoString messageTag;
3692 aMessages[i]->GetTag(messageTag);
3694 nsAutoString category;
3695 aMessages[i]->GetCategory(category);
3697 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3698 NS_ConvertUTF16toUTF8(category), this,
3699 nsContentUtils::eSECURITY_PROPERTIES,
3700 NS_ConvertUTF16toUTF8(messageTag).get());
3704 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3705 nsresult rv = NS_OK;
3706 if (!aSpeculative) {
3707 // 1) apply settings from regular CSP
3708 if (mCSP) {
3709 // Set up 'block-all-mixed-content' if not already inherited
3710 // from the parent context or set by any other CSP.
3711 if (!mBlockAllMixedContent) {
3712 bool block = false;
3713 rv = mCSP->GetBlockAllMixedContent(&block);
3714 NS_ENSURE_SUCCESS_VOID(rv);
3715 mBlockAllMixedContent = block;
3717 if (!mBlockAllMixedContentPreloads) {
3718 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3721 // Set up 'upgrade-insecure-requests' if not already inherited
3722 // from the parent context or set by any other CSP.
3723 if (!mUpgradeInsecureRequests) {
3724 bool upgrade = false;
3725 rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
3726 NS_ENSURE_SUCCESS_VOID(rv);
3727 mUpgradeInsecureRequests = upgrade;
3729 if (!mUpgradeInsecurePreloads) {
3730 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3732 // Update csp settings in the parent process
3733 if (auto* wgc = GetWindowGlobalChild()) {
3734 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3735 mUpgradeInsecureRequests);
3738 return;
3741 // 2) apply settings from speculative csp
3742 if (mPreloadCSP) {
3743 if (!mBlockAllMixedContentPreloads) {
3744 bool block = false;
3745 rv = mPreloadCSP->GetBlockAllMixedContent(&block);
3746 NS_ENSURE_SUCCESS_VOID(rv);
3747 mBlockAllMixedContent = block;
3749 if (!mUpgradeInsecurePreloads) {
3750 bool upgrade = false;
3751 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
3752 NS_ENSURE_SUCCESS_VOID(rv);
3753 mUpgradeInsecurePreloads = upgrade;
3758 nsresult Document::InitCSP(nsIChannel* aChannel) {
3759 MOZ_ASSERT(!mScriptGlobalObject,
3760 "CSP must be initialized before mScriptGlobalObject is set!");
3762 // If this is a data document - no need to set CSP.
3763 if (mLoadedAsData) {
3764 return NS_OK;
3767 // If this is an image, no need to set a CSP. Otherwise SVG images
3768 // served with a CSP might block internally applied inline styles.
3769 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3770 if (loadInfo->GetExternalContentPolicyType() ==
3771 ExtContentPolicy::TYPE_IMAGE ||
3772 loadInfo->GetExternalContentPolicyType() ==
3773 ExtContentPolicy::TYPE_IMAGESET) {
3774 return NS_OK;
3777 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3779 // If there is a CSP that needs to be inherited from whatever
3780 // global is considered the client of the document fetch then
3781 // we query it here from the loadinfo in case the newly created
3782 // document needs to inherit the CSP. See:
3783 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3784 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
3785 if (inheritedCSP) {
3786 mCSP = loadInfo->GetCspToInherit();
3789 // If there is no CSP to inherit, then we create a new CSP here so
3790 // that history entries always have the right reference in case a
3791 // Meta CSP gets dynamically added after the history entry has
3792 // already been created.
3793 if (!mCSP) {
3794 mCSP = new nsCSPContext();
3797 // Always overwrite the requesting context of the CSP so that any new
3798 // 'self' keyword added to an inherited CSP translates correctly.
3799 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3800 if (NS_WARN_IF(NS_FAILED(rv))) {
3801 return rv;
3804 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3806 nsCOMPtr<nsIHttpChannel> httpChannel;
3807 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3808 if (NS_WARN_IF(NS_FAILED(rv))) {
3809 return rv;
3812 if (httpChannel) {
3813 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3814 tCspHeaderValue);
3816 Unused << httpChannel->GetResponseHeader(
3817 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3819 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3820 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3822 // Check if this is a document from a WebExtension.
3823 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3824 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3826 // If there's no CSP to apply, go ahead and return early
3827 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
3828 cspROHeaderValue.IsEmpty()) {
3829 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3830 nsCOMPtr<nsIURI> chanURI;
3831 aChannel->GetURI(getter_AddRefs(chanURI));
3832 nsAutoCString aspec;
3833 chanURI->GetAsciiSpec(aspec);
3834 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3835 ("no CSP for document, %s", aspec.get()));
3838 return NS_OK;
3841 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3842 ("Document is an add-on or CSP header specified %p", this));
3844 // ----- if the doc is an addon, apply its CSP.
3845 if (addonPolicy) {
3846 mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
3848 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3849 // Bug 1548468: Move CSP off ExpandedPrincipal
3850 // Currently the LoadInfo holds the source of truth for every resource load
3851 // because LoadInfo::GetCsp() queries the CSP from an ExpandedPrincipal
3852 // (and not from the Client) if the load was triggered by an extension.
3853 auto* basePrin = BasePrincipal::Cast(principal);
3854 if (basePrin->Is<ExpandedPrincipal>()) {
3855 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3859 // ----- if there's a full-strength CSP header, apply it.
3860 if (!cspHeaderValue.IsEmpty()) {
3861 mHasCSPDeliveredThroughHeader = true;
3862 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3863 NS_ENSURE_SUCCESS(rv, rv);
3866 // ----- if there's a report-only CSP header, apply it.
3867 if (!cspROHeaderValue.IsEmpty()) {
3868 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3869 NS_ENSURE_SUCCESS(rv, rv);
3872 // ----- Enforce sandbox policy if supplied in CSP header
3873 // The document may already have some sandbox flags set (e.g. if the document
3874 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3875 // directive, intersect the CSP sandbox flags with the existing flags. This
3876 // corresponds to the _least_ permissive policy.
3877 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3878 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3879 NS_ENSURE_SUCCESS(rv, rv);
3881 // Probably the iframe sandbox attribute already caused the creation of a
3882 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3883 // and no one has been created yet.
3884 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3885 !(mSandboxFlags & SANDBOXED_ORIGIN);
3887 mSandboxFlags |= cspSandboxFlags;
3889 if (needNewNullPrincipal) {
3890 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3891 // Skip setting the content blocking allowlist principal to NullPrincipal.
3892 // The principal is only used to enable/disable trackingprotection via
3893 // permission and can be shared with the top level sandboxed site.
3894 // See Bug 1654546.
3895 SetPrincipals(principal, principal);
3898 ApplySettingsFromCSP(false);
3899 return NS_OK;
3902 static Document* GetInProcessParentDocumentFrom(BrowsingContext* aContext) {
3903 BrowsingContext* parentContext = aContext->GetParent();
3904 if (!parentContext) {
3905 return nullptr;
3908 WindowContext* windowContext = parentContext->GetCurrentWindowContext();
3909 if (!windowContext) {
3910 return nullptr;
3913 return windowContext->GetDocument();
3916 already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
3917 BrowsingContext* browsingContext = GetBrowsingContext();
3918 if (!browsingContext) {
3919 return nullptr;
3921 if (!browsingContext->IsContentSubframe()) {
3922 return nullptr;
3925 HTMLIFrameElement* iframe =
3926 HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement());
3927 if (iframe) {
3928 return do_AddRef(iframe->FeaturePolicy());
3931 if (XRE_IsParentProcess()) {
3932 return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy());
3935 if (Document* parentDocument =
3936 GetInProcessParentDocumentFrom(browsingContext)) {
3937 return do_AddRef(parentDocument->FeaturePolicy());
3940 WindowContext* windowContext = browsingContext->GetCurrentWindowContext();
3941 if (!windowContext) {
3942 return nullptr;
3945 WindowGlobalChild* child = windowContext->GetWindowGlobalChild();
3946 if (!child) {
3947 return nullptr;
3950 return do_AddRef(child->GetContainerFeaturePolicy());
3953 void Document::InitFeaturePolicy() {
3954 MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created");
3956 mFeaturePolicy->ResetDeclaredPolicy();
3958 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
3960 RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
3961 if (parentPolicy) {
3962 // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
3963 mFeaturePolicy->InheritPolicy(parentPolicy);
3964 mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
3968 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
3969 InitFeaturePolicy();
3971 // We don't want to parse the http Feature-Policy header if this pref is off.
3972 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
3973 return NS_OK;
3976 nsCOMPtr<nsIHttpChannel> httpChannel;
3977 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3978 if (NS_WARN_IF(NS_FAILED(rv))) {
3979 return rv;
3982 if (!httpChannel) {
3983 return NS_OK;
3986 // query the policy from the header
3987 nsAutoCString value;
3988 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
3989 if (NS_SUCCEEDED(rv)) {
3990 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
3991 NodePrincipal(), nullptr);
3994 return NS_OK;
3997 void Document::EnsureNotEnteringAndExitFullscreen() {
3998 Document::ClearPendingFullscreenRequests(this);
3999 if (GetFullscreenElement()) {
4000 Document::AsyncExitFullscreen(this);
4004 void Document::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
4005 mReferrerInfo = aReferrerInfo;
4006 mCachedReferrerInfoForInternalCSSAndSVGResources = nullptr;
4007 mCachedURLData = nullptr;
4010 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
4011 MOZ_ASSERT(mReferrerInfo);
4012 MOZ_ASSERT(mPreloadReferrerInfo);
4014 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
4015 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
4016 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
4017 // they have an opaque origin.
4018 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
4019 if (BrowsingContext* bc = GetBrowsingContext()) {
4020 // At this point the document is not fully created and mParentDocument has
4021 // not been set yet,
4022 Document* parentDoc = bc->GetEmbedderElement()
4023 ? bc->GetEmbedderElement()->OwnerDoc()
4024 : nullptr;
4025 if (parentDoc) {
4026 SetReferrerInfo(parentDoc->GetReferrerInfo());
4027 mPreloadReferrerInfo = mReferrerInfo;
4028 return NS_OK;
4031 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
4032 "srcdoc without null principal as toplevel!");
4036 nsCOMPtr<nsIHttpChannel> httpChannel;
4037 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4038 if (NS_WARN_IF(NS_FAILED(rv))) {
4039 return rv;
4042 if (!httpChannel) {
4043 return NS_OK;
4046 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo()) {
4047 SetReferrerInfo(referrerInfo);
4050 // Override policy if we get one from Referrerr-Policy header
4051 mozilla::dom::ReferrerPolicy policy =
4052 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
4053 nsCOMPtr<nsIReferrerInfo> clone =
4054 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
4055 ->CloneWithNewPolicy(policy);
4056 SetReferrerInfo(clone);
4057 mPreloadReferrerInfo = mReferrerInfo;
4058 return NS_OK;
4061 nsresult Document::InitCOEP(nsIChannel* aChannel) {
4062 nsCOMPtr<nsIHttpChannel> httpChannel;
4063 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
4064 if (NS_FAILED(rv)) {
4065 return NS_OK;
4068 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
4070 if (!intChannel) {
4071 return NS_OK;
4074 nsILoadInfo::CrossOriginEmbedderPolicy policy =
4075 nsILoadInfo::EMBEDDER_POLICY_NULL;
4076 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(
4077 mTrials.IsEnabled(OriginTrial::CoepCredentialless), &policy))) {
4078 mEmbedderPolicy = Some(policy);
4081 return NS_OK;
4084 void Document::StopDocumentLoad() {
4085 if (mParser) {
4086 mParserAborted = true;
4087 mParser->Terminate();
4091 void Document::SetDocumentURI(nsIURI* aURI) {
4092 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
4093 mDocumentURI = aURI;
4094 nsIURI* newBase = GetDocBaseURI();
4096 mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI);
4098 bool equalBases = false;
4099 // Changing just the ref of a URI does not change how relative URIs would
4100 // resolve wrt to it, so we can treat the bases as equal as long as they're
4101 // equal ignoring the ref.
4102 if (oldBase && newBase) {
4103 oldBase->EqualsExceptRef(newBase, &equalBases);
4104 } else {
4105 equalBases = !oldBase && !newBase;
4108 // If this is the first time we're setting the document's URI, set the
4109 // document's original URI.
4110 if (!mOriginalURI) mOriginalURI = mDocumentURI;
4112 // If changing the document's URI changed the base URI of the document, we
4113 // need to refresh the hrefs of all the links on the page.
4114 if (!equalBases) {
4115 mCachedURLData = nullptr;
4116 RefreshLinkHrefs();
4119 // Recalculate our base domain
4120 mBaseDomain.Truncate();
4121 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
4122 if (thirdPartyUtil) {
4123 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
4126 // Tell our WindowGlobalParent that the document's URI has been changed.
4127 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
4128 wgc->SetDocumentURI(mDocumentURI);
4132 static void GetFormattedTimeString(PRTime aTime,
4133 nsAString& aFormattedTimeString) {
4134 PRExplodedTime prtime;
4135 PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
4136 // "MM/DD/YYYY hh:mm:ss"
4137 char formatedTime[24];
4138 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
4139 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
4140 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
4141 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
4142 } else {
4143 // If we for whatever reason failed to find the last modified time
4144 // (or even the current time), fall back to what NS4.x returned.
4145 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
4149 void Document::GetLastModified(nsAString& aLastModified) const {
4150 if (!mLastModified.IsEmpty()) {
4151 aLastModified.Assign(mLastModified);
4152 } else {
4153 GetFormattedTimeString(PR_Now(), aLastModified);
4157 static void IncrementExpandoGeneration(Document& aDoc) {
4158 ++aDoc.mExpandoAndGeneration.generation;
4161 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
4162 MOZ_ASSERT(
4163 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
4164 "Only put elements that need to be exposed as document['name'] in "
4165 "the named table.");
4167 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
4169 // Null for out-of-memory
4170 if (entry) {
4171 if (!entry->HasNameElement() &&
4172 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4173 IncrementExpandoGeneration(*this);
4175 entry->AddNameElement(this, aElement);
4179 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
4180 // Speed up document teardown
4181 if (mIdentifierMap.Count() == 0) return;
4183 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
4184 if (!entry) // Could be false if the element was anonymous, hence never added
4185 return;
4187 entry->RemoveNameElement(aElement);
4188 if (!entry->HasNameElement() &&
4189 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4190 IncrementExpandoGeneration(*this);
4194 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
4195 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
4197 if (entry) { /* True except on OOM */
4198 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4199 !entry->HasNameElement() &&
4200 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4201 IncrementExpandoGeneration(*this);
4203 entry->AddIdElement(aElement);
4207 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
4208 NS_ASSERTION(aId, "huhwhatnow?");
4210 // Speed up document teardown
4211 if (mIdentifierMap.Count() == 0) {
4212 return;
4215 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
4216 if (!entry) // Can be null for XML elements with changing ids.
4217 return;
4219 entry->RemoveIdElement(aElement);
4220 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4221 !entry->HasNameElement() &&
4222 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4223 IncrementExpandoGeneration(*this);
4225 if (entry->IsEmpty()) {
4226 mIdentifierMap.RemoveEntry(entry);
4230 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
4231 bool aPreload) {
4232 ReferrerPolicyEnum policy =
4233 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
4234 // The empty string "" corresponds to no referrer policy, causing a fallback
4235 // to a referrer policy defined elsewhere.
4236 if (policy == ReferrerPolicy::_empty) {
4237 return;
4240 MOZ_ASSERT(mReferrerInfo);
4241 MOZ_ASSERT(mPreloadReferrerInfo);
4243 if (aPreload) {
4244 mPreloadReferrerInfo =
4245 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
4246 ->CloneWithNewPolicy(policy);
4247 } else {
4248 nsCOMPtr<nsIReferrerInfo> clone =
4249 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
4250 ->CloneWithNewPolicy(policy);
4251 SetReferrerInfo(clone);
4255 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
4256 nsIPrincipal* aNewPartitionedPrincipal) {
4257 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
4258 if (aNewPrincipal && mAllowDNSPrefetch &&
4259 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
4260 if (aNewPrincipal->SchemeIs("https")) {
4261 mAllowDNSPrefetch = false;
4265 mCSSLoader->DeregisterFromSheetCache();
4267 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
4268 mPartitionedPrincipal = aNewPartitionedPrincipal;
4270 mCachedURLData = nullptr;
4272 mCSSLoader->RegisterInSheetCache();
4274 RecomputeResistFingerprinting();
4276 #ifdef DEBUG
4277 // Validate that the docgroup is set correctly by calling its getter and
4278 // triggering its sanity check.
4280 // If we're setting the principal to null, we don't want to perform the check,
4281 // as the document is entering an intermediate state where it does not have a
4282 // principal. It will be given another real principal shortly which we will
4283 // check. It's not unsafe to have a document which has a null principal in the
4284 // same docgroup as another document, so this should not be a problem.
4285 if (aNewPrincipal) {
4286 GetDocGroup();
4288 #endif
4291 #ifdef DEBUG
4292 void Document::AssertDocGroupMatchesKey() const {
4293 // Sanity check that we have an up-to-date and accurate docgroup
4294 // We only check if the principal when we can get the browsing context.
4296 // Note that we can be invoked during cycle collection, so we need to handle
4297 // the browsingcontext being partially unlinked - normally you shouldn't
4298 // null-check `Group()` as it shouldn't return nullptr.
4299 if (!GetBrowsingContext() || !GetBrowsingContext()->Group()) {
4300 return;
4303 if (mDocGroup && mDocGroup->GetBrowsingContextGroup()) {
4304 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
4305 GetBrowsingContext()->Group());
4307 // GetKey() can fail, e.g. after the TLD service has shut down.
4308 nsAutoCString docGroupKey;
4309 nsresult rv = mozilla::dom::DocGroup::GetKey(
4310 NodePrincipal(),
4311 GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated(),
4312 docGroupKey);
4313 if (NS_SUCCEEDED(rv)) {
4314 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
4318 #endif
4320 nsresult Document::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) const {
4321 return SchedulerGroup::Dispatch(std::move(aRunnable));
4324 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
4325 bool aIsTracking) {
4326 if (aIsTracking) {
4327 mTrackingScripts.Insert(aURL);
4328 } else {
4329 MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
4333 bool Document::IsScriptTracking(JSContext* aCx) const {
4334 JS::AutoFilename filename;
4335 if (!JS::DescribeScriptedCaller(aCx, &filename)) {
4336 return false;
4338 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
4341 void Document::GetContentType(nsAString& aContentType) {
4342 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
4345 void Document::SetContentType(const nsACString& aContentType) {
4346 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
4347 aContentType.EqualsLiteral("application/xhtml+xml")) {
4348 mDefaultElementType = kNameSpaceID_XHTML;
4351 mCachedEncoder = nullptr;
4352 mContentType = aContentType;
4355 bool Document::GetAllowPlugins() {
4356 // First, we ask our docshell if it allows plugins.
4357 auto* browsingContext = GetBrowsingContext();
4359 if (browsingContext) {
4360 if (!browsingContext->GetAllowPlugins()) {
4361 return false;
4364 // If the docshell allows plugins, we check whether
4365 // we are sandboxed and plugins should not be allowed.
4366 if (mSandboxFlags & SANDBOXED_PLUGINS) {
4367 return false;
4371 return true;
4374 bool Document::HasPendingInitialTranslation() {
4375 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
4378 bool Document::HasPendingL10nMutations() const {
4379 return mDocumentL10n && mDocumentL10n->HasPendingMutations();
4382 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
4383 JS::Rooted<JSObject*> object(aCx, aObject);
4384 nsCOMPtr<nsIPrincipal> callerPrincipal =
4385 nsContentUtils::SubjectPrincipal(aCx);
4386 nsGlobalWindowInner* win = xpc::WindowOrNull(object);
4387 bool allowed = false;
4388 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
4389 &allowed);
4390 return allowed;
4393 void Document::LocalizationLinkAdded(Element* aLinkElement) {
4394 if (!AllowsL10n()) {
4395 return;
4398 nsAutoString href;
4399 aLinkElement->GetAttr(nsGkAtoms::href, href);
4401 if (!mDocumentL10n) {
4402 Element* elem = GetDocumentElement();
4403 MOZ_DIAGNOSTIC_ASSERT(elem);
4405 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
4406 mDocumentL10n = DocumentL10n::Create(this, isSync);
4407 if (NS_WARN_IF(!mDocumentL10n)) {
4408 return;
4412 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
4414 if (mReadyState >= READYSTATE_INTERACTIVE) {
4415 nsContentUtils::AddScriptRunner(NewRunnableMethod(
4416 "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
4417 &DocumentL10n::TriggerInitialTranslation));
4418 } else {
4419 if (!mDocumentL10n->mBlockingLayout) {
4420 // Our initial translation is going to block layout start. Make sure
4421 // we don't fire the load event until after that stops happening and
4422 // layout has a chance to start.
4423 BlockOnload();
4424 mDocumentL10n->mBlockingLayout = true;
4429 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
4430 if (!AllowsL10n()) {
4431 return;
4434 if (mDocumentL10n) {
4435 nsAutoString href;
4436 aLinkElement->GetAttr(nsGkAtoms::href, href);
4437 uint32_t remaining =
4438 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
4439 if (remaining == 0) {
4440 if (mDocumentL10n->mBlockingLayout) {
4441 mDocumentL10n->mBlockingLayout = false;
4442 UnblockOnload(/* aFireSync = */ false);
4444 mDocumentL10n = nullptr;
4450 * This method should be called once the end of the l10n
4451 * resource container has been parsed.
4453 * In XUL this is the end of the first </linkset>,
4454 * In XHTML/HTML this is the end of </head>.
4456 * This milestone is used to allow for batch
4457 * localization context I/O and building done
4458 * once when all resources in the document have been
4459 * collected.
4461 void Document::OnL10nResourceContainerParsed() {
4462 // XXX: This is a scaffolding for where we might inject prefetch
4463 // in bug 1717241.
4466 void Document::OnParsingCompleted() {
4467 // Let's call it again, in case the resource
4468 // container has not been closed, and only
4469 // now we're closing the document.
4470 OnL10nResourceContainerParsed();
4472 if (mDocumentL10n) {
4473 RefPtr<DocumentL10n> l10n = mDocumentL10n;
4474 l10n->TriggerInitialTranslation();
4478 void Document::InitialTranslationCompleted(bool aL10nCached) {
4479 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4480 // This means we blocked the load event in LocalizationLinkAdded. It's
4481 // important that the load blocker removal here be async, because our caller
4482 // will notify the content sink after us, and we want the content sync's
4483 // work to happen before the load event fires.
4484 mDocumentL10n->mBlockingLayout = false;
4485 UnblockOnload(/* aFireSync = */ false);
4488 mL10nProtoElements.Clear();
4490 nsXULPrototypeDocument* proto = GetPrototype();
4491 if (proto) {
4492 proto->SetIsL10nCached(aL10nCached);
4496 bool Document::AllowsL10n() const {
4497 if (IsStaticDocument()) {
4498 // We don't allow l10n on static documents, because the nodes are already
4499 // cloned translated, and static docs don't get parsed so we never
4500 // TriggerInitialTranslation, etc, so a load blocker would keep hanging
4501 // forever.
4502 return false;
4504 bool allowed = false;
4505 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4506 return allowed;
4509 bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
4510 JSObject* /*unused*/
4512 MOZ_ASSERT(NS_IsMainThread());
4514 return nsContentUtils::IsSystemCaller(aCx) ||
4515 StaticPrefs::dom_animations_api_getAnimations_enabled();
4518 bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
4519 JSObject* /*unused*/
4521 MOZ_ASSERT(NS_IsMainThread());
4523 return nsContentUtils::IsSystemCaller(aCx) ||
4524 StaticPrefs::dom_animations_api_timelines_enabled();
4527 DocumentTimeline* Document::Timeline() {
4528 if (!mDocumentTimeline) {
4529 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4532 return mDocumentTimeline;
4535 SVGSVGElement* Document::GetSVGRootElement() const {
4536 Element* root = GetRootElement();
4537 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4538 return nullptr;
4540 return static_cast<SVGSVGElement*>(root);
4543 /* Return true if the document is in the focused top-level window, and is an
4544 * ancestor of the focused DOMWindow. */
4545 bool Document::HasFocus(ErrorResult& rv) const {
4546 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4547 if (!fm) {
4548 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4549 return false;
4552 BrowsingContext* bc = GetBrowsingContext();
4553 if (!bc) {
4554 return false;
4557 if (!fm->IsInActiveWindow(bc)) {
4558 return false;
4561 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
4564 bool Document::ThisDocumentHasFocus() const {
4565 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4566 return fm && fm->GetFocusedWindow() &&
4567 fm->GetFocusedWindow()->GetExtantDoc() == this;
4570 void Document::GetDesignMode(nsAString& aDesignMode) {
4571 if (IsInDesignMode()) {
4572 aDesignMode.AssignLiteral("on");
4573 } else {
4574 aDesignMode.AssignLiteral("off");
4578 void Document::SetDesignMode(const nsAString& aDesignMode,
4579 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4580 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4583 static void NotifyEditableStateChange(Document& aDoc) {
4584 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4585 nsMutationGuard g;
4586 #endif
4587 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4588 node = node->GetNextNode(&aDoc)) {
4589 if (auto* element = Element::FromNode(node)) {
4590 element->UpdateEditableState(true);
4593 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4596 void Document::SetDesignMode(const nsAString& aDesignMode,
4597 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4598 ErrorResult& rv) {
4599 if (aSubjectPrincipal.isSome() &&
4600 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4601 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4602 return;
4604 const bool editableMode = IsInDesignMode();
4605 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4606 SetEditableFlag(!editableMode);
4607 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4608 // state of all descendant elements of it. Update that now.
4609 NotifyEditableStateChange(*this);
4610 rv = EditingStateChanged();
4614 nsCommandManager* Document::GetMidasCommandManager() {
4615 // check if we have it cached
4616 if (mMidasCommandManager) {
4617 return mMidasCommandManager;
4620 nsPIDOMWindowOuter* window = GetWindow();
4621 if (!window) {
4622 return nullptr;
4625 nsIDocShell* docshell = window->GetDocShell();
4626 if (!docshell) {
4627 return nullptr;
4630 mMidasCommandManager = docshell->GetCommandManager();
4631 return mMidasCommandManager;
4634 // static
4635 void Document::EnsureInitializeInternalCommandDataHashtable() {
4636 if (sInternalCommandDataHashtable) {
4637 return;
4639 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
4640 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4641 // clang-format off
4642 sInternalCommandDataHashtable->InsertOrUpdate(
4643 u"bold"_ns,
4644 InternalCommandData(
4645 "cmd_bold",
4646 Command::FormatBold,
4647 ExecCommandParam::Ignore,
4648 StyleUpdatingCommand::GetInstance,
4649 CommandOnTextEditor::Disabled));
4650 sInternalCommandDataHashtable->InsertOrUpdate(
4651 u"italic"_ns,
4652 InternalCommandData(
4653 "cmd_italic",
4654 Command::FormatItalic,
4655 ExecCommandParam::Ignore,
4656 StyleUpdatingCommand::GetInstance,
4657 CommandOnTextEditor::Disabled));
4658 sInternalCommandDataHashtable->InsertOrUpdate(
4659 u"underline"_ns,
4660 InternalCommandData(
4661 "cmd_underline",
4662 Command::FormatUnderline,
4663 ExecCommandParam::Ignore,
4664 StyleUpdatingCommand::GetInstance,
4665 CommandOnTextEditor::Disabled));
4666 sInternalCommandDataHashtable->InsertOrUpdate(
4667 u"strikethrough"_ns,
4668 InternalCommandData(
4669 "cmd_strikethrough",
4670 Command::FormatStrikeThrough,
4671 ExecCommandParam::Ignore,
4672 StyleUpdatingCommand::GetInstance,
4673 CommandOnTextEditor::Disabled));
4674 sInternalCommandDataHashtable->InsertOrUpdate(
4675 u"subscript"_ns,
4676 InternalCommandData(
4677 "cmd_subscript",
4678 Command::FormatSubscript,
4679 ExecCommandParam::Ignore,
4680 StyleUpdatingCommand::GetInstance,
4681 CommandOnTextEditor::Disabled));
4682 sInternalCommandDataHashtable->InsertOrUpdate(
4683 u"superscript"_ns,
4684 InternalCommandData(
4685 "cmd_superscript",
4686 Command::FormatSuperscript,
4687 ExecCommandParam::Ignore,
4688 StyleUpdatingCommand::GetInstance,
4689 CommandOnTextEditor::Disabled));
4690 sInternalCommandDataHashtable->InsertOrUpdate(
4691 u"cut"_ns,
4692 InternalCommandData(
4693 "cmd_cut",
4694 Command::Cut,
4695 ExecCommandParam::Ignore,
4696 CutCommand::GetInstance,
4697 CommandOnTextEditor::Enabled));
4698 sInternalCommandDataHashtable->InsertOrUpdate(
4699 u"copy"_ns,
4700 InternalCommandData(
4701 "cmd_copy",
4702 Command::Copy,
4703 ExecCommandParam::Ignore,
4704 CopyCommand::GetInstance,
4705 CommandOnTextEditor::Enabled));
4706 sInternalCommandDataHashtable->InsertOrUpdate(
4707 u"paste"_ns,
4708 InternalCommandData(
4709 "cmd_paste",
4710 Command::Paste,
4711 ExecCommandParam::Ignore,
4712 PasteCommand::GetInstance,
4713 CommandOnTextEditor::Enabled));
4714 sInternalCommandDataHashtable->InsertOrUpdate(
4715 u"delete"_ns,
4716 InternalCommandData(
4717 "cmd_deleteCharBackward",
4718 Command::DeleteCharBackward,
4719 ExecCommandParam::Ignore,
4720 DeleteCommand::GetInstance,
4721 CommandOnTextEditor::Enabled));
4722 sInternalCommandDataHashtable->InsertOrUpdate(
4723 u"forwarddelete"_ns,
4724 InternalCommandData(
4725 "cmd_deleteCharForward",
4726 Command::DeleteCharForward,
4727 ExecCommandParam::Ignore,
4728 DeleteCommand::GetInstance,
4729 CommandOnTextEditor::Enabled));
4730 sInternalCommandDataHashtable->InsertOrUpdate(
4731 u"selectall"_ns,
4732 InternalCommandData(
4733 "cmd_selectAll",
4734 Command::SelectAll,
4735 ExecCommandParam::Ignore,
4736 SelectAllCommand::GetInstance,
4737 CommandOnTextEditor::Enabled));
4738 sInternalCommandDataHashtable->InsertOrUpdate(
4739 u"undo"_ns,
4740 InternalCommandData(
4741 "cmd_undo",
4742 Command::HistoryUndo,
4743 ExecCommandParam::Ignore,
4744 UndoCommand::GetInstance,
4745 CommandOnTextEditor::Enabled));
4746 sInternalCommandDataHashtable->InsertOrUpdate(
4747 u"redo"_ns,
4748 InternalCommandData(
4749 "cmd_redo",
4750 Command::HistoryRedo,
4751 ExecCommandParam::Ignore,
4752 RedoCommand::GetInstance,
4753 CommandOnTextEditor::Enabled));
4754 sInternalCommandDataHashtable->InsertOrUpdate(
4755 u"indent"_ns,
4756 InternalCommandData("cmd_indent",
4757 Command::FormatIndent,
4758 ExecCommandParam::Ignore,
4759 IndentCommand::GetInstance,
4760 CommandOnTextEditor::Disabled));
4761 sInternalCommandDataHashtable->InsertOrUpdate(
4762 u"outdent"_ns,
4763 InternalCommandData(
4764 "cmd_outdent",
4765 Command::FormatOutdent,
4766 ExecCommandParam::Ignore,
4767 OutdentCommand::GetInstance,
4768 CommandOnTextEditor::Disabled));
4769 sInternalCommandDataHashtable->InsertOrUpdate(
4770 u"backcolor"_ns,
4771 InternalCommandData(
4772 "cmd_highlight",
4773 Command::FormatBackColor,
4774 ExecCommandParam::String,
4775 HighlightColorStateCommand::GetInstance,
4776 CommandOnTextEditor::Disabled));
4777 sInternalCommandDataHashtable->InsertOrUpdate(
4778 u"hilitecolor"_ns,
4779 InternalCommandData(
4780 "cmd_highlight",
4781 Command::FormatBackColor,
4782 ExecCommandParam::String,
4783 HighlightColorStateCommand::GetInstance,
4784 CommandOnTextEditor::Disabled));
4785 sInternalCommandDataHashtable->InsertOrUpdate(
4786 u"forecolor"_ns,
4787 InternalCommandData(
4788 "cmd_fontColor",
4789 Command::FormatFontColor,
4790 ExecCommandParam::String,
4791 FontColorStateCommand::GetInstance,
4792 CommandOnTextEditor::Disabled));
4793 sInternalCommandDataHashtable->InsertOrUpdate(
4794 u"fontname"_ns,
4795 InternalCommandData(
4796 "cmd_fontFace",
4797 Command::FormatFontName,
4798 ExecCommandParam::String,
4799 FontFaceStateCommand::GetInstance,
4800 CommandOnTextEditor::Disabled));
4801 sInternalCommandDataHashtable->InsertOrUpdate(
4802 u"fontsize"_ns,
4803 InternalCommandData(
4804 "cmd_fontSize",
4805 Command::FormatFontSize,
4806 ExecCommandParam::String,
4807 FontSizeStateCommand::GetInstance,
4808 CommandOnTextEditor::Disabled));
4809 sInternalCommandDataHashtable->InsertOrUpdate(
4810 u"inserthorizontalrule"_ns,
4811 InternalCommandData(
4812 "cmd_insertHR",
4813 Command::InsertHorizontalRule,
4814 ExecCommandParam::Ignore,
4815 InsertTagCommand::GetInstance,
4816 CommandOnTextEditor::Disabled));
4817 sInternalCommandDataHashtable->InsertOrUpdate(
4818 u"createlink"_ns,
4819 InternalCommandData(
4820 "cmd_insertLinkNoUI",
4821 Command::InsertLink,
4822 ExecCommandParam::String,
4823 InsertTagCommand::GetInstance,
4824 CommandOnTextEditor::Disabled));
4825 sInternalCommandDataHashtable->InsertOrUpdate(
4826 u"insertimage"_ns,
4827 InternalCommandData(
4828 "cmd_insertImageNoUI",
4829 Command::InsertImage,
4830 ExecCommandParam::String,
4831 InsertTagCommand::GetInstance,
4832 CommandOnTextEditor::Disabled));
4833 sInternalCommandDataHashtable->InsertOrUpdate(
4834 u"inserthtml"_ns,
4835 InternalCommandData(
4836 "cmd_insertHTML",
4837 Command::InsertHTML,
4838 ExecCommandParam::String,
4839 InsertHTMLCommand::GetInstance,
4840 // TODO: Chromium inserts text content of the document fragment
4841 // created from the param.
4842 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
4843 CommandOnTextEditor::Disabled));
4844 sInternalCommandDataHashtable->InsertOrUpdate(
4845 u"inserttext"_ns,
4846 InternalCommandData(
4847 "cmd_insertText",
4848 Command::InsertText,
4849 ExecCommandParam::String,
4850 InsertPlaintextCommand::GetInstance,
4851 CommandOnTextEditor::Enabled));
4852 sInternalCommandDataHashtable->InsertOrUpdate(
4853 u"justifyleft"_ns,
4854 InternalCommandData(
4855 "cmd_align",
4856 Command::FormatJustifyLeft,
4857 ExecCommandParam::Ignore, // Will be set to "left"
4858 AlignCommand::GetInstance,
4859 CommandOnTextEditor::Disabled));
4860 sInternalCommandDataHashtable->InsertOrUpdate(
4861 u"justifyright"_ns,
4862 InternalCommandData(
4863 "cmd_align",
4864 Command::FormatJustifyRight,
4865 ExecCommandParam::Ignore, // Will be set to "right"
4866 AlignCommand::GetInstance,
4867 CommandOnTextEditor::Disabled));
4868 sInternalCommandDataHashtable->InsertOrUpdate(
4869 u"justifycenter"_ns,
4870 InternalCommandData(
4871 "cmd_align",
4872 Command::FormatJustifyCenter,
4873 ExecCommandParam::Ignore, // Will be set to "center"
4874 AlignCommand::GetInstance,
4875 CommandOnTextEditor::Disabled));
4876 sInternalCommandDataHashtable->InsertOrUpdate(
4877 u"justifyfull"_ns,
4878 InternalCommandData(
4879 "cmd_align",
4880 Command::FormatJustifyFull,
4881 ExecCommandParam::Ignore, // Will be set to "justify"
4882 AlignCommand::GetInstance,
4883 CommandOnTextEditor::Disabled));
4884 sInternalCommandDataHashtable->InsertOrUpdate(
4885 u"removeformat"_ns,
4886 InternalCommandData(
4887 "cmd_removeStyles",
4888 Command::FormatRemove,
4889 ExecCommandParam::Ignore,
4890 RemoveStylesCommand::GetInstance,
4891 CommandOnTextEditor::Disabled));
4892 sInternalCommandDataHashtable->InsertOrUpdate(
4893 u"unlink"_ns,
4894 InternalCommandData(
4895 "cmd_removeLinks",
4896 Command::FormatRemoveLink,
4897 ExecCommandParam::Ignore,
4898 StyleUpdatingCommand::GetInstance,
4899 CommandOnTextEditor::Disabled));
4900 sInternalCommandDataHashtable->InsertOrUpdate(
4901 u"insertorderedlist"_ns,
4902 InternalCommandData(
4903 "cmd_ol",
4904 Command::InsertOrderedList,
4905 ExecCommandParam::Ignore,
4906 ListCommand::GetInstance,
4907 CommandOnTextEditor::Disabled));
4908 sInternalCommandDataHashtable->InsertOrUpdate(
4909 u"insertunorderedlist"_ns,
4910 InternalCommandData(
4911 "cmd_ul",
4912 Command::InsertUnorderedList,
4913 ExecCommandParam::Ignore,
4914 ListCommand::GetInstance,
4915 CommandOnTextEditor::Disabled));
4916 sInternalCommandDataHashtable->InsertOrUpdate(
4917 u"insertparagraph"_ns,
4918 InternalCommandData(
4919 "cmd_insertParagraph",
4920 Command::InsertParagraph,
4921 ExecCommandParam::Ignore,
4922 InsertParagraphCommand::GetInstance,
4923 CommandOnTextEditor::Enabled));
4924 sInternalCommandDataHashtable->InsertOrUpdate(
4925 u"insertlinebreak"_ns,
4926 InternalCommandData(
4927 "cmd_insertLineBreak",
4928 Command::InsertLineBreak,
4929 ExecCommandParam::Ignore,
4930 InsertLineBreakCommand::GetInstance,
4931 CommandOnTextEditor::Enabled));
4932 sInternalCommandDataHashtable->InsertOrUpdate(
4933 u"formatblock"_ns,
4934 InternalCommandData(
4935 "cmd_formatBlock",
4936 Command::FormatBlock,
4937 ExecCommandParam::String,
4938 FormatBlockStateCommand::GetInstance,
4939 CommandOnTextEditor::Disabled));
4940 sInternalCommandDataHashtable->InsertOrUpdate(
4941 u"styleWithCSS"_ns,
4942 InternalCommandData(
4943 "cmd_setDocumentUseCSS",
4944 Command::SetDocumentUseCSS,
4945 ExecCommandParam::Boolean,
4946 SetDocumentStateCommand::GetInstance,
4947 CommandOnTextEditor::FallThrough));
4948 sInternalCommandDataHashtable->InsertOrUpdate(
4949 u"usecss"_ns, // Legacy command
4950 InternalCommandData(
4951 "cmd_setDocumentUseCSS",
4952 Command::SetDocumentUseCSS,
4953 ExecCommandParam::InvertedBoolean,
4954 SetDocumentStateCommand::GetInstance,
4955 CommandOnTextEditor::FallThrough));
4956 sInternalCommandDataHashtable->InsertOrUpdate(
4957 u"contentReadOnly"_ns,
4958 InternalCommandData(
4959 "cmd_setDocumentReadOnly",
4960 Command::SetDocumentReadOnly,
4961 ExecCommandParam::Boolean,
4962 SetDocumentStateCommand::GetInstance,
4963 CommandOnTextEditor::Enabled));
4964 sInternalCommandDataHashtable->InsertOrUpdate(
4965 u"insertBrOnReturn"_ns,
4966 InternalCommandData(
4967 "cmd_insertBrOnReturn",
4968 Command::SetDocumentInsertBROnEnterKeyPress,
4969 ExecCommandParam::Boolean,
4970 SetDocumentStateCommand::GetInstance,
4971 CommandOnTextEditor::FallThrough));
4972 sInternalCommandDataHashtable->InsertOrUpdate(
4973 u"defaultParagraphSeparator"_ns,
4974 InternalCommandData(
4975 "cmd_defaultParagraphSeparator",
4976 Command::SetDocumentDefaultParagraphSeparator,
4977 ExecCommandParam::String,
4978 SetDocumentStateCommand::GetInstance,
4979 CommandOnTextEditor::FallThrough));
4980 sInternalCommandDataHashtable->InsertOrUpdate(
4981 u"enableObjectResizing"_ns,
4982 InternalCommandData(
4983 "cmd_enableObjectResizing",
4984 Command::ToggleObjectResizers,
4985 ExecCommandParam::Boolean,
4986 SetDocumentStateCommand::GetInstance,
4987 CommandOnTextEditor::FallThrough));
4988 sInternalCommandDataHashtable->InsertOrUpdate(
4989 u"enableInlineTableEditing"_ns,
4990 InternalCommandData(
4991 "cmd_enableInlineTableEditing",
4992 Command::ToggleInlineTableEditor,
4993 ExecCommandParam::Boolean,
4994 SetDocumentStateCommand::GetInstance,
4995 CommandOnTextEditor::FallThrough));
4996 sInternalCommandDataHashtable->InsertOrUpdate(
4997 u"enableAbsolutePositionEditing"_ns,
4998 InternalCommandData(
4999 "cmd_enableAbsolutePositionEditing",
5000 Command::ToggleAbsolutePositionEditor,
5001 ExecCommandParam::Boolean,
5002 SetDocumentStateCommand::GetInstance,
5003 CommandOnTextEditor::FallThrough));
5004 sInternalCommandDataHashtable->InsertOrUpdate(
5005 u"enableCompatibleJoinSplitDirection"_ns,
5006 InternalCommandData("cmd_enableCompatibleJoinSplitNodeDirection",
5007 Command::EnableCompatibleJoinSplitNodeDirection,
5008 ExecCommandParam::Boolean,
5009 SetDocumentStateCommand::GetInstance,
5010 CommandOnTextEditor::FallThrough));
5011 #if 0
5012 // with empty string
5013 sInternalCommandDataHashtable->InsertOrUpdate(
5014 u"justifynone"_ns,
5015 InternalCommandData(
5016 "cmd_align",
5017 Command::Undefined,
5018 ExecCommandParam::Ignore,
5019 nullptr,
5020 CommandOnTextEditor::Disabled)); // Not implemented yet.
5021 // REQUIRED SPECIAL REVIEW special review
5022 sInternalCommandDataHashtable->InsertOrUpdate(
5023 u"saveas"_ns,
5024 InternalCommandData(
5025 "cmd_saveAs",
5026 Command::Undefined,
5027 ExecCommandParam::Boolean,
5028 nullptr,
5029 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5030 // REQUIRED SPECIAL REVIEW special review
5031 sInternalCommandDataHashtable->InsertOrUpdate(
5032 u"print"_ns,
5033 InternalCommandData(
5034 "cmd_print",
5035 Command::Undefined,
5036 ExecCommandParam::Boolean,
5037 nullptr,
5038 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5039 #endif // #if 0
5040 // clang-format on
5043 Document::InternalCommandData Document::ConvertToInternalCommand(
5044 const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */,
5045 nsAString* aAdjustedValue /* = nullptr */) {
5046 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
5047 EnsureInitializeInternalCommandDataHashtable();
5048 InternalCommandData commandData;
5049 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
5050 return InternalCommandData();
5052 // Ignore if the command is disabled by a corresponding pref due to Gecko
5053 // specific.
5054 switch (commandData.mCommand) {
5055 case Command::SetDocumentReadOnly:
5056 if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() &&
5057 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) {
5058 return InternalCommandData();
5060 break;
5061 case Command::SetDocumentInsertBROnEnterKeyPress:
5062 MOZ_DIAGNOSTIC_ASSERT(
5063 aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn"));
5064 if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) {
5065 return InternalCommandData();
5067 break;
5068 default:
5069 break;
5071 if (!aAdjustedValue) {
5072 // No further work to do
5073 return commandData;
5075 switch (commandData.mExecCommandParam) {
5076 case ExecCommandParam::Ignore:
5077 // Just have to copy it, no checking
5078 switch (commandData.mCommand) {
5079 case Command::FormatJustifyLeft:
5080 aAdjustedValue->AssignLiteral("left");
5081 break;
5082 case Command::FormatJustifyRight:
5083 aAdjustedValue->AssignLiteral("right");
5084 break;
5085 case Command::FormatJustifyCenter:
5086 aAdjustedValue->AssignLiteral("center");
5087 break;
5088 case Command::FormatJustifyFull:
5089 aAdjustedValue->AssignLiteral("justify");
5090 break;
5091 default:
5092 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
5093 EditorCommandParamType::None);
5094 break;
5096 return commandData;
5098 case ExecCommandParam::Boolean:
5099 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5100 EditorCommandParamType::Bool));
5101 // If this is a boolean value and it's not explicitly false (e.g. no
5102 // value). We default to "true" (see bug 301490).
5103 if (!aValue.LowerCaseEqualsLiteral("false")) {
5104 aAdjustedValue->AssignLiteral("true");
5105 } else {
5106 aAdjustedValue->AssignLiteral("false");
5108 return commandData;
5110 case ExecCommandParam::InvertedBoolean:
5111 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5112 EditorCommandParamType::Bool));
5113 // For old backwards commands we invert the check.
5114 if (aValue.LowerCaseEqualsLiteral("false")) {
5115 aAdjustedValue->AssignLiteral("true");
5116 } else {
5117 aAdjustedValue->AssignLiteral("false");
5119 return commandData;
5121 case ExecCommandParam::String:
5122 MOZ_ASSERT(!!(
5123 EditorCommand::GetParamType(commandData.mCommand) &
5124 (EditorCommandParamType::String | EditorCommandParamType::CString)));
5125 switch (commandData.mCommand) {
5126 case Command::FormatBlock: {
5127 const char16_t* start = aValue.BeginReading();
5128 const char16_t* end = aValue.EndReading();
5129 if (start != end && *start == '<' && *(end - 1) == '>') {
5130 ++start;
5131 --end;
5133 // XXX Should we reorder this array with actual usage?
5134 static const nsStaticAtom* kFormattableBlockTags[] = {
5135 // clang-format off
5136 nsGkAtoms::address,
5137 nsGkAtoms::article,
5138 nsGkAtoms::aside,
5139 nsGkAtoms::blockquote,
5140 nsGkAtoms::dd,
5141 nsGkAtoms::div,
5142 nsGkAtoms::dl,
5143 nsGkAtoms::dt,
5144 nsGkAtoms::footer,
5145 nsGkAtoms::h1,
5146 nsGkAtoms::h2,
5147 nsGkAtoms::h3,
5148 nsGkAtoms::h4,
5149 nsGkAtoms::h5,
5150 nsGkAtoms::h6,
5151 nsGkAtoms::header,
5152 nsGkAtoms::hgroup,
5153 nsGkAtoms::main,
5154 nsGkAtoms::nav,
5155 nsGkAtoms::p,
5156 nsGkAtoms::pre,
5157 nsGkAtoms::section,
5158 // clang-format on
5160 nsAutoString value(nsDependentSubstring(start, end));
5161 ToLowerCase(value);
5162 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
5163 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
5164 if (valueAtom == kTag) {
5165 kTag->ToString(*aAdjustedValue);
5166 return commandData;
5169 return InternalCommandData();
5171 case Command::FormatFontSize: {
5172 // Per editing spec as of April 23, 2012, we need to reject the value
5173 // if it's not a valid floating-point number surrounded by optional
5174 // whitespace. Otherwise, we parse it as a legacy font size. For
5175 // now, we just parse as a legacy font size regardless (matching
5176 // WebKit) -- bug 747879.
5177 int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
5178 if (!size) {
5179 return InternalCommandData();
5181 MOZ_ASSERT(aAdjustedValue->IsEmpty());
5182 aAdjustedValue->AppendInt(size);
5183 return commandData;
5185 case Command::InsertImage:
5186 case Command::InsertLink:
5187 if (aValue.IsEmpty()) {
5188 // Invalid value, return false
5189 return InternalCommandData();
5191 aAdjustedValue->Assign(aValue);
5192 return commandData;
5193 case Command::SetDocumentDefaultParagraphSeparator:
5194 if (!aValue.LowerCaseEqualsLiteral("div") &&
5195 !aValue.LowerCaseEqualsLiteral("p") &&
5196 !aValue.LowerCaseEqualsLiteral("br")) {
5197 // Invalid value
5198 return InternalCommandData();
5200 aAdjustedValue->Assign(aValue);
5201 return commandData;
5202 default:
5203 aAdjustedValue->Assign(aValue);
5204 return commandData;
5207 default:
5208 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
5209 return InternalCommandData();
5213 Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
5214 Document& aDocument, const InternalCommandData& aCommandData)
5215 : mCommandData(aCommandData) {
5216 // We'll retrieve an editor with current DOM tree and layout information.
5217 // However, JS may have already hidden or remove exposed root content of
5218 // the editor. Therefore, we need the latest layout information here.
5219 aDocument.FlushPendingNotifications(FlushType::Layout);
5220 if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) {
5221 mDoNothing = true;
5222 return;
5225 if (nsPresContext* presContext = aDocument.GetPresContext()) {
5226 // Consider context of command handling which is automatically resolved
5227 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5228 // The order is:
5229 // 1. TextEditor if there is an active element and it has TextEditor like
5230 // <input type="text"> or <textarea>.
5231 // 2. HTMLEditor for the document, if there is.
5232 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
5233 if (aCommandData.IsCutOrCopyCommand()) {
5234 // Note that we used to use DocShell to handle `cut` and `copy` command
5235 // for dispatching corresponding events for making possible web apps to
5236 // implement their own editor without editable elements but supports
5237 // standard shortcut keys, etc. In this case, we prefer to use active
5238 // element's editor to keep same behavior.
5239 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5240 } else {
5241 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5242 mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
5243 if (!mActiveEditor) {
5244 mActiveEditor = mHTMLEditor;
5249 // Then, retrieve editor command class instance which should handle it
5250 // and can handle it now.
5251 if (!mActiveEditor) {
5252 // If the command is available without editor, we should redirect the
5253 // command to focused descendant with DocShell.
5254 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5255 mDoNothing = true;
5256 return;
5258 return;
5261 // Otherwise, we should use EditorCommand instance (which is singleton
5262 // instance) when it's enabled.
5263 mEditorCommand = aCommandData.mGetEditorCommandFunc
5264 ? aCommandData.mGetEditorCommandFunc()
5265 : nullptr;
5266 if (!mEditorCommand) {
5267 mDoNothing = true;
5268 mActiveEditor = nullptr;
5269 mHTMLEditor = nullptr;
5270 return;
5273 if (IsCommandEnabled()) {
5274 return;
5277 // If the EditorCommand instance is disabled, we should do nothing if
5278 // the command requires an editor.
5279 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5280 // Do nothing if editor specific commands is disabled (bug 760052).
5281 mDoNothing = true;
5282 return;
5285 // Otherwise, we should redirect it to focused descendant with DocShell.
5286 mEditorCommand = nullptr;
5287 mActiveEditor = nullptr;
5288 mHTMLEditor = nullptr;
5291 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
5292 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
5293 switch (mCommandData.mCommandOnTextEditor) {
5294 case CommandOnTextEditor::Enabled:
5295 return mActiveEditor;
5296 case CommandOnTextEditor::Disabled:
5297 return mActiveEditor && mActiveEditor->IsTextEditor()
5298 ? nullptr
5299 : mActiveEditor.get();
5300 case CommandOnTextEditor::FallThrough:
5301 return mHTMLEditor;
5303 return nullptr;
5306 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
5307 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
5308 // Make sure frames are up to date, since that can affect whether
5309 // we're editable.
5310 doc->FlushPendingNotifications(FlushType::Frames);
5312 EditorBase* targetEditor = GetTargetEditor();
5313 if (targetEditor && targetEditor->IsTextEditor()) {
5314 // FYI: When `disabled` attribute is set, `TextEditor` treats it as
5315 // "readonly" too.
5316 return !targetEditor->IsReadonly();
5318 return aDocument->IsEditingOn();
5321 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
5322 EditorBase* targetEditor = GetTargetEditor();
5323 if (!targetEditor) {
5324 return false;
5326 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5327 return MOZ_KnownLive(mEditorCommand)
5328 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
5331 nsresult Document::AutoEditorCommandTarget::DoCommand(
5332 nsIPrincipal* aPrincipal) const {
5333 MOZ_ASSERT(!DoNothing());
5334 MOZ_ASSERT(mEditorCommand);
5335 EditorBase* targetEditor = GetTargetEditor();
5336 if (!targetEditor) {
5337 return NS_SUCCESS_DOM_NO_OPERATION;
5339 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5340 return MOZ_KnownLive(mEditorCommand)
5341 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
5342 aPrincipal);
5345 template <typename ParamType>
5346 nsresult Document::AutoEditorCommandTarget::DoCommandParam(
5347 const ParamType& aParam, nsIPrincipal* aPrincipal) const {
5348 MOZ_ASSERT(!DoNothing());
5349 MOZ_ASSERT(mEditorCommand);
5350 EditorBase* targetEditor = GetTargetEditor();
5351 if (!targetEditor) {
5352 return NS_SUCCESS_DOM_NO_OPERATION;
5354 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5355 return MOZ_KnownLive(mEditorCommand)
5356 ->DoCommandParam(mCommandData.mCommand, aParam,
5357 MOZ_KnownLive(*targetEditor), aPrincipal);
5360 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
5361 nsCommandParams& aParams) const {
5362 MOZ_ASSERT(mEditorCommand);
5363 EditorBase* targetEditor = GetTargetEditor();
5364 if (!targetEditor) {
5365 return NS_OK;
5367 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5368 return MOZ_KnownLive(mEditorCommand)
5369 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
5370 MOZ_KnownLive(targetEditor), nullptr);
5373 Document::AutoRunningExecCommandMarker::AutoRunningExecCommandMarker(
5374 Document& aDocument, nsIPrincipal* aPrincipal)
5375 : mDocument(aDocument),
5376 mTreatAsUserInput(EditorBase::TreatAsUserInput(aPrincipal)),
5377 mHasBeenRunningByContent(aDocument.mIsRunningExecCommandByContent),
5378 mHasBeenRunningByChromeOrAddon(
5379 aDocument.mIsRunningExecCommandByChromeOrAddon) {
5380 if (mTreatAsUserInput) {
5381 aDocument.mIsRunningExecCommandByChromeOrAddon = true;
5382 } else {
5383 aDocument.mIsRunningExecCommandByContent = true;
5387 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
5388 const nsAString& aValue,
5389 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
5390 // Only allow on HTML documents.
5391 if (!IsHTMLOrXHTML()) {
5392 aRv.ThrowInvalidStateError(
5393 "execCommand is only supported on HTML documents");
5394 return false;
5396 // Otherwise, don't throw exception for compatibility with Chrome.
5398 // if they are requesting UI from us, let's fail since we have no UI
5399 if (aShowUI) {
5400 return false;
5403 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
5404 // this might add some ugly JS dependencies?
5406 nsAutoString adjustedValue;
5407 InternalCommandData commandData =
5408 ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
5409 switch (commandData.mCommand) {
5410 case Command::DoNothing:
5411 return false;
5412 case Command::SetDocumentReadOnly:
5413 SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly);
5414 break;
5415 case Command::EnableCompatibleJoinSplitNodeDirection:
5416 // We didn't allow to enable the legacy behavior once we've enabled the
5417 // new behavior by default. For keeping the behavior at supporting both
5418 // mode, we should keep returning `false` if the web app to enable the
5419 // legacy mode. Additionally, we don't support the legacy direction
5420 // anymore. Therefore, we can return `false` here even if the caller is
5421 // an addon or chrome script.
5422 if (!adjustedValue.EqualsLiteral("true")) {
5423 return false;
5425 break;
5426 default:
5427 break;
5430 AutoRunningExecCommandMarker markRunningExecCommand(*this,
5431 &aSubjectPrincipal);
5433 // If we're running an execCommand, we should just return false.
5434 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
5435 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
5436 !markRunningExecCommand.IsSafeToRun()) {
5437 return false;
5440 // Do security check first.
5441 if (commandData.IsCutOrCopyCommand()) {
5442 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
5443 // We have rejected the event due to it not being performed in an
5444 // input-driven context therefore, we report the error to the console.
5445 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
5446 this, nsContentUtils::eDOM_PROPERTIES,
5447 "ExecCommandCutCopyDeniedNotInputDriven");
5448 return false;
5450 } else if (commandData.IsPasteCommand()) {
5451 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
5452 nsGkAtoms::clipboardRead)) {
5453 return false;
5457 // Next, consider context of command handling which is automatically resolved
5458 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5459 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5460 if (commandData.IsAvailableOnlyWhenEditable() &&
5461 !editCommandTarget.IsEditable(this)) {
5462 return false;
5465 if (editCommandTarget.DoNothing()) {
5466 return false;
5469 // If we cannot use EditorCommand instance directly, we need to handle the
5470 // command with traditional path (i.e., with DocShell or nsCommandManager).
5471 if (!editCommandTarget.IsEditor()) {
5472 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
5474 // Special case clipboard write commands like Command::Cut and
5475 // Command::Copy. For such commands, we need the behaviour from
5476 // nsWindowRoot::GetControllers() which is to look at the focused element,
5477 // and defer to a focused textbox's controller. The code past taken by
5478 // other commands in ExecCommand() always uses the window directly, rather
5479 // than deferring to the textbox, which is desireable for most editor
5480 // commands, but not these commands (as those should allow copying out of
5481 // embedded editors). This behaviour is invoked if we call DoCommand()
5482 // directly on the docShell.
5483 // XXX This means that we allow web app to pick up selected content in
5484 // descendant document and write it into the clipboard when a
5485 // descendant document has focus. However, Chromium does not allow
5486 // this and this seems that it's not good behavior from point of view
5487 // of security. We should treat this issue in another bug.
5488 if (commandData.IsCutOrCopyCommand()) {
5489 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
5490 if (!docShell) {
5491 return false;
5493 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
5494 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
5495 return false;
5497 return NS_SUCCEEDED(rv);
5500 // Otherwise (currently, only clipboard read commands like Command::Paste),
5501 // we don't need to redirect the command to focused subdocument.
5502 // Therefore, we should handle it with nsCommandManager as used to be.
5503 // It may dispatch only preceding event of editing on non-editable element
5504 // to make web apps possible to handle standard shortcut key, etc in
5505 // their own editor.
5506 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5507 if (!commandManager) {
5508 return false;
5511 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5512 if (!window) {
5513 return false;
5516 // Return false for disabled commands (bug 760052)
5517 if (!commandManager->IsCommandEnabled(
5518 nsDependentCString(commandData.mXULCommandName), window)) {
5519 return false;
5522 MOZ_ASSERT(commandData.IsPasteCommand() ||
5523 commandData.mCommand == Command::SelectAll);
5524 nsresult rv =
5525 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
5526 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5529 // Now, our target is fixed to the editor. So, we can use EditorCommand
5530 // in EditorCommandTarget directly.
5532 EditorCommandParamType paramType =
5533 EditorCommand::GetParamType(commandData.mCommand);
5535 // If we don't have meaningful parameter or the EditorCommand does not
5536 // require additional parameter, we can use `DoCommand()`.
5537 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
5538 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
5539 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
5540 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5543 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
5544 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
5545 // a `bool` value.
5546 if (!!(paramType & EditorCommandParamType::Bool)) {
5547 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
5548 adjustedValue.EqualsLiteral("false"));
5549 nsresult rv = editCommandTarget.DoCommandParam(
5550 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
5551 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5554 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
5555 // in this case. However, `paramType` may contain both `String` and
5556 // `CString` but in such case, we should use `DoCommandParam()` which
5557 // takes `nsAString`. So, we should check whether `paramType` contains
5558 // `String` or not first.
5559 if (!!(paramType & EditorCommandParamType::String)) {
5560 MOZ_ASSERT(!adjustedValue.IsVoid());
5561 nsresult rv =
5562 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
5563 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5566 // Finally, `paramType` should have `CString`. We should use
5567 // `DoCommandParam()` which takes `nsACString`.
5568 if (!!(paramType & EditorCommandParamType::CString)) {
5569 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
5570 MOZ_ASSERT(!utf8Value.IsVoid());
5571 nsresult rv =
5572 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
5573 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5576 MOZ_ASSERT_UNREACHABLE(
5577 "Not yet implemented to handle new EditorCommandParamType");
5578 return false;
5581 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
5582 nsIPrincipal& aSubjectPrincipal,
5583 ErrorResult& aRv) {
5584 // Only allow on HTML documents.
5585 if (!IsHTMLOrXHTML()) {
5586 aRv.ThrowInvalidStateError(
5587 "queryCommandEnabled is only supported on HTML documents");
5588 return false;
5590 // Otherwise, don't throw exception for compatibility with Chrome.
5592 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5593 switch (commandData.mCommand) {
5594 case Command::DoNothing:
5595 return false;
5596 case Command::SetDocumentReadOnly:
5597 SetUseCounter(
5598 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5599 break;
5600 case Command::SetDocumentInsertBROnEnterKeyPress:
5601 SetUseCounter(
5602 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5603 break;
5604 default:
5605 break;
5608 // cut & copy are always allowed
5609 if (commandData.IsCutOrCopyCommand()) {
5610 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
5613 // Report false for restricted commands
5614 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
5615 return false;
5618 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5619 if (commandData.IsAvailableOnlyWhenEditable() &&
5620 !editCommandTarget.IsEditable(this)) {
5621 return false;
5624 if (editCommandTarget.IsEditor()) {
5625 return editCommandTarget.IsCommandEnabled();
5628 // get command manager and dispatch command to our window if it's acceptable
5629 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5630 if (!commandManager) {
5631 return false;
5634 nsPIDOMWindowOuter* window = GetWindow();
5635 if (!window) {
5636 return false;
5639 return commandManager->IsCommandEnabled(
5640 nsDependentCString(commandData.mXULCommandName), window);
5643 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5644 ErrorResult& aRv) {
5645 // Only allow on HTML documents.
5646 if (!IsHTMLOrXHTML()) {
5647 aRv.ThrowInvalidStateError(
5648 "queryCommandIndeterm is only supported on HTML documents");
5649 return false;
5651 // Otherwise, don't throw exception for compatibility with Chrome.
5653 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5654 if (commandData.mCommand == Command::DoNothing) {
5655 return false;
5658 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5659 if (commandData.IsAvailableOnlyWhenEditable() &&
5660 !editCommandTarget.IsEditable(this)) {
5661 return false;
5663 RefPtr<nsCommandParams> params = new nsCommandParams();
5664 if (editCommandTarget.IsEditor()) {
5665 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5666 return false;
5668 } else {
5669 // get command manager and dispatch command to our window if it's acceptable
5670 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5671 if (!commandManager) {
5672 return false;
5675 nsPIDOMWindowOuter* window = GetWindow();
5676 if (!window) {
5677 return false;
5680 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5681 window, params))) {
5682 return false;
5686 // If command does not have a state_mixed value, this call fails and sets
5687 // retval to false. This is fine -- we want to return false in that case
5688 // anyway (bug 738385), so we just don't throw regardless.
5689 return params->GetBool("state_mixed");
5692 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5693 ErrorResult& aRv) {
5694 // Only allow on HTML documents.
5695 if (!IsHTMLOrXHTML()) {
5696 aRv.ThrowInvalidStateError(
5697 "queryCommandState is only supported on HTML documents");
5698 return false;
5700 // Otherwise, don't throw exception for compatibility with Chrome.
5702 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5703 switch (commandData.mCommand) {
5704 case Command::DoNothing:
5705 return false;
5706 case Command::SetDocumentReadOnly:
5707 SetUseCounter(
5708 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5709 break;
5710 case Command::SetDocumentInsertBROnEnterKeyPress:
5711 SetUseCounter(
5712 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5713 break;
5714 default:
5715 break;
5718 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5719 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5720 // return false always.
5721 return false;
5724 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5725 if (commandData.IsAvailableOnlyWhenEditable() &&
5726 !editCommandTarget.IsEditable(this)) {
5727 return false;
5729 RefPtr<nsCommandParams> params = new nsCommandParams();
5730 if (editCommandTarget.IsEditor()) {
5731 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5732 return false;
5734 } else {
5735 // get command manager and dispatch command to our window if it's acceptable
5736 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5737 if (!commandManager) {
5738 return false;
5741 nsPIDOMWindowOuter* window = GetWindow();
5742 if (!window) {
5743 return false;
5746 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5747 window, params))) {
5748 return false;
5752 // handle alignment as a special case (possibly other commands too?)
5753 // Alignment is special because the external api is individual
5754 // commands but internally we use cmd_align with different
5755 // parameters. When getting the state of this command, we need to
5756 // return the boolean for this particular alignment rather than the
5757 // string of 'which alignment is this?'
5758 switch (commandData.mCommand) {
5759 case Command::FormatJustifyLeft: {
5760 nsAutoCString currentValue;
5761 nsresult rv = params->GetCString("state_attribute", currentValue);
5762 if (NS_FAILED(rv)) {
5763 return false;
5765 return currentValue.EqualsLiteral("left");
5767 case Command::FormatJustifyRight: {
5768 nsAutoCString currentValue;
5769 nsresult rv = params->GetCString("state_attribute", currentValue);
5770 if (NS_FAILED(rv)) {
5771 return false;
5773 return currentValue.EqualsLiteral("right");
5775 case Command::FormatJustifyCenter: {
5776 nsAutoCString currentValue;
5777 nsresult rv = params->GetCString("state_attribute", currentValue);
5778 if (NS_FAILED(rv)) {
5779 return false;
5781 return currentValue.EqualsLiteral("center");
5783 case Command::FormatJustifyFull: {
5784 nsAutoCString currentValue;
5785 nsresult rv = params->GetCString("state_attribute", currentValue);
5786 if (NS_FAILED(rv)) {
5787 return false;
5789 return currentValue.EqualsLiteral("justify");
5791 default:
5792 break;
5795 // If command does not have a state_all value, this call fails and sets
5796 // retval to false. This is fine -- we want to return false in that case
5797 // anyway (bug 738385), so we just succeed and return false regardless.
5798 return params->GetBool("state_all");
5801 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5802 CallerType aCallerType, ErrorResult& aRv) {
5803 // Only allow on HTML documents.
5804 if (!IsHTMLOrXHTML()) {
5805 aRv.ThrowInvalidStateError(
5806 "queryCommandSupported is only supported on HTML documents");
5807 return false;
5809 // Otherwise, don't throw exception for compatibility with Chrome.
5811 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5812 switch (commandData.mCommand) {
5813 case Command::DoNothing:
5814 return false;
5815 case Command::SetDocumentReadOnly:
5816 SetUseCounter(
5817 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly);
5818 break;
5819 case Command::SetDocumentInsertBROnEnterKeyPress:
5820 SetUseCounter(
5821 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5822 break;
5823 default:
5824 break;
5827 // Gecko technically supports all the clipboard commands including
5828 // cut/copy/paste, but non-privileged content will be unable to call
5829 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5830 // may also be disallowed to be called from non-privileged content.
5831 // For that reason, we report the support status of corresponding
5832 // command accordingly.
5833 if (aCallerType != CallerType::System) {
5834 if (commandData.IsPasteCommand()) {
5835 return false;
5837 if (commandData.IsCutOrCopyCommand() &&
5838 !StaticPrefs::dom_allow_cut_copy()) {
5839 // XXXbz should we worry about correctly reporting "true" in the
5840 // "restricted, but we're an addon with clipboardWrite permissions" case?
5841 // See also nsContentUtils::IsCutCopyAllowed.
5842 return false;
5846 // aHTMLCommandName is supported if it can be converted to a Midas command
5847 return true;
5850 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5851 nsAString& aValue, ErrorResult& aRv) {
5852 aValue.Truncate();
5854 // Only allow on HTML documents.
5855 if (!IsHTMLOrXHTML()) {
5856 aRv.ThrowInvalidStateError(
5857 "queryCommandValue is only supported on HTML documents");
5858 return;
5860 // Otherwise, don't throw exception for compatibility with Chrome.
5862 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5863 switch (commandData.mCommand) {
5864 case Command::DoNothing:
5865 // Return empty string
5866 return;
5867 case Command::SetDocumentReadOnly:
5868 SetUseCounter(
5869 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly);
5870 break;
5871 case Command::SetDocumentInsertBROnEnterKeyPress:
5872 SetUseCounter(
5873 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5874 break;
5875 default:
5876 break;
5879 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5880 if (commandData.IsAvailableOnlyWhenEditable() &&
5881 !editCommandTarget.IsEditable(this)) {
5882 return;
5884 RefPtr<nsCommandParams> params = new nsCommandParams();
5885 if (editCommandTarget.IsEditor()) {
5886 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5887 return;
5890 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5891 return;
5893 } else {
5894 // get command manager and dispatch command to our window if it's acceptable
5895 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5896 if (!commandManager) {
5897 return;
5900 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5901 if (!window) {
5902 return;
5905 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5906 return;
5909 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5910 window, params))) {
5911 return;
5915 // If command does not have a state_attribute value, this call fails, and
5916 // aValue will wind up being the empty string. This is fine -- we want to
5917 // return "" in that case anyway (bug 738385), so we just return NS_OK
5918 // regardless.
5919 nsAutoCString result;
5920 params->GetCString("state_attribute", result);
5921 CopyUTF8toUTF16(result, aValue);
5924 void Document::MaybeEditingStateChanged() {
5925 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
5926 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
5927 if (nsContentUtils::IsSafeToRunScript()) {
5928 EditingStateChanged();
5929 } else if (!mInDestructor) {
5930 nsContentUtils::AddScriptRunner(
5931 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
5932 &Document::MaybeEditingStateChanged));
5937 void Document::NotifyFetchOrXHRSuccess() {
5938 if (mShouldNotifyFetchSuccess) {
5939 nsContentUtils::DispatchEventOnlyToChrome(
5940 this, this, u"DOMDocFetchSuccess"_ns, CanBubble::eNo, Cancelable::eNo,
5941 /* DefaultAction */ nullptr);
5945 void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
5946 mShouldNotifyFetchSuccess = aShouldNotify;
5949 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
5950 mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
5953 void Document::TearingDownEditor() {
5954 if (IsEditingOn()) {
5955 mEditingState = EditingState::eTearingDown;
5956 if (IsHTMLOrXHTML()) {
5957 RemoveContentEditableStyleSheets();
5962 nsresult Document::TurnEditingOff() {
5963 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
5965 nsPIDOMWindowOuter* window = GetWindow();
5966 if (!window) {
5967 return NS_ERROR_FAILURE;
5970 nsIDocShell* docshell = window->GetDocShell();
5971 if (!docshell) {
5972 return NS_ERROR_FAILURE;
5975 bool isBeingDestroyed = false;
5976 docshell->IsBeingDestroyed(&isBeingDestroyed);
5977 if (isBeingDestroyed) {
5978 return NS_ERROR_FAILURE;
5981 nsCOMPtr<nsIEditingSession> editSession;
5982 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5983 NS_ENSURE_SUCCESS(rv, rv);
5985 // turn editing off
5986 rv = editSession->TearDownEditorOnWindow(window);
5987 NS_ENSURE_SUCCESS(rv, rv);
5989 mEditingState = EditingState::eOff;
5991 // Editor resets selection since it is being destroyed. But if focus is
5992 // still into editable control, we have to initialize selection again.
5993 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
5994 if (RefPtr<TextControlElement> textControlElement =
5995 TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
5996 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
5997 textEditor->ReinitializeSelection(*textControlElement);
6002 return NS_OK;
6005 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
6006 nsIDocShell* docShell = aWindow->GetDocShell();
6007 if (!docShell) {
6008 return false;
6010 return docShell->GetPresShell() != nullptr;
6013 HTMLEditor* Document::GetHTMLEditor() const {
6014 nsPIDOMWindowOuter* window = GetWindow();
6015 if (!window) {
6016 return nullptr;
6019 nsIDocShell* docshell = window->GetDocShell();
6020 if (!docshell) {
6021 return nullptr;
6024 return docshell->GetHTMLEditor();
6027 nsresult Document::EditingStateChanged() {
6028 if (mRemovedFromDocShell) {
6029 return NS_OK;
6032 if (mEditingState == EditingState::eSettingUp ||
6033 mEditingState == EditingState::eTearingDown) {
6034 // XXX We shouldn't recurse
6035 return NS_OK;
6038 const bool designMode = IsInDesignMode();
6039 EditingState newState =
6040 designMode ? EditingState::eDesignMode
6041 : (mContentEditableCount > 0 ? EditingState::eContentEditable
6042 : EditingState::eOff);
6043 if (mEditingState == newState) {
6044 // No changes in editing mode.
6045 return NS_OK;
6048 const bool thisDocumentHasFocus = ThisDocumentHasFocus();
6049 if (newState == EditingState::eOff) {
6050 // Editing is being turned off.
6051 nsAutoScriptBlocker scriptBlocker;
6052 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6053 NotifyEditableStateChange(*this);
6054 nsresult rv = TurnEditingOff();
6055 // If this document has focus and the editing state of this document
6056 // becomes "off", it means that HTMLEditor won't handle any inputs nor
6057 // modify the DOM tree. However, HTMLEditor may not receive `blur`
6058 // event for this state change since this may occur without focus change.
6059 // Therefore, let's notify HTMLEditor of this editing state change.
6060 // Note that even if focusedElement is an editable text control element,
6061 // it becomes not editable from HTMLEditor point of view since text
6062 // control elements are manged by TextEditor.
6063 RefPtr<Element> focusedElement =
6064 nsFocusManager::GetFocusManager()
6065 ? nsFocusManager::GetFocusManager()->GetFocusedElement()
6066 : nullptr;
6067 DebugOnly<nsresult> rvIgnored =
6068 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6069 htmlEditor, *this, focusedElement);
6070 NS_WARNING_ASSERTION(
6071 NS_SUCCEEDED(rvIgnored),
6072 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but "
6073 "ignored");
6074 return rv;
6077 // Flush out style changes on our _parent_ document, if any, so that
6078 // our check for a presshell won't get stale information.
6079 if (mParentDocument) {
6080 mParentDocument->FlushPendingNotifications(FlushType::Style);
6083 // get editing session, make sure this is a strong reference so the
6084 // window can't get deleted during the rest of this call.
6085 const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6086 if (!window) {
6087 return NS_ERROR_FAILURE;
6090 nsIDocShell* docshell = window->GetDocShell();
6091 if (!docshell) {
6092 return NS_ERROR_FAILURE;
6095 // FlushPendingNotifications might destroy our docshell.
6096 bool isBeingDestroyed = false;
6097 docshell->IsBeingDestroyed(&isBeingDestroyed);
6098 if (isBeingDestroyed) {
6099 return NS_ERROR_FAILURE;
6102 nsCOMPtr<nsIEditingSession> editSession;
6103 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6104 NS_ENSURE_SUCCESS(rv, rv);
6106 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
6107 if (htmlEditor) {
6108 // We might already have an editor if it was set up for mail, let's see
6109 // if this is actually the case.
6110 uint32_t flags = 0;
6111 htmlEditor->GetFlags(&flags);
6112 if (flags & nsIEditor::eEditorMailMask) {
6113 // We already have a mail editor, then we should not attempt to create
6114 // another one.
6115 return NS_OK;
6119 if (!HasPresShell(window)) {
6120 // We should not make the window editable or setup its editor.
6121 // It's probably style=display:none.
6122 return NS_OK;
6125 bool makeWindowEditable = mEditingState == EditingState::eOff;
6126 bool spellRecheckAll = false;
6127 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
6128 htmlEditor = nullptr;
6131 EditingState oldState = mEditingState;
6132 nsAutoEditingState push(this, EditingState::eSettingUp);
6134 RefPtr<PresShell> presShell = GetPresShell();
6135 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
6137 // If we're entering the design mode from non-editable state, put the
6138 // selection at the beginning of the document for compatibility reasons.
6139 bool collapseSelectionAtBeginningOfDocument =
6140 designMode && oldState == EditingState::eOff;
6141 // However, mEditingState may be eOff even if there is some
6142 // `contenteditable` area and selection has been initialized for it because
6143 // mEditingState for `contenteditable` may have been scheduled to modify
6144 // when safe. In such case, we should not reinitialize selection.
6145 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
6146 Selection* selection =
6147 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
6148 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
6149 if (selection && selection->RangeCount()) {
6150 // Perhaps, we don't need to check whether the selection is in
6151 // an editing host or not because all contents will be editable
6152 // in designMode. (And we don't want to make this code so complicated
6153 // because of legacy API.)
6154 collapseSelectionAtBeginningOfDocument = false;
6158 MOZ_ASSERT(mStyleSetFilled);
6160 // Before making this window editable, we need to modify UA style sheet
6161 // because new style may change whether focused element will be focusable
6162 // or not.
6163 if (IsHTMLOrXHTML()) {
6164 AddContentEditableStyleSheetsToStyleSet(designMode);
6167 if (designMode) {
6168 // designMode is being turned on (overrides contentEditable).
6169 spellRecheckAll = oldState == EditingState::eContentEditable;
6172 // Adjust focused element with new style but blur event shouldn't be fired
6173 // until mEditingState is modified with newState.
6174 nsAutoScriptBlocker scriptBlocker;
6175 if (designMode) {
6176 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6177 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6178 window, nsFocusManager::eOnlyCurrentWindow,
6179 getter_AddRefs(focusedWindow));
6180 if (focusedContent) {
6181 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
6182 bool clearFocus = focusedFrame
6183 ? !focusedFrame->IsFocusable()
6184 : !focusedContent->IsFocusableWithoutStyle();
6185 if (clearFocus) {
6186 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
6187 fm->ClearFocus(window);
6188 // If we need to dispatch blur event, we should put off after
6189 // modifying mEditingState since blur event handler may change
6190 // designMode state again.
6191 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
6197 if (makeWindowEditable) {
6198 // Editing is being turned on (through designMode or contentEditable)
6199 // Turn on editor.
6200 // XXX This can cause flushing which can change the editing state, so make
6201 // sure to avoid recursing.
6202 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
6203 NS_ENSURE_SUCCESS(rv, rv);
6206 // XXX Need to call TearDownEditorOnWindow for all failures.
6207 htmlEditor = docshell->GetHTMLEditor();
6208 if (!htmlEditor) {
6209 // Return NS_OK even though we've failed to create an editor here. This
6210 // is so that the setter of designMode on non-HTML documents does not
6211 // fail.
6212 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
6213 // would detect that we can't support the mimetype if appropriate and
6214 // would fall onto the eEditorErrorCantEditMimeType path.
6215 return NS_OK;
6218 if (collapseSelectionAtBeginningOfDocument) {
6219 htmlEditor->BeginningOfDocument();
6222 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6223 nsContentUtils::AddScriptBlocker();
6227 mEditingState = newState;
6228 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6229 nsContentUtils::RemoveScriptBlocker();
6230 // If mEditingState is overwritten by another call and already disabled
6231 // the editing, we shouldn't keep making window editable.
6232 if (mEditingState == EditingState::eOff) {
6233 return NS_OK;
6237 if (makeWindowEditable) {
6238 // TODO: We should do this earlier in this method.
6239 // Previously, we called `ExecCommand` with `insertBrOnReturn` command
6240 // whose argument is false here. Then, if it returns error, we
6241 // stopped making it editable. However, after bug 1697078 fixed,
6242 // `ExecCommand` returns error only when the document is not XHTML's
6243 // nor HTML's. Therefore, we use same error handling for now.
6244 if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
6245 // Editor setup failed. Editing is not on after all.
6246 // XXX Should we reset the editable flag on nodes?
6247 editSession->TearDownEditorOnWindow(window);
6248 mEditingState = EditingState::eOff;
6249 return NS_ERROR_DOM_INVALID_STATE_ERR;
6251 // Set the editor to not insert <br> elements on return when in <p> elements
6252 // by default.
6253 htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
6256 // Resync the editor's spellcheck state.
6257 if (spellRecheckAll) {
6258 nsCOMPtr<nsISelectionController> selectionController =
6259 htmlEditor->GetSelectionController();
6260 if (NS_WARN_IF(!selectionController)) {
6261 return NS_ERROR_FAILURE;
6264 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
6265 nsISelectionController::SELECTION_SPELLCHECK);
6266 if (spellCheckSelection) {
6267 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
6270 htmlEditor->SyncRealTimeSpell();
6272 MaybeDispatchCheckKeyPressEventModelEvent();
6274 // If this document keeps having focus and the HTMLEditor is in the design
6275 // mode, it may not receive `focus` event for this editing state change since
6276 // this may occur without a focus change. Therefore, let's notify HTMLEditor
6277 // of this editing state change.
6278 if (thisDocumentHasFocus && htmlEditor->IsInDesignMode() &&
6279 ThisDocumentHasFocus()) {
6280 DebugOnly<nsresult> rvIgnored =
6281 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, nullptr);
6282 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6283 "HTMLEditor::FocusedElementOrDocumentBecomesEditable()"
6284 " failed, but ignored");
6287 return NS_OK;
6290 // Helper class, used below in ChangeContentEditableCount().
6291 class DeferredContentEditableCountChangeEvent : public Runnable {
6292 public:
6293 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
6294 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
6295 mDoc(aDoc),
6296 mElement(aElement) {}
6298 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
6299 if (mElement && mElement->OwnerDoc() == mDoc) {
6300 RefPtr<Document> doc = std::move(mDoc);
6301 RefPtr<Element> element = std::move(mElement);
6302 doc->DeferredContentEditableCountChange(element);
6304 return NS_OK;
6307 private:
6308 RefPtr<Document> mDoc;
6309 RefPtr<Element> mElement;
6312 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
6313 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
6314 "Trying to decrement too much.");
6316 mContentEditableCount += aChange;
6318 if (aElement) {
6319 nsContentUtils::AddScriptRunner(
6320 new DeferredContentEditableCountChangeEvent(this, aElement));
6324 void Document::DeferredContentEditableCountChange(Element* aElement) {
6325 const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
6326 const bool elementHasFocus =
6327 aElement && fm && fm->GetFocusedElement() == aElement;
6328 if (elementHasFocus) {
6329 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
6330 // When contenteditable of aElement is changed and HTMLEditor works with it
6331 // or needs to start working with it, HTMLEditor may not receive `focus`
6332 // event nor `blur` event because this may occur without a focus change.
6333 // Therefore, we need to notify HTMLEditor of this contenteditable attribute
6334 // change.
6335 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
6336 if (aElement->HasFlag(NODE_IS_EDITABLE)) {
6337 if (htmlEditor) {
6338 DebugOnly<nsresult> rvIgnored =
6339 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this,
6340 aElement);
6341 NS_WARNING_ASSERTION(
6342 NS_SUCCEEDED(rvIgnored),
6343 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6344 "ignored");
6346 } else {
6347 DebugOnly<nsresult> rvIgnored =
6348 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
6349 htmlEditor, *this, aElement);
6350 NS_WARNING_ASSERTION(
6351 NS_SUCCEEDED(rvIgnored),
6352 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, "
6353 "but ignored");
6357 if (mParser ||
6358 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
6359 return;
6362 EditingState oldState = mEditingState;
6364 nsresult rv = EditingStateChanged();
6365 NS_ENSURE_SUCCESS_VOID(rv);
6367 if (oldState == mEditingState &&
6368 mEditingState == EditingState::eContentEditable) {
6369 // We just changed the contentEditable state of a node, we need to reset
6370 // the spellchecking state of that node.
6371 if (aElement) {
6372 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6373 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
6374 rv = htmlEditor->GetInlineSpellChecker(false,
6375 getter_AddRefs(spellChecker));
6376 NS_ENSURE_SUCCESS_VOID(rv);
6378 if (spellChecker &&
6379 aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) {
6380 RefPtr<nsRange> range = nsRange::Create(aElement);
6381 IgnoredErrorResult res;
6382 range->SelectNode(*aElement, res);
6383 if (res.Failed()) {
6384 // The node might be detached from the document at this point,
6385 // which would cause this call to fail. In this case, we can
6386 // safely ignore the contenteditable count change.
6387 return;
6390 rv = spellChecker->SpellCheckRange(range);
6391 NS_ENSURE_SUCCESS_VOID(rv);
6397 // aElement causes creating new HTMLEditor and the element had and keep
6398 // having focus, the HTMLEditor won't receive `focus` event. Therefore, we
6399 // need to notify HTMLEditor of it becomes editable.
6400 if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) &&
6401 fm->GetFocusedElement() == aElement) {
6402 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
6403 DebugOnly<nsresult> rvIgnored =
6404 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement);
6405 NS_WARNING_ASSERTION(
6406 NS_SUCCEEDED(rvIgnored),
6407 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but "
6408 "ignored");
6413 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
6414 // Currently, we need to check only when we're becoming editable for
6415 // contenteditable.
6416 if (mEditingState != EditingState::eContentEditable) {
6417 return;
6420 if (mHasBeenEditable) {
6421 return;
6423 mHasBeenEditable = true;
6425 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
6426 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
6427 // with proper keypress event for the active web app.
6428 WidgetEvent checkEvent(true, eUnidentifiedEvent);
6429 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
6430 checkEvent.mFlags.mCancelable = false;
6431 checkEvent.mFlags.mBubbles = false;
6432 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
6433 // Post the event rather than dispatching it synchronously because we need
6434 // a call of SetKeyPressEventModel() before first key input. Therefore, we
6435 // can avoid paying unnecessary runtime cost for most web apps.
6436 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
6439 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
6440 PresShell* presShell = GetPresShell();
6441 if (!presShell) {
6442 return;
6444 presShell->SetKeyPressEventModel(aKeyPressEventModel);
6447 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
6449 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
6450 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
6451 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
6452 aFocusTime >= mLastFocusTime);
6453 mLastFocusTime = aFocusTime;
6456 void Document::GetReferrer(nsAString& aReferrer) const {
6457 aReferrer.Truncate();
6458 if (!mReferrerInfo) {
6459 return;
6462 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
6463 if (!referrer) {
6464 return;
6467 nsAutoCString uri;
6468 nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
6469 if (NS_WARN_IF(NS_FAILED(rv))) {
6470 return;
6473 CopyUTF8toUTF16(uri, aReferrer);
6476 void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
6477 aCookie.Truncate(); // clear current cookie in case service fails;
6478 // no cookie isn't an error condition.
6480 if (mDisableCookieAccess) {
6481 return;
6484 // If the document's sandboxed origin flag is set, then reading cookies
6485 // is prohibited.
6486 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6487 aRv.ThrowSecurityError(
6488 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6489 "flag.");
6490 return;
6493 StorageAccess storageAccess = CookieAllowedForDocument(this);
6494 if (storageAccess == StorageAccess::eDeny) {
6495 return;
6498 if (ShouldPartitionStorage(storageAccess) &&
6499 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6500 return;
6503 // If the document is a cookie-averse Document... return the empty string.
6504 if (IsCookieAverse()) {
6505 return;
6508 // not having a cookie service isn't an error
6509 nsCOMPtr<nsICookieService> service =
6510 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6511 if (service) {
6512 nsAutoCString cookie;
6513 service->GetCookieStringFromDocument(this, cookie);
6514 // CopyUTF8toUTF16 doesn't handle error
6515 // because it assumes that the input is valid.
6516 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
6520 void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
6521 if (mDisableCookieAccess) {
6522 return;
6525 // If the document's sandboxed origin flag is set, then setting cookies
6526 // is prohibited.
6527 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6528 aRv.ThrowSecurityError(
6529 "Forbidden in a sandboxed document without the 'allow-same-origin' "
6530 "flag.");
6531 return;
6534 StorageAccess storageAccess = CookieAllowedForDocument(this);
6535 if (storageAccess == StorageAccess::eDeny) {
6536 return;
6539 if (ShouldPartitionStorage(storageAccess) &&
6540 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6541 return;
6544 // If the document is a cookie-averse Document... do nothing.
6545 if (IsCookieAverse()) {
6546 return;
6549 if (!mDocumentURI) {
6550 return;
6553 // not having a cookie service isn't an error
6554 nsCOMPtr<nsICookieService> service =
6555 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6556 if (!service) {
6557 return;
6560 NS_ConvertUTF16toUTF8 cookie(aCookie);
6561 nsresult rv = service->SetCookieStringFromDocument(this, cookie);
6563 // No warning messages here.
6564 if (NS_FAILED(rv)) {
6565 return;
6568 nsCOMPtr<nsIObserverService> observerService =
6569 mozilla::services::GetObserverService();
6570 if (observerService) {
6571 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
6572 nsString(aCookie).get());
6576 ReferrerPolicy Document::GetReferrerPolicy() const {
6577 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
6578 : ReferrerPolicy::_empty;
6581 void Document::GetAlinkColor(nsAString& aAlinkColor) {
6582 aAlinkColor.Truncate();
6584 HTMLBodyElement* body = GetBodyElement();
6585 if (body) {
6586 body->GetALink(aAlinkColor);
6590 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
6591 HTMLBodyElement* body = GetBodyElement();
6592 if (body) {
6593 body->SetALink(aAlinkColor);
6597 void Document::GetLinkColor(nsAString& aLinkColor) {
6598 aLinkColor.Truncate();
6600 HTMLBodyElement* body = GetBodyElement();
6601 if (body) {
6602 body->GetLink(aLinkColor);
6606 void Document::SetLinkColor(const nsAString& aLinkColor) {
6607 HTMLBodyElement* body = GetBodyElement();
6608 if (body) {
6609 body->SetLink(aLinkColor);
6613 void Document::GetVlinkColor(nsAString& aVlinkColor) {
6614 aVlinkColor.Truncate();
6616 HTMLBodyElement* body = GetBodyElement();
6617 if (body) {
6618 body->GetVLink(aVlinkColor);
6622 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
6623 HTMLBodyElement* body = GetBodyElement();
6624 if (body) {
6625 body->SetVLink(aVlinkColor);
6629 void Document::GetBgColor(nsAString& aBgColor) {
6630 aBgColor.Truncate();
6632 HTMLBodyElement* body = GetBodyElement();
6633 if (body) {
6634 body->GetBgColor(aBgColor);
6638 void Document::SetBgColor(const nsAString& aBgColor) {
6639 HTMLBodyElement* body = GetBodyElement();
6640 if (body) {
6641 body->SetBgColor(aBgColor);
6645 void Document::GetFgColor(nsAString& aFgColor) {
6646 aFgColor.Truncate();
6648 HTMLBodyElement* body = GetBodyElement();
6649 if (body) {
6650 body->GetText(aFgColor);
6654 void Document::SetFgColor(const nsAString& aFgColor) {
6655 HTMLBodyElement* body = GetBodyElement();
6656 if (body) {
6657 body->SetText(aFgColor);
6661 void Document::CaptureEvents() {
6662 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
6665 void Document::ReleaseEvents() {
6666 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
6669 HTMLAllCollection* Document::All() {
6670 if (!mAll) {
6671 mAll = new HTMLAllCollection(this);
6673 return mAll;
6676 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
6677 if (mIsSrcdocDocument) {
6678 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
6679 if (inStrmChan) {
6680 return inStrmChan->GetSrcdocData(aSrcdocData);
6683 aSrcdocData = VoidString();
6684 return NS_OK;
6687 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
6688 nsPIDOMWindowOuter* win = GetWindow();
6689 if (!win) {
6690 return nullptr;
6692 return WindowProxyHolder(win->GetBrowsingContext());
6695 nsIContent* Document::GetUnretargetedFocusedContent(
6696 IncludeChromeOnly aIncludeChromeOnly) const {
6697 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6698 if (!window) {
6699 return nullptr;
6701 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6702 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6703 window, nsFocusManager::eOnlyCurrentWindow,
6704 getter_AddRefs(focusedWindow));
6705 if (!focusedContent) {
6706 return nullptr;
6708 // be safe and make sure the element is from this document
6709 if (focusedContent->OwnerDoc() != this) {
6710 return nullptr;
6712 if (focusedContent->ChromeOnlyAccess() &&
6713 aIncludeChromeOnly == IncludeChromeOnly::No) {
6714 return focusedContent->FindFirstNonChromeOnlyAccessContent();
6716 return focusedContent;
6719 Element* Document::GetActiveElement() {
6720 // Get the focused element.
6721 Element* focusedElement = GetRetargetedFocusedElement();
6722 if (focusedElement) {
6723 return focusedElement;
6726 // No focused element anywhere in this document. Try to get the BODY.
6727 if (IsHTMLOrXHTML()) {
6728 Element* bodyElement = AsHTMLDocument()->GetBody();
6729 if (bodyElement) {
6730 return bodyElement;
6732 // Special case to handle the transition to XHTML from XUL documents
6733 // where there currently isn't a body element, but we need to match the
6734 // XUL behavior. This should be removed when bug 1540278 is resolved.
6735 if (nsContentUtils::IsChromeDoc(this)) {
6736 Element* docElement = GetDocumentElement();
6737 if (docElement && docElement->IsXULElement()) {
6738 return docElement;
6741 // Because of IE compatibility, return null when html document doesn't have
6742 // a body.
6743 return nullptr;
6746 // If we couldn't get a BODY, return the root element.
6747 return GetDocumentElement();
6750 Element* Document::GetCurrentScript() {
6751 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
6752 return el;
6755 void Document::ReleaseCapture() const {
6756 // only release the capture if the caller can access it. This prevents a
6757 // page from stopping a scrollbar grab for example.
6758 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
6759 if (node && nsContentUtils::CanCallerAccess(node)) {
6760 PresShell::ReleaseCapturingContent();
6764 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
6765 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
6766 return mChromeXHRDocBaseURI;
6769 return GetDocBaseURI();
6772 void Document::SetBaseURI(nsIURI* aURI) {
6773 if (!aURI && !mDocumentBaseURI) {
6774 return;
6777 // Don't do anything if the URI wasn't actually changed.
6778 if (aURI && mDocumentBaseURI) {
6779 bool equalBases = false;
6780 mDocumentBaseURI->Equals(aURI, &equalBases);
6781 if (equalBases) {
6782 return;
6786 mDocumentBaseURI = aURI;
6787 mCachedURLData = nullptr;
6788 RefreshLinkHrefs();
6791 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
6792 const nsAString& aURI) {
6793 RefPtr<nsIURI> resolvedURI;
6794 MOZ_TRY(
6795 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
6796 return OwningNonNull<nsIURI>(std::move(resolvedURI));
6799 nsIReferrerInfo* Document::ReferrerInfoForInternalCSSAndSVGResources() {
6800 if (!mCachedReferrerInfoForInternalCSSAndSVGResources) {
6801 mCachedReferrerInfoForInternalCSSAndSVGResources =
6802 ReferrerInfo::CreateForInternalCSSAndSVGResources(this);
6804 return mCachedReferrerInfoForInternalCSSAndSVGResources;
6807 URLExtraData* Document::DefaultStyleAttrURLData() {
6808 MOZ_ASSERT(NS_IsMainThread());
6809 if (!mCachedURLData) {
6810 mCachedURLData = new URLExtraData(
6811 GetDocBaseURI(), ReferrerInfoForInternalCSSAndSVGResources(),
6812 NodePrincipal());
6814 return mCachedURLData;
6817 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
6818 if (mCharacterSet != aEncoding) {
6819 mCharacterSet = aEncoding;
6820 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
6821 RecomputeLanguageFromCharset();
6823 if (nsPresContext* context = GetPresContext()) {
6824 context->DocumentCharSetChanged(aEncoding);
6829 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
6830 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
6833 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
6834 aData.Truncate();
6835 const HeaderData* data = mHeaderData.get();
6836 while (data) {
6837 if (data->mField == aHeaderField) {
6838 aData = data->mData;
6839 break;
6841 data = data->mNext.get();
6845 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
6846 if (!aHeaderField) {
6847 NS_ERROR("null headerField");
6848 return;
6851 if (!mHeaderData) {
6852 if (!aData.IsEmpty()) { // don't bother storing empty string
6853 mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
6855 } else {
6856 HeaderData* data = mHeaderData.get();
6857 UniquePtr<HeaderData>* lastPtr = &mHeaderData;
6858 bool found = false;
6859 do { // look for existing and replace
6860 if (data->mField == aHeaderField) {
6861 if (!aData.IsEmpty()) {
6862 data->mData.Assign(aData);
6863 } else { // don't store empty string
6864 // Note that data->mNext is moved to a temporary before the old value
6865 // of *lastPtr is deleted.
6866 *lastPtr = std::move(data->mNext);
6868 found = true;
6870 break;
6872 lastPtr = &data->mNext;
6873 data = lastPtr->get();
6874 } while (data);
6876 if (!aData.IsEmpty() && !found) {
6877 // didn't find, append
6878 *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
6882 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
6883 if (aData.IsEmpty()) {
6884 mContentLanguage = nullptr;
6885 } else {
6886 mContentLanguage = NS_AtomizeMainThread(aData);
6888 mMayNeedFontPrefsUpdate = true;
6889 if (auto* presContext = GetPresContext()) {
6890 presContext->ContentLanguageChanged();
6894 if (aHeaderField == nsGkAtoms::origin_trial) {
6895 mTrials.UpdateFromToken(aData, NodePrincipal());
6896 if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) {
6897 InitCOEP(mChannel);
6899 // If we still don't have a WindowContext, WindowContext::OnNewDocument
6900 // will take care of this.
6901 if (WindowContext* ctx = GetWindowContext()) {
6902 if (mEmbedderPolicy) {
6903 Unused << ctx->SetEmbedderPolicy(mEmbedderPolicy.value());
6909 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
6910 SetPreferredStyleSheetSet(aData);
6913 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
6914 // We get into this code before we have a script global yet, so get to our
6915 // container via mDocumentContainer.
6916 if (mDocumentContainer) {
6917 // Note: using mDocumentURI instead of mBaseURI here, for consistency
6918 // (used to just use the current URI of our webnavigation, but that
6919 // should really be the same thing). Note that this code can run
6920 // before the current URI of the webnavigation has been updated, so we
6921 // can't assert equality here.
6922 mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
6926 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
6927 mAllowDNSPrefetch) {
6928 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
6929 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
6932 if (aHeaderField == nsGkAtoms::handheldFriendly) {
6933 mViewportType = Unknown;
6937 void Document::SetEarlyHints(
6938 nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) {
6939 mEarlyHints = std::move(aEarlyHints);
6942 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
6943 NotNull<const Encoding*>& aEncoding,
6944 nsHtml5TreeOpExecutor* aExecutor) {
6945 if (aChannel) {
6946 nsAutoCString charsetVal;
6947 nsresult rv = aChannel->GetContentCharset(charsetVal);
6948 if (NS_SUCCEEDED(rv)) {
6949 const Encoding* preferred = Encoding::ForLabel(charsetVal);
6950 if (preferred) {
6951 if (aExecutor && preferred == REPLACEMENT_ENCODING) {
6952 aExecutor->ComplainAboutBogusProtocolCharset(this, false);
6954 aEncoding = WrapNotNull(preferred);
6955 aCharsetSource = kCharsetFromChannel;
6956 return;
6957 } else if (aExecutor && !charsetVal.IsEmpty()) {
6958 aExecutor->ComplainAboutBogusProtocolCharset(this, true);
6964 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
6965 #ifdef DEBUG
6966 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
6967 const Element* element = Element::FromNode(node);
6968 if (!element) {
6969 continue;
6971 MOZ_ASSERT(!element->HasServoData());
6973 #endif
6976 already_AddRefed<PresShell> Document::CreatePresShell(
6977 nsPresContext* aContext, nsViewManager* aViewManager) {
6978 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
6980 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
6982 AssertNoStaleServoDataIn(*this);
6984 RefPtr<PresShell> presShell = new PresShell(this);
6985 // Note: we don't hold a ref to the shell (it holds a ref to us)
6986 mPresShell = presShell;
6988 if (!mStyleSetFilled) {
6989 FillStyleSet();
6992 presShell->Init(aContext, aViewManager);
6993 if (RefPtr<class HighlightRegistry> highlightRegistry = mHighlightRegistry) {
6994 highlightRegistry->AddHighlightSelectionsToFrameSelection();
6996 // Gaining a shell causes changes in how media queries are evaluated, so
6997 // invalidate that.
6998 aContext->MediaFeatureValuesChanged(
6999 {MediaFeatureChange::kAllChanges},
7000 MediaFeatureChangePropagation::JustThisDocument);
7002 // Make sure to never paint if we belong to an invisible DocShell.
7003 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7004 if (docShell && docShell->IsInvisible()) {
7005 presShell->SetNeverPainting(true);
7008 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
7009 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
7010 presShell.get(), docShell.get()));
7012 mExternalResourceMap.ShowViewers();
7014 UpdateFrameRequestCallbackSchedulingState();
7016 if (mDocumentL10n) {
7017 // In case we already accumulated mutations,
7018 // we'll trigger the refresh driver now.
7019 mDocumentL10n->OnCreatePresShell();
7022 if (HasAutoFocusCandidates()) {
7023 ScheduleFlushAutoFocusCandidates();
7025 // Now that we have a shell, we might have @font-face rules (the presence of a
7026 // shell may change which rules apply to us). We don't need to do anything
7027 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
7028 // is ready to update we'll flush the font set.
7029 MarkUserFontSetDirty();
7031 // Take the author style disabled state from the top browsing cvontext.
7032 // (PageStyleChild.sys.mjs ensures this is up to date.)
7033 if (BrowsingContext* bc = GetBrowsingContext()) {
7034 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
7037 return presShell.forget();
7040 void Document::UpdateFrameRequestCallbackSchedulingState(
7041 PresShell* aOldPresShell) {
7042 // If this condition changes to depend on some other variable, make sure to
7043 // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
7044 // that variable can change. Also consider if you should change
7045 // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
7046 // condition.
7047 bool shouldBeScheduled =
7048 WouldScheduleFrameRequestCallbacks() && !mFrameRequestManager.IsEmpty();
7049 if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
7050 // nothing to do
7051 return;
7054 PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
7055 MOZ_RELEASE_ASSERT(presShell);
7057 nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
7058 if (shouldBeScheduled) {
7059 rd->ScheduleFrameRequestCallbacks(this);
7060 } else {
7061 rd->RevokeFrameRequestCallbacks(this);
7064 mFrameRequestCallbacksScheduled = shouldBeScheduled;
7067 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
7068 MOZ_ASSERT(aCallbacks.IsEmpty());
7069 mFrameRequestManager.Take(aCallbacks);
7070 // No need to manually remove ourselves from the refresh driver; it will
7071 // handle that part. But we do have to update our state.
7072 mFrameRequestCallbacksScheduled = false;
7075 bool Document::ShouldThrottleFrameRequests() const {
7076 if (mStaticCloneCount > 0) {
7077 // Even if we're not visible, a static clone may be, so run at full speed.
7078 return false;
7081 if (Hidden()) {
7082 // We're not visible (probably in a background tab or the bf cache).
7083 return true;
7086 if (!mPresShell) {
7087 // Can't do anything smarter. We don't run frame requests in documents
7088 // without a pres shell anyways.
7089 return false;
7092 if (!mPresShell->IsActive()) {
7093 // The pres shell is not active (we're an invisible OOP iframe or such), so
7094 // throttle.
7095 return true;
7098 if (mPresShell->IsPaintingSuppressed()) {
7099 // Historically we have throttled frame requests until we've painted at
7100 // least once, so keep doing that.
7101 return true;
7104 if (mPresShell->IsUnderHiddenEmbedderElement()) {
7105 // For display: none and visibility: hidden we always throttle, for
7106 // consistency with OOP iframes.
7107 return true;
7110 Element* el = GetEmbedderElement();
7111 if (!el) {
7112 // If we're not in-process, our refresh driver is throttled separately (via
7113 // PresShell::SetIsActive, so not much more we can do here.
7114 return false;
7117 if (!StaticPrefs::layout_throttle_in_process_iframes()) {
7118 return false;
7121 // Note that because we have to scroll this document into view at least once
7122 // to unthrottle it, we will drop one requestAnimationFrame frame when a
7123 // document that previously wasn't visible scrolls into view. This is
7124 // acceptable / unlikely to be human-perceivable, though we could improve on
7125 // it if needed by adding an intersection margin or something of that sort.
7126 const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
7127 *el->OwnerDoc(), /* aRoot = */ nullptr, /* aRootMargin = */ nullptr);
7128 const IntersectionOutput output =
7129 DOMIntersectionObserver::Intersect(input, *el);
7130 return !output.Intersects();
7133 void Document::DeletePresShell() {
7134 mExternalResourceMap.HideViewers();
7135 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
7136 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
7137 presContext->RefreshDriver()->CancelFlushAutoFocus(this);
7140 // When our shell goes away, request that all our images be immediately
7141 // discarded, so we don't carry around decoded image data for a document we
7142 // no longer intend to paint.
7143 ImageTracker()->RequestDiscardAll();
7145 // Now that we no longer have a shell, we need to forget about any FontFace
7146 // objects for @font-face rules that came from the style set. There's no need
7147 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
7148 // no point on it.
7149 MarkUserFontSetDirty();
7151 if (IsEditingOn()) {
7152 TurnEditingOff();
7155 PresShell* oldPresShell = mPresShell;
7156 mPresShell = nullptr;
7157 UpdateFrameRequestCallbackSchedulingState(oldPresShell);
7159 ClearStaleServoData();
7160 AssertNoStaleServoDataIn(*this);
7162 mStyleSet->ShellDetachedFromDocument();
7163 mStyleSetFilled = false;
7164 mQuirkSheetAdded = false;
7165 mContentEditableSheetAdded = false;
7166 mDesignModeSheetAdded = false;
7169 void Document::DisallowBFCaching(uint32_t aStatus) {
7170 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
7171 if (!mBFCacheDisallowed) {
7172 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
7173 wgc->SendUpdateBFCacheStatus(aStatus, 0);
7176 mBFCacheDisallowed = true;
7179 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
7180 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
7182 if (mPresShell) {
7183 if (aEntry) {
7184 mPresShell->StopObservingRefreshDriver();
7185 } else if (mBFCacheEntry) {
7186 mPresShell->StartObservingRefreshDriver();
7189 mBFCacheEntry = aEntry;
7192 bool Document::RemoveFromBFCacheSync() {
7193 bool removed = false;
7194 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
7195 entry->RemoveFromBFCacheSync();
7196 removed = true;
7197 } else if (!IsCurrentActiveDocument()) {
7198 // In the old bfcache implementation while the new page is loading, but
7199 // before nsIDocumentViewer.show() has been called, the previous page
7200 // doesn't yet have nsIBFCacheEntry. However, the previous page isn't the
7201 // current active document anymore.
7202 DisallowBFCaching();
7203 removed = true;
7206 if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
7207 if (BrowsingContext* bc = GetBrowsingContext()) {
7208 if (bc->IsInBFCache()) {
7209 ContentChild* cc = ContentChild::GetSingleton();
7210 // IPC is asynchronous but the caller is supposed to check the return
7211 // value. The reason for 'Sync' in the method name is that the old
7212 // implementation may run scripts. There is Async variant in
7213 // the old session history implementation for the cases where
7214 // synchronous operation isn't safe.
7215 cc->SendRemoveFromBFCache(bc->Top());
7216 removed = true;
7220 return removed;
7223 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
7224 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
7226 NS_RELEASE(e->mKey);
7227 if (e->mSubDocument) {
7228 e->mSubDocument->SetParentDocument(nullptr);
7229 NS_RELEASE(e->mSubDocument);
7233 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
7234 SubDocMapEntry* e =
7235 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
7237 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
7238 NS_ADDREF(e->mKey);
7240 e->mSubDocument = nullptr;
7243 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
7244 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
7246 if (!aSubDoc) {
7247 // aSubDoc is nullptr, remove the mapping
7249 if (mSubDocuments) {
7250 mSubDocuments->Remove(aElement);
7252 } else {
7253 if (!mSubDocuments) {
7254 // Create a new hashtable
7256 static const PLDHashTableOps hash_table_ops = {
7257 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
7258 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
7260 mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
7263 // Add a mapping to the hash table
7264 auto entry =
7265 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
7267 if (!entry) {
7268 return NS_ERROR_OUT_OF_MEMORY;
7271 if (entry->mSubDocument) {
7272 entry->mSubDocument->SetParentDocument(nullptr);
7274 // Release the old sub document
7275 NS_RELEASE(entry->mSubDocument);
7278 entry->mSubDocument = aSubDoc;
7279 NS_ADDREF(entry->mSubDocument);
7281 aSubDoc->SetParentDocument(this);
7284 return NS_OK;
7287 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
7288 if (mSubDocuments && aContent->IsElement()) {
7289 auto entry = static_cast<SubDocMapEntry*>(
7290 mSubDocuments->Search(aContent->AsElement()));
7292 if (entry) {
7293 return entry->mSubDocument;
7297 return nullptr;
7300 Element* Document::GetEmbedderElement() const {
7301 // We check if we're the active document in our BrowsingContext
7302 // by comparing against its document, rather than checking if the
7303 // WindowContext is cached, since mWindow may be null when we're
7304 // called (such as in nsPresContext::Init).
7305 if (BrowsingContext* bc = GetBrowsingContext()) {
7306 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
7309 return nullptr;
7312 Element* Document::GetRootElement() const {
7313 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
7314 ? mCachedRootElement
7315 : GetRootElementInternal();
7318 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
7320 Element* Document::GetRootElementInternal() const {
7321 // We invoke GetRootElement() immediately before the servo traversal, so we
7322 // should always have a cache hit from Servo.
7323 MOZ_ASSERT(NS_IsMainThread());
7325 // Loop backwards because any non-elements, such as doctypes and PIs
7326 // are likely to appear before the root element.
7327 for (nsIContent* child = GetLastChild(); child;
7328 child = child->GetPreviousSibling()) {
7329 if (Element* element = Element::FromNode(child)) {
7330 const_cast<Document*>(this)->mCachedRootElement = element;
7331 return element;
7335 const_cast<Document*>(this)->mCachedRootElement = nullptr;
7336 return nullptr;
7339 void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
7340 bool aNotify, ErrorResult& aRv) {
7341 if (aKid->IsElement() && GetRootElement()) {
7342 NS_WARNING("Inserting root element when we already have one");
7343 aRv.ThrowHierarchyRequestError("There is already a root element.");
7344 return;
7347 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
7350 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
7351 Maybe<mozAutoDocUpdate> updateBatch;
7352 if (aKid->IsElement()) {
7353 updateBatch.emplace(this, aNotify);
7354 // Destroy the link map up front before we mess with the child list.
7355 DestroyElementMaps();
7358 // Preemptively clear mCachedRootElement, since we may be about to remove it
7359 // from our child list, and we don't want to return this maybe-obsolete value
7360 // from any GetRootElement() calls that happen inside of RemoveChildNode().
7361 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
7362 // GetRootElement() calls until after it's removed the child from mChildren.
7363 // Any call before that point would restore this soon-to-be-obsolete cached
7364 // answer, and our clearing here would be fruitless.)
7365 mCachedRootElement = nullptr;
7366 nsINode::RemoveChildNode(aKid, aNotify);
7367 MOZ_ASSERT(mCachedRootElement != aKid,
7368 "Stale pointer in mCachedRootElement, after we tried to clear it "
7369 "(maybe somebody called GetRootElement() too early?)");
7372 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
7373 if (mStyleSetFilled) {
7374 EnsureStyleSet().AddDocStyleSheet(aSheet);
7375 ApplicableStylesChanged();
7379 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
7380 EnsureStyleSet().RecordShadowStyleChange(aShadowRoot);
7381 ApplicableStylesChanged(/* aKnownInShadowTree= */ true);
7384 void Document::ApplicableStylesChanged(bool aKnownInShadowTree) {
7385 // TODO(emilio): if we decide to resolve style in display: none iframes, then
7386 // we need to always track style changes and remove the mStyleSetFilled.
7387 if (!mStyleSetFilled) {
7388 return;
7390 if (!aKnownInShadowTree) {
7391 MarkUserFontSetDirty();
7393 PresShell* ps = GetPresShell();
7394 if (!ps) {
7395 return;
7398 ps->EnsureStyleFlush();
7399 nsPresContext* pc = ps->GetPresContext();
7400 if (!pc) {
7401 return;
7404 if (!aKnownInShadowTree) {
7405 pc->MarkCounterStylesDirty();
7406 pc->MarkFontFeatureValuesDirty();
7407 pc->MarkFontPaletteValuesDirty();
7409 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
7412 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
7413 if (mStyleSetFilled) {
7414 mStyleSet->RemoveStyleSheet(aSheet);
7415 ApplicableStylesChanged();
7419 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
7420 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
7422 if (aSheet.IsApplicable()) {
7423 AddStyleSheetToStyleSets(aSheet);
7427 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
7428 const bool applicable = aSheet.IsApplicable();
7429 // If we're actually in the document style sheet list
7430 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
7431 if (applicable) {
7432 AddStyleSheetToStyleSets(aSheet);
7433 } else {
7434 RemoveStyleSheetFromStyleSets(aSheet);
7439 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
7440 if (!StyleSheetChangeEventsEnabled()) {
7441 return;
7444 StyleSheetApplicableStateChangeEventInit init;
7445 init.mBubbles = true;
7446 init.mCancelable = true;
7447 init.mStylesheet = &aSheet;
7448 init.mApplicable = aSheet.IsApplicable();
7450 RefPtr<StyleSheetApplicableStateChangeEvent> event =
7451 StyleSheetApplicableStateChangeEvent::Constructor(
7452 this, u"StyleSheetApplicableStateChanged"_ns, init);
7453 event->SetTrusted(true);
7454 event->SetTarget(this);
7455 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7456 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7457 asyncDispatcher->PostDOMEvent();
7460 void Document::PostStyleSheetRemovedEvent(StyleSheet& aSheet) {
7461 if (!StyleSheetChangeEventsEnabled()) {
7462 return;
7465 StyleSheetRemovedEventInit init;
7466 init.mBubbles = true;
7467 init.mCancelable = false;
7468 init.mStylesheet = &aSheet;
7470 RefPtr<StyleSheetRemovedEvent> event =
7471 StyleSheetRemovedEvent::Constructor(this, u"StyleSheetRemoved"_ns, init);
7472 event->SetTrusted(true);
7473 event->SetTarget(this);
7474 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7475 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7476 asyncDispatcher->PostDOMEvent();
7479 void Document::PostCustomPropertyRegistered(
7480 const PropertyDefinition& aDefinition) {
7481 if (!StyleSheetChangeEventsEnabled()) {
7482 return;
7485 CSSCustomPropertyRegisteredEventInit init;
7486 init.mBubbles = true;
7487 init.mCancelable = false;
7489 InspectorCSSPropertyDefinition property;
7491 property.mName.Append(aDefinition.mName);
7492 property.mSyntax.Append(aDefinition.mSyntax);
7493 property.mInherits = aDefinition.mInherits;
7494 if (aDefinition.mInitialValue.WasPassed()) {
7495 property.mInitialValue.Append(aDefinition.mInitialValue.Value());
7496 } else {
7497 property.mInitialValue.SetIsVoid(true);
7499 property.mFromJS = true;
7500 init.mPropertyDefinition = property;
7502 RefPtr<CSSCustomPropertyRegisteredEvent> event =
7503 CSSCustomPropertyRegisteredEvent::Constructor(
7504 this, u"csscustompropertyregistered"_ns, init);
7505 event->SetTrusted(true);
7506 event->SetTarget(this);
7507 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7508 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes);
7509 asyncDispatcher->PostDOMEvent();
7512 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
7513 nsIURI* aSheetURI) {
7514 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
7515 bool bEqual;
7516 nsIURI* uri = aSheets[i]->GetSheetURI();
7518 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
7519 return i;
7522 return -1;
7525 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
7526 nsIURI* aSheetURI) {
7527 MOZ_ASSERT(aSheetURI, "null arg");
7529 // Checking if we have loaded this one already.
7530 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
7531 return NS_ERROR_INVALID_ARG;
7533 // Loading the sheet sync.
7534 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
7536 css::SheetParsingMode parsingMode;
7537 switch (aType) {
7538 case Document::eAgentSheet:
7539 parsingMode = css::eAgentSheetFeatures;
7540 break;
7542 case Document::eUserSheet:
7543 parsingMode = css::eUserSheetFeatures;
7544 break;
7546 case Document::eAuthorSheet:
7547 parsingMode = css::eAuthorSheetFeatures;
7548 break;
7550 default:
7551 MOZ_CRASH("impossible value for aType");
7554 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
7555 css::Loader::UseSystemPrincipal::Yes);
7556 if (result.isErr()) {
7557 return result.unwrapErr();
7560 RefPtr<StyleSheet> sheet = result.unwrap();
7562 sheet->SetAssociatedDocumentOrShadowRoot(this);
7563 MOZ_ASSERT(sheet->IsApplicable());
7565 return AddAdditionalStyleSheet(aType, sheet);
7568 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
7569 StyleSheet* aSheet) {
7570 if (mAdditionalSheets[aType].Contains(aSheet)) {
7571 return NS_ERROR_INVALID_ARG;
7574 if (!aSheet->IsApplicable()) {
7575 return NS_ERROR_INVALID_ARG;
7578 mAdditionalSheets[aType].AppendElement(aSheet);
7580 if (mStyleSetFilled) {
7581 EnsureStyleSet().AppendStyleSheet(*aSheet);
7582 ApplicableStylesChanged();
7584 return NS_OK;
7587 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
7588 nsIURI* aSheetURI) {
7589 MOZ_ASSERT(aSheetURI);
7591 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
7593 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
7594 if (i >= 0) {
7595 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
7596 sheets.RemoveElementAt(i);
7598 if (!mIsGoingAway) {
7599 MOZ_ASSERT(sheetRef->IsApplicable());
7600 if (mStyleSetFilled) {
7601 EnsureStyleSet().RemoveStyleSheet(*sheetRef);
7602 ApplicableStylesChanged();
7605 sheetRef->ClearAssociatedDocumentOrShadowRoot();
7609 nsIGlobalObject* Document::GetScopeObject() const {
7610 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
7611 return scope;
7614 DocGroup* Document::GetDocGroupOrCreate() {
7615 if (!mDocGroup && GetBrowsingContext()) {
7616 BrowsingContextGroup* group = GetBrowsingContext()->Group();
7617 MOZ_ASSERT(group);
7619 nsAutoCString docGroupKey;
7620 nsresult rv = mozilla::dom::DocGroup::GetKey(
7621 NodePrincipal(), group->IsPotentiallyCrossOriginIsolated(),
7622 docGroupKey);
7623 if (NS_SUCCEEDED(rv)) {
7624 mDocGroup = group->AddDocument(docGroupKey, this);
7627 return mDocGroup;
7630 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
7631 mScopeObject = do_GetWeakReference(aGlobal);
7632 if (aGlobal) {
7633 mHasHadScriptHandlingObject = true;
7635 nsPIDOMWindowInner* window = aGlobal->GetAsInnerWindow();
7636 if (!window) {
7637 return;
7640 // Same origin data documents should have the same docGroup as their scope
7641 // window.
7642 if (mLoadedAsData && window->GetExtantDoc() &&
7643 window->GetExtantDoc() != this &&
7644 window->GetExtantDoc()->NodePrincipal() == NodePrincipal()) {
7645 DocGroup* docGroup = window->GetExtantDoc()->GetDocGroup();
7647 if (docGroup) {
7648 if (!mDocGroup) {
7649 mDocGroup = docGroup;
7650 mDocGroup->AddDocument(this);
7651 } else {
7652 MOZ_ASSERT(mDocGroup == docGroup,
7653 "Data document has a mismatched doc group?");
7655 #ifdef DEBUG
7656 AssertDocGroupMatchesKey();
7657 #endif
7658 return;
7661 MOZ_ASSERT_UNREACHABLE(
7662 "Scope window doesn't have DocGroup when creating data document?");
7663 // ... but fall through to be safe.
7666 BrowsingContextGroup* browsingContextGroup =
7667 window->GetBrowsingContextGroup();
7669 // We should already have the principal, and now that we have been added
7670 // to a window, we should be able to join a DocGroup!
7671 nsAutoCString docGroupKey;
7672 nsresult rv = mozilla::dom::DocGroup::GetKey(
7673 NodePrincipal(),
7674 browsingContextGroup->IsPotentiallyCrossOriginIsolated(), docGroupKey);
7675 if (mDocGroup) {
7676 if (NS_SUCCEEDED(rv)) {
7677 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
7679 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
7680 browsingContextGroup);
7681 } else {
7682 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
7684 MOZ_ASSERT(mDocGroup);
7687 MOZ_ASSERT_IF(
7688 mNodeInfoManager->GetArenaAllocator(),
7689 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
7693 bool Document::ContainsEMEContent() {
7694 nsPIDOMWindowInner* win = GetInnerWindow();
7695 // Note this case is different from checking just media elements in that
7696 // it covers when we've created MediaKeys but not associated them with a
7697 // media element.
7698 return win && win->HasActiveMediaKeysInstance();
7701 bool Document::ContainsMSEContent() {
7702 bool containsMSE = false;
7704 auto check = [&containsMSE](nsISupports* aSupports) {
7705 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7706 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7707 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
7708 if (ms) {
7709 containsMSE = true;
7714 EnumerateActivityObservers(check);
7715 return containsMSE;
7718 static void NotifyActivityChangedCallback(nsISupports* aSupports) {
7719 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7720 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7721 mediaElem->NotifyOwnerDocumentActivityChanged();
7723 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
7724 do_QueryInterface(aSupports));
7725 if (objectDocumentActivity) {
7726 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
7727 } else {
7728 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
7729 do_QueryInterface(aSupports));
7730 if (imageLoadingContent) {
7731 auto* ilc =
7732 static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
7733 ilc->NotifyOwnerDocumentActivityChanged();
7738 void Document::NotifyActivityChanged() {
7739 EnumerateActivityObservers(NotifyActivityChangedCallback);
7742 void Document::SetContainer(nsDocShell* aContainer) {
7743 if (aContainer) {
7744 mDocumentContainer = aContainer;
7745 } else {
7746 mDocumentContainer = WeakPtr<nsDocShell>();
7749 mInChromeDocShell =
7750 aContainer && aContainer->GetBrowsingContext()->IsChrome();
7752 NotifyActivityChanged();
7754 // IsTopLevelWindowInactive depends on the docshell, so
7755 // update the cached value now that it's available.
7756 UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false);
7757 if (!aContainer) {
7758 return;
7761 BrowsingContext* context = aContainer->GetBrowsingContext();
7762 MOZ_ASSERT_IF(context && mDocGroup,
7763 context->Group() == mDocGroup->GetBrowsingContextGroup());
7764 if (context && context->IsContent()) {
7765 SetIsTopLevelContentDocument(context->IsTopContent());
7766 SetIsContentDocument(true);
7767 } else {
7768 SetIsTopLevelContentDocument(false);
7769 SetIsContentDocument(false);
7773 nsISupports* Document::GetContainer() const {
7774 return static_cast<nsIDocShell*>(mDocumentContainer);
7777 void Document::SetScriptGlobalObject(
7778 nsIScriptGlobalObject* aScriptGlobalObject) {
7779 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
7780 mAnimationController->IsPausedByType(
7781 SMILTimeContainer::PAUSE_PAGEHIDE |
7782 SMILTimeContainer::PAUSE_BEGIN),
7783 "Clearing window pointer while animations are unpaused");
7785 if (mScriptGlobalObject && !aScriptGlobalObject) {
7786 // We're detaching from the window. We need to grab a pointer to
7787 // our layout history state now.
7788 mLayoutHistoryState = GetLayoutHistoryState();
7790 // Also make sure to remove our onload blocker now if we haven't done it yet
7791 if (mOnloadBlockCount != 0) {
7792 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
7793 if (loadGroup) {
7794 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
7798 if (GetController().isSome()) {
7799 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
7800 loader->ClearCacheForControlledDocument(this);
7803 // We may become controlled again if this document comes back out
7804 // of bfcache. Clear our state to allow that to happen. Only
7805 // clear this flag if we are actually controlled, though, so pages
7806 // that were force reloaded don't become controlled when they
7807 // come out of bfcache.
7808 mMaybeServiceWorkerControlled = false;
7811 if (GetWindowContext()) {
7812 // The document is about to lose its window, so this is a good time to
7813 // send our page use counters, while we still have access to our
7814 // WindowContext.
7816 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
7817 // catches some cases of documents losing their window that don't
7818 // get in here.)
7819 SendPageUseCounters();
7823 // BlockOnload() might be called before mScriptGlobalObject is set.
7824 // We may need to add the blocker once mScriptGlobalObject is set.
7825 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
7827 mScriptGlobalObject = aScriptGlobalObject;
7829 if (needOnloadBlocker) {
7830 EnsureOnloadBlocker();
7833 UpdateFrameRequestCallbackSchedulingState();
7835 if (aScriptGlobalObject) {
7836 // Go back to using the docshell for the layout history state
7837 mLayoutHistoryState = nullptr;
7838 SetScopeObject(aScriptGlobalObject);
7839 mHasHadDefaultView = true;
7841 if (mAllowDNSPrefetch) {
7842 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7843 if (docShell) {
7844 #ifdef DEBUG
7845 nsCOMPtr<nsIWebNavigation> webNav =
7846 do_GetInterface(aScriptGlobalObject);
7847 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
7848 "Unexpected container or script global?");
7849 #endif
7850 bool allowDNSPrefetch;
7851 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
7852 mAllowDNSPrefetch = allowDNSPrefetch;
7856 // If we are set in a window that is already focused we should remember this
7857 // as the time the document gained focus.
7858 if (HasFocus(IgnoreErrors())) {
7859 SetLastFocusTime(TimeStamp::Now());
7863 // Remember the pointer to our window (or lack there of), to avoid
7864 // having to QI every time it's asked for.
7865 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
7866 mWindow = window;
7868 // Now that we know what our window is, we can flush the CSP errors to the
7869 // Web Console. We are flushing all messages that occurred and were stored in
7870 // the queue prior to this point.
7871 if (mCSP) {
7872 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
7875 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
7876 do_QueryInterface(GetChannel());
7877 if (internalChannel) {
7878 nsCOMArray<nsISecurityConsoleMessage> messages;
7879 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
7880 MOZ_ASSERT(NS_SUCCEEDED(rv));
7881 SendToConsole(messages);
7884 // Set our visibility state, but do not fire the event. This is correct
7885 // because either we're coming out of bfcache (in which case IsVisible() will
7886 // still test false at this point and no state change will happen) or we're
7887 // doing the initial document load and don't want to fire the event for this
7888 // change.
7890 // When the visibility is changed, notify it to observers.
7891 // Some observers need the notification, for example HTMLMediaElement uses
7892 // it to update internal media resource allocation.
7893 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
7894 // creation are already done before Document::SetScriptGlobalObject() call.
7895 // MediaDecoder decides whether starting decoding is decided based on
7896 // document's visibility. When the MediaDecoder is created,
7897 // Document::SetScriptGlobalObject() is not yet called and document is
7898 // hidden state. Therefore the MediaDecoder decides that decoding is
7899 // not yet necessary. But soon after Document::SetScriptGlobalObject()
7900 // call, the document becomes not hidden. At the time, MediaDecoder needs
7901 // to know it and needs to start updating decoding.
7902 UpdateVisibilityState(DispatchVisibilityChange::No);
7904 // The global in the template contents owner document should be the same.
7905 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
7906 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
7909 // Tell the script loader about the new global object.
7910 if (mScriptLoader && !IsTemplateContentsOwner()) {
7911 mScriptLoader->SetGlobalObject(mScriptGlobalObject);
7914 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
7915 mScriptGlobalObject && GetChannel()) {
7916 // If we are shift-reloaded, don't associate with a ServiceWorker.
7917 if (mDocumentContainer->IsForceReloading()) {
7918 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
7919 return;
7922 mMaybeServiceWorkerControlled = true;
7926 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
7927 MOZ_ASSERT(!mScriptGlobalObject,
7928 "Do not call this when mScriptGlobalObject is set!");
7929 if (mHasHadDefaultView) {
7930 return nullptr;
7933 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
7934 do_QueryReferent(mScopeObject);
7935 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
7936 if (win) {
7937 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
7938 if (!outer || outer->GetCurrentInnerWindow() != win) {
7939 NS_WARNING("Wrong inner/outer window combination!");
7940 return nullptr;
7943 return scriptHandlingObject;
7945 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
7946 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
7947 "Wrong script object!");
7948 if (aScriptObject) {
7949 SetScopeObject(aScriptObject);
7950 mHasHadDefaultView = false;
7954 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
7955 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
7956 // Let's use mScriptGlobalObject. Even if the document is already removed from
7957 // the docshell, the outer window might be still obtainable from the it.
7958 nsCOMPtr<nsPIDOMWindowOuter> win;
7959 if (mRemovedFromDocShell) {
7960 // The docshell returns the outer window we are done.
7961 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
7962 if (kungFuDeathGrip) {
7963 win = kungFuDeathGrip->GetWindow();
7965 } else {
7966 if (nsCOMPtr<nsPIDOMWindowInner> inner =
7967 do_QueryInterface(mScriptGlobalObject)) {
7968 // mScriptGlobalObject is always the inner window, let's get the outer.
7969 win = inner->GetOuterWindow();
7973 return win;
7976 bool Document::InternalAllowXULXBL() {
7977 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
7978 mAllowXULXBL = eTriTrue;
7979 return true;
7982 mAllowXULXBL = eTriFalse;
7983 return false;
7986 // Note: We don't hold a reference to the document observer; we assume
7987 // that it has a live reference to the document.
7988 void Document::AddObserver(nsIDocumentObserver* aObserver) {
7989 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
7990 "Observer already in the list");
7991 mObservers.AppendElement(aObserver);
7992 AddMutationObserver(aObserver);
7995 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
7996 // If we're in the process of destroying the document (and we're
7997 // informing the observers of the destruction), don't remove the
7998 // observers from the list. This is not a big deal, since we
7999 // don't hold a live reference to the observers.
8000 if (!mInDestructor) {
8001 RemoveMutationObserver(aObserver);
8002 return mObservers.RemoveElement(aObserver);
8005 return mObservers.Contains(aObserver);
8008 void Document::BeginUpdate() {
8009 ++mUpdateNestLevel;
8010 nsContentUtils::AddScriptBlocker();
8011 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
8014 void Document::EndUpdate() {
8015 const bool reset = !mPendingMaybeEditingStateChanged;
8016 mPendingMaybeEditingStateChanged = true;
8018 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
8020 --mUpdateNestLevel;
8022 nsContentUtils::RemoveScriptBlocker();
8024 if (mXULBroadcastManager) {
8025 mXULBroadcastManager->MaybeBroadcast();
8028 if (reset) {
8029 mPendingMaybeEditingStateChanged = false;
8031 MaybeEditingStateChanged();
8034 void Document::BeginLoad() {
8035 if (IsEditingOn()) {
8036 // Reset() blows away all event listeners in the document, and our
8037 // editor relies heavily on those. Midas is turned on, to make it
8038 // work, re-initialize it to give it a chance to add its event
8039 // listeners again.
8041 TurnEditingOff();
8042 EditingStateChanged();
8045 MOZ_ASSERT(!mDidCallBeginLoad);
8046 mDidCallBeginLoad = true;
8048 // Block onload here to prevent having to deal with blocking and
8049 // unblocking it while we know the document is loading.
8050 BlockOnload();
8051 mDidFireDOMContentLoaded = false;
8052 BlockDOMContentLoaded();
8054 if (mScriptLoader) {
8055 mScriptLoader->BeginDeferringScripts();
8058 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
8061 void Document::MozSetImageElement(const nsAString& aImageElementId,
8062 Element* aElement) {
8063 if (aImageElementId.IsEmpty()) return;
8065 // Hold a script blocker while calling SetImageElement since that can call
8066 // out to id-observers
8067 nsAutoScriptBlocker scriptBlocker;
8069 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
8070 if (entry) {
8071 entry->SetImageElement(aElement);
8072 if (entry->IsEmpty()) {
8073 mIdentifierMap.RemoveEntry(entry);
8078 void Document::DispatchContentLoadedEvents() {
8079 // If you add early returns from this method, make sure you're
8080 // calling UnblockOnload properly.
8082 // Unpin references to preloaded images
8083 mPreloadingImages.Clear();
8085 // DOM manipulation after content loaded should not care if the element
8086 // came from the preloader.
8087 mPreloadedPreconnects.Clear();
8089 if (mTiming) {
8090 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
8093 // Dispatch observer notification to notify observers document is interactive.
8094 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
8095 if (os) {
8096 nsIPrincipal* principal = NodePrincipal();
8097 os->NotifyObservers(ToSupports(this),
8098 principal->IsSystemPrincipal()
8099 ? "chrome-document-interactive"
8100 : "content-document-interactive",
8101 nullptr);
8104 // Fire a DOM event notifying listeners that this document has been
8105 // loaded (excluding images and other loads initiated by this
8106 // document).
8107 nsContentUtils::DispatchTrustedEvent(this, this, u"DOMContentLoaded"_ns,
8108 CanBubble::eYes, Cancelable::eNo);
8110 if (auto* const window = GetInnerWindow()) {
8111 const RefPtr<ServiceWorkerContainer> serviceWorker =
8112 window->Navigator()->ServiceWorker();
8114 // This could cause queued messages from a service worker to get
8115 // dispatched on serviceWorker.
8116 serviceWorker->StartMessages();
8119 if (MayStartLayout()) {
8120 MaybeResolveReadyForIdle();
8123 if (mTiming) {
8124 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
8127 // If this document is a [i]frame, fire a DOMFrameContentLoaded
8128 // event on all parent documents notifying that the HTML (excluding
8129 // other external files such as images and stylesheets) in a frame
8130 // has finished loading.
8132 // target_frame is the [i]frame element that will be used as the
8133 // target for the event. It's the [i]frame whose content is done
8134 // loading.
8135 nsCOMPtr<Element> target_frame = GetEmbedderElement();
8137 if (target_frame && target_frame->IsInComposedDoc()) {
8138 nsCOMPtr<Document> parent = target_frame->OwnerDoc();
8139 while (parent) {
8140 RefPtr<Event> event;
8141 if (parent) {
8142 IgnoredErrorResult ignored;
8143 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
8146 if (event) {
8147 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
8149 event->SetTarget(target_frame);
8150 event->SetTrusted(true);
8152 // To dispatch this event we must manually call
8153 // EventDispatcher::Dispatch() on the ancestor document since the
8154 // target is not in the same document, so the event would never reach
8155 // the ancestor document if we used the normal event
8156 // dispatching code.
8158 WidgetEvent* innerEvent = event->WidgetEventPtr();
8159 if (innerEvent) {
8160 nsEventStatus status = nsEventStatus_eIgnore;
8162 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
8163 EventDispatcher::Dispatch(parent, context, innerEvent, event,
8164 &status);
8169 parent = parent->GetInProcessParentDocument();
8173 nsPIDOMWindowInner* inner = GetInnerWindow();
8174 if (inner) {
8175 inner->NoteDOMContentLoaded();
8178 // TODO
8179 if (mMaybeServiceWorkerControlled) {
8180 using mozilla::dom::ServiceWorkerManager;
8181 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
8182 if (swm) {
8183 Maybe<ClientInfo> clientInfo = GetClientInfo();
8184 if (clientInfo.isSome()) {
8185 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
8190 if (mSetCompleteAfterDOMContentLoaded) {
8191 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
8192 mSetCompleteAfterDOMContentLoaded = false;
8195 UnblockOnload(true);
8198 void Document::EndLoad() {
8199 bool turnOnEditing =
8200 mParser && (IsInDesignMode() || mContentEditableCount > 0);
8202 #if defined(DEBUG)
8203 // only assert if nothing stopped the load on purpose
8204 if (!mParserAborted) {
8205 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
8207 #endif
8209 // EndLoad may have been called without a matching call to BeginLoad, in the
8210 // case of a failed parse (for example, due to timeout). In such a case, we
8211 // still want to execute part of this code to do appropriate cleanup, but we
8212 // gate part of it because it is intended to match 1-for-1 with calls to
8213 // BeginLoad. We have an explicit flag bit for this purpose, since it's
8214 // complicated and error prone to derive this condition from other related
8215 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
8217 // Part 1: Code that always executes to cleanup end of parsing, whether
8218 // that parsing was successful or not.
8220 // Drop the ref to our parser, if any, but keep hold of the sink so that we
8221 // can flush it from FlushPendingNotifications as needed. We might have to
8222 // do that to get a StartLayout() to happen.
8223 if (mParser) {
8224 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
8225 mParser = nullptr;
8228 // Update the attributes on the PerformanceNavigationTiming before notifying
8229 // the onload observers.
8230 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
8231 if (RefPtr<Performance> performance = window->GetPerformance()) {
8232 performance->UpdateNavigationTimingEntry();
8236 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
8238 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
8240 if (!mDidCallBeginLoad) {
8241 return;
8243 mDidCallBeginLoad = false;
8245 UnblockDOMContentLoaded();
8247 if (turnOnEditing) {
8248 EditingStateChanged();
8251 if (!GetWindow()) {
8252 // This is a document that's not in a window. For example, this could be an
8253 // XMLHttpRequest responseXML document, or a document created via DOMParser
8254 // or DOMImplementation. We don't reach this code normally for such
8255 // documents (which is not obviously correct), but can reach it via
8256 // document.open()/document.close().
8258 // Such documents don't fire load events, but per spec should set their
8259 // readyState to "complete" when parsing and all loading of subresources is
8260 // done. Parsing is done now, and documents not in a window don't load
8261 // subresources, so just go ahead and mark ourselves as complete.
8262 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
8263 /* updateTimingInformation = */ false);
8265 // Reset mSkipLoadEventAfterClose just in case.
8266 mSkipLoadEventAfterClose = false;
8270 void Document::UnblockDOMContentLoaded() {
8271 MOZ_ASSERT(mBlockDOMContentLoaded);
8272 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
8273 return;
8276 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
8277 ("DOCUMENT %p UnblockDOMContentLoaded", this));
8279 mDidFireDOMContentLoaded = true;
8280 if (PresShell* presShell = GetPresShell()) {
8281 presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
8284 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
8285 if (!mSynchronousDOMContentLoaded) {
8286 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8287 nsCOMPtr<nsIRunnable> ev =
8288 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
8289 &Document::DispatchContentLoadedEvents);
8290 Dispatch(ev.forget());
8291 } else {
8292 DispatchContentLoadedEvents();
8296 void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) {
8297 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
8298 "Someone forgot a scriptblocker");
8299 NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged,
8300 (this, aElement, aStateMask));
8303 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
8304 StyleRuleChangeKind) {
8305 if (aSheet.IsApplicable()) {
8306 ApplicableStylesChanged();
8310 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
8311 if (aRule.IsIncompleteImportRule()) {
8312 return;
8315 if (aSheet.IsApplicable()) {
8316 ApplicableStylesChanged();
8320 void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
8321 if (aSheet.IsApplicable()) {
8322 ApplicableStylesChanged();
8326 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
8327 if (aSheet.IsApplicable()) {
8328 ApplicableStylesChanged();
8332 static Element* GetCustomContentContainer(PresShell* aPresShell) {
8333 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
8334 return nullptr;
8337 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
8340 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
8341 bool aForce, ErrorResult& aRv) {
8342 RefPtr<PresShell> shell = GetPresShell();
8343 if (aForce && !GetCustomContentContainer(shell)) {
8344 FlushPendingNotifications(FlushType::Layout);
8345 shell = GetPresShell();
8348 nsAutoScriptBlocker scriptBlocker;
8350 RefPtr<AnonymousContent> anonContent = AnonymousContent::Create(*this);
8351 if (!anonContent) {
8352 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
8353 return nullptr;
8356 mAnonymousContents.AppendElement(anonContent);
8358 if (RefPtr<Element> container = GetCustomContentContainer(shell)) {
8359 // If the container is empty and we have other anon content we should be
8360 // about to show all the other anonymous content nodes.
8361 if (container->HasChildren() || mAnonymousContents.Length() == 1) {
8362 container->AppendChildTo(anonContent->Host(), true, IgnoreErrors());
8363 if (auto* canvasFrame = shell->GetCanvasFrame()) {
8364 canvasFrame->ShowCustomContentContainer();
8369 return anonContent.forget();
8372 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
8373 PresShell* aPresShell) {
8374 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
8375 if (!container) {
8376 return;
8378 container->RemoveChild(*aAnonContent.Host(), IgnoreErrors());
8381 void Document::RemoveAnonymousContent(AnonymousContent& aContent) {
8382 nsAutoScriptBlocker scriptBlocker;
8384 auto index = mAnonymousContents.IndexOf(&aContent);
8385 if (index == mAnonymousContents.NoIndex) {
8386 return;
8389 mAnonymousContents.RemoveElementAt(index);
8390 RemoveAnonContentFromCanvas(aContent, GetPresShell());
8392 if (mAnonymousContents.IsEmpty() &&
8393 GetCustomContentContainer(GetPresShell())) {
8394 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
8398 Element* Document::GetAnonRootIfInAnonymousContentContainer(
8399 nsINode* aNode) const {
8400 if (!aNode->IsInNativeAnonymousSubtree()) {
8401 return nullptr;
8404 PresShell* presShell = GetPresShell();
8405 if (!presShell || !presShell->GetCanvasFrame()) {
8406 return nullptr;
8409 nsAutoScriptBlocker scriptBlocker;
8410 nsCOMPtr<Element> customContainer =
8411 presShell->GetCanvasFrame()->GetCustomContentContainer();
8412 if (!customContainer) {
8413 return nullptr;
8416 // An arbitrary number of elements can be inserted as children of the custom
8417 // container frame. We want the one that was added that contains aNode, so
8418 // we need to keep track of the last child separately using |child| here.
8419 nsINode* child = aNode;
8420 nsINode* parent = aNode->GetParentNode();
8421 while (parent && parent->IsInNativeAnonymousSubtree()) {
8422 if (parent == customContainer) {
8423 return Element::FromNode(child);
8425 child = parent;
8426 parent = child->GetParentNode();
8428 return nullptr;
8431 Maybe<ClientInfo> Document::GetClientInfo() const {
8432 if (const Document* orig = GetOriginalDocument()) {
8433 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
8434 return info;
8438 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8439 return inner->GetClientInfo();
8442 return Maybe<ClientInfo>();
8445 Maybe<ClientState> Document::GetClientState() const {
8446 if (const Document* orig = GetOriginalDocument()) {
8447 if (Maybe<ClientState> state = orig->GetClientState()) {
8448 return state;
8452 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8453 return inner->GetClientState();
8456 return Maybe<ClientState>();
8459 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
8460 if (const Document* orig = GetOriginalDocument()) {
8461 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
8462 return controller;
8466 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8467 return inner->GetController();
8470 return Maybe<ServiceWorkerDescriptor>();
8474 // Document interface
8476 DocumentType* Document::GetDoctype() const {
8477 for (nsIContent* child = GetFirstChild(); child;
8478 child = child->GetNextSibling()) {
8479 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
8480 return static_cast<DocumentType*>(child);
8483 return nullptr;
8486 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
8487 if (!mDOMImplementation) {
8488 nsCOMPtr<nsIURI> uri;
8489 NS_NewURI(getter_AddRefs(uri), "about:blank");
8490 if (!uri) {
8491 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
8492 return nullptr;
8494 bool hasHadScriptObject = true;
8495 nsIScriptGlobalObject* scriptObject =
8496 GetScriptHandlingObject(hasHadScriptObject);
8497 if (!scriptObject && hasHadScriptObject) {
8498 rv.Throw(NS_ERROR_UNEXPECTED);
8499 return nullptr;
8501 mDOMImplementation = new DOMImplementation(
8502 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
8505 return mDOMImplementation;
8508 bool IsLowercaseASCII(const nsAString& aValue) {
8509 int32_t len = aValue.Length();
8510 for (int32_t i = 0; i < len; ++i) {
8511 char16_t c = aValue[i];
8512 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
8513 return false;
8516 return true;
8519 already_AddRefed<Element> Document::CreateElement(
8520 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8521 ErrorResult& rv) {
8522 rv = nsContentUtils::CheckQName(aTagName, false);
8523 if (rv.Failed()) {
8524 return nullptr;
8527 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
8528 nsAutoString lcTagName;
8529 if (needsLowercase) {
8530 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
8533 const nsString* is = nullptr;
8534 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
8535 if (aOptions.IsElementCreationOptions()) {
8536 const ElementCreationOptions& options =
8537 aOptions.GetAsElementCreationOptions();
8539 if (options.mIs.WasPassed()) {
8540 is = &options.mIs.Value();
8543 // Check 'pseudo' and throw an exception if it's not one allowed
8544 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
8545 if (options.mPseudo.WasPassed()) {
8546 Maybe<PseudoStyleType> type =
8547 nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
8548 if (!type || *type == PseudoStyleType::NotPseudo ||
8549 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
8550 rv.ThrowNotSupportedError("Invalid pseudo-element");
8551 return nullptr;
8553 pseudoType = *type;
8557 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
8558 nullptr, mDefaultElementType, is);
8560 if (pseudoType != PseudoStyleType::NotPseudo) {
8561 elem->SetPseudoElementType(pseudoType);
8564 return elem.forget();
8567 already_AddRefed<Element> Document::CreateElementNS(
8568 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8569 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
8570 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8571 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8572 mNodeInfoManager, ELEMENT_NODE,
8573 getter_AddRefs(nodeInfo));
8574 if (rv.Failed()) {
8575 return nullptr;
8578 const nsString* is = nullptr;
8579 if (aOptions.IsElementCreationOptions()) {
8580 const ElementCreationOptions& options =
8581 aOptions.GetAsElementCreationOptions();
8582 if (options.mIs.WasPassed()) {
8583 is = &options.mIs.Value();
8587 nsCOMPtr<Element> element;
8588 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
8589 NOT_FROM_PARSER, is);
8590 if (rv.Failed()) {
8591 return nullptr;
8594 return element.forget();
8597 already_AddRefed<Element> Document::CreateXULElement(
8598 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8599 ErrorResult& aRv) {
8600 aRv = nsContentUtils::CheckQName(aTagName, false);
8601 if (aRv.Failed()) {
8602 return nullptr;
8605 const nsString* is = nullptr;
8606 if (aOptions.IsElementCreationOptions()) {
8607 const ElementCreationOptions& options =
8608 aOptions.GetAsElementCreationOptions();
8609 if (options.mIs.WasPassed()) {
8610 is = &options.mIs.Value();
8614 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
8615 if (!elem) {
8616 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
8617 return nullptr;
8619 return elem.forget();
8622 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
8623 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8624 return text.forget();
8627 already_AddRefed<nsTextNode> Document::CreateTextNode(
8628 const nsAString& aData) const {
8629 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8630 // Don't notify; this node is still being created.
8631 text->SetText(aData, false);
8632 return text.forget();
8635 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
8636 RefPtr<DocumentFragment> frag =
8637 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
8638 return frag.forget();
8641 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
8642 already_AddRefed<dom::Comment> Document::CreateComment(
8643 const nsAString& aData) const {
8644 RefPtr<dom::Comment> comment =
8645 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
8647 // Don't notify; this node is still being created.
8648 comment->SetText(aData, false);
8649 return comment.forget();
8652 already_AddRefed<CDATASection> Document::CreateCDATASection(
8653 const nsAString& aData, ErrorResult& rv) {
8654 if (IsHTMLDocument()) {
8655 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8656 return nullptr;
8659 if (FindInReadable(u"]]>"_ns, aData)) {
8660 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8661 return nullptr;
8664 RefPtr<CDATASection> cdata =
8665 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
8667 // Don't notify; this node is still being created.
8668 cdata->SetText(aData, false);
8670 return cdata.forget();
8673 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
8674 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
8675 nsresult res = nsContentUtils::CheckQName(aTarget, false);
8676 if (NS_FAILED(res)) {
8677 rv.Throw(res);
8678 return nullptr;
8681 if (FindInReadable(u"?>"_ns, aData)) {
8682 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8683 return nullptr;
8686 RefPtr<ProcessingInstruction> pi =
8687 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
8689 return pi.forget();
8692 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
8693 ErrorResult& rv) {
8694 if (!mNodeInfoManager) {
8695 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8696 return nullptr;
8699 nsresult res = nsContentUtils::CheckQName(aName, false);
8700 if (NS_FAILED(res)) {
8701 rv.Throw(res);
8702 return nullptr;
8705 nsAutoString name;
8706 if (IsHTMLDocument()) {
8707 nsContentUtils::ASCIIToLower(aName, name);
8708 } else {
8709 name = aName;
8712 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8713 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
8714 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
8715 if (NS_FAILED(res)) {
8716 rv.Throw(res);
8717 return nullptr;
8720 RefPtr<Attr> attribute =
8721 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8722 return attribute.forget();
8725 already_AddRefed<Attr> Document::CreateAttributeNS(
8726 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8727 ErrorResult& rv) {
8728 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8729 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8730 mNodeInfoManager, ATTRIBUTE_NODE,
8731 getter_AddRefs(nodeInfo));
8732 if (rv.Failed()) {
8733 return nullptr;
8736 RefPtr<Attr> attribute =
8737 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8738 return attribute.forget();
8741 void Document::ScheduleForPresAttrEvaluation(Element* aElement) {
8742 MOZ_ASSERT(aElement->IsInComposedDoc());
8743 DebugOnly<bool> inserted = mLazyPresElements.EnsureInserted(aElement);
8744 MOZ_ASSERT(inserted);
8745 if (aElement->HasServoData()) {
8746 // TODO(emilio): RESTYLE_SELF is too strong, there should be no need to
8747 // re-selector-match, but right now this is needed to pick up the new mapped
8748 // attributes. We need something like RESTYLE_STYLE_ATTRIBUTE but for mapped
8749 // attributes.
8750 nsLayoutUtils::PostRestyleEvent(aElement, RestyleHint::RESTYLE_SELF,
8751 nsChangeHint(0));
8755 void Document::UnscheduleForPresAttrEvaluation(Element* aElement) {
8756 mLazyPresElements.Remove(aElement);
8759 void Document::DoResolveScheduledPresAttrs() {
8760 MOZ_ASSERT(!mLazyPresElements.IsEmpty());
8761 for (Element* el : mLazyPresElements) {
8762 MOZ_ASSERT(el->IsInComposedDoc(),
8763 "Un-schedule when removing from the document");
8764 MOZ_ASSERT(el->IsPendingMappedAttributeEvaluation());
8765 if (auto* svg = SVGElement::FromNode(el)) {
8766 // SVG does its own (very similar) thing, for now at least.
8767 svg->UpdateMappedDeclarationBlock();
8768 } else {
8769 MappedDeclarationsBuilder builder(*el, *this,
8770 el->GetMappedAttributeStyle());
8771 auto function = el->GetAttributeMappingFunction();
8772 function(builder);
8773 el->SetMappedDeclarationBlock(builder.TakeDeclarationBlock());
8775 MOZ_ASSERT(!el->IsPendingMappedAttributeEvaluation());
8777 mLazyPresElements.Clear();
8780 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
8781 const {
8782 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
8784 for (const nsWeakPtr& weakNode : mBlockedNodesByClassifier) {
8785 if (nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode)) {
8786 // Consider only nodes to which we have managed to get strong references.
8787 // Coping with nullptrs since it's expected for nodes to disappear when
8788 // nobody else is referring to them.
8789 list->AppendElement(node);
8793 return list.forget();
8796 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
8797 aSheetSet.Truncate();
8799 // Look through our sheets, find the selected set title
8800 size_t count = SheetCount();
8801 nsAutoString title;
8802 for (size_t index = 0; index < count; index++) {
8803 StyleSheet* sheet = SheetAt(index);
8804 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8806 if (sheet->Disabled()) {
8807 // Disabled sheets don't affect the currently selected set
8808 continue;
8811 sheet->GetTitle(title);
8813 if (aSheetSet.IsEmpty()) {
8814 aSheetSet = title;
8815 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
8816 // Sheets from multiple sets enabled; return null string, per spec.
8817 SetDOMStringToNull(aSheetSet);
8818 return;
8823 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
8824 if (DOMStringIsNull(aSheetSet)) {
8825 return;
8828 // Must update mLastStyleSheetSet before doing anything else with stylesheets
8829 // or CSSLoaders.
8830 mLastStyleSheetSet = aSheetSet;
8831 EnableStyleSheetsForSetInternal(aSheetSet, true);
8834 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
8835 mPreferredStyleSheetSet = aSheetSet;
8836 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
8837 // spec.
8838 if (DOMStringIsNull(mLastStyleSheetSet)) {
8839 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
8840 // per spec. The idea here is that we're changing our preferred set and
8841 // that shouldn't change the value of lastStyleSheetSet. Also, we're
8842 // using the Internal version so we can update the CSSLoader and not have
8843 // to worry about null strings.
8844 EnableStyleSheetsForSetInternal(aSheetSet, true);
8848 DOMStringList* Document::StyleSheetSets() {
8849 if (!mStyleSheetSetList) {
8850 mStyleSheetSetList = new DOMStyleSheetSetList(this);
8852 return mStyleSheetSetList;
8855 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
8856 // Per spec, passing in null is a no-op.
8857 if (!DOMStringIsNull(aSheetSet)) {
8858 // Note: must make sure to not change the CSSLoader's preferred sheet --
8859 // that value should be equal to either our lastStyleSheetSet (if that's
8860 // non-null) or to our preferredStyleSheetSet. And this method doesn't
8861 // change either of those.
8862 EnableStyleSheetsForSetInternal(aSheetSet, false);
8866 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
8867 bool aUpdateCSSLoader) {
8868 size_t count = SheetCount();
8869 nsAutoString title;
8870 for (size_t index = 0; index < count; index++) {
8871 StyleSheet* sheet = SheetAt(index);
8872 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8874 sheet->GetTitle(title);
8875 if (!title.IsEmpty()) {
8876 sheet->SetEnabled(title.Equals(aSheetSet));
8879 if (aUpdateCSSLoader) {
8880 CSSLoader()->DocumentStyleSheetSetChanged();
8882 if (EnsureStyleSet().StyleSheetsHaveChanged()) {
8883 ApplicableStylesChanged();
8887 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
8888 nsAutoCString charset;
8889 GetDocumentCharacterSet()->Name(charset);
8890 CopyASCIItoUTF16(charset, aCharacterSet);
8893 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
8894 ErrorResult& rv) const {
8895 nsINode* imported = &aNode;
8897 switch (imported->NodeType()) {
8898 case DOCUMENT_NODE: {
8899 break;
8901 case DOCUMENT_FRAGMENT_NODE:
8902 case ATTRIBUTE_NODE:
8903 case ELEMENT_NODE:
8904 case PROCESSING_INSTRUCTION_NODE:
8905 case TEXT_NODE:
8906 case CDATA_SECTION_NODE:
8907 case COMMENT_NODE:
8908 case DOCUMENT_TYPE_NODE: {
8909 return imported->Clone(aDeep, mNodeInfoManager, rv);
8911 default: {
8912 NS_WARNING("Don't know how to clone this nodetype for importNode.");
8916 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8917 return nullptr;
8920 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
8921 return nsRange::Create(this, 0, this, 0, rv);
8924 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
8925 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
8926 ErrorResult& rv) const {
8927 RefPtr<NodeIterator> iterator =
8928 new NodeIterator(&aRoot, aWhatToShow, aFilter);
8929 return iterator.forget();
8932 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
8933 uint32_t aWhatToShow,
8934 NodeFilter* aFilter,
8935 ErrorResult& rv) const {
8936 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
8937 return walker.forget();
8940 already_AddRefed<Location> Document::GetLocation() const {
8941 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
8943 if (!w) {
8944 return nullptr;
8947 return do_AddRef(w->Location());
8950 already_AddRefed<nsIURI> Document::GetDomainURI() {
8951 nsIPrincipal* principal = NodePrincipal();
8953 nsCOMPtr<nsIURI> uri;
8954 principal->GetDomain(getter_AddRefs(uri));
8955 if (uri) {
8956 return uri.forget();
8958 auto* basePrin = BasePrincipal::Cast(principal);
8959 basePrin->GetURI(getter_AddRefs(uri));
8960 return uri.forget();
8963 void Document::GetDomain(nsAString& aDomain) {
8964 nsCOMPtr<nsIURI> uri = GetDomainURI();
8966 if (!uri) {
8967 aDomain.Truncate();
8968 return;
8971 nsAutoCString hostName;
8972 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
8973 if (NS_SUCCEEDED(rv)) {
8974 CopyUTF8toUTF16(hostName, aDomain);
8975 } else {
8976 // If we can't get the host from the URI (e.g. about:, javascript:,
8977 // etc), just return an empty string.
8978 aDomain.Truncate();
8982 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
8983 if (!GetBrowsingContext()) {
8984 // If our browsing context is null; disallow setting domain
8985 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8986 return;
8989 if (mSandboxFlags & SANDBOXED_DOMAIN) {
8990 // We're sandboxed; disallow setting domain
8991 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8992 return;
8995 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
8996 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8997 return;
9000 if (aDomain.IsEmpty()) {
9001 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
9002 return;
9005 nsCOMPtr<nsIURI> uri = GetDomainURI();
9006 if (!uri) {
9007 rv.Throw(NS_ERROR_FAILURE);
9008 return;
9011 // Check new domain - must be a superdomain of the current host
9012 // For example, a page from foo.bar.com may set domain to bar.com,
9013 // but not to ar.com, baz.com, or fi.foo.bar.com.
9015 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
9016 if (!newURI) {
9017 // Error: illegal domain
9018 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
9019 return;
9022 if (GetBrowsingContext()->Group()->IsPotentiallyCrossOriginIsolated()) {
9023 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
9024 return;
9027 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
9028 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
9029 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
9030 wgc->SendSetDocumentDomain(WrapNotNull(newURI));
9034 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
9035 const nsACString& aHostString) {
9036 if (aHostString.IsEmpty()) {
9037 return nullptr;
9040 // Create new URI
9041 nsCOMPtr<nsIURI> uri = GetDomainURI();
9042 if (!uri) {
9043 return nullptr;
9046 nsresult rv;
9047 rv = NS_MutateURI(uri)
9048 .SetUserPass(""_ns)
9049 .SetPort(-1) // we want to reset the port number if needed.
9050 .SetHostPort(aHostString)
9051 .Finalize(uri);
9052 if (NS_FAILED(rv)) {
9053 return nullptr;
9056 return uri.forget();
9059 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
9060 const nsAString& aNewDomain, nsIURI* aOrigHost) {
9061 if (NS_WARN_IF(!aOrigHost)) {
9062 return nullptr;
9065 nsCOMPtr<nsIURI> newURI =
9066 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
9067 if (!newURI) {
9068 // Error: failed to parse input domain
9069 return nullptr;
9072 if (!IsValidDomain(aOrigHost, newURI)) {
9073 // Error: illegal domain
9074 return nullptr;
9077 nsAutoCString domain;
9078 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
9079 return nullptr;
9082 return CreateInheritingURIForHost(domain);
9085 /* static */
9086 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
9087 // Check new domain - must be a superdomain of the current host
9088 // For example, a page from foo.bar.com may set domain to bar.com,
9089 // but not to ar.com, baz.com, or fi.foo.bar.com.
9090 nsAutoCString current;
9091 nsAutoCString domain;
9092 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
9093 current.Truncate();
9095 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
9096 domain.Truncate();
9099 bool ok = current.Equals(domain);
9100 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
9101 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
9102 // We're golden if the new domain is the current page's base domain or a
9103 // subdomain of it.
9104 nsCOMPtr<nsIEffectiveTLDService> tldService =
9105 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
9106 if (!tldService) {
9107 return false;
9110 nsAutoCString currentBaseDomain;
9111 ok = NS_SUCCEEDED(
9112 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
9113 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
9114 (domain.Length() >= currentBaseDomain.Length()),
9115 "uh-oh! slight optimization wasn't valid somehow!");
9116 ok = ok && domain.Length() >= currentBaseDomain.Length();
9119 return ok;
9122 Element* Document::GetHtmlElement() const {
9123 Element* rootElement = GetRootElement();
9124 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
9125 return rootElement;
9126 return nullptr;
9129 Element* Document::GetHtmlChildElement(nsAtom* aTag) {
9130 Element* html = GetHtmlElement();
9131 if (!html) return nullptr;
9133 // Look for the element with aTag inside html. This needs to run
9134 // forwards to find the first such element.
9135 for (nsIContent* child = html->GetFirstChild(); child;
9136 child = child->GetNextSibling()) {
9137 if (child->IsHTMLElement(aTag)) return child->AsElement();
9139 return nullptr;
9142 nsGenericHTMLElement* Document::GetBody() {
9143 Element* html = GetHtmlElement();
9144 if (!html) {
9145 return nullptr;
9148 for (nsIContent* child = html->GetFirstChild(); child;
9149 child = child->GetNextSibling()) {
9150 if (child->IsHTMLElement(nsGkAtoms::body) ||
9151 child->IsHTMLElement(nsGkAtoms::frameset)) {
9152 return static_cast<nsGenericHTMLElement*>(child);
9156 return nullptr;
9159 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
9160 nsCOMPtr<Element> root = GetRootElement();
9162 // The body element must be either a body tag or a frameset tag. And we must
9163 // have a root element to be able to add kids to it.
9164 if (!newBody ||
9165 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
9166 rv.ThrowHierarchyRequestError(
9167 "The new body must be either a body tag or frameset tag.");
9168 return;
9171 if (!root) {
9172 rv.ThrowHierarchyRequestError("No root element.");
9173 return;
9176 // Use DOM methods so that we pass through the appropriate security checks.
9177 nsCOMPtr<Element> currentBody = GetBody();
9178 if (currentBody) {
9179 root->ReplaceChild(*newBody, *currentBody, rv);
9180 } else {
9181 root->AppendChild(*newBody, rv);
9185 HTMLSharedElement* Document::GetHead() {
9186 return static_cast<HTMLSharedElement*>(GetHeadElement());
9189 Element* Document::GetTitleElement() {
9190 // mMayHaveTitleElement will have been set to true if any HTML or SVG
9191 // <title> element has been bound to this document. So if it's false,
9192 // we know there is nothing to do here. This avoids us having to search
9193 // the whole DOM if someone calls document.title on a large document
9194 // without a title.
9195 if (!mMayHaveTitleElement) {
9196 return nullptr;
9199 Element* root = GetRootElement();
9200 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
9201 // In SVG, the document's title must be a child
9202 for (nsIContent* child = root->GetFirstChild(); child;
9203 child = child->GetNextSibling()) {
9204 if (child->IsSVGElement(nsGkAtoms::title)) {
9205 return child->AsElement();
9208 return nullptr;
9211 // We check the HTML namespace even for non-HTML documents, except SVG. This
9212 // matches the spec and the behavior of all tested browsers.
9213 for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) {
9214 if (node->IsHTMLElement(nsGkAtoms::title)) {
9215 return node->AsElement();
9218 return nullptr;
9221 void Document::GetTitle(nsAString& aTitle) {
9222 aTitle.Truncate();
9224 Element* rootElement = GetRootElement();
9225 if (!rootElement) {
9226 return;
9229 if (rootElement->IsXULElement()) {
9230 rootElement->GetAttr(nsGkAtoms::title, aTitle);
9231 } else if (Element* title = GetTitleElement()) {
9232 nsContentUtils::GetNodeTextContent(title, false, aTitle);
9233 } else {
9234 return;
9237 aTitle.CompressWhitespace();
9240 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
9241 Element* rootElement = GetRootElement();
9242 if (!rootElement) {
9243 return;
9246 if (rootElement->IsXULElement()) {
9247 aRv =
9248 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
9249 return;
9252 Maybe<mozAutoDocUpdate> updateBatch;
9253 nsCOMPtr<Element> title = GetTitleElement();
9254 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
9255 if (!title) {
9256 // Batch updates so that mutation events don't change "the title
9257 // element" under us
9258 updateBatch.emplace(this, true);
9259 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
9260 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
9261 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
9262 NOT_FROM_PARSER);
9263 if (!title) {
9264 return;
9266 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
9267 IgnoreErrors());
9269 } else if (rootElement->IsHTMLElement()) {
9270 if (!title) {
9271 // Batch updates so that mutation events don't change "the title
9272 // element" under us
9273 updateBatch.emplace(this, true);
9274 Element* head = GetHeadElement();
9275 if (!head) {
9276 return;
9279 RefPtr<mozilla::dom::NodeInfo> titleInfo;
9280 titleInfo = mNodeInfoManager->GetNodeInfo(
9281 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
9282 title = NS_NewHTMLTitleElement(titleInfo.forget());
9283 if (!title) {
9284 return;
9287 head->AppendChildTo(title, true, IgnoreErrors());
9289 } else {
9290 return;
9293 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
9296 class Document::TitleChangeEvent final : public Runnable {
9297 public:
9298 explicit TitleChangeEvent(Document* aDoc)
9299 : Runnable("Document::TitleChangeEvent"),
9300 mDoc(aDoc)
9301 #ifndef ANDROID
9303 mBlockOnload(aDoc->IsInChromeDocShell())
9304 #endif
9306 if (mBlockOnload) {
9307 mDoc->BlockOnload();
9311 NS_IMETHOD Run() final {
9312 if (!mDoc) {
9313 return NS_OK;
9315 const RefPtr<Document> doc = mDoc;
9316 const bool blockOnload = mBlockOnload;
9317 mDoc = nullptr;
9318 doc->DoNotifyPossibleTitleChange();
9319 if (blockOnload) {
9320 doc->UnblockOnload(/* aFireSync = */ true);
9322 return NS_OK;
9325 void Revoke() {
9326 if (mDoc) {
9327 if (mBlockOnload) {
9328 mDoc->UnblockOnload(/* aFireSync = */ false);
9330 mDoc = nullptr;
9334 private:
9335 // Weak, caller is responsible for calling Revoke() when needed.
9336 Document* mDoc;
9337 // title changes should block the load event on chrome docshells, so that the
9338 // window title is consistently set by the time the top window is displayed.
9339 // Otherwise, some window manager integrations don't work properly,
9340 // see bug 1874766.
9341 const bool mBlockOnload = false;
9344 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
9345 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
9346 "Setting a title while unlinking or destroying the element?");
9347 if (mInUnlinkOrDeletion) {
9348 return;
9351 if (aBoundTitleElement) {
9352 mMayHaveTitleElement = true;
9355 if (mPendingTitleChangeEvent.IsPending()) {
9356 return;
9359 MOZ_RELEASE_ASSERT(NS_IsMainThread());
9360 RefPtr<TitleChangeEvent> event = new TitleChangeEvent(this);
9361 if (NS_WARN_IF(NS_FAILED(Dispatch(do_AddRef(event))))) {
9362 event->Revoke();
9363 return;
9365 mPendingTitleChangeEvent = std::move(event);
9368 void Document::DoNotifyPossibleTitleChange() {
9369 if (!mPendingTitleChangeEvent.IsPending()) {
9370 return;
9372 // Make sure the pending runnable method is cleared.
9373 mPendingTitleChangeEvent.Forget();
9374 mHaveFiredTitleChange = true;
9376 nsAutoString title;
9377 GetTitle(title);
9379 if (RefPtr<PresShell> presShell = GetPresShell()) {
9380 nsCOMPtr<nsISupports> container =
9381 presShell->GetPresContext()->GetContainerWeak();
9382 if (container) {
9383 if (nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container)) {
9384 docShellWin->SetTitle(title);
9389 if (WindowGlobalChild* child = GetWindowGlobalChild()) {
9390 child->SendUpdateDocumentTitle(title);
9393 // Fire a DOM event for the title change.
9394 nsContentUtils::DispatchChromeEvent(this, this, u"DOMTitleChanged"_ns,
9395 CanBubble::eYes, Cancelable::eYes);
9397 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
9398 if (obs) {
9399 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
9403 already_AddRefed<MediaQueryList> Document::MatchMedia(
9404 const nsACString& aMediaQueryList, CallerType aCallerType) {
9405 RefPtr<MediaQueryList> result =
9406 new MediaQueryList(this, aMediaQueryList, aCallerType);
9408 mDOMMediaQueryLists.insertBack(result);
9410 return result.forget();
9413 void Document::SetMayStartLayout(bool aMayStartLayout) {
9414 mMayStartLayout = aMayStartLayout;
9415 if (MayStartLayout()) {
9416 // Before starting layout, check whether we're a toplevel chrome
9417 // window. If we are, setup some state so that we don't have to restyle
9418 // the whole tree after StartLayout.
9419 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
9420 // We're the chrome document!
9421 win->BeforeStartLayout();
9423 ReadyState state = GetReadyStateEnum();
9424 if (state >= READYSTATE_INTERACTIVE) {
9425 // DOMContentLoaded has fired already.
9426 MaybeResolveReadyForIdle();
9430 MaybeEditingStateChanged();
9433 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
9434 mInitializableFrameLoaders.RemoveElement(aLoader);
9435 // Don't even try to initialize.
9436 if (mInDestructor) {
9437 NS_WARNING(
9438 "Trying to initialize a frame loader while"
9439 "document is being deleted");
9440 return NS_ERROR_FAILURE;
9443 mInitializableFrameLoaders.AppendElement(aLoader);
9444 if (!mFrameLoaderRunner) {
9445 mFrameLoaderRunner =
9446 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9447 &Document::MaybeInitializeFinalizeFrameLoaders);
9448 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9449 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9451 return NS_OK;
9454 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
9455 nsIRunnable* aFinalizer) {
9456 mInitializableFrameLoaders.RemoveElement(aLoader);
9457 if (mInDestructor) {
9458 return NS_ERROR_FAILURE;
9461 LogRunnable::LogDispatch(aFinalizer);
9462 mFrameLoaderFinalizers.AppendElement(aFinalizer);
9463 if (!mFrameLoaderRunner) {
9464 mFrameLoaderRunner =
9465 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9466 &Document::MaybeInitializeFinalizeFrameLoaders);
9467 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9468 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9470 return NS_OK;
9473 void Document::MaybeInitializeFinalizeFrameLoaders() {
9474 if (mDelayFrameLoaderInitialization) {
9475 // This method will be recalled when !mDelayFrameLoaderInitialization.
9476 mFrameLoaderRunner = nullptr;
9477 return;
9480 // We're not in an update, but it is not safe to run scripts, so
9481 // postpone frameloader initialization and finalization.
9482 if (!nsContentUtils::IsSafeToRunScript()) {
9483 if (!mInDestructor && !mFrameLoaderRunner &&
9484 (mInitializableFrameLoaders.Length() ||
9485 mFrameLoaderFinalizers.Length())) {
9486 mFrameLoaderRunner = NewRunnableMethod(
9487 "Document::MaybeInitializeFinalizeFrameLoaders", this,
9488 &Document::MaybeInitializeFinalizeFrameLoaders);
9489 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9491 return;
9493 mFrameLoaderRunner = nullptr;
9495 // Don't use a temporary array for mInitializableFrameLoaders, because
9496 // loading a frame may cause some other frameloader to be removed from the
9497 // array. But be careful to keep the loader alive when starting the load!
9498 while (mInitializableFrameLoaders.Length()) {
9499 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
9500 mInitializableFrameLoaders.RemoveElementAt(0);
9501 NS_ASSERTION(loader, "null frameloader in the array?");
9502 loader->ReallyStartLoading();
9505 uint32_t length = mFrameLoaderFinalizers.Length();
9506 if (length > 0) {
9507 nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
9508 std::move(mFrameLoaderFinalizers);
9509 for (uint32_t i = 0; i < length; ++i) {
9510 LogRunnable::Run run(finalizers[i]);
9511 finalizers[i]->Run();
9516 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
9517 uint32_t length = mInitializableFrameLoaders.Length();
9518 for (uint32_t i = 0; i < length; ++i) {
9519 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
9520 mInitializableFrameLoaders.RemoveElementAt(i);
9521 return;
9526 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
9527 mPrototypeDocument = aPrototype;
9528 mSynchronousDOMContentLoaded = true;
9531 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
9532 return GetPermissionDelegateHandler();
9535 Document* Document::RequestExternalResource(
9536 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
9537 ExternalResourceLoad** aPendingLoad) {
9538 MOZ_ASSERT(aURI, "Must have a URI");
9539 MOZ_ASSERT(aRequestingNode, "Must have a node");
9540 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
9541 if (mDisplayDocument) {
9542 return mDisplayDocument->RequestExternalResource(
9543 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
9546 return mExternalResourceMap.RequestResource(
9547 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
9550 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
9551 mExternalResourceMap.EnumerateResources(aCallback);
9554 SMILAnimationController* Document::GetAnimationController() {
9555 // We create the animation controller lazily because most documents won't want
9556 // one and only SVG documents and the like will call this
9557 if (mAnimationController) return mAnimationController;
9558 // Refuse to create an Animation Controller for data documents.
9559 if (mLoadedAsData) return nullptr;
9561 mAnimationController = new SMILAnimationController(this);
9563 // If there's a presContext then check the animation mode and pause if
9564 // necessary.
9565 nsPresContext* context = GetPresContext();
9566 if (mAnimationController && context &&
9567 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
9568 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
9571 // If we're hidden (or being hidden), notify the newly-created animation
9572 // controller. (Skip this check for SVG-as-an-image documents, though,
9573 // because they don't get OnPageShow / OnPageHide calls).
9574 if (!mIsShowing && !mIsBeingUsedAsImage) {
9575 mAnimationController->OnPageHide();
9578 return mAnimationController;
9581 ScrollTimelineAnimationTracker*
9582 Document::GetOrCreateScrollTimelineAnimationTracker() {
9583 if (!mScrollTimelineAnimationTracker) {
9584 mScrollTimelineAnimationTracker = new ScrollTimelineAnimationTracker(this);
9587 return mScrollTimelineAnimationTracker;
9591 * Retrieve the "direction" property of the document.
9593 * @lina 01/09/2001
9595 void Document::GetDir(nsAString& aDirection) const {
9596 aDirection.Truncate();
9597 Element* rootElement = GetHtmlElement();
9598 if (rootElement) {
9599 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
9604 * Set the "direction" property of the document.
9606 * @lina 01/09/2001
9608 void Document::SetDir(const nsAString& aDirection) {
9609 Element* rootElement = GetHtmlElement();
9610 if (rootElement) {
9611 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
9615 nsIHTMLCollection* Document::Images() {
9616 if (!mImages) {
9617 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
9618 nsGkAtoms::img);
9620 return mImages;
9623 nsIHTMLCollection* Document::Embeds() {
9624 if (!mEmbeds) {
9625 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
9626 nsGkAtoms::embed);
9628 return mEmbeds;
9631 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9632 void* aData) {
9633 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
9634 aElement->HasAttr(nsGkAtoms::href);
9637 nsIHTMLCollection* Document::Links() {
9638 if (!mLinks) {
9639 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
9641 return mLinks;
9644 nsIHTMLCollection* Document::Forms() {
9645 if (!mForms) {
9646 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
9647 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
9648 nsGkAtoms::form);
9651 return mForms;
9654 nsIHTMLCollection* Document::Scripts() {
9655 if (!mScripts) {
9656 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
9657 nsGkAtoms::script);
9659 return mScripts;
9662 nsIHTMLCollection* Document::Applets() {
9663 if (!mApplets) {
9664 mApplets = new nsEmptyContentList(this);
9666 return mApplets;
9669 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9670 void* aData) {
9671 return aElement->IsHTMLElement(nsGkAtoms::a) &&
9672 aElement->HasAttr(nsGkAtoms::name);
9675 nsIHTMLCollection* Document::Anchors() {
9676 if (!mAnchors) {
9677 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
9679 return mAnchors;
9682 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
9683 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
9684 ErrorResult& rv) {
9685 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9686 "XOW should have caught this!");
9688 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
9689 if (!window) {
9690 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
9691 return nullptr;
9693 nsCOMPtr<nsPIDOMWindowOuter> outer =
9694 nsPIDOMWindowOuter::GetFromCurrentInner(window);
9695 if (!outer) {
9696 rv.Throw(NS_ERROR_NOT_INITIALIZED);
9697 return nullptr;
9699 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
9700 RefPtr<BrowsingContext> newBC;
9701 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
9702 if (!newBC) {
9703 return nullptr;
9705 return WindowProxyHolder(std::move(newBC));
9708 Document* Document::Open(const Optional<nsAString>& /* unused */,
9709 const Optional<nsAString>& /* unused */,
9710 ErrorResult& aError) {
9711 // Implements
9712 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
9714 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9715 "XOW should have caught this!");
9717 // Step 1 -- throw if we're an XML document.
9718 if (!IsHTMLDocument() || mDisableDocWrite) {
9719 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9720 return nullptr;
9723 // Step 2 -- throw if dynamic markup insertion should throw.
9724 if (ShouldThrowOnDynamicMarkupInsertion()) {
9725 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9726 return nullptr;
9729 // Step 3 -- get the entry document, so we can use it for security checks.
9730 nsCOMPtr<Document> callerDoc = GetEntryDocument();
9731 if (!callerDoc) {
9732 // If we're called from C++ or in some other way without an originating
9733 // document we can't do a document.open w/o changing the principal of the
9734 // document to something like about:blank (as that's the only sane thing to
9735 // do when we don't know the origin of this call), and since we can't
9736 // change the principals of a document for security reasons we'll have to
9737 // refuse to go ahead with this call.
9739 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9740 return nullptr;
9743 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
9744 // the entry document.
9745 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
9746 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9747 return nullptr;
9750 // Step 5 -- if we have an active parser with a nonzero script nesting level,
9751 // just no-op.
9752 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
9753 return this;
9756 // Step 6 -- check for open() during unload. Per spec, this is just a check
9757 // of the ignore-opens-during-unload counter, but our unload event code
9758 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
9759 // do), so we check for unload directly.
9760 if (ShouldIgnoreOpens()) {
9761 return this;
9764 RefPtr<nsDocShell> shell(mDocumentContainer);
9765 if (shell) {
9766 bool inUnload;
9767 shell->GetIsInUnload(&inUnload);
9768 if (inUnload) {
9769 return this;
9773 // At this point we know this is a valid-enough document.open() call
9774 // and not a no-op. Increment our use counter.
9775 SetUseCounter(eUseCounter_custom_DocumentOpen);
9777 // Step 7 -- stop existing navigation of our browsing context (and all other
9778 // loads it's doing) if we're the active document of our browsing context.
9779 // Note that we do not want to stop anything if there is no existing
9780 // navigation.
9781 if (shell && IsCurrentActiveDocument() &&
9782 shell->GetIsAttemptingToNavigate()) {
9783 shell->Stop(nsIWebNavigation::STOP_NETWORK);
9785 // The Stop call may have cancelled the onload blocker request or
9786 // prevented it from getting added, so we need to make sure it gets added
9787 // to the document again otherwise the document could have a non-zero
9788 // onload block count without the onload blocker request being in the
9789 // loadgroup.
9790 EnsureOnloadBlocker();
9793 // Step 8 -- clear event listeners out of our DOM tree
9794 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
9795 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
9796 elm->RemoveAllListeners();
9800 // Step 9 -- clear event listeners from our window, if we have one.
9802 // Note that we explicitly want the inner window, and only if we're its
9803 // document. We want to do this (per spec) even when we're not the "active
9804 // document", so we can't go through GetWindow(), because it might forward to
9805 // the wrong inner.
9806 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
9807 if (win->GetExtantDoc() == this) {
9808 if (EventListenerManager* elm =
9809 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
9810 elm->RemoveAllListeners();
9815 // If we have a parser that has a zero script nesting level, we need to
9816 // properly terminate it. We do that after we've removed all the event
9817 // listeners (so termination won't trigger event listeners if it does
9818 // something to the DOM), but before we remove all elements from the document
9819 // (so if termination does modify the DOM in some way we will just blow it
9820 // away immediately. See the similar code in WriteCommon that handles the
9821 // !IsInsertionPointDefined() case and should stay in sync with this code.
9822 if (mParser) {
9823 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
9824 "Why didn't we take the early return?");
9825 // Make sure we don't re-enter.
9826 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9827 mParser->Terminate();
9828 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9831 // Step 10 -- remove all our DOM kids without firing any mutation events.
9833 // We want to ignore any recursive calls to Open() that happen while
9834 // disconnecting the node tree. The spec doesn't say to do this, but the
9835 // spec also doesn't envision unload events on subframes firing while we do
9836 // this, while all browsers fire them in practice. See
9837 // <https://github.com/whatwg/html/issues/4611>.
9838 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9839 DisconnectNodeTree();
9842 // Step 11 -- if we're the current document in our docshell, do the
9843 // equivalent of pushState() with the new URL we should have.
9844 if (shell && IsCurrentActiveDocument()) {
9845 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
9846 if (callerDoc != this) {
9847 nsCOMPtr<nsIURI> noFragmentURI;
9848 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
9849 if (NS_WARN_IF(NS_FAILED(rv))) {
9850 aError.Throw(rv);
9851 return nullptr;
9853 newURI = std::move(noFragmentURI);
9856 // UpdateURLAndHistory might do various member-setting, so make sure we're
9857 // holding strong refs to all the refcounted args on the stack. We can
9858 // assume that our caller is holding on to "this" already.
9859 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
9860 bool equalURIs;
9861 nsresult rv = currentURI->Equals(newURI, &equalURIs);
9862 if (NS_WARN_IF(NS_FAILED(rv))) {
9863 aError.Throw(rv);
9864 return nullptr;
9866 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
9867 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
9868 /* aReplace = */ true, currentURI,
9869 equalURIs);
9870 if (NS_WARN_IF(NS_FAILED(rv))) {
9871 aError.Throw(rv);
9872 return nullptr;
9875 // And use the security info of the caller document as well, since
9876 // it's the thing providing our data.
9877 mSecurityInfo = callerDoc->GetSecurityInfo();
9879 // This is not mentioned in the spec, but I think that's a spec bug. See
9880 // <https://github.com/whatwg/html/issues/4299>. In any case, since our
9881 // URL may be changing away from about:blank here, we really want to unset
9882 // this flag no matter what, since only about:blank can be an initial
9883 // document.
9884 SetIsInitialDocument(false);
9886 // And let our docloader know that it will need to track our load event.
9887 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
9890 // Per spec nothing happens with our URI in other cases, though note
9891 // <https://github.com/whatwg/html/issues/4286>.
9893 // Note that we don't need to do anything here with base URIs per spec.
9894 // That said, this might be assuming that we implement
9895 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
9896 // correctly, which we don't right now for the about:blank case.
9898 // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
9899 mSkipLoadEventAfterClose = mLoadEventFiring;
9901 // Preliminary to steps 13-16. Set our ready state to uninitialized before
9902 // we do anything else, so we can then proceed to later ready state levels.
9903 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
9904 /* updateTimingInformation = */ false);
9905 // Reset a flag that affects readyState behavior.
9906 mSetCompleteAfterDOMContentLoaded = false;
9908 // Step 13 -- set our compat mode to standards.
9909 SetCompatibilityMode(eCompatibility_FullStandards);
9911 // Step 14 -- create a new parser associated with document. This also does
9912 // step 16 implicitly.
9913 mParserAborted = false;
9914 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
9915 mParser = parser;
9916 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
9917 nsresult rv = parser->StartExecutor();
9918 if (NS_WARN_IF(NS_FAILED(rv))) {
9919 aError.Throw(rv);
9920 return nullptr;
9923 // Clear out our form control state, because the state of controls
9924 // in the pre-open() document should not affect the state of
9925 // controls that are now going to be written.
9926 mLayoutHistoryState = nullptr;
9928 if (shell) {
9929 // Prepare the docshell and the document viewer for the impending
9930 // out-of-band document.write()
9931 shell->PrepareForNewContentModel();
9933 nsCOMPtr<nsIDocumentViewer> viewer;
9934 shell->GetDocViewer(getter_AddRefs(viewer));
9935 if (viewer) {
9936 viewer->LoadStart(this);
9940 // Step 15.
9941 SetReadyStateInternal(Document::READYSTATE_LOADING,
9942 /* updateTimingInformation = */ false);
9944 // Step 16 happened with step 14 above.
9946 // Step 17.
9947 return this;
9950 void Document::Close(ErrorResult& rv) {
9951 if (!IsHTMLDocument()) {
9952 // No calling document.close() on XHTML!
9954 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9955 return;
9958 if (ShouldThrowOnDynamicMarkupInsertion()) {
9959 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9960 return;
9963 if (!mParser || !mParser->IsScriptCreated()) {
9964 return;
9967 ++mWriteLevel;
9968 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
9969 ->Parse(u""_ns, nullptr, true);
9970 --mWriteLevel;
9973 void Document::WriteCommon(const Sequence<nsString>& aText,
9974 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
9975 // Fast path the common case
9976 if (aText.Length() == 1) {
9977 WriteCommon(aText[0], aNewlineTerminate, rv);
9978 } else {
9979 // XXXbz it would be nice if we could pass all the strings to the parser
9980 // without having to do all this copying and then ask it to start
9981 // parsing....
9982 nsString text;
9983 for (size_t i = 0; i < aText.Length(); ++i) {
9984 text.Append(aText[i]);
9986 WriteCommon(text, aNewlineTerminate, rv);
9990 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
9991 ErrorResult& aRv) {
9992 #ifdef DEBUG
9994 // Assert that we do not use or accidentally introduce doc.write()
9995 // in system privileged context or in any of our about: pages.
9996 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
9997 bool isAboutOrPrivContext = principal->IsSystemPrincipal();
9998 if (!isAboutOrPrivContext) {
9999 if (principal->SchemeIs("about")) {
10000 // about:blank inherits the security contetext and this assertion
10001 // is only meant for actual about: pages.
10002 nsAutoCString host;
10003 principal->GetHost(host);
10004 isAboutOrPrivContext = !host.EqualsLiteral("blank");
10007 // Some automated tests use an empty string to kick off some parsing
10008 // mechansims, but they do not do any harm since they use an empty string.
10009 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
10010 "do not use doc.write in privileged context!");
10012 #endif
10014 mTooDeepWriteRecursion =
10015 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
10016 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
10017 aRv.Throw(NS_ERROR_UNEXPECTED);
10018 return;
10021 if (!IsHTMLDocument() || mDisableDocWrite) {
10022 // No calling document.write*() on XHTML!
10024 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
10025 return;
10028 if (ShouldThrowOnDynamicMarkupInsertion()) {
10029 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
10030 return;
10033 if (mParserAborted) {
10034 // Hixie says aborting the parser doesn't undefine the insertion point.
10035 // However, since we null out mParser in that case, we track the
10036 // theoretically defined insertion point using mParserAborted.
10037 return;
10040 // Implement Step 4.1 of:
10041 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
10042 if (ShouldIgnoreOpens()) {
10043 return;
10046 void* key = GenerateParserKey();
10047 if (mParser && !mParser->IsInsertionPointDefined()) {
10048 if (mIgnoreDestructiveWritesCounter) {
10049 // Instead of implying a call to document.open(), ignore the call.
10050 nsContentUtils::ReportToConsole(
10051 nsIScriptError::warningFlag, "DOM Events"_ns, this,
10052 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
10053 return;
10055 // The spec doesn't tell us to ignore opens from here, but we need to
10056 // ensure opens are ignored here. See similar code in Open() that handles
10057 // the case of an existing parser which is not currently running script and
10058 // should stay in sync with this code.
10059 IgnoreOpensDuringUnload ignoreOpenGuard(this);
10060 mParser->Terminate();
10061 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
10064 if (!mParser) {
10065 if (mIgnoreDestructiveWritesCounter) {
10066 // Instead of implying a call to document.open(), ignore the call.
10067 nsContentUtils::ReportToConsole(
10068 nsIScriptError::warningFlag, "DOM Events"_ns, this,
10069 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
10070 return;
10073 Open({}, {}, aRv);
10075 // If Open() fails, or if it didn't create a parser (as it won't
10076 // if the user chose to not discard the current document through
10077 // onbeforeunload), don't write anything.
10078 if (aRv.Failed() || !mParser) {
10079 return;
10083 static constexpr auto new_line = u"\n"_ns;
10085 ++mWriteLevel;
10087 // This could be done with less code, but for performance reasons it
10088 // makes sense to have the code for two separate Parse() calls here
10089 // since the concatenation of strings costs more than we like. And
10090 // why pay that price when we don't need to?
10091 if (aNewlineTerminate) {
10092 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
10093 ->Parse(aText + new_line, key, false);
10094 } else {
10095 aRv =
10096 (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
10099 --mWriteLevel;
10101 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
10104 void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
10105 WriteCommon(aText, false, rv);
10108 void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
10109 WriteCommon(aText, true, rv);
10112 void* Document::GenerateParserKey(void) {
10113 if (!mScriptLoader) {
10114 // If we don't have a script loader, then the parser probably isn't parsing
10115 // anything anyway, so just return null.
10116 return nullptr;
10119 // The script loader provides us with the currently executing script element,
10120 // which is guaranteed to be unique per script.
10121 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
10122 if (script && mParser && mParser->IsScriptCreated()) {
10123 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
10124 if (creatorParser != mParser) {
10125 // Make scripts that aren't inserted by the active parser of this document
10126 // participate in the context of the script that document.open()ed
10127 // this document.
10128 return nullptr;
10131 return script;
10134 /* static */
10135 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
10136 nsAtom* aAtom, void* aData) {
10137 MOZ_ASSERT(aElement, "Must have element to work with!");
10139 if (!aElement->HasName()) {
10140 return false;
10143 nsString* elementName = static_cast<nsString*>(aData);
10144 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
10145 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
10146 eCaseMatters);
10149 /* static */
10150 void* Document::UseExistingNameString(nsINode* aRootNode,
10151 const nsString* aName) {
10152 return const_cast<nsString*>(aName);
10155 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
10156 if (mDocumentURI) {
10157 nsAutoCString uri;
10158 nsresult rv = mDocumentURI->GetSpec(uri);
10159 NS_ENSURE_SUCCESS(rv, rv);
10161 CopyUTF8toUTF16(uri, aDocumentURI);
10162 } else {
10163 aDocumentURI.Truncate();
10166 return NS_OK;
10169 // Alias of above
10170 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
10172 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
10173 CallerType aCallerType,
10174 ErrorResult& aRv) const {
10175 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
10176 aRv = GetDocumentURI(aDocumentURI);
10177 return;
10180 nsAutoCString uri;
10181 nsresult res = mChromeXHRDocURI->GetSpec(uri);
10182 if (NS_FAILED(res)) {
10183 aRv.Throw(res);
10184 return;
10186 CopyUTF8toUTF16(uri, aDocumentURI);
10189 nsIURI* Document::GetDocumentURIObject() const {
10190 if (!mChromeXHRDocURI) {
10191 return GetDocumentURI();
10194 return mChromeXHRDocURI;
10197 void Document::GetCompatMode(nsString& aCompatMode) const {
10198 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
10199 mCompatMode == eCompatibility_AlmostStandards ||
10200 mCompatMode == eCompatibility_FullStandards,
10201 "mCompatMode is neither quirks nor strict for this document");
10203 if (mCompatMode == eCompatibility_NavQuirks) {
10204 aCompatMode.AssignLiteral("BackCompat");
10205 } else {
10206 aCompatMode.AssignLiteral("CSS1Compat");
10210 } // namespace dom
10211 } // namespace mozilla
10213 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
10214 if (Element* element = Element::FromNode(aNode)) {
10215 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
10216 while (true) {
10217 RefPtr<Attr> attr;
10219 // Use an iterator to get an arbitrary attribute from the
10220 // cache. The iterator must be destroyed before any other
10221 // operations on mAttributeCache, to avoid hash table
10222 // assertions.
10223 auto iter = map->mAttributeCache.ConstIter();
10224 if (iter.Done()) {
10225 break;
10227 attr = iter.UserData();
10230 BlastSubtreeToPieces(attr);
10232 mozilla::DebugOnly<nsresult> rv =
10233 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
10234 attr->NodeInfo()->NameAtom(), false);
10236 // XXX Should we abort here?
10237 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
10241 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
10242 BlastSubtreeToPieces(shadow);
10243 element->UnattachShadow();
10247 while (aNode->HasChildren()) {
10248 nsIContent* node = aNode->GetFirstChild();
10249 BlastSubtreeToPieces(node);
10250 aNode->RemoveChildNode(node, false);
10254 namespace mozilla::dom {
10256 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv,
10257 bool aAcceptShadowRoot) {
10258 OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
10259 if (adoptedNode->IsShadowRoot() && !aAcceptShadowRoot) {
10260 rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
10261 return nullptr;
10264 // Scope firing mutation events so that we don't carry any state that
10265 // might be stale
10267 if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
10268 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
10272 nsAutoScriptBlocker scriptBlocker;
10274 switch (adoptedNode->NodeType()) {
10275 case ATTRIBUTE_NODE: {
10276 // Remove from ownerElement.
10277 OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
10279 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement();
10280 if (rv.Failed()) {
10281 return nullptr;
10284 if (ownerElement) {
10285 OwningNonNull<Attr> newAttr =
10286 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
10287 if (rv.Failed()) {
10288 return nullptr;
10292 break;
10294 case DOCUMENT_FRAGMENT_NODE:
10295 case ELEMENT_NODE:
10296 case PROCESSING_INSTRUCTION_NODE:
10297 case TEXT_NODE:
10298 case CDATA_SECTION_NODE:
10299 case COMMENT_NODE:
10300 case DOCUMENT_TYPE_NODE: {
10301 // Don't allow adopting a node's anonymous subtree out from under it.
10302 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
10303 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10304 return nullptr;
10307 // We don't want to adopt an element into its own contentDocument or into
10308 // a descendant contentDocument, so we check if the frameElement of this
10309 // document or any of its parents is the adopted node or one of its
10310 // descendants.
10311 RefPtr<BrowsingContext> bc = GetBrowsingContext();
10312 while (bc) {
10313 nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
10314 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
10315 rv.ThrowHierarchyRequestError(
10316 "Trying to adopt a node into its own contentDocument or a "
10317 "descendant contentDocument.");
10318 return nullptr;
10321 if (XRE_IsParentProcess()) {
10322 bc = bc->Canonical()->GetParentCrossChromeBoundary();
10323 } else {
10324 bc = bc->GetParent();
10328 // Remove from parent.
10329 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
10330 if (parent) {
10331 parent->RemoveChildNode(adoptedNode->AsContent(), true);
10332 } else {
10333 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
10336 break;
10338 case DOCUMENT_NODE: {
10339 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10340 return nullptr;
10342 default: {
10343 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
10345 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10346 return nullptr;
10350 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
10351 bool sameDocument = oldDocument == this;
10353 AutoJSContext cx;
10354 JS::Rooted<JSObject*> newScope(cx, nullptr);
10355 if (!sameDocument) {
10356 newScope = GetWrapper();
10357 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
10358 // Make sure cx is in a semi-sane compartment before we call WrapNative.
10359 // It's kind of irrelevant, given that we're passing aAllowWrapping =
10360 // false, and documents should always insist on being wrapped in an
10361 // canonical scope. But we try to pass something sane anyway.
10362 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
10363 JSAutoRealm ar(cx, globalObject);
10364 JS::Rooted<JS::Value> v(cx);
10365 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
10366 /* aAllowWrapping = */ false);
10367 if (rv.Failed()) return nullptr;
10368 newScope = &v.toObject();
10372 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
10373 if (rv.Failed()) {
10374 // Disconnect all nodes from their parents, since some have the old document
10375 // as their ownerDocument and some have this as their ownerDocument.
10376 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
10377 return nullptr;
10380 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
10381 "Should still be in the document we just got adopted into");
10383 return adoptedNode;
10386 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
10388 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
10389 const nsString& aScaleString) {
10390 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
10391 if (aScaleString.EqualsLiteral("device-width") ||
10392 aScaleString.EqualsLiteral("device-height")) {
10393 return Some(LayoutDeviceToScreenScale(10.0f));
10394 } else if (aScaleString.EqualsLiteral("yes")) {
10395 return Some(LayoutDeviceToScreenScale(1.0f));
10396 } else if (aScaleString.EqualsLiteral("no")) {
10397 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10398 } else if (aScaleString.IsEmpty()) {
10399 return Nothing();
10402 nsresult scaleErrorCode;
10403 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
10404 if (NS_FAILED(scaleErrorCode)) {
10405 return Some(LayoutDeviceToScreenScale(ViewportMinScale()));
10408 if (scale < 0) {
10409 return Nothing();
10411 return Some(clamped(LayoutDeviceToScreenScale(scale), ViewportMinScale(),
10412 ViewportMaxScale()));
10415 void Document::ParseScalesInViewportMetaData(
10416 const ViewportMetaData& aViewportMetaData) {
10417 Maybe<LayoutDeviceToScreenScale> scale;
10419 scale = ParseScaleString(aViewportMetaData.mInitialScale);
10420 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
10421 mValidScaleFloat = scale.isSome();
10423 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
10424 // Chrome uses '5' for the fallback value of maximum-scale, we might
10425 // consider matching it in future.
10426 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
10427 mScaleMaxFloat = scale.valueOr(ViewportMaxScale());
10428 mValidMaxScale = scale.isSome();
10430 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
10431 mScaleMinFloat = scale.valueOr(ViewportMinScale());
10432 mValidMinScale = scale.isSome();
10434 // Resolve min-zoom and max-zoom values.
10435 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
10436 if (mValidMaxScale && mValidMinScale) {
10437 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
10441 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
10442 const nsAString& aHeightString,
10443 bool aHasValidScale) {
10444 // The width and height properties
10445 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
10447 // The width and height viewport <META> properties are translated into width
10448 // and height descriptors, setting the min-width/min-height value to
10449 // extend-to-zoom and the max-width/max-height value to the length from the
10450 // viewport <META> property as follows:
10452 // 1. Non-negative number values are translated to pixel lengths, clamped to
10453 // the range: [1px, 10000px]
10454 // 2. Negative number values are dropped
10455 // 3. device-width and device-height translate to 100vw and 100vh respectively
10456 // 4. Other keywords and unknown values are also dropped
10457 mMinWidth = nsViewportInfo::kAuto;
10458 mMaxWidth = nsViewportInfo::kAuto;
10459 if (!aWidthString.IsEmpty()) {
10460 mMinWidth = nsViewportInfo::kExtendToZoom;
10461 if (aWidthString.EqualsLiteral("device-width")) {
10462 mMaxWidth = nsViewportInfo::kDeviceSize;
10463 } else {
10464 nsresult widthErrorCode;
10465 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
10466 if (NS_FAILED(widthErrorCode)) {
10467 mMaxWidth = nsViewportInfo::kAuto;
10468 } else if (mMaxWidth >= 0.0f) {
10469 mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
10470 } else {
10471 mMaxWidth = nsViewportInfo::kAuto;
10474 } else if (aHasValidScale) {
10475 if (aHeightString.IsEmpty()) {
10476 mMinWidth = nsViewportInfo::kExtendToZoom;
10477 mMaxWidth = nsViewportInfo::kExtendToZoom;
10479 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
10480 mMinWidth = nsViewportInfo::kExtendToZoom;
10481 mMaxWidth = nsViewportInfo::kDeviceSize;
10484 mMinHeight = nsViewportInfo::kAuto;
10485 mMaxHeight = nsViewportInfo::kAuto;
10486 if (!aHeightString.IsEmpty()) {
10487 mMinHeight = nsViewportInfo::kExtendToZoom;
10488 if (aHeightString.EqualsLiteral("device-height")) {
10489 mMaxHeight = nsViewportInfo::kDeviceSize;
10490 } else {
10491 nsresult heightErrorCode;
10492 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
10493 if (NS_FAILED(heightErrorCode)) {
10494 mMaxHeight = nsViewportInfo::kAuto;
10495 } else if (mMaxHeight >= 0.0f) {
10496 mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
10497 } else {
10498 mMaxHeight = nsViewportInfo::kAuto;
10504 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
10505 MOZ_ASSERT(mPresShell);
10507 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
10508 // widget scale and the full zoom.
10509 nsPresContext* context = mPresShell->GetPresContext();
10510 // When querying the full zoom, get it from the device context rather than
10511 // directly from the pres context, because the device context's value can
10512 // include an adjustment necessary to keep the number of app units per device
10513 // pixel an integer, and we want the adjusted value.
10514 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
10515 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
10516 CSSToLayoutDeviceScale layoutDeviceScale =
10517 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
10519 CSSToScreenScale defaultScale =
10520 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
10522 // Special behaviour for desktop mode, provided we are not on an about: page,
10523 // or fullscreen.
10524 const bool fullscreen = Fullscreen();
10525 auto* bc = GetBrowsingContext();
10526 if (bc && bc->ForceDesktopViewport() && !IsAboutPage() && !fullscreen) {
10527 CSSCoord viewportWidth =
10528 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
10529 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
10530 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
10531 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
10532 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
10533 return nsViewportInfo(fakeDesktopSize, scaleToFit,
10534 nsViewportInfo::ZoomFlag::AllowZoom,
10535 nsViewportInfo::ZoomBehaviour::Mobile,
10536 nsViewportInfo::AutoScaleFlag::AutoScale);
10539 // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
10540 if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
10541 return nsViewportInfo(aDisplaySize, defaultScale,
10542 nsLayoutUtils::AllowZoomingForDocument(this)
10543 ? nsViewportInfo::ZoomFlag::AllowZoom
10544 : nsViewportInfo::ZoomFlag::DisallowZoom,
10545 StaticPrefs::apz_allow_zooming_out()
10546 ? nsViewportInfo::ZoomBehaviour::Mobile
10547 : nsViewportInfo::ZoomBehaviour::Desktop);
10550 // In cases where the width of the CSS viewport is less than or equal to the
10551 // width of the display (i.e. width <= device-width) then we disable
10552 // double-tap-to-zoom behaviour. See bug 941995 for details.
10554 switch (mViewportType) {
10555 case DisplayWidthHeight:
10556 return nsViewportInfo(aDisplaySize, defaultScale,
10557 nsViewportInfo::ZoomFlag::AllowZoom,
10558 nsViewportInfo::ZoomBehaviour::Mobile);
10559 case Unknown: {
10560 // We might early exit if the viewport is empty. Even if we don't,
10561 // at the end of this case we'll note that it was empty. Later, when
10562 // we're using the cached values, this will trigger alternate code paths.
10563 if (!mLastModifiedViewportMetaData) {
10564 // If the docType specifies that we are on a site optimized for mobile,
10565 // then we want to return specially crafted defaults for the viewport
10566 // info.
10567 if (RefPtr<DocumentType> docType = GetDoctype()) {
10568 nsAutoString docId;
10569 docType->GetPublicId(docId);
10570 if ((docId.Find(u"WAP") != -1) || (docId.Find(u"Mobile") != -1) ||
10571 (docId.Find(u"WML") != -1)) {
10572 // We're making an assumption that the docType can't change here
10573 mViewportType = DisplayWidthHeight;
10574 return nsViewportInfo(aDisplaySize, defaultScale,
10575 nsViewportInfo::ZoomFlag::AllowZoom,
10576 nsViewportInfo::ZoomBehaviour::Mobile);
10580 nsAutoString handheldFriendly;
10581 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
10582 if (handheldFriendly.EqualsLiteral("true")) {
10583 mViewportType = DisplayWidthHeight;
10584 return nsViewportInfo(aDisplaySize, defaultScale,
10585 nsViewportInfo::ZoomFlag::AllowZoom,
10586 nsViewportInfo::ZoomBehaviour::Mobile);
10590 ViewportMetaData metaData = GetViewportMetaData();
10592 // Parse initial-scale, minimum-scale and maximum-scale.
10593 ParseScalesInViewportMetaData(metaData);
10595 // Parse width and height properties
10596 // This function sets m{Min,Max}{Width,Height}.
10597 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
10598 mValidScaleFloat);
10600 mAllowZoom = true;
10601 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
10602 (metaData.mUserScalable.EqualsLiteral("no")) ||
10603 (metaData.mUserScalable.EqualsLiteral("false"))) {
10604 mAllowZoom = false;
10607 // Resolve viewport-fit value.
10608 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
10609 mViewportFit = ViewportFitType::Auto;
10610 if (!metaData.mViewportFit.IsEmpty()) {
10611 if (metaData.mViewportFit.EqualsLiteral("contain")) {
10612 mViewportFit = ViewportFitType::Contain;
10613 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
10614 mViewportFit = ViewportFitType::Cover;
10618 mWidthStrEmpty = metaData.mWidth.IsEmpty();
10620 mViewportType = Specified;
10621 [[fallthrough]];
10623 case Specified:
10624 default:
10625 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
10626 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
10627 bool effectiveValidMaxScale = mValidMaxScale;
10629 nsViewportInfo::ZoomFlag effectiveZoomFlag =
10630 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
10631 : nsViewportInfo::ZoomFlag::DisallowZoom;
10632 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
10633 // If the pref to force user-scalable is enabled, we ignore the values
10634 // from the meta-viewport tag for these properties and just assume they
10635 // allow the page to be scalable. Note in particular that this code is
10636 // in the "Specified" branch of the enclosing switch statement, so that
10637 // calls to GetViewportInfo always use the latest value of the
10638 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
10639 // return nsViewportInfo instances are all consistent with
10640 // browser_ui_zoom_force_user_scalable() already.
10641 effectiveMinScale = ViewportMinScale();
10642 effectiveMaxScale = ViewportMaxScale();
10643 effectiveValidMaxScale = true;
10644 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
10647 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
10648 auto ComputeExtendZoom = [&]() -> float {
10649 if (mValidScaleFloat && effectiveValidMaxScale) {
10650 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
10652 if (mValidScaleFloat) {
10653 return mScaleFloat.scale;
10655 if (effectiveValidMaxScale) {
10656 return effectiveMaxScale.scale;
10658 return nsViewportInfo::kAuto;
10661 // Resolving 'extend-to-zoom'
10662 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
10663 float extendZoom = ComputeExtendZoom();
10665 CSSCoord minWidth = mMinWidth;
10666 CSSCoord maxWidth = mMaxWidth;
10667 CSSCoord minHeight = mMinHeight;
10668 CSSCoord maxHeight = mMaxHeight;
10670 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
10671 // viewport size. We need to use this scaled size for any clamping of
10672 // width or height.
10673 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
10675 // Our min and max width and height values are mostly as specified by
10676 // the viewport declaration, but we make an exception for max width.
10677 // Max width, if auto, and if there's no initial-scale, will be set
10678 // to a default size. This is to support legacy site design with no
10679 // viewport declaration, and to do that using the same scheme as
10680 // Chrome does, in order to maintain web compatibility. Since the
10681 // default size has a complicated calculation, we fixup the maxWidth
10682 // value after setting it, above.
10683 if (maxWidth == nsViewportInfo::kAuto && !mValidScaleFloat) {
10684 if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
10685 bc->InRDMPane()) {
10686 // If RDM and touch simulation are active, then use the simulated
10687 // screen width to accommodate for cases where the screen width is
10688 // larger than the desktop viewport default.
10689 maxWidth = nsViewportInfo::Max(
10690 displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
10691 } else {
10692 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
10694 // Divide by fullZoom to stretch CSS pixel size of viewport in order
10695 // to keep device pixel size unchanged after full zoom applied.
10696 // See bug 974242.
10697 maxWidth /= fullZoom;
10699 // We set minWidth to ExtendToZoom, which will cause our later width
10700 // calculation to expand to maxWidth, if scale restrictions allow it.
10701 minWidth = nsViewportInfo::kExtendToZoom;
10704 // Resolve device-width and device-height first.
10705 if (maxWidth == nsViewportInfo::kDeviceSize) {
10706 maxWidth = displaySize.width;
10708 if (maxHeight == nsViewportInfo::kDeviceSize) {
10709 maxHeight = displaySize.height;
10711 if (extendZoom == nsViewportInfo::kAuto) {
10712 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10713 maxWidth = nsViewportInfo::kAuto;
10715 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10716 maxHeight = nsViewportInfo::kAuto;
10718 if (minWidth == nsViewportInfo::kExtendToZoom) {
10719 minWidth = maxWidth;
10721 if (minHeight == nsViewportInfo::kExtendToZoom) {
10722 minHeight = maxHeight;
10724 } else {
10725 CSSSize extendSize = displaySize / extendZoom;
10726 if (maxWidth == nsViewportInfo::kExtendToZoom) {
10727 maxWidth = extendSize.width;
10729 if (maxHeight == nsViewportInfo::kExtendToZoom) {
10730 maxHeight = extendSize.height;
10732 if (minWidth == nsViewportInfo::kExtendToZoom) {
10733 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
10735 if (minHeight == nsViewportInfo::kExtendToZoom) {
10736 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
10740 // Resolve initial width and height from min/max descriptors
10741 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
10742 CSSCoord width = nsViewportInfo::kAuto;
10743 if (minWidth != nsViewportInfo::kAuto ||
10744 maxWidth != nsViewportInfo::kAuto) {
10745 width = nsViewportInfo::Max(
10746 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
10748 CSSCoord height = nsViewportInfo::kAuto;
10749 if (minHeight != nsViewportInfo::kAuto ||
10750 maxHeight != nsViewportInfo::kAuto) {
10751 height = nsViewportInfo::Max(
10752 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
10755 // Resolve width value
10756 // https://drafts.csswg.org/css-device-adapt/#resolve-width
10757 if (width == nsViewportInfo::kAuto) {
10758 if (height == nsViewportInfo::kAuto || aDisplaySize.height == 0) {
10759 width = displaySize.width;
10760 } else {
10761 width = height * aDisplaySize.width / aDisplaySize.height;
10765 // Resolve height value
10766 // https://drafts.csswg.org/css-device-adapt/#resolve-height
10767 if (height == nsViewportInfo::kAuto) {
10768 if (aDisplaySize.width == 0) {
10769 height = displaySize.height;
10770 } else {
10771 height = width * aDisplaySize.height / aDisplaySize.width;
10774 MOZ_ASSERT(width != nsViewportInfo::kAuto &&
10775 height != nsViewportInfo::kAuto);
10777 CSSSize size(width, height);
10779 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
10780 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
10781 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
10783 nsViewportInfo::AutoSizeFlag sizeFlag =
10784 nsViewportInfo::AutoSizeFlag::FixedSize;
10785 if (mMaxWidth == nsViewportInfo::kDeviceSize ||
10786 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::kDeviceSize ||
10787 mScaleFloat.scale == 1.0f)) ||
10788 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::kAuto &&
10789 mMaxHeight < 0)) {
10790 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
10793 // FIXME: Resolving width and height should be done above 'Resolve width
10794 // value' and 'Resolve height value'.
10795 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
10796 size = displaySize;
10799 // The purpose of clamping the viewport width to a minimum size is to
10800 // prevent page authors from setting it to a ridiculously small value.
10801 // If the page is actually being rendered in a very small area (as might
10802 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
10803 // prevent the viewport from taking on that size.
10804 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
10806 size.width = clamped(size.width, effectiveMinSize.width,
10807 float(kViewportMaxSize.width));
10809 // Also recalculate the default zoom, if it wasn't specified in the
10810 // metadata, and the width is specified.
10811 if (!mValidScaleFloat && !mWidthStrEmpty) {
10812 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
10813 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
10816 size.height = clamped(size.height, effectiveMinSize.height,
10817 float(kViewportMaxSize.height));
10819 // In cases of user-scalable=no, if we have a positive scale, clamp it to
10820 // min and max, and then use the clamped value for the scale, the min, and
10821 // the max. If we don't have a positive scale, assert that we are setting
10822 // the auto scale flag.
10823 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
10824 scaleFloat > CSSToScreenScale(0.0f)) {
10825 scaleFloat = scaleMinFloat = scaleMaxFloat =
10826 clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
10828 MOZ_ASSERT(
10829 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
10830 "If we don't have a positive scale, we should be using auto scale.");
10832 // We need to perform a conversion, but only if the initial or maximum
10833 // scale were set explicitly by the user.
10834 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
10835 scaleFloat <= scaleMaxFloat) {
10836 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
10837 size.width = std::max(size.width, displaySize.width);
10838 size.height = std::max(size.height, displaySize.height);
10839 } else if (effectiveValidMaxScale) {
10840 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
10841 size.width = std::max(size.width, displaySize.width);
10842 size.height = std::max(size.height, displaySize.height);
10845 return nsViewportInfo(
10846 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
10847 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
10848 : nsViewportInfo::AutoScaleFlag::AutoScale,
10849 effectiveZoomFlag, mViewportFit);
10853 ViewportMetaData Document::GetViewportMetaData() const {
10854 return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
10855 : ViewportMetaData();
10858 void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
10859 mLastModifiedViewportMetaData = std::move(aData);
10860 // Trigger recomputation of the nsViewportInfo the next time it's queried.
10861 mViewportType = Unknown;
10863 AsyncEventDispatcher::RunDOMEventWhenSafe(
10864 *this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes,
10865 ChromeOnlyDispatch::eYes);
10868 EventListenerManager* Document::GetOrCreateListenerManager() {
10869 if (!mListenerManager) {
10870 mListenerManager =
10871 new EventListenerManager(static_cast<EventTarget*>(this));
10872 SetFlags(NODE_HAS_LISTENERMANAGER);
10875 return mListenerManager;
10878 EventListenerManager* Document::GetExistingListenerManager() const {
10879 return mListenerManager;
10882 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
10883 aVisitor.mCanHandle = true;
10884 // FIXME! This is a hack to make middle mouse paste working also in Editor.
10885 // Bug 329119
10886 aVisitor.mForceContentDispatch = true;
10888 // Load events must not propagate to |window| object, see bug 335251.
10889 if (aVisitor.mEvent->mMessage != eLoad) {
10890 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
10891 aVisitor.SetParentTarget(
10892 window ? window->GetTargetForEventTargetChain() : nullptr, false);
10896 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
10897 CallerType aCallerType,
10898 ErrorResult& rv) const {
10899 nsPresContext* presContext = GetPresContext();
10901 // Create event even without presContext.
10902 RefPtr<Event> ev =
10903 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
10904 nullptr, aEventType, aCallerType);
10905 if (!ev) {
10906 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10907 return nullptr;
10909 WidgetEvent* e = ev->WidgetEventPtr();
10910 e->mFlags.mBubbles = false;
10911 e->mFlags.mCancelable = false;
10912 return ev.forget();
10915 void Document::FlushPendingNotifications(FlushType aType) {
10916 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
10917 FlushPendingNotifications(flush);
10920 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
10921 FlushType flushType = aFlush.mFlushType;
10923 RefPtr<Document> documentOnStack = this;
10925 // We need to flush the sink for non-HTML documents (because the XML
10926 // parser still does insertion with deferred notifications). We
10927 // also need to flush the sink if this is a layout-related flush, to
10928 // make sure that layout is started as needed. But we can skip that
10929 // part if we have no presshell or if it's already done an initial
10930 // reflow.
10931 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
10932 mPresShell && !mPresShell->DidInitialize())) &&
10933 (mParser || mWeakSink)) {
10934 nsCOMPtr<nsIContentSink> sink;
10935 if (mParser) {
10936 sink = mParser->GetContentSink();
10937 } else {
10938 sink = do_QueryReferent(mWeakSink);
10939 if (!sink) {
10940 mWeakSink = nullptr;
10943 // Determine if it is safe to flush the sink notifications
10944 // by determining if it safe to flush all the presshells.
10945 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
10946 sink->FlushPendingNotifications(flushType);
10950 // Should we be flushing pending binding constructors in here?
10952 if (flushType <= FlushType::ContentAndNotify) {
10953 // Nothing to do here
10954 return;
10957 // If we have a parent we must flush the parent too to ensure that our
10958 // container is reflowed if its size was changed.
10960 // We do it only if the subdocument and the parent can observe each other
10961 // synchronously (that is, if we're not cross-origin), to avoid work that is
10962 // not observable, and if the parent document has finished loading all its
10963 // render-blocking stylesheets and may start laying out the document, to avoid
10964 // unnecessary flashes of unstyled content on the parent document. Note that
10965 // this last bit means that size-dependent media queries in this document may
10966 // produce incorrect results temporarily.
10968 // But if it's not safe to flush ourselves, then don't flush the parent, since
10969 // that can cause things like resizes of our frame's widget, which we can't
10970 // handle while flushing is unsafe.
10971 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
10972 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
10973 ChangesToFlush parentFlush = aFlush;
10974 if (flushType >= FlushType::Style) {
10975 // Since media queries mean that a size change of our container can affect
10976 // style, we need to promote a style flush on ourself to a layout flush on
10977 // our parent, since we need our container to be the correct size to
10978 // determine the correct style.
10979 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
10981 mParentDocument->FlushPendingNotifications(parentFlush);
10984 if (RefPtr<PresShell> presShell = GetPresShell()) {
10985 presShell->FlushPendingNotifications(aFlush);
10989 void Document::FlushExternalResources(FlushType aType) {
10990 NS_ASSERTION(
10991 aType >= FlushType::Style,
10992 "should only need to flush for style or higher in external resources");
10993 if (GetDisplayDocument()) {
10994 return;
10997 auto flush = [aType](Document& aDoc) {
10998 aDoc.FlushPendingNotifications(aType);
10999 return CallState::Continue;
11002 EnumerateExternalResources(flush);
11005 void Document::SetXMLDeclaration(const char16_t* aVersion,
11006 const char16_t* aEncoding,
11007 const int32_t aStandalone) {
11008 if (!aVersion || *aVersion == '\0') {
11009 mXMLDeclarationBits = 0;
11010 return;
11013 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
11015 if (aEncoding && *aEncoding != '\0') {
11016 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
11019 if (aStandalone == 1) {
11020 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
11021 XML_DECLARATION_BITS_STANDALONE_YES;
11022 } else if (aStandalone == 0) {
11023 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
11027 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
11028 nsAString& aStandalone) {
11029 aVersion.Truncate();
11030 aEncoding.Truncate();
11031 aStandalone.Truncate();
11033 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
11034 return;
11037 // always until we start supporting 1.1 etc.
11038 aVersion.AssignLiteral("1.0");
11040 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
11041 // This is what we have stored, not necessarily what was written
11042 // in the original
11043 GetCharacterSet(aEncoding);
11046 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
11047 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
11048 aStandalone.AssignLiteral("yes");
11049 } else {
11050 aStandalone.AssignLiteral("no");
11055 void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
11056 mColorSchemeMetaTags.Insert(aMeta);
11057 RecomputeColorScheme();
11060 void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
11061 mColorSchemeMetaTags.RemoveElement(aMeta);
11062 RecomputeColorScheme();
11065 void Document::RecomputeColorScheme() {
11066 auto oldColorScheme = mColorSchemeBits;
11067 mColorSchemeBits = 0;
11068 const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
11069 for (const HTMLMetaElement* el : elements) {
11070 nsAutoString content;
11071 if (!el->GetAttr(nsGkAtoms::content, content)) {
11072 continue;
11075 NS_ConvertUTF16toUTF8 contentU8(content);
11076 if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
11077 break;
11081 if (mColorSchemeBits == oldColorScheme) {
11082 return;
11085 if (nsPresContext* pc = GetPresContext()) {
11086 // This affects system colors, which are inherited, so we need to recascade.
11087 pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
11091 bool Document::IsScriptEnabled() const {
11092 // If this document is sandboxed without 'allow-scripts'
11093 // script is not enabled
11094 if (HasScriptsBlockedBySandbox()) {
11095 return false;
11098 nsCOMPtr<nsIScriptGlobalObject> globalObject =
11099 do_QueryInterface(GetInnerWindow());
11100 if (!globalObject || !globalObject->HasJSGlobal()) {
11101 return false;
11104 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
11105 .Allowed();
11108 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
11109 PRTime modDate = 0;
11110 nsresult rv;
11112 nsCOMPtr<nsIHttpChannel> httpChannel;
11113 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
11114 if (NS_WARN_IF(NS_FAILED(rv))) {
11115 return;
11118 if (httpChannel) {
11119 nsAutoCString tmp;
11120 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
11122 if (NS_SUCCEEDED(rv)) {
11123 PRTime time;
11124 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
11125 if (st == PR_SUCCESS) {
11126 modDate = time;
11130 static const char* const headers[] = {
11131 "default-style", "content-style-type", "content-language",
11132 "content-disposition", "refresh", "x-dns-prefetch-control",
11133 "x-frame-options", "origin-trial",
11134 // add more http headers if you need
11135 // XXXbz don't add content-location support without reading bug
11136 // 238654 and its dependencies/dups first.
11139 nsAutoCString headerVal;
11140 const char* const* name = headers;
11141 while (*name) {
11142 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
11143 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
11144 RefPtr<nsAtom> key = NS_Atomize(*name);
11145 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
11147 ++name;
11149 } else {
11150 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
11151 if (fileChannel) {
11152 nsCOMPtr<nsIFile> file;
11153 fileChannel->GetFile(getter_AddRefs(file));
11154 if (file) {
11155 PRTime msecs;
11156 rv = file->GetLastModifiedTime(&msecs);
11158 if (NS_SUCCEEDED(rv)) {
11159 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
11162 } else {
11163 nsAutoCString contentDisp;
11164 rv = aChannel->GetContentDispositionHeader(contentDisp);
11165 if (NS_SUCCEEDED(rv)) {
11166 SetHeaderData(nsGkAtoms::headerContentDisposition,
11167 NS_ConvertASCIItoUTF16(contentDisp));
11172 mLastModified.Truncate();
11173 if (modDate != 0) {
11174 GetFormattedTimeString(modDate, mLastModified);
11178 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
11179 // set any HTTP-EQUIV data into document's header data as well as url
11180 nsAutoString header;
11181 aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
11182 if (!header.IsEmpty()) {
11183 // Ignore META REFRESH when document is sandboxed from automatic features.
11184 nsContentUtils::ASCIIToLower(header);
11185 if (nsGkAtoms::refresh->Equals(header) &&
11186 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
11187 return;
11190 nsAutoString result;
11191 aMetaElement->GetAttr(nsGkAtoms::content, result);
11192 if (!result.IsEmpty()) {
11193 RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
11194 SetHeaderData(fieldAtom, result);
11198 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
11199 nsGkAtoms::handheldFriendly, eIgnoreCase)) {
11200 nsAutoString result;
11201 aMetaElement->GetAttr(nsGkAtoms::content, result);
11202 if (!result.IsEmpty()) {
11203 nsContentUtils::ASCIIToLower(result);
11204 SetHeaderData(nsGkAtoms::handheldFriendly, result);
11209 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
11210 nsAtom* aPrefix,
11211 int32_t aNamespaceID,
11212 const nsAString* aIs) {
11213 #ifdef DEBUG
11214 nsAutoString qName;
11215 if (aPrefix) {
11216 aPrefix->ToString(qName);
11217 qName.Append(':');
11219 qName.Append(aName);
11221 // Note: "a:b:c" is a valid name in non-namespaces XML, and
11222 // Document::CreateElement can call us with such a name and no prefix,
11223 // which would cause an error if we just used true here.
11224 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
11225 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
11226 "Don't pass invalid prefixes to Document::CreateElem, "
11227 "check caller.");
11228 #endif
11230 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
11231 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
11232 getter_AddRefs(nodeInfo));
11233 NS_ENSURE_TRUE(nodeInfo, nullptr);
11235 nsCOMPtr<Element> element;
11236 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
11237 NOT_FROM_PARSER, aIs);
11238 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
11241 bool Document::IsSafeToFlush() const {
11242 PresShell* presShell = GetPresShell();
11243 if (!presShell) {
11244 return true;
11246 return presShell->IsSafeToFlush();
11249 void Document::Sanitize() {
11250 // Sanitize the document by resetting all (current and former) password fields
11251 // and any form fields with autocomplete=off to their default values. We do
11252 // this now, instead of when the presentation is restored, to offer some
11253 // protection in case there is ever an exploit that allows a cached document
11254 // to be accessed from a different document.
11256 // First locate all input elements, regardless of whether they are
11257 // in a form, and reset the password and autocomplete=off elements.
11259 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
11261 nsAutoString value;
11263 uint32_t length = nodes->Length(true);
11264 for (uint32_t i = 0; i < length; ++i) {
11265 NS_ASSERTION(nodes->Item(i), "null item in node list!");
11267 RefPtr<HTMLInputElement> input =
11268 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
11269 if (!input) continue;
11271 input->GetAttr(nsGkAtoms::autocomplete, value);
11272 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
11273 input->Reset();
11277 // Now locate all _form_ elements that have autocomplete=off and reset them
11278 nodes = GetElementsByTagName(u"form"_ns);
11280 length = nodes->Length(true);
11281 for (uint32_t i = 0; i < length; ++i) {
11282 // Reset() may change the list dynamically.
11283 RefPtr<HTMLFormElement> form =
11284 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
11285 if (!form) continue;
11287 form->GetAttr(nsGkAtoms::autocomplete, value);
11288 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
11292 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
11293 if (!mSubDocuments) {
11294 return;
11297 // PLDHashTable::Iterator can't handle modifications while iterating so we
11298 // copy all entries to an array first before calling any callbacks.
11299 AutoTArray<RefPtr<Document>, 8> subdocs;
11300 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11301 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11302 if (Document* subdoc = entry->mSubDocument) {
11303 subdocs.AppendElement(subdoc);
11306 for (auto& subdoc : subdocs) {
11307 if (aCallback(*subdoc) == CallState::Stop) {
11308 break;
11313 void Document::CollectDescendantDocuments(
11314 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
11315 if (!mSubDocuments) {
11316 return;
11319 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11320 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11321 const Document* subdoc = entry->mSubDocument;
11322 if (subdoc) {
11323 if (aCallback(subdoc)) {
11324 aDescendants.AppendElement(entry->mSubDocument);
11326 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
11331 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
11332 uint32_t& aBFCacheCombo,
11333 bool aIncludeSubdocuments,
11334 bool aAllowUnloadListeners) {
11335 bool ret = true;
11337 if (!IsBFCachingAllowed()) {
11338 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
11339 ret = false;
11342 nsAutoCString uri;
11343 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11344 if (mDocumentURI) {
11345 mDocumentURI->GetSpec(uri);
11349 if (EventHandlingSuppressed()) {
11350 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11351 ("Save of %s blocked on event handling suppression", uri.get()));
11352 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
11353 ret = false;
11356 // Do not allow suspended windows to be placed in the
11357 // bfcache. This method is also used to verify a document
11358 // coming out of the bfcache is ok to restore, though. So
11359 // we only want to block suspend windows that aren't also
11360 // frozen.
11361 nsPIDOMWindowInner* win = GetInnerWindow();
11362 if (win && win->IsSuspended() && !win->IsFrozen()) {
11363 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11364 ("Save of %s blocked on suspended Window", uri.get()));
11365 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
11366 ret = false;
11369 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
11370 bool thirdParty = false;
11371 // Currently some other mobile browsers seem to bfcache only cross-domain
11372 // pages, but bfcache those also when there are unload event listeners, so
11373 // this is trying to match that behavior as much as possible.
11374 bool allowUnloadListeners =
11375 aAllowUnloadListeners &&
11376 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
11377 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
11378 channel, &thirdParty)) &&
11379 thirdParty));
11381 // Check our event listener manager for unload/beforeunload listeners.
11382 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
11383 if (!allowUnloadListeners && piTarget) {
11384 EventListenerManager* manager = piTarget->GetExistingListenerManager();
11385 if (manager) {
11386 if (manager->HasUnloadListeners()) {
11387 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11388 ("Save of %s blocked due to unload handlers", uri.get()));
11389 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
11390 ret = false;
11392 if (manager->HasBeforeUnloadListeners()) {
11393 if (!mozilla::SessionHistoryInParent() ||
11394 !StaticPrefs::
11395 docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
11396 MOZ_LOG(
11397 gPageCacheLog, mozilla::LogLevel::Verbose,
11398 ("Save of %s blocked due to beforeUnload handlers", uri.get()));
11399 aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
11400 ret = false;
11406 // Check if we have pending network requests
11407 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11408 if (loadGroup) {
11409 nsCOMPtr<nsISimpleEnumerator> requests;
11410 loadGroup->GetRequests(getter_AddRefs(requests));
11412 bool hasMore = false;
11414 // We want to bail out if we have any requests other than aNewRequest (or
11415 // in the case when aNewRequest is a part of a multipart response the base
11416 // channel the multipart response is coming in on).
11417 nsCOMPtr<nsIChannel> baseChannel;
11418 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
11419 if (part) {
11420 part->GetBaseChannel(getter_AddRefs(baseChannel));
11423 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11424 nsCOMPtr<nsISupports> elem;
11425 requests->GetNext(getter_AddRefs(elem));
11427 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11428 if (request && request != aNewRequest && request != baseChannel) {
11429 // Favicon loads don't need to block caching.
11430 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
11431 if (channel) {
11432 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
11433 if (li->InternalContentPolicyType() ==
11434 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
11435 continue;
11439 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11440 nsAutoCString requestName;
11441 request->GetName(requestName);
11442 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
11443 ("Save of %s blocked because document has request %s",
11444 uri.get(), requestName.get()));
11446 aBFCacheCombo |= BFCacheStatus::REQUEST;
11447 ret = false;
11452 // Check if we have active GetUserMedia use
11453 if (MediaManager::Exists() && win &&
11454 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
11455 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11456 ("Save of %s blocked due to GetUserMedia", uri.get()));
11457 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
11458 ret = false;
11461 #ifdef MOZ_WEBRTC
11462 // Check if we have active PeerConnections
11463 if (win && win->HasActivePeerConnections()) {
11464 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11465 ("Save of %s blocked due to PeerConnection", uri.get()));
11466 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
11467 ret = false;
11469 #endif // MOZ_WEBRTC
11471 // Don't save presentations for documents containing EME content, so that
11472 // CDMs reliably shutdown upon user navigation.
11473 if (ContainsEMEContent()) {
11474 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
11475 ret = false;
11478 // Don't save presentations for documents containing MSE content, to
11479 // reduce memory usage.
11480 if (ContainsMSEContent()) {
11481 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11482 ("Save of %s blocked due to MSE use", uri.get()));
11483 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
11484 ret = false;
11487 if (aIncludeSubdocuments && mSubDocuments) {
11488 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11489 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11490 Document* subdoc = entry->mSubDocument;
11492 uint32_t subDocBFCacheCombo = 0;
11493 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
11494 bool canCache =
11495 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
11496 true, allowUnloadListeners)
11497 : false;
11498 if (!canCache) {
11499 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11500 ("Save of %s blocked due to subdocument blocked", uri.get()));
11501 aBFCacheCombo |= subDocBFCacheCombo;
11502 ret = false;
11507 if (!mozilla::BFCacheInParent()) {
11508 // BFCache is currently not compatible with remote subframes (bug 1609324)
11509 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
11510 for (auto& child : browsingContext->Children()) {
11511 if (!child->IsInProcess()) {
11512 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
11513 ret = false;
11514 break;
11520 if (win) {
11521 auto* globalWindow = nsGlobalWindowInner::Cast(win);
11522 #ifdef MOZ_WEBSPEECH
11523 if (globalWindow->HasActiveSpeechSynthesis()) {
11524 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11525 ("Save of %s blocked due to Speech use", uri.get()));
11526 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
11527 ret = false;
11529 #endif
11530 if (globalWindow->HasUsedVR()) {
11531 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11532 ("Save of %s blocked due to having used VR", uri.get()));
11533 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
11534 ret = false;
11537 if (win->HasActiveLocks()) {
11538 MOZ_LOG(
11539 gPageCacheLog, mozilla::LogLevel::Verbose,
11540 ("Save of %s blocked due to having active lock requests", uri.get()));
11541 aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
11542 ret = false;
11545 if (win->HasActiveWebTransports()) {
11546 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11547 ("Save of %s blocked due to WebTransport", uri.get()));
11548 aBFCacheCombo |= BFCacheStatus::ACTIVE_WEBTRANSPORT;
11549 ret = false;
11553 return ret;
11556 void Document::Destroy() {
11557 // The ContentViewer wants to release the document now. So, tell our content
11558 // to drop any references to the document so that it can be destroyed.
11559 if (mIsGoingAway) {
11560 return;
11563 ReportDocumentUseCounters();
11564 ReportLCP();
11565 SetDevToolsWatchingDOMMutations(false);
11567 mIsGoingAway = true;
11569 ScriptLoader()->Destroy();
11570 SetScriptGlobalObject(nullptr);
11571 RemovedFromDocShell();
11573 bool oldVal = mInUnlinkOrDeletion;
11574 mInUnlinkOrDeletion = true;
11576 #ifdef DEBUG
11577 uint32_t oldChildCount = GetChildCount();
11578 #endif
11580 for (nsIContent* child = GetFirstChild(); child;
11581 child = child->GetNextSibling()) {
11582 child->DestroyContent();
11583 MOZ_ASSERT(child->GetParentNode() == this);
11585 MOZ_ASSERT(oldChildCount == GetChildCount());
11586 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
11588 mInUnlinkOrDeletion = oldVal;
11590 mLayoutHistoryState = nullptr;
11592 if (mOriginalDocument) {
11593 mOriginalDocument->mLatestStaticClone = nullptr;
11596 if (IsStaticDocument()) {
11597 RemoveProperty(nsGkAtoms::printisfocuseddoc);
11598 RemoveProperty(nsGkAtoms::printselectionranges);
11601 // Shut down our external resource map. We might not need this for
11602 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
11603 // tearing down all those frame trees right now is the right thing to do.
11604 mExternalResourceMap.Shutdown();
11606 // Manually break cycles via promise's global object pointer.
11607 mReadyForIdle = nullptr;
11608 mOrientationPendingPromise = nullptr;
11610 // To break cycles.
11611 mPreloadService.ClearAllPreloads();
11613 if (mDocumentL10n) {
11614 mDocumentL10n->Destroy();
11617 if (!mPresShell) {
11618 DropStyleSet();
11622 void Document::RemovedFromDocShell() {
11623 mEditingState = EditingState::eOff;
11625 if (mRemovedFromDocShell) return;
11627 mRemovedFromDocShell = true;
11628 NotifyActivityChanged();
11630 for (nsIContent* child = GetFirstChild(); child;
11631 child = child->GetNextSibling()) {
11632 child->SaveSubtreeState();
11635 nsIDocShell* docShell = GetDocShell();
11636 if (docShell) {
11637 docShell->SynchronizeLayoutHistoryState();
11641 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
11642 const {
11643 nsCOMPtr<nsILayoutHistoryState> state;
11644 if (!mScriptGlobalObject) {
11645 state = mLayoutHistoryState;
11646 } else {
11647 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
11648 if (docShell) {
11649 docShell->GetLayoutHistoryState(getter_AddRefs(state));
11653 return state.forget();
11656 void Document::EnsureOnloadBlocker() {
11657 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11658 // -- it's not ours.
11659 if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
11660 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11661 if (loadGroup) {
11662 // Check first to see if mOnloadBlocker is in the loadgroup.
11663 nsCOMPtr<nsISimpleEnumerator> requests;
11664 loadGroup->GetRequests(getter_AddRefs(requests));
11666 bool hasMore = false;
11667 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11668 nsCOMPtr<nsISupports> elem;
11669 requests->GetNext(getter_AddRefs(elem));
11670 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11671 if (request && request == mOnloadBlocker) {
11672 return;
11676 // Not in the loadgroup, so add it.
11677 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11682 void Document::BlockOnload() {
11683 if (mDisplayDocument) {
11684 mDisplayDocument->BlockOnload();
11685 return;
11688 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11689 // -- it's not ours.
11690 if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
11691 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11692 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11695 ++mOnloadBlockCount;
11698 void Document::UnblockOnload(bool aFireSync) {
11699 if (mDisplayDocument) {
11700 mDisplayDocument->UnblockOnload(aFireSync);
11701 return;
11704 --mOnloadBlockCount;
11706 if (mOnloadBlockCount == 0) {
11707 if (mScriptGlobalObject) {
11708 // Only manipulate the loadgroup in this case, because if
11709 // mScriptGlobalObject is null, it's not ours.
11710 if (aFireSync) {
11711 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
11712 ++mOnloadBlockCount;
11713 DoUnblockOnload();
11714 } else {
11715 PostUnblockOnloadEvent();
11717 } else if (mIsBeingUsedAsImage) {
11718 // To correctly unblock onload for a document that contains an SVG
11719 // image, we need to know when all of the SVG document's resources are
11720 // done loading, in a way comparable to |window.onload|. We fire this
11721 // event to indicate that the SVG should be considered fully loaded.
11722 // Because scripting is disabled on SVG-as-image documents, this event
11723 // is not accessible to content authors. (See bug 837315.)
11724 RefPtr<AsyncEventDispatcher> asyncDispatcher =
11725 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
11726 CanBubble::eNo, ChromeOnlyDispatch::eNo);
11727 asyncDispatcher->PostDOMEvent();
11732 class nsUnblockOnloadEvent : public Runnable {
11733 public:
11734 explicit nsUnblockOnloadEvent(Document* aDoc)
11735 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
11736 NS_IMETHOD Run() override {
11737 mDoc->DoUnblockOnload();
11738 return NS_OK;
11741 private:
11742 RefPtr<Document> mDoc;
11745 void Document::PostUnblockOnloadEvent() {
11746 MOZ_RELEASE_ASSERT(NS_IsMainThread());
11747 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
11748 nsresult rv = Dispatch(evt.forget());
11749 if (NS_SUCCEEDED(rv)) {
11750 // Stabilize block count so we don't post more events while this one is up
11751 ++mOnloadBlockCount;
11752 } else {
11753 NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
11757 void Document::DoUnblockOnload() {
11758 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
11759 MOZ_ASSERT(mOnloadBlockCount != 0,
11760 "Shouldn't have a count of zero here, since we stabilized in "
11761 "PostUnblockOnloadEvent");
11763 --mOnloadBlockCount;
11765 if (mOnloadBlockCount != 0) {
11766 // We blocked again after the last unblock. Nothing to do here. We'll
11767 // post a new event when we unblock again.
11768 return;
11771 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11772 // -- it's not ours.
11773 if (mScriptGlobalObject) {
11774 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11775 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
11780 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
11781 for (nsIFrame* f = aFrame; f;
11782 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
11783 nsIContent* content = f->GetContent();
11784 if (!content) {
11785 continue;
11788 if (content->OwnerDoc() == this) {
11789 return content;
11791 // We must be in a subdocument so jump directly to the root frame.
11792 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
11793 // the containing document.
11794 f = f->PresContext()->GetPresShell()->GetRootFrame();
11797 return nullptr;
11800 void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
11801 const nsAString& aType, bool aInFrameSwap,
11802 bool aPersisted, bool aOnlySystemGroup) {
11803 if (!aDispatchTarget) {
11804 return;
11807 PageTransitionEventInit init;
11808 init.mBubbles = true;
11809 init.mCancelable = true;
11810 init.mPersisted = aPersisted;
11811 init.mInFrameSwap = aInFrameSwap;
11813 RefPtr<PageTransitionEvent> event =
11814 PageTransitionEvent::Constructor(this, aType, init);
11816 event->SetTrusted(true);
11817 event->SetTarget(this);
11818 if (aOnlySystemGroup) {
11819 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
11821 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
11822 nullptr);
11825 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
11826 bool aOnlySystemGroup) {
11827 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11828 nsCString uri;
11829 if (GetDocumentURI()) {
11830 uri = GetDocumentURI()->GetSpecOrDefault();
11832 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11833 ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
11836 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11837 MOZ_DIAGNOSTIC_ASSERT(
11838 inFrameLoaderSwap ==
11839 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11841 Element* root = GetRootElement();
11842 if (aPersisted && root) {
11843 // Send out notifications that our <link> elements are attached.
11844 RefPtr<nsContentList> links =
11845 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11847 uint32_t linkCount = links->Length(true);
11848 for (uint32_t i = 0; i < linkCount; ++i) {
11849 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
11853 // See Document
11854 if (!inFrameLoaderSwap) {
11855 if (aPersisted) {
11856 ImageTracker()->SetAnimatingState(true);
11859 // Set mIsShowing before firing events, in case those event handlers
11860 // move us around.
11861 mIsShowing = true;
11862 mVisible = true;
11864 UpdateVisibilityState();
11867 NotifyActivityChanged();
11869 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11870 aExternalResource.OnPageShow(aPersisted, nullptr);
11871 return CallState::Continue;
11873 EnumerateExternalResources(notifyExternal);
11875 if (mAnimationController) {
11876 mAnimationController->OnPageShow();
11879 if (!mIsBeingUsedAsImage) {
11880 // Dispatch observer notification to notify observers page is shown.
11881 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11882 if (os) {
11883 nsIPrincipal* principal = NodePrincipal();
11884 os->NotifyObservers(ToSupports(this),
11885 principal->IsSystemPrincipal() ? "chrome-page-shown"
11886 : "content-page-shown",
11887 nullptr);
11890 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11891 if (!target) {
11892 target = do_QueryInterface(GetWindow());
11894 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
11895 aPersisted, aOnlySystemGroup);
11899 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
11900 if (nsPresContext* presContext = aDocument.GetPresContext()) {
11901 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
11902 FullscreenEventType::Change, &aDocument, aTarget);
11903 presContext->RefreshDriver()->ScheduleFullscreenEvent(
11904 std::move(pendingEvent));
11908 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
11909 bool aOnlySystemGroup) {
11910 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11911 nsCString uri;
11912 if (GetDocumentURI()) {
11913 uri = GetDocumentURI()->GetSpecOrDefault();
11915 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11916 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
11919 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11920 MOZ_DIAGNOSTIC_ASSERT(
11921 inFrameLoaderSwap ==
11922 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11924 if (mAnimationController) {
11925 mAnimationController->OnPageHide();
11928 if (!inFrameLoaderSwap) {
11929 if (aPersisted) {
11930 // We do not stop the animations (bug 1024343) when the page is refreshing
11931 // while being dragged out.
11932 ImageTracker()->SetAnimatingState(false);
11935 // Set mIsShowing before firing events, in case those event handlers
11936 // move us around.
11937 mIsShowing = false;
11938 mVisible = false;
11941 ExitPointerLock();
11943 if (!mIsBeingUsedAsImage) {
11944 // Dispatch observer notification to notify observers page is hidden.
11945 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11946 if (os) {
11947 nsIPrincipal* principal = NodePrincipal();
11948 os->NotifyObservers(ToSupports(this),
11949 principal->IsSystemPrincipal()
11950 ? "chrome-page-hidden"
11951 : "content-page-hidden",
11952 nullptr);
11955 // Now send out a PageHide event.
11956 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11957 if (!target) {
11958 target = do_QueryInterface(GetWindow());
11961 PageUnloadingEventTimeStamp timeStamp(this);
11962 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
11963 aPersisted, aOnlySystemGroup);
11967 if (!inFrameLoaderSwap) {
11968 UpdateVisibilityState();
11971 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11972 aExternalResource.OnPageHide(aPersisted, nullptr);
11973 return CallState::Continue;
11975 EnumerateExternalResources(notifyExternal);
11976 NotifyActivityChanged();
11978 ClearPendingFullscreenRequests(this);
11979 if (Fullscreen()) {
11980 // If this document was fullscreen, we should exit fullscreen in this
11981 // doctree branch. This ensures that if the user navigates while in
11982 // fullscreen mode we don't leave its still visible ancestor documents
11983 // in fullscreen mode. So exit fullscreen in the document's fullscreen
11984 // root document, as this will exit fullscreen in all the root's
11985 // descendant documents. Note that documents are removed from the
11986 // doctree by the time OnPageHide() is called, so we must store a
11987 // reference to the root (in Document::mFullscreenRoot) since we can't
11988 // just traverse the doctree to get the root.
11989 Document::ExitFullscreenInDocTree(this);
11991 // Since the document is removed from the doctree before OnPageHide() is
11992 // called, ExitFullscreen() can't traverse from the root down to *this*
11993 // document, so we must manually call CleanupFullscreenState() below too.
11994 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
11995 // so we *must* call it after ExitFullscreen(), not before.
11996 // OnPageHide() is called in every hidden (i.e. descendant) document,
11997 // so calling CleanupFullscreenState() here will ensure all hidden
11998 // documents have their fullscreen state reset.
11999 CleanupFullscreenState();
12001 // The fullscreenchange event is to be queued in the refresh driver,
12002 // however a hidden page wouldn't trigger that again, so it makes no
12003 // sense to dispatch such event here.
12007 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
12008 NS_ASSERTION(
12009 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
12010 "mSubtreeModifiedTargets not cleared after dispatching?");
12011 ++mSubtreeModifiedDepth;
12012 if (aTarget) {
12013 // MayDispatchMutationEvent is often called just before this method,
12014 // so it has already appended the node to mSubtreeModifiedTargets.
12015 int32_t count = mSubtreeModifiedTargets.Count();
12016 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
12017 mSubtreeModifiedTargets.AppendObject(aTarget);
12022 void Document::MutationEventDispatched(nsINode* aTarget) {
12023 if (--mSubtreeModifiedDepth) {
12024 return;
12027 int32_t count = mSubtreeModifiedTargets.Count();
12028 if (!count) {
12029 return;
12032 nsPIDOMWindowInner* window = GetInnerWindow();
12033 if (window &&
12034 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
12035 mSubtreeModifiedTargets.Clear();
12036 return;
12039 nsCOMArray<nsINode> realTargets;
12040 for (nsINode* possibleTarget : mSubtreeModifiedTargets) {
12041 if (possibleTarget->ChromeOnlyAccess()) {
12042 continue;
12045 nsINode* commonAncestor = nullptr;
12046 int32_t realTargetCount = realTargets.Count();
12047 for (int32_t j = 0; j < realTargetCount; ++j) {
12048 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
12049 possibleTarget, realTargets[j]);
12050 if (commonAncestor) {
12051 realTargets.ReplaceObjectAt(commonAncestor, j);
12052 break;
12055 if (!commonAncestor) {
12056 realTargets.AppendObject(possibleTarget);
12060 mSubtreeModifiedTargets.Clear();
12062 for (const nsCOMPtr<nsINode>& target : realTargets) {
12063 InternalMutationEvent mutation(true, eLegacySubtreeModified);
12064 // MOZ_KnownLive due to bug 1620312
12065 AsyncEventDispatcher::RunDOMEventWhenSafe(MOZ_KnownLive(*target), mutation);
12069 void Document::DestroyElementMaps() {
12070 #ifdef DEBUG
12071 mStyledLinksCleared = true;
12072 #endif
12073 mStyledLinks.Clear();
12074 // Notify ID change listeners before clearing the identifier map.
12075 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
12076 iter.Get()->ClearAndNotify();
12078 mIdentifierMap.Clear();
12079 mComposedShadowRoots.Clear();
12080 mResponsiveContent.Clear();
12081 IncrementExpandoGeneration(*this);
12084 void Document::RefreshLinkHrefs() {
12085 // Get a list of all links we know about. We will reset them, which will
12086 // remove them from the document, so we need a copy of what is in the
12087 // hashtable.
12088 const nsTArray<Link*> linksToNotify = ToArray(mStyledLinks);
12090 // Reset all of our styled links.
12091 nsAutoScriptBlocker scriptBlocker;
12092 for (Link* link : linksToNotify) {
12093 link->ResetLinkState(true);
12097 nsresult Document::CloneDocHelper(Document* clone) const {
12098 clone->mIsStaticDocument = mCreatingStaticClone;
12100 // Init document
12101 nsresult rv = clone->Init(NodePrincipal(), mPartitionedPrincipal);
12102 NS_ENSURE_SUCCESS(rv, rv);
12104 if (mCreatingStaticClone) {
12105 if (mOriginalDocument) {
12106 clone->mOriginalDocument = mOriginalDocument;
12107 } else {
12108 clone->mOriginalDocument = const_cast<Document*>(this);
12110 clone->mOriginalDocument->mLatestStaticClone = clone;
12111 clone->mOriginalDocument->mStaticCloneCount++;
12113 nsCOMPtr<nsILoadGroup> loadGroup;
12115 // |mDocumentContainer| is the container of the document that is being
12116 // created and not the original container. See CreateStaticClone function().
12117 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
12118 if (docLoader) {
12119 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
12121 nsCOMPtr<nsIChannel> channel = GetChannel();
12122 nsCOMPtr<nsIURI> uri;
12123 if (channel) {
12124 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
12125 } else {
12126 uri = Document::GetDocumentURI();
12128 clone->mChannel = channel;
12129 clone->mShouldResistFingerprinting = mShouldResistFingerprinting;
12130 if (uri) {
12131 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
12134 clone->mIsSrcdocDocument = mIsSrcdocDocument;
12135 clone->SetContainer(mDocumentContainer);
12137 // Setup the navigation time. This will be needed by any animations in the
12138 // document, even if they are only paused.
12139 MOZ_ASSERT(!clone->GetNavigationTiming(),
12140 "Navigation time was already set?");
12141 if (mTiming) {
12142 RefPtr<nsDOMNavigationTiming> timing =
12143 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
12144 clone->SetNavigationTiming(timing);
12146 clone->SetCsp(mCSP);
12149 // Now ensure that our clone has the same URI, base URI, and principal as us.
12150 // We do this after the mCreatingStaticClone block above, because that block
12151 // can set the base URI to an incorrect value in cases when base URI
12152 // information came from the channel. So we override explicitly, and do it
12153 // for all these properties, in case ResetToURI messes with any of the rest of
12154 // them.
12155 clone->SetDocumentURI(Document::GetDocumentURI());
12156 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
12157 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
12158 clone->mActiveCookiePrincipal = mActiveCookiePrincipal;
12159 // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
12160 // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
12161 // when printed standalone via window.print() (where there won't be a parent
12162 // document to grab the URI from).
12163 clone->mDocumentBaseURI = GetDocBaseURI();
12164 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
12165 clone->mReferrerInfo =
12166 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
12167 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
12169 bool hasHadScriptObject = true;
12170 nsIScriptGlobalObject* scriptObject =
12171 GetScriptHandlingObject(hasHadScriptObject);
12172 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
12173 if (mCreatingStaticClone) {
12174 // If we're doing a static clone (print, print preview), then we're going to
12175 // be setting a scope object after the clone. It's better to set it only
12176 // once, so we don't do that here. However, we do want to act as if there is
12177 // a script handling object. So we set mHasHadScriptHandlingObject.
12178 clone->mHasHadScriptHandlingObject = true;
12179 } else if (scriptObject) {
12180 clone->SetScriptHandlingObject(scriptObject);
12181 } else {
12182 clone->SetScopeObject(GetScopeObject());
12184 // Make the clone a data document
12185 clone->SetLoadedAsData(
12186 true,
12187 /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
12189 // Misc state
12191 // State from Document
12192 clone->mCharacterSet = mCharacterSet;
12193 clone->mCharacterSetSource = mCharacterSetSource;
12194 clone->SetCompatibilityMode(mCompatMode);
12195 clone->mBidiOptions = mBidiOptions;
12196 clone->mContentLanguage = mContentLanguage;
12197 clone->SetContentType(GetContentTypeInternal());
12198 clone->mSecurityInfo = mSecurityInfo;
12200 // State from Document
12201 clone->mType = mType;
12202 clone->mXMLDeclarationBits = mXMLDeclarationBits;
12203 clone->mBaseTarget = mBaseTarget;
12205 return NS_OK;
12208 void Document::NotifyLoading(bool aNewParentIsLoading,
12209 const ReadyState& aCurrentState,
12210 ReadyState aNewState) {
12211 // Mirror the top-level loading state down to all subdocuments
12212 bool was_loading = mAncestorIsLoading ||
12213 aCurrentState == READYSTATE_LOADING ||
12214 aCurrentState == READYSTATE_INTERACTIVE;
12215 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
12216 aNewState == READYSTATE_INTERACTIVE; // new value for state
12217 bool set_load_state = was_loading != is_loading;
12219 MOZ_LOG(
12220 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12221 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
12222 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
12223 "set_load_state: %d",
12224 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
12225 (int)aNewState, was_loading, is_loading, set_load_state));
12227 mAncestorIsLoading = aNewParentIsLoading;
12228 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
12229 // Tell our innerwindow (and thus TimeoutManager)
12230 nsPIDOMWindowInner* inner = GetInnerWindow();
12231 if (inner) {
12232 inner->SetActiveLoadingState(is_loading);
12234 BrowsingContext* context = GetBrowsingContext();
12235 if (context) {
12236 // Don't use PreOrderWalk to mirror this down; go down one level as a
12237 // time so we can set mAncestorIsLoading and take into account the
12238 // readystates of the subdocument. In the child process it will call
12239 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
12240 // iterate it's children
12241 for (auto& child : context->Children()) {
12242 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12243 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
12244 // Setting ancestor loading on a discarded browsing context has no
12245 // effect.
12246 Unused << child->SetAncestorLoading(is_loading);
12252 void Document::SetReadyStateInternal(ReadyState aReadyState,
12253 bool aUpdateTimingInformation) {
12254 if (aReadyState == READYSTATE_UNINITIALIZED) {
12255 // Transition back to uninitialized happens only to keep assertions happy
12256 // right before readyState transitions to something else. Make this
12257 // transition undetectable by Web content.
12258 mReadyState = aReadyState;
12259 return;
12262 if (IsTopLevelContentDocument()) {
12263 if (aReadyState == READYSTATE_LOADING) {
12264 AddToplevelLoadingDocument(this);
12265 } else if (aReadyState == READYSTATE_COMPLETE) {
12266 RemoveToplevelLoadingDocument(this);
12270 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
12271 SetLoadingOrRestoredFromBFCacheTimeStampToNow();
12273 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
12274 mReadyState = aReadyState;
12275 if (aUpdateTimingInformation && mTiming) {
12276 switch (aReadyState) {
12277 case READYSTATE_LOADING:
12278 mTiming->NotifyDOMLoading(GetDocumentURI());
12279 break;
12280 case READYSTATE_INTERACTIVE:
12281 mTiming->NotifyDOMInteractive(GetDocumentURI());
12282 break;
12283 case READYSTATE_COMPLETE:
12284 mTiming->NotifyDOMComplete(GetDocumentURI());
12285 break;
12286 default:
12287 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
12288 break;
12291 // At the time of loading start, we don't have timing object, record time.
12293 if (READYSTATE_INTERACTIVE == aReadyState &&
12294 NodePrincipal()->IsSystemPrincipal()) {
12295 if (!mXULPersist && XRE_IsParentProcess()) {
12296 mXULPersist = new XULPersist(this);
12297 mXULPersist->Init();
12299 if (!mChromeObserver) {
12300 mChromeObserver = new ChromeObserver(this);
12301 mChromeObserver->Init();
12305 if (aUpdateTimingInformation) {
12306 RecordNavigationTiming(aReadyState);
12309 AsyncEventDispatcher::RunDOMEventWhenSafe(
12310 *this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
12313 void Document::GetReadyState(nsAString& aReadyState) const {
12314 switch (mReadyState) {
12315 case READYSTATE_LOADING:
12316 aReadyState.AssignLiteral(u"loading");
12317 break;
12318 case READYSTATE_INTERACTIVE:
12319 aReadyState.AssignLiteral(u"interactive");
12320 break;
12321 case READYSTATE_COMPLETE:
12322 aReadyState.AssignLiteral(u"complete");
12323 break;
12324 default:
12325 aReadyState.AssignLiteral(u"uninitialized");
12329 void Document::SuppressEventHandling(uint32_t aIncrease) {
12330 mEventsSuppressed += aIncrease;
12331 if (mEventsSuppressed == aIncrease) {
12332 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
12333 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12336 UpdateFrameRequestCallbackSchedulingState();
12337 for (uint32_t i = 0; i < aIncrease; ++i) {
12338 ScriptLoader()->AddExecuteBlocker();
12341 auto suppressInSubDoc = [aIncrease](Document& aSubDoc) {
12342 aSubDoc.SuppressEventHandling(aIncrease);
12343 return CallState::Continue;
12346 EnumerateSubDocuments(suppressInSubDoc);
12349 void Document::NotifyAbortedLoad() {
12350 // If we still have outstanding work blocking DOMContentLoaded,
12351 // then don't try to change the readystate now, but wait until
12352 // they finish and then do so.
12353 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
12354 mSetCompleteAfterDOMContentLoaded = true;
12355 return;
12358 // Otherwise we're fully done at this point, so set the
12359 // readystate to complete.
12360 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
12361 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
12365 MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents(
12366 nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) {
12367 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
12368 if (MOZ_UNLIKELY(!fm)) {
12369 return;
12372 nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments);
12373 for (uint32_t i = 0; i < documents.Length(); ++i) {
12374 nsCOMPtr<Document> document = std::move(documents[i]);
12375 // NB: Don't bother trying to fire delayed events on documents that were
12376 // closed before this event ran.
12377 if (!document->EventHandlingSuppressed()) {
12378 fm->FireDelayedEvents(document);
12379 RefPtr<PresShell> presShell = document->GetPresShell();
12380 if (presShell) {
12381 // Only fire events for active documents.
12382 bool fire = aFireEvents && document->GetInnerWindow() &&
12383 document->GetInnerWindow()->IsCurrentInnerWindow();
12384 presShell->FireOrClearDelayedEvents(fire);
12386 document->FireOrClearPostMessageEvents(aFireEvents);
12391 void Document::PreloadPictureClosed() {
12392 MOZ_ASSERT(mPreloadPictureDepth > 0);
12393 mPreloadPictureDepth--;
12394 if (mPreloadPictureDepth == 0) {
12395 mPreloadPictureFoundSource.SetIsVoid(true);
12399 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
12400 const nsAString& aSizesAttr,
12401 const nsAString& aTypeAttr,
12402 const nsAString& aMediaAttr) {
12403 // Nested pictures are not valid syntax, so while we'll eventually load them,
12404 // it's not worth tracking sources mixed between nesting levels to preload
12405 // them effectively.
12406 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
12407 // <picture> selects the first matching source, so if this returns a URI we
12408 // needn't consider new sources until a new <picture> is encountered.
12409 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
12410 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
12411 aMediaAttr, mPreloadPictureFoundSource);
12412 if (found && mPreloadPictureFoundSource.IsVoid()) {
12413 // Found an empty source, which counts
12414 mPreloadPictureFoundSource.SetIsVoid(false);
12419 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
12420 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
12421 const nsAString& aSizesAttr, bool* aIsImgSet) {
12422 nsString sourceURL;
12423 bool isImgSet;
12424 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
12425 // We're in a <picture> element and found a URI from a source previous to
12426 // this image, use it.
12427 sourceURL = mPreloadPictureFoundSource;
12428 isImgSet = true;
12429 } else {
12430 // Otherwise try to use this <img> as a source
12431 HTMLImageElement::SelectSourceForTagWithAttrs(
12432 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
12433 VoidString(), sourceURL);
12434 isImgSet = !aSrcsetAttr.IsEmpty();
12437 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
12438 if (sourceURL.IsEmpty()) {
12439 return nullptr;
12442 // Construct into URI using passed baseURI (the parser may know of base URI
12443 // changes that have not reached us)
12444 nsresult rv;
12445 nsCOMPtr<nsIURI> uri;
12446 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
12447 this, aBaseURI);
12448 if (NS_FAILED(rv)) {
12449 return nullptr;
12452 *aIsImgSet = isImgSet;
12454 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
12455 // this this <picture> share the same <sources> (though this is not valid per
12456 // spec)
12457 return uri.forget();
12460 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
12461 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
12462 bool aLinkPreload, uint64_t aEarlyHintPreloaderId) {
12463 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
12464 nsContentUtils::CORSModeToLoadImageFlags(
12465 Element::StringToCORSMode(aCrossOriginAttr));
12467 nsContentPolicyType policyType =
12468 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
12469 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
12471 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12472 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12474 RefPtr<imgRequestProxy> request;
12476 nsLiteralString initiator = aEarlyHintPreloaderId
12477 ? u"early-hints"_ns
12478 : (aLinkPreload ? u"link"_ns : u"img"_ns);
12480 nsresult rv = nsContentUtils::LoadImage(
12481 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
12482 nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request),
12483 policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId);
12485 // Pin image-reference to avoid evicting it from the img-cache before
12486 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
12487 // unlink
12488 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
12489 mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
12493 void Document::MaybePreLoadImage(nsIURI* aUri,
12494 const nsAString& aCrossOriginAttr,
12495 ReferrerPolicyEnum aReferrerPolicy,
12496 bool aIsImgSet, bool aLinkPreload) {
12497 const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr);
12498 if (aLinkPreload) {
12499 // Check if the image was already preloaded in this document to avoid
12500 // duplicate preloading.
12501 PreloadHashKey key =
12502 PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode);
12503 if (!mPreloadService.PreloadExists(key)) {
12504 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12505 aLinkPreload, 0);
12507 return;
12510 // Early exit if the img is already present in the img-cache
12511 // which indicates that the "real" load has already started and
12512 // that we shouldn't preload it.
12513 if (nsContentUtils::IsImageAvailable(aUri, NodePrincipal(), corsMode, this)) {
12514 return;
12517 // Image not in cache - trigger preload
12518 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload,
12522 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
12523 if (!StaticPrefs::network_preconnect()) {
12524 return;
12527 NS_MutateURI mutator(aOrigURI);
12528 if (NS_FAILED(mutator.GetStatus())) {
12529 return;
12532 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
12533 // which ignores the path and uses only the origin. The other is for the
12534 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
12535 // non-Anonymous preconnects create different connections on the wire and
12536 // therefore should not be considred duplicates of each other and we
12537 // normalize the path before putting it in the hash to accomplish that.
12539 if (aCORSMode == CORS_ANONYMOUS) {
12540 mutator.SetPathQueryRef("/anonymous"_ns);
12541 } else {
12542 mutator.SetPathQueryRef("/"_ns);
12545 nsCOMPtr<nsIURI> uri;
12546 nsresult rv = mutator.Finalize(uri);
12547 if (NS_FAILED(rv)) {
12548 return;
12551 const bool existingEntryFound =
12552 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
12553 if (entry) {
12554 return true;
12556 entry.Insert(true);
12557 return false;
12559 if (existingEntryFound) {
12560 return;
12563 nsCOMPtr<nsISpeculativeConnect> speculator =
12564 mozilla::components::IO::Service();
12565 if (!speculator) {
12566 return;
12569 OriginAttributes oa;
12570 StoragePrincipalHelper::GetOriginAttributesForNetworkState(this, oa);
12571 speculator->SpeculativeConnectWithOriginAttributesNative(
12572 uri, std::move(oa), nullptr, aCORSMode == CORS_ANONYMOUS);
12575 void Document::ForgetImagePreload(nsIURI* aURI) {
12576 // Checking count is faster than hashing the URI in the common
12577 // case of empty table.
12578 if (mPreloadingImages.Count() != 0) {
12579 nsCOMPtr<imgIRequest> req;
12580 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
12581 if (req) {
12582 // Make sure to cancel the request so imagelib knows it's gone.
12583 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
12588 void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates,
12589 bool aNotify) {
12590 const DocumentState oldStates = mState;
12591 if (aMaybeChangedStates.HasAtLeastOneOfStates(
12592 DocumentState::ALL_LOCALEDIR_BITS)) {
12593 mState &= ~DocumentState::ALL_LOCALEDIR_BITS;
12594 if (IsDocumentRightToLeft()) {
12595 mState |= DocumentState::RTL_LOCALE;
12596 } else {
12597 mState |= DocumentState::LTR_LOCALE;
12601 if (aMaybeChangedStates.HasAtLeastOneOfStates(DocumentState::LWTHEME)) {
12602 if (ComputeDocumentLWTheme()) {
12603 mState |= DocumentState::LWTHEME;
12604 } else {
12605 mState &= ~DocumentState::LWTHEME;
12609 if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) {
12610 BrowsingContext* bc = GetBrowsingContext();
12611 if (!bc || !bc->GetIsActiveBrowserWindow()) {
12612 mState |= DocumentState::WINDOW_INACTIVE;
12613 } else {
12614 mState &= ~DocumentState::WINDOW_INACTIVE;
12618 const DocumentState changedStates = oldStates ^ mState;
12619 if (aNotify && !changedStates.IsEmpty()) {
12620 if (PresShell* ps = GetObservingPresShell()) {
12621 ps->DocumentStatesChanged(changedStates);
12626 namespace {
12629 * Stub for LoadSheet(), since all we want is to get the sheet into
12630 * the CSSLoader's style cache
12632 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
12633 ~StubCSSLoaderObserver() = default;
12635 public:
12636 NS_IMETHOD
12637 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
12638 NS_DECL_ISUPPORTS
12640 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
12642 } // namespace
12644 SheetPreloadStatus Document::PreloadStyle(
12645 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
12646 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aNonce,
12647 const nsAString& aIntegrity, css::StylePreloadKind aKind,
12648 uint64_t aEarlyHintPreloaderId, const nsAString& aFetchPriority) {
12649 MOZ_ASSERT(aKind != css::StylePreloadKind::None);
12651 // The CSSLoader will retain this object after we return.
12652 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
12654 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12655 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12657 // Charset names are always ASCII.
12658 auto result = CSSLoader()->LoadSheet(
12659 uri, aKind, aEncoding, referrerInfo, obs, aEarlyHintPreloaderId,
12660 Element::StringToCORSMode(aCrossOriginAttr), aNonce, aIntegrity,
12661 nsGenericHTMLElement::ToFetchPriority(aFetchPriority));
12662 if (result.isErr()) {
12663 return SheetPreloadStatus::Errored;
12665 RefPtr<StyleSheet> sheet = result.unwrap();
12666 if (sheet->IsComplete()) {
12667 return SheetPreloadStatus::AlreadyComplete;
12669 return SheetPreloadStatus::InProgress;
12672 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
12673 return CSSLoader()
12674 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
12675 .unwrapOr(nullptr);
12678 void Document::ResetDocumentDirection() {
12679 if (!nsContentUtils::IsChromeDoc(this)) {
12680 return;
12682 UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true);
12685 bool Document::IsDocumentRightToLeft() {
12686 if (!nsContentUtils::IsChromeDoc(this)) {
12687 return false;
12689 // setting the localedir attribute on the root element forces a
12690 // specific direction for the document.
12691 Element* element = GetRootElement();
12692 if (element) {
12693 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
12694 nullptr};
12695 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
12696 strings, eCaseMatters)) {
12697 case 0:
12698 return false;
12699 case 1:
12700 return true;
12701 default:
12702 break; // otherwise, not a valid value, so fall through
12706 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
12707 !mDocumentURI->SchemeIs("resource")) {
12708 return false;
12711 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
12714 class nsDelayedEventDispatcher : public Runnable {
12715 public:
12716 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
12717 : mozilla::Runnable("nsDelayedEventDispatcher"),
12718 mDocuments(std::move(aDocuments)) {}
12719 virtual ~nsDelayedEventDispatcher() = default;
12721 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
12722 // bug 1535398.
12723 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
12724 FireOrClearDelayedEvents(std::move(mDocuments), true);
12725 return NS_OK;
12728 private:
12729 nsTArray<nsCOMPtr<Document>> mDocuments;
12732 static void GetAndUnsuppressSubDocuments(
12733 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
12734 if (aDocument.EventHandlingSuppressed() > 0) {
12735 aDocument.DecreaseEventSuppression();
12736 aDocument.ScriptLoader()->RemoveExecuteBlocker();
12738 aDocuments.AppendElement(&aDocument);
12739 auto recurse = [&aDocuments](Document& aSubDoc) {
12740 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
12741 return CallState::Continue;
12743 aDocument.EnumerateSubDocuments(recurse);
12746 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
12747 nsTArray<nsCOMPtr<Document>> documents;
12748 GetAndUnsuppressSubDocuments(*this, documents);
12750 for (nsCOMPtr<Document>& doc : documents) {
12751 if (!doc->EventHandlingSuppressed()) {
12752 if (WindowGlobalChild* wgc = doc->GetWindowGlobalChild()) {
12753 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12756 MOZ_ASSERT(NS_IsMainThread());
12757 nsTArray<RefPtr<net::ChannelEventQueue>> queues =
12758 std::move(doc->mSuspendedQueues);
12759 for (net::ChannelEventQueue* queue : queues) {
12760 queue->Resume();
12763 // If there have been any events driven by the refresh driver which were
12764 // delayed due to events being suppressed in this document, make sure
12765 // there is a refresh scheduled soon so the events will run.
12766 if (doc->mHasDelayedRefreshEvent) {
12767 doc->mHasDelayedRefreshEvent = false;
12769 if (doc->mPresShell) {
12770 nsRefreshDriver* rd =
12771 doc->mPresShell->GetPresContext()->RefreshDriver();
12772 rd->RunDelayedEventsSoon();
12778 if (aFireEvents) {
12779 MOZ_RELEASE_ASSERT(NS_IsMainThread());
12780 nsCOMPtr<nsIRunnable> ded =
12781 new nsDelayedEventDispatcher(std::move(documents));
12782 Dispatch(ded.forget());
12783 } else {
12784 FireOrClearDelayedEvents(std::move(documents), false);
12788 bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
12789 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
12792 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
12793 MOZ_ASSERT(NS_IsMainThread());
12794 MOZ_ASSERT(EventHandlingSuppressed());
12795 mSuspendedQueues.AppendElement(aQueue);
12798 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
12799 MOZ_ASSERT(NS_IsMainThread());
12801 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
12802 mSuspendedPostMessageEvents.AppendElement(aEvent);
12803 return true;
12805 return false;
12808 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
12809 nsTArray<RefPtr<PostMessageEvent>> events =
12810 std::move(mSuspendedPostMessageEvents);
12812 if (aFireEvents) {
12813 for (PostMessageEvent* event : events) {
12814 event->Run();
12819 void Document::SetSuppressedEventListener(EventListener* aListener) {
12820 mSuppressedEventListener = aListener;
12821 auto setOnSubDocs = [&](Document& aDocument) {
12822 aDocument.SetSuppressedEventListener(aListener);
12823 return CallState::Continue;
12825 EnumerateSubDocuments(setOnSubDocs);
12828 bool Document::IsActive() const {
12829 return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
12830 !GetBrowsingContext()->IsInBFCache();
12833 nsISupports* Document::GetCurrentContentSink() {
12834 return mParser ? mParser->GetContentSink() : nullptr;
12837 Document* Document::GetTemplateContentsOwner() {
12838 if (!mTemplateContentsOwner) {
12839 bool hasHadScriptObject = true;
12840 nsIScriptGlobalObject* scriptObject =
12841 GetScriptHandlingObject(hasHadScriptObject);
12843 nsCOMPtr<Document> document;
12844 nsresult rv = NS_NewDOMDocument(
12845 getter_AddRefs(document),
12846 u""_ns, // aNamespaceURI
12847 u""_ns, // aQualifiedName
12848 nullptr, // aDoctype
12849 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
12850 true, // aLoadedAsData
12851 scriptObject, // aEventObject
12852 IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
12853 NS_ENSURE_SUCCESS(rv, nullptr);
12855 mTemplateContentsOwner = document;
12856 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
12858 if (!scriptObject) {
12859 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
12862 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
12864 // Set |mTemplateContentsOwner| as the template contents owner of itself so
12865 // that it is the template contents owner of nested template elements.
12866 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
12869 MOZ_ASSERT(mTemplateContentsOwner->IsTemplateContentsOwner());
12870 return mTemplateContentsOwner;
12873 // https://html.spec.whatwg.org/#the-autofocus-attribute
12874 void Document::ElementWithAutoFocusInserted(Element* aAutoFocusCandidate) {
12875 BrowsingContext* bc = GetBrowsingContext();
12876 if (!bc) {
12877 return;
12880 // If target is not fully active, then return.
12881 if (!IsCurrentActiveDocument()) {
12882 return;
12885 // If target's active sandboxing flag set has the sandboxed automatic features
12886 // browsing context flag, then return.
12887 if (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES) {
12888 return;
12891 // For each ancestorBC of target's browsing context's ancestor browsing
12892 // contexts: if ancestorBC's active document's origin is not same origin with
12893 // target's origin, then return.
12894 while (bc) {
12895 BrowsingContext* parent = bc->GetParent();
12896 if (!parent) {
12897 break;
12899 // AncestorBC is not the same site
12900 if (!parent->IsInProcess()) {
12901 return;
12904 Document* currentDocument = bc->GetDocument();
12905 if (!currentDocument) {
12906 return;
12909 Document* parentDocument = parent->GetDocument();
12910 if (!parentDocument) {
12911 return;
12914 // Not same origin
12915 if (!currentDocument->NodePrincipal()->Equals(
12916 parentDocument->NodePrincipal())) {
12917 return;
12920 bc = parent;
12922 MOZ_ASSERT(bc->IsTop());
12924 Document* topDocument = bc->GetDocument();
12925 MOZ_ASSERT(topDocument);
12926 topDocument->AppendAutoFocusCandidateToTopDocument(aAutoFocusCandidate);
12929 void Document::ScheduleFlushAutoFocusCandidates() {
12930 MOZ_ASSERT(mPresShell && mPresShell->DidInitialize());
12931 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12932 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
12933 rd->ScheduleAutoFocusFlush(this);
12937 void Document::AppendAutoFocusCandidateToTopDocument(
12938 Element* aAutoFocusCandidate) {
12939 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12940 if (mAutoFocusFired) {
12941 return;
12944 if (!HasAutoFocusCandidates()) {
12945 // PresShell may be initialized later
12946 if (mPresShell && mPresShell->DidInitialize()) {
12947 ScheduleFlushAutoFocusCandidates();
12951 nsWeakPtr element = do_GetWeakReference(aAutoFocusCandidate);
12952 mAutoFocusCandidates.RemoveElement(element);
12953 mAutoFocusCandidates.AppendElement(element);
12956 void Document::SetAutoFocusFired() {
12957 mAutoFocusCandidates.Clear();
12958 mAutoFocusFired = true;
12961 // https://html.spec.whatwg.org/#flush-autofocus-candidates
12962 void Document::FlushAutoFocusCandidates() {
12963 MOZ_ASSERT(GetBrowsingContext()->IsTop());
12964 if (mAutoFocusFired) {
12965 return;
12968 if (!mPresShell) {
12969 return;
12972 MOZ_ASSERT(HasAutoFocusCandidates());
12973 MOZ_ASSERT(mPresShell->DidInitialize());
12975 nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetWindow();
12976 // We should be the top document
12977 if (!topWindow) {
12978 return;
12981 #ifdef DEBUG
12983 // Trying to find the top window (equivalent to window.top).
12984 nsCOMPtr<nsPIDOMWindowOuter> top = topWindow->GetInProcessTop();
12985 MOZ_ASSERT(topWindow == top);
12987 #endif
12989 // Don't steal the focus from the user
12990 if (topWindow->GetFocusedElement()) {
12991 SetAutoFocusFired();
12992 return;
12995 MOZ_ASSERT(mDocumentURI);
12996 nsAutoCString ref;
12997 // GetRef never fails
12998 nsresult rv = mDocumentURI->GetRef(ref);
12999 if (NS_SUCCEEDED(rv) &&
13000 nsContentUtils::GetTargetElement(this, NS_ConvertUTF8toUTF16(ref))) {
13001 SetAutoFocusFired();
13002 return;
13005 nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mAutoFocusCandidates);
13006 while (iter.HasMore()) {
13007 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(iter.GetNext());
13008 if (!autoFocusElement) {
13009 continue;
13011 RefPtr<Document> autoFocusElementDoc = autoFocusElement->OwnerDoc();
13012 // Get the latest info about the frame and allow scripts
13013 // to run which might affect the focusability of this element.
13014 autoFocusElementDoc->FlushPendingNotifications(FlushType::Frames);
13016 // Above layout flush may cause the PresShell to disappear.
13017 if (!mPresShell) {
13018 return;
13021 // Re-get the element because the ownerDoc() might have changed
13022 autoFocusElementDoc = autoFocusElement->OwnerDoc();
13023 BrowsingContext* bc = autoFocusElementDoc->GetBrowsingContext();
13024 if (!bc) {
13025 continue;
13028 // If doc is not fully active, then remove element from candidates, and
13029 // continue.
13030 if (!autoFocusElementDoc->IsCurrentActiveDocument()) {
13031 iter.Remove();
13032 continue;
13035 nsCOMPtr<nsIContentSink> sink =
13036 do_QueryInterface(autoFocusElementDoc->GetCurrentContentSink());
13037 if (sink) {
13038 nsHtml5TreeOpExecutor* executor =
13039 static_cast<nsHtml5TreeOpExecutor*>(sink->AsExecutor());
13040 if (executor) {
13041 // This is a HTML5 document
13042 MOZ_ASSERT(autoFocusElementDoc->IsHTMLDocument());
13043 // If doc's script-blocking style sheet counter is greater than 0, th
13044 // return.
13045 if (executor->WaitForPendingSheets()) {
13046 // In this case, element is the currently-best candidate, but doc is
13047 // not ready for autofocusing. We'll try again next time flush
13048 // autofocus candidates is called.
13049 ScheduleFlushAutoFocusCandidates();
13050 return;
13055 // The autofocus element could be moved to a different
13056 // top level BC.
13057 if (bc->Top()->GetDocument() != this) {
13058 continue;
13061 iter.Remove();
13063 // Let inclusiveAncestorDocuments be a list consisting of doc, plus the
13064 // active documents of each of doc's browsing context's ancestor browsing
13065 // contexts.
13066 // If any Document in inclusiveAncestorDocuments has non-null target
13067 // element, then continue.
13068 bool shouldFocus = true;
13069 while (bc) {
13070 Document* doc = bc->GetDocument();
13071 if (!doc) {
13072 shouldFocus = false;
13073 break;
13076 nsIURI* uri = doc->GetDocumentURI();
13077 if (!uri) {
13078 shouldFocus = false;
13079 break;
13082 nsAutoCString ref;
13083 nsresult rv = uri->GetRef(ref);
13084 // If there is an element in the document tree that has an ID equal to
13085 // fragment
13086 if (NS_SUCCEEDED(rv) &&
13087 nsContentUtils::GetTargetElement(doc, NS_ConvertUTF8toUTF16(ref))) {
13088 shouldFocus = false;
13089 break;
13091 bc = bc->GetParent();
13094 if (!shouldFocus) {
13095 continue;
13098 MOZ_ASSERT(topWindow);
13099 if (TryAutoFocusCandidate(*autoFocusElement)) {
13100 // We've successfully autofocused an element, don't
13101 // need to try to focus the rest.
13102 SetAutoFocusFired();
13103 break;
13107 if (HasAutoFocusCandidates()) {
13108 ScheduleFlushAutoFocusCandidates();
13112 bool Document::TryAutoFocusCandidate(Element& aElement) {
13113 const FocusOptions options;
13114 if (RefPtr<Element> target = nsFocusManager::GetTheFocusableArea(
13115 &aElement, nsFocusManager::ProgrammaticFocusFlags(options))) {
13116 target->Focus(options, CallerType::NonSystem, IgnoreErrors());
13117 return true;
13120 return false;
13123 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
13124 if (!aDocumentURI) {
13125 return;
13128 nsAutoCString ref;
13130 // Since all URI's that pass through here aren't URL's we can't
13131 // rely on the nsIURI implementation for providing a way for
13132 // finding the 'ref' part of the URI, we'll haveto revert to
13133 // string routines for finding the data past '#'
13135 nsresult rv = aDocumentURI->GetSpec(ref);
13136 if (NS_FAILED(rv)) {
13137 Unused << aDocumentURI->GetRef(mScrollToRef);
13138 return;
13141 nsReadingIterator<char> start, end;
13143 ref.BeginReading(start);
13144 ref.EndReading(end);
13146 if (FindCharInReadable('#', start, end)) {
13147 ++start; // Skip over the '#'
13149 mScrollToRef = Substring(start, end);
13153 // https://html.spec.whatwg.org/#scrolling-to-a-fragment
13154 void Document::ScrollToRef() {
13155 if (mScrolledToRefAlready) {
13156 RefPtr<PresShell> presShell = GetPresShell();
13157 if (presShell) {
13158 presShell->ScrollToAnchor();
13160 return;
13163 // 2. If fragment is the empty string, then return the special value top of
13164 // the document.
13165 if (mScrollToRef.IsEmpty()) {
13166 return;
13169 RefPtr<PresShell> presShell = GetPresShell();
13170 if (!presShell) {
13171 return;
13174 // 3. Let potentialIndicatedElement be the result of finding a potential
13175 // indicated element given document and fragment.
13176 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
13177 auto rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
13179 // 4. If potentialIndicatedElement is not null, then return
13180 // potentialIndicatedElement.
13181 if (NS_SUCCEEDED(rv)) {
13182 mScrolledToRefAlready = true;
13183 return;
13186 // 5. Let fragmentBytes be the result of percent-decoding fragment.
13187 nsAutoCString fragmentBytes;
13188 const bool unescaped =
13189 NS_UnescapeURL(mScrollToRef.Data(), mScrollToRef.Length(),
13190 /* aFlags = */ 0, fragmentBytes);
13192 if (!unescaped || fragmentBytes.IsEmpty()) {
13193 // Another attempt is only necessary if characters were unescaped.
13194 return;
13197 // 6. Let decodedFragment be the result of running UTF-8 decode without BOM on
13198 // fragmentBytes.
13199 nsAutoString decodedFragment;
13200 rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(fragmentBytes, decodedFragment);
13201 NS_ENSURE_SUCCESS_VOID(rv);
13203 // 7. Set potentialIndicatedElement to the result of finding a potential
13204 // indicated element given document and decodedFragment.
13205 rv = presShell->GoToAnchor(decodedFragment,
13206 mChangeScrollPosWhenScrollingToRef);
13207 if (NS_SUCCEEDED(rv)) {
13208 mScrolledToRefAlready = true;
13212 void Document::RegisterActivityObserver(nsISupports* aSupports) {
13213 if (!mActivityObservers) {
13214 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
13216 mActivityObservers->Insert(aSupports);
13219 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
13220 if (!mActivityObservers) {
13221 return false;
13223 return mActivityObservers->EnsureRemoved(aSupports);
13226 void Document::EnumerateActivityObservers(
13227 ActivityObserverEnumerator aEnumerator) {
13228 if (!mActivityObservers) {
13229 return;
13232 const auto keyArray =
13233 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
13234 for (auto& observer : keyArray) {
13235 aEnumerator(observer.get());
13239 void Document::RegisterPendingLinkUpdate(Link* aLink) {
13240 if (aLink->HasPendingLinkUpdate()) {
13241 return;
13244 aLink->SetHasPendingLinkUpdate();
13246 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
13247 nsCOMPtr<nsIRunnable> event =
13248 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
13249 &Document::FlushPendingLinkUpdates);
13250 // Do this work in a second in the worst case.
13251 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
13252 EventQueuePriority::Idle);
13253 if (NS_FAILED(rv)) {
13254 // If during shutdown posting a runnable doesn't succeed, we probably
13255 // don't need to update link states.
13256 return;
13258 mHasLinksToUpdateRunnable = true;
13261 mLinksToUpdate.InfallibleAppend(aLink);
13264 void Document::FlushPendingLinkUpdates() {
13265 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
13266 MOZ_ASSERT(mHasLinksToUpdateRunnable);
13267 mHasLinksToUpdateRunnable = false;
13269 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
13270 mFlushingPendingLinkUpdates = true;
13272 while (!mLinksToUpdate.IsEmpty()) {
13273 LinksToUpdateList links(std::move(mLinksToUpdate));
13274 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
13275 Link* link = iter.Get();
13276 Element* element = link->GetElement();
13277 if (element->OwnerDoc() == this) {
13278 link->ClearHasPendingLinkUpdate();
13279 if (element->IsInComposedDoc()) {
13280 link->TriggerLinkUpdate(/* aNotify = */ true);
13288 * Retrieves the node in a static-clone document that corresponds to aOrigNode,
13289 * which is a node in the original document from which aStaticClone was cloned.
13291 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
13292 Document& aStaticClone) {
13293 MOZ_ASSERT(aOrigNode);
13295 // Selections in anonymous subtrees aren't supported.
13296 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
13297 return nullptr;
13300 // If the node is disconnected, this is a bug in the selection code, but it
13301 // can happen with shadow DOM so handle it.
13302 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
13303 return nullptr;
13306 AutoTArray<Maybe<uint32_t>, 32> indexArray;
13307 const nsINode* current = aOrigNode;
13308 while (const nsINode* parent = current->GetParentNode()) {
13309 Maybe<uint32_t> index = parent->ComputeIndexOf(current);
13310 NS_ENSURE_TRUE(index.isSome(), nullptr);
13311 indexArray.AppendElement(std::move(index));
13312 current = parent;
13314 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
13315 nsINode* correspondingNode = [&]() -> nsINode* {
13316 if (current->IsDocument()) {
13317 return &aStaticClone;
13319 const auto* shadow = ShadowRoot::FromNode(*current);
13320 if (!shadow) {
13321 return nullptr;
13323 nsINode* correspondingHost =
13324 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
13325 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
13326 return nullptr;
13328 return correspondingHost->AsElement()->GetShadowRoot();
13329 }();
13331 if (NS_WARN_IF(!correspondingNode)) {
13332 return nullptr;
13334 for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
13335 correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
13336 NS_ENSURE_TRUE(correspondingNode, nullptr);
13338 return correspondingNode;
13342 * Caches the selection ranges from the source document onto the static clone in
13343 * case the "Print Selection Only" functionality is invoked.
13345 * Note that we cannot use the selection obtained from GetOriginalDocument()
13346 * since that selection may have mutated after the print was invoked.
13348 * Note also that because nsRange objects point into a specific document's
13349 * nodes, we cannot reuse an array of nsRange objects across multiple static
13350 * clone documents. For that reason we cache a new array of ranges on each
13351 * static clone that we create.
13353 * TODO(emilio): This can be simplified once we don't re-clone from static
13354 * documents.
13356 * @param aSourceDoc the document from which we are caching selection ranges
13357 * @param aStaticClone the document that will hold the cache
13358 * @return true if a selection range was cached
13360 static void CachePrintSelectionRanges(const Document& aSourceDoc,
13361 Document& aStaticClone) {
13362 MOZ_ASSERT(aStaticClone.IsStaticDocument());
13363 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc));
13364 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
13366 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
13368 // When the user opts to "Print Selection Only", the print code prefers any
13369 // selection in the static clone corresponding to the focused frame. If this
13370 // is that static clone, flag it for the printing code:
13371 const bool isFocusedDoc = [&] {
13372 if (sourceDocIsStatic) {
13373 return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc));
13375 nsPIDOMWindowOuter* window = aSourceDoc.GetWindow();
13376 if (!window) {
13377 return false;
13379 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
13380 if (!rootWindow) {
13381 return false;
13383 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
13384 nsFocusManager::GetFocusedDescendant(rootWindow,
13385 nsFocusManager::eIncludeAllDescendants,
13386 getter_AddRefs(focusedWindow));
13387 return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc;
13388 }();
13389 if (isFocusedDoc) {
13390 aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc,
13391 reinterpret_cast<void*>(true));
13394 const Selection* origSelection = nullptr;
13395 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
13397 if (sourceDocIsStatic) {
13398 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
13399 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
13400 } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
13401 origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
13404 if (!origSelection && !origRanges) {
13405 return;
13408 const uint32_t rangeCount =
13409 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
13410 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
13412 for (const uint32_t i : IntegerRange(rangeCount)) {
13413 MOZ_ASSERT_IF(!sourceDocIsStatic,
13414 origSelection->RangeCount() == rangeCount);
13415 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
13416 : origSelection->GetRangeAt(i);
13417 MOZ_ASSERT(range);
13418 nsINode* startContainer = range->GetStartContainer();
13419 nsINode* endContainer = range->GetEndContainer();
13421 if (!startContainer || !endContainer) {
13422 continue;
13425 nsINode* startNode =
13426 GetCorrespondingNodeInDocument(startContainer, aStaticClone);
13427 nsINode* endNode =
13428 GetCorrespondingNodeInDocument(endContainer, aStaticClone);
13430 if (NS_WARN_IF(!startNode || !endNode)) {
13431 continue;
13434 RefPtr<nsRange> clonedRange =
13435 nsRange::Create(startNode, range->StartOffset(), endNode,
13436 range->EndOffset(), IgnoreErrors());
13437 if (clonedRange && !clonedRange->Collapsed()) {
13438 printRanges->AppendElement(std::move(clonedRange));
13442 if (printRanges->IsEmpty()) {
13443 return;
13446 aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
13447 printRanges.release(),
13448 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
13451 already_AddRefed<Document> Document::CreateStaticClone(
13452 nsIDocShell* aCloneContainer, nsIDocumentViewer* aViewer,
13453 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
13454 MOZ_ASSERT(!mCreatingStaticClone);
13455 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
13456 MOZ_DIAGNOSTIC_ASSERT(aViewer);
13458 mCreatingStaticClone = true;
13459 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
13460 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
13462 auto raii = MakeScopeExit([&] {
13463 RemoveProperty(nsGkAtoms::adoptedsheetclones);
13464 mCreatingStaticClone = false;
13467 // Make document use different container during cloning.
13469 // FIXME(emilio): Why is this needed?
13470 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
13471 SetContainer(nsDocShell::Cast(aCloneContainer));
13472 IgnoredErrorResult rv;
13473 nsCOMPtr<nsINode> clonedNode = CloneNode(true, rv);
13474 SetContainer(originalShell);
13475 if (rv.Failed()) {
13476 return nullptr;
13479 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
13480 if (!clonedDoc) {
13481 return nullptr;
13484 size_t sheetsCount = SheetCount();
13485 for (size_t i = 0; i < sheetsCount; ++i) {
13486 RefPtr<StyleSheet> sheet = SheetAt(i);
13487 if (sheet) {
13488 if (sheet->IsApplicable()) {
13489 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13490 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13491 if (clonedSheet) {
13492 clonedDoc->AddStyleSheet(clonedSheet);
13497 clonedDoc->CloneAdoptedSheetsFrom(*this);
13499 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
13500 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
13501 for (StyleSheet* sheet : sheets) {
13502 if (sheet->IsApplicable()) {
13503 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13504 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13505 if (clonedSheet) {
13506 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
13507 clonedSheet);
13513 // Font faces created with the JS API will not be reflected in the
13514 // stylesheets and need to be copied over to the cloned document.
13515 if (const FontFaceSet* set = GetFonts()) {
13516 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
13519 clonedDoc->mReferrerInfo =
13520 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
13521 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
13522 CachePrintSelectionRanges(*this, *clonedDoc);
13524 // We're done with the clone, embed ourselves into the document viewer and
13525 // clone our children. The order here is pretty important, because our
13526 // document our document needs to have an owner global before we can create
13527 // the frame loaders for subdocuments.
13528 aViewer->SetDocument(clonedDoc);
13530 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
13532 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
13533 for (const auto& clone : pendingClones) {
13534 RefPtr<Element> element = do_QueryObject(clone.mElement);
13535 RefPtr<nsFrameLoader> frameLoader =
13536 nsFrameLoader::Create(element, /* aNetworkCreated */ false);
13538 if (NS_WARN_IF(!frameLoader)) {
13539 continue;
13542 clone.mElement->SetFrameLoader(frameLoader);
13544 nsresult rv = frameLoader->FinishStaticClone(
13545 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
13546 Unused << NS_WARN_IF(NS_FAILED(rv));
13549 return clonedDoc.forget();
13552 void Document::UnlinkOriginalDocumentIfStatic() {
13553 if (IsStaticDocument() && mOriginalDocument) {
13554 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
13555 mOriginalDocument->mStaticCloneCount--;
13556 mOriginalDocument = nullptr;
13558 MOZ_ASSERT(!mOriginalDocument);
13561 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
13562 int32_t* aHandle) {
13563 nsresult rv = mFrameRequestManager.Schedule(aCallback, aHandle);
13564 if (NS_FAILED(rv)) {
13565 return rv;
13568 UpdateFrameRequestCallbackSchedulingState();
13569 return NS_OK;
13572 void Document::CancelFrameRequestCallback(int32_t aHandle) {
13573 if (mFrameRequestManager.Cancel(aHandle)) {
13574 UpdateFrameRequestCallbackSchedulingState();
13578 bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
13579 return mFrameRequestManager.IsCanceled(aHandle);
13582 nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
13583 // Get the document's current state object. This is the object backing both
13584 // history.state and popStateEvent.state.
13586 // mStateObjectContainer may be null; this just means that there's no
13587 // current state object.
13589 if (!mCachedStateObjectValid) {
13590 if (mStateObjectContainer) {
13591 AutoJSAPI jsapi;
13592 // Init with null is "OK" in the sense that it will just fail.
13593 if (!jsapi.Init(GetScopeObject())) {
13594 return NS_ERROR_UNEXPECTED;
13596 JS::Rooted<JS::Value> value(jsapi.cx());
13597 nsresult rv =
13598 mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value);
13599 NS_ENSURE_SUCCESS(rv, rv);
13601 mCachedStateObject = value;
13602 if (!value.isNullOrUndefined()) {
13603 mozilla::HoldJSObjects(this);
13605 } else {
13606 mCachedStateObject = JS::NullValue();
13608 mCachedStateObjectValid = true;
13611 aState.set(mCachedStateObject);
13612 return NS_OK;
13615 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
13616 mTiming = aTiming;
13617 if (!mLoadingOrRestoredFromBFCacheTimeStamp.IsNull() && mTiming) {
13618 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(),
13619 mLoadingOrRestoredFromBFCacheTimeStamp);
13622 // If there's already the DocumentTimeline instance, tell it since the
13623 // DocumentTimeline is based on both the navigation start time stamp and the
13624 // refresh driver timestamp.
13625 if (mDocumentTimeline) {
13626 mDocumentTimeline->UpdateLastRefreshDriverTime();
13630 nsContentList* Document::ImageMapList() {
13631 if (!mImageMaps) {
13632 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
13633 nsGkAtoms::map);
13636 return mImageMaps;
13639 #define DEPRECATED_OPERATION(_op) #_op "Warning",
13640 static const char* kDeprecationWarnings[] = {
13641 #include "nsDeprecatedOperationList.h"
13642 nullptr};
13643 #undef DEPRECATED_OPERATION
13645 #define DOCUMENT_WARNING(_op) #_op "Warning",
13646 static const char* kDocumentWarnings[] = {
13647 #include "nsDocumentWarningList.h"
13648 nullptr};
13649 #undef DOCUMENT_WARNING
13651 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
13652 switch (aOperation) {
13653 #define DEPRECATED_OPERATION(_op) \
13654 case DeprecatedOperations::e##_op: \
13655 return eUseCounter_##_op;
13656 #include "nsDeprecatedOperationList.h"
13657 #undef DEPRECATED_OPERATION
13658 default:
13659 MOZ_CRASH();
13663 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
13664 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
13667 void Document::WarnOnceAbout(
13668 DeprecatedOperations aOperation, bool asError /* = false */,
13669 const nsTArray<nsString>& aParams /* = empty array */) const {
13670 MOZ_ASSERT(NS_IsMainThread());
13671 if (HasWarnedAbout(aOperation)) {
13672 return;
13674 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
13675 // Don't count deprecated operations for about pages since those pages
13676 // are almost in our control, and we always need to remove uses there
13677 // before we remove the operation itself anyway.
13678 if (!IsAboutPage()) {
13679 const_cast<Document*>(this)->SetUseCounter(
13680 OperationToUseCounter(aOperation));
13682 uint32_t flags =
13683 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13684 nsContentUtils::ReportToConsole(
13685 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
13686 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
13689 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
13690 return mDocWarningWarnedAbout[aWarning];
13693 void Document::WarnOnceAbout(
13694 DocumentWarnings aWarning, bool asError /* = false */,
13695 const nsTArray<nsString>& aParams /* = empty array */) const {
13696 MOZ_ASSERT(NS_IsMainThread());
13697 if (HasWarnedAbout(aWarning)) {
13698 return;
13700 mDocWarningWarnedAbout[aWarning] = true;
13701 uint32_t flags =
13702 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13703 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
13704 nsContentUtils::eDOM_PROPERTIES,
13705 kDocumentWarnings[aWarning], aParams);
13708 mozilla::dom::ImageTracker* Document::ImageTracker() {
13709 if (!mImageTracker) {
13710 mImageTracker = new mozilla::dom::ImageTracker;
13712 return mImageTracker;
13715 void Document::ScheduleSVGUseElementShadowTreeUpdate(
13716 SVGUseElement& aUseElement) {
13717 MOZ_ASSERT(aUseElement.IsInComposedDoc());
13719 if (MOZ_UNLIKELY(mIsStaticDocument)) {
13720 // Printing doesn't deal well with dynamic DOM mutations.
13721 return;
13724 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
13726 if (PresShell* presShell = GetPresShell()) {
13727 presShell->EnsureStyleFlush();
13731 void Document::DoUpdateSVGUseElementShadowTrees() {
13732 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13734 MOZ_ASSERT(!mCloningForSVGUse);
13735 mCloningForSVGUse = true;
13737 do {
13738 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
13739 mSVGUseElementsNeedingShadowTreeUpdate);
13740 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
13742 for (const auto& useElement : useElementsToUpdate) {
13743 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
13744 // The element was in another <use> shadow tree which we processed
13745 // already and also needed an update, and is removed from the document
13746 // now, so nothing to do here.
13747 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
13748 continue;
13750 useElement->UpdateShadowTree();
13752 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13754 mCloningForSVGUse = false;
13757 void Document::NotifyMediaFeatureValuesChanged() {
13758 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
13759 imageElement->MediaFeatureValuesChanged();
13763 already_AddRefed<Touch> Document::CreateTouch(
13764 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
13765 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
13766 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
13767 float aRotationAngle, float aForce) {
13768 RefPtr<Touch> touch =
13769 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
13770 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
13771 return touch.forget();
13774 already_AddRefed<TouchList> Document::CreateTouchList() {
13775 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13776 return retval.forget();
13779 already_AddRefed<TouchList> Document::CreateTouchList(
13780 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
13781 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13782 retval->Append(&aTouch);
13783 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13784 retval->Append(aTouches[i].get());
13786 return retval.forget();
13789 already_AddRefed<TouchList> Document::CreateTouchList(
13790 const Sequence<OwningNonNull<Touch>>& aTouches) {
13791 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13792 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13793 retval->Append(aTouches[i].get());
13795 return retval.forget();
13798 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
13799 float aX, float aY) {
13800 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
13802 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
13803 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
13804 nsPoint pt(x, y);
13806 FlushPendingNotifications(FlushType::Layout);
13808 PresShell* presShell = GetPresShell();
13809 if (!presShell) {
13810 return nullptr;
13813 nsIFrame* rootFrame = presShell->GetRootFrame();
13815 // XUL docs, unlike HTML, have no frame tree until everything's done loading
13816 if (!rootFrame) {
13817 return nullptr;
13820 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
13821 RelativeTo{rootFrame}, pt,
13822 {{FrameForPointOption::IgnorePaintSuppression,
13823 FrameForPointOption::IgnoreCrossDoc}});
13824 if (!ptFrame) {
13825 return nullptr;
13828 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
13829 nsPoint adjustedPoint = pt;
13830 if (nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
13831 adjustedPoint) !=
13832 nsLayoutUtils::TRANSFORM_SUCCEEDED) {
13833 return nullptr;
13836 nsIFrame::ContentOffsets offsets =
13837 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
13839 nsCOMPtr<nsIContent> node = offsets.content;
13840 uint32_t offset = offsets.offset;
13841 nsCOMPtr<nsIContent> anonNode = node;
13842 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
13843 if (nodeIsAnonymous) {
13844 node = ptFrame->GetContent();
13845 nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
13846 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
13847 nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
13848 if (textFrame) {
13849 // If the anonymous content node has a child, then we need to make sure
13850 // that we get the appropriate child, as otherwise the offset may not be
13851 // correct when we construct a range for it.
13852 nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
13853 if (firstChild) {
13854 anonNode = firstChild;
13857 if (textArea) {
13858 offset =
13859 nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
13862 node = nonanon;
13863 } else {
13864 node = nullptr;
13865 offset = 0;
13869 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
13870 if (nodeIsAnonymous) {
13871 aCaretPos->SetAnonymousContentNode(anonNode);
13873 return aCaretPos.forget();
13876 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
13877 // We rely on correct frame information here, so need to flush frames.
13878 FlushPendingNotifications(FlushType::Frames);
13880 // An element that is the HTML body element is potentially scrollable if all
13881 // of the following conditions are true:
13883 // The element has an associated CSS layout box.
13884 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
13885 if (!bodyFrame) {
13886 return false;
13889 // The element's parent element's computed value of the overflow-x and
13890 // overflow-y properties are visible.
13891 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
13892 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
13893 if (parentFrame &&
13894 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
13895 return false;
13898 // The element's computed value of the overflow-x or overflow-y properties is
13899 // not visible.
13900 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
13903 Element* Document::GetScrollingElement() {
13904 // Keep this in sync with IsScrollingElement.
13905 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
13906 RefPtr<HTMLBodyElement> body = GetBodyElement();
13907 if (body && !IsPotentiallyScrollable(body)) {
13908 return body;
13911 return nullptr;
13914 return GetRootElement();
13917 bool Document::IsScrollingElement(Element* aElement) {
13918 // Keep this in sync with GetScrollingElement.
13919 MOZ_ASSERT(aElement);
13921 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
13922 return aElement == GetRootElement();
13925 // In the common case when aElement != body, avoid refcounting.
13926 HTMLBodyElement* body = GetBodyElement();
13927 if (aElement != body) {
13928 return false;
13931 // Now we know body is non-null, since aElement is not null. It's the
13932 // scrolling element for the document if it itself is not potentially
13933 // scrollable.
13934 RefPtr<HTMLBodyElement> strongBody(body);
13935 return !IsPotentiallyScrollable(strongBody);
13938 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
13939 public:
13940 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
13941 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
13943 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
13944 const BlockParsingOptions& aOptions)
13945 : mPromise(aPromise) {
13946 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
13947 if (parser &&
13948 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
13949 parser->BlockParser();
13950 mParser = do_GetWeakReference(parser);
13951 mDocument = aDocument;
13952 mDocument->BlockOnload();
13953 mDocument->BlockDOMContentLoaded();
13957 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13958 ErrorResult& aRv) override {
13959 MaybeUnblockParser();
13961 mPromise->MaybeResolve(aValue);
13964 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13965 ErrorResult& aRv) override {
13966 MaybeUnblockParser();
13968 mPromise->MaybeReject(aValue);
13971 protected:
13972 virtual ~UnblockParsingPromiseHandler() {
13973 // If we're being cleaned up by the cycle collector, our mDocument reference
13974 // may have been unlinked while our mParser weak reference is still alive.
13975 if (mDocument) {
13976 MaybeUnblockParser();
13980 private:
13981 void MaybeUnblockParser() {
13982 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
13983 if (parser) {
13984 MOZ_DIAGNOSTIC_ASSERT(mDocument);
13985 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
13986 if (parser == docParser) {
13987 parser->UnblockParser();
13988 parser->ContinueInterruptedParsingAsync();
13991 if (mDocument) {
13992 // We blocked DOMContentLoaded and load events on this document. Unblock
13993 // them. Note that we want to do that no matter what's going on with the
13994 // parser state for this document. Maybe someone caused it to stop being
13995 // parsed, so CreatorParserOrNull() is returning null, but we still want
13996 // to unblock these.
13997 mDocument->UnblockDOMContentLoaded();
13998 mDocument->UnblockOnload(false);
14000 mParser = nullptr;
14001 mDocument = nullptr;
14004 nsWeakPtr mParser;
14005 RefPtr<Promise> mPromise;
14006 RefPtr<Document> mDocument;
14009 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
14011 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
14012 NS_INTERFACE_MAP_ENTRY(nsISupports)
14013 NS_INTERFACE_MAP_END
14015 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
14016 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
14018 already_AddRefed<Promise> Document::BlockParsing(
14019 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
14020 RefPtr<Promise> resultPromise =
14021 Promise::Create(aPromise.GetParentObject(), aRv);
14022 if (aRv.Failed()) {
14023 return nullptr;
14026 RefPtr<PromiseNativeHandler> promiseHandler =
14027 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
14028 aPromise.AppendNativeHandler(promiseHandler);
14030 return resultPromise.forget();
14033 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
14034 if (mFailedChannel) {
14035 nsCOMPtr<nsIURI> failedURI;
14036 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
14037 return failedURI.forget();
14041 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
14042 if (!uri) {
14043 return nullptr;
14046 return uri.forget();
14049 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
14050 if (mIsGoingAway) {
14051 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
14052 return nullptr;
14055 if (!mReadyForIdle) {
14056 nsIGlobalObject* global = GetScopeObject();
14057 if (!global) {
14058 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
14059 return nullptr;
14062 mReadyForIdle = Promise::Create(global, aRv);
14063 if (aRv.Failed()) {
14064 return nullptr;
14068 return mReadyForIdle;
14071 void Document::MaybeResolveReadyForIdle() {
14072 IgnoredErrorResult rv;
14073 Promise* readyPromise = GetDocumentReadyForIdle(rv);
14074 if (readyPromise) {
14075 readyPromise->MaybeResolveWithUndefined();
14079 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
14080 // The policy is created when the document is initialized. We _must_ have a
14081 // policy here even if the featurePolicy pref is off. If this assertion fails,
14082 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
14083 MOZ_ASSERT(mFeaturePolicy);
14084 return mFeaturePolicy;
14087 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
14088 // Only chrome documents are allowed to use command dispatcher.
14089 if (!nsContentUtils::IsChromeDoc(this)) {
14090 return nullptr;
14092 if (!mCommandDispatcher) {
14093 // Create our command dispatcher and hook it up.
14094 mCommandDispatcher = new nsXULCommandDispatcher(this);
14096 return mCommandDispatcher;
14099 void Document::InitializeXULBroadcastManager() {
14100 if (mXULBroadcastManager) {
14101 return;
14103 mXULBroadcastManager = new XULBroadcastManager(this);
14106 namespace {
14108 class DevToolsMutationObserver final : public nsStubMutationObserver {
14109 NS_DECL_ISUPPORTS
14110 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
14111 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
14112 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
14114 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
14115 // relies on the event firing _before_ the removal happens.
14116 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
14118 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
14119 // data changes right now (maybe intentionally?).
14120 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
14122 DevToolsMutationObserver() = default;
14124 private:
14125 void FireEvent(nsINode* aTarget, const nsAString& aType);
14127 ~DevToolsMutationObserver() = default;
14130 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
14132 void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
14133 const nsAString& aType) {
14134 AsyncEventDispatcher::RunDOMEventWhenSafe(*aTarget, aType, CanBubble::eNo,
14135 ChromeOnlyDispatch::eYes,
14136 Composed::eYes);
14139 void DevToolsMutationObserver::AttributeChanged(Element* aElement,
14140 int32_t aNamespaceID,
14141 nsAtom* aAttribute,
14142 int32_t aModType,
14143 const nsAttrValue* aOldValue) {
14144 FireEvent(aElement, u"devtoolsattrmodified"_ns);
14147 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
14148 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
14149 ContentInserted(c);
14153 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
14154 FireEvent(aChild, u"devtoolschildinserted"_ns);
14157 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
14159 } // namespace
14161 void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
14162 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
14163 return;
14165 mDevToolsWatchingDOMMutations = aValue;
14166 if (aValue) {
14167 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
14168 sDevToolsMutationObserver = new DevToolsMutationObserver();
14169 ClearOnShutdown(&sDevToolsMutationObserver);
14171 AddMutationObserver(sDevToolsMutationObserver);
14172 } else if (sDevToolsMutationObserver) {
14173 RemoveMutationObserver(sDevToolsMutationObserver);
14177 void EvaluateMediaQueryLists(nsTArray<RefPtr<MediaQueryList>>& aListsToNotify,
14178 Document& aDocument, bool aRecurse) {
14179 if (nsPresContext* pc = aDocument.GetPresContext()) {
14180 pc->FlushPendingMediaFeatureValuesChanged();
14183 for (MediaQueryList* mql : aDocument.MediaQueryLists()) {
14184 if (mql->EvaluateOnRenderingUpdate()) {
14185 aListsToNotify.AppendElement(mql);
14188 if (!aRecurse) {
14189 return;
14191 auto recurse = [&](Document& aSubDoc) {
14192 EvaluateMediaQueryLists(aListsToNotify, aSubDoc, true);
14193 return CallState::Continue;
14195 aDocument.EnumerateSubDocuments(recurse);
14198 void Document::EvaluateMediaQueriesAndReportChanges(bool aRecurse) {
14199 AutoTArray<RefPtr<MediaQueryList>, 32> mqls;
14200 EvaluateMediaQueryLists(mqls, *this, aRecurse);
14201 for (auto& mql : mqls) {
14202 mql->FireChangeEvent();
14206 void Document::MaybeWarnAboutZoom() {
14207 if (mHasWarnedAboutZoom) {
14208 return;
14210 const bool usedZoom = Servo_IsPropertyIdRecordedInUseCounter(
14211 mStyleUseCounters.get(), eCSSProperty_zoom);
14212 if (!usedZoom) {
14213 return;
14216 mHasWarnedAboutZoom = true;
14217 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
14218 this, nsContentUtils::eLAYOUT_PROPERTIES,
14219 "ZoomPropertyWarning");
14222 nsIHTMLCollection* Document::Children() {
14223 if (!mChildrenCollection) {
14224 mChildrenCollection =
14225 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
14226 nsGkAtoms::_asterisk, false);
14229 return mChildrenCollection;
14232 uint32_t Document::ChildElementCount() { return Children()->Length(); }
14234 // Singleton class to manage the list of fullscreen documents which are the
14235 // root of a branch which contains fullscreen documents. We maintain this list
14236 // so that we can easily exit all windows from fullscreen when the user
14237 // presses the escape key.
14238 class FullscreenRoots {
14239 public:
14240 // Adds the root of given document to the manager. Calling this method
14241 // with a document whose root is already contained has no effect.
14242 static void Add(Document* aDoc);
14244 // Iterates over every root in the root list, and calls aFunction, passing
14245 // each root once to aFunction. It is safe to call Add() and Remove() while
14246 // iterating over the list (i.e. in aFunction). Documents that are removed
14247 // from the manager during traversal are not traversed, and documents that
14248 // are added to the manager during traversal are also not traversed.
14249 static void ForEach(void (*aFunction)(Document* aDoc));
14251 // Removes the root of a specific document from the manager.
14252 static void Remove(Document* aDoc);
14254 // Returns true if all roots added to the list have been removed.
14255 static bool IsEmpty();
14257 private:
14258 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
14259 MOZ_COUNTED_DTOR(FullscreenRoots)
14261 using RootsArray = nsTArray<WeakPtr<Document>>;
14263 // Returns true if aRoot is in the list of fullscreen roots.
14264 static bool Contains(Document* aRoot);
14266 // Singleton instance of the FullscreenRoots. This is instantiated when a
14267 // root is added, and it is deleted when the last root is removed.
14268 static FullscreenRoots* sInstance;
14270 // List of weak pointers to roots.
14271 RootsArray mRoots;
14274 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
14276 /* static */
14277 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
14278 if (!sInstance) {
14279 return;
14281 // Create a copy of the roots array, and iterate over the copy. This is so
14282 // that if an element is removed from mRoots we don't mess up our iteration.
14283 RootsArray roots(sInstance->mRoots.Clone());
14284 // Call aFunction on all entries.
14285 for (uint32_t i = 0; i < roots.Length(); i++) {
14286 nsCOMPtr<Document> root(roots[i]);
14287 // Check that the root isn't in the manager. This is so that new additions
14288 // while we were running don't get traversed.
14289 if (root && FullscreenRoots::Contains(root)) {
14290 aFunction(root);
14295 /* static */
14296 bool FullscreenRoots::Contains(Document* aRoot) {
14297 return sInstance && sInstance->mRoots.Contains(aRoot);
14300 /* static */
14301 void FullscreenRoots::Add(Document* aDoc) {
14302 nsCOMPtr<Document> root =
14303 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14304 if (!FullscreenRoots::Contains(root)) {
14305 if (!sInstance) {
14306 sInstance = new FullscreenRoots();
14308 sInstance->mRoots.AppendElement(root);
14312 /* static */
14313 void FullscreenRoots::Remove(Document* aDoc) {
14314 nsCOMPtr<Document> root =
14315 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14316 if (!sInstance || !sInstance->mRoots.RemoveElement(root)) {
14317 NS_ERROR("Should only try to remove roots which are still added!");
14318 return;
14320 if (sInstance->mRoots.IsEmpty()) {
14321 delete sInstance;
14322 sInstance = nullptr;
14326 /* static */
14327 bool FullscreenRoots::IsEmpty() { return !sInstance; }
14329 // Any fullscreen change waiting for the widget to finish transition
14330 // is queued here. This is declared static instead of a member of
14331 // Document because in the majority of time, there would be at most
14332 // one document requesting or exiting fullscreen. We shouldn't waste
14333 // the space to hold for it in every document.
14334 class PendingFullscreenChangeList {
14335 public:
14336 PendingFullscreenChangeList() = delete;
14338 template <typename T>
14339 static void Add(UniquePtr<T> aChange) {
14340 sList.insertBack(aChange.release());
14343 static const FullscreenChange* GetLast() { return sList.getLast(); }
14345 enum IteratorOption {
14346 // When we are committing fullscreen changes or preparing for
14347 // that, we generally want to iterate all requests in the same
14348 // window with eDocumentsWithSameRoot option.
14349 eDocumentsWithSameRoot,
14350 // If we are removing a document from the tree, we would only
14351 // want to remove the requests from the given document and its
14352 // descendants. For that case, use eInclusiveDescendants.
14353 eInclusiveDescendants
14356 template <typename T>
14357 class Iterator {
14358 public:
14359 explicit Iterator(Document* aDoc, IteratorOption aOption)
14360 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
14361 if (mCurrent) {
14362 if (aDoc->GetBrowsingContext()) {
14363 mRootBCForIteration = aDoc->GetBrowsingContext();
14364 if (aOption == eDocumentsWithSameRoot) {
14365 RefPtr<BrowsingContext> bc =
14366 GetParentIgnoreChromeBoundary(mRootBCForIteration);
14367 while (bc) {
14368 mRootBCForIteration = bc;
14369 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
14373 SkipToNextMatch();
14377 UniquePtr<T> TakeAndNext() {
14378 auto thisChange = TakeAndNextInternal();
14379 SkipToNextMatch();
14380 return thisChange;
14382 bool AtEnd() const { return mCurrent == nullptr; }
14384 private:
14385 already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
14386 BrowsingContext* aBC) {
14387 // Chrome BrowsingContexts are only available in the parent process, so if
14388 // we're in a content process, we only worry about the context tree.
14389 if (XRE_IsParentProcess()) {
14390 return aBC->Canonical()->GetParentCrossChromeBoundary();
14392 return do_AddRef(aBC->GetParent());
14395 UniquePtr<T> TakeAndNextInternal() {
14396 FullscreenChange* thisChange = mCurrent;
14397 MOZ_ASSERT(thisChange->Type() == T::kType);
14398 mCurrent = mCurrent->removeAndGetNext();
14399 return WrapUnique(static_cast<T*>(thisChange));
14401 void SkipToNextMatch() {
14402 while (mCurrent) {
14403 if (mCurrent->Type() == T::kType) {
14404 RefPtr<BrowsingContext> bc =
14405 mCurrent->Document()->GetBrowsingContext();
14406 if (!bc) {
14407 // Always automatically drop fullscreen changes which are
14408 // from a document detached from the doc shell.
14409 UniquePtr<T> change = TakeAndNextInternal();
14410 change->MayRejectPromise("Document is not active");
14411 continue;
14413 while (bc && bc != mRootBCForIteration) {
14414 bc = GetParentIgnoreChromeBoundary(bc);
14416 if (bc) {
14417 break;
14420 // The current one either don't have matched type, or isn't
14421 // inside the given subtree, so skip this item.
14422 mCurrent = mCurrent->getNext();
14426 FullscreenChange* mCurrent;
14427 RefPtr<BrowsingContext> mRootBCForIteration;
14430 private:
14431 static LinkedList<FullscreenChange> sList;
14434 /* static */
14435 LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
14437 size_t Document::CountFullscreenElements() const {
14438 size_t count = 0;
14439 for (const nsWeakPtr& ptr : mTopLayer) {
14440 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14441 if (elem->State().HasState(ElementState::FULLSCREEN)) {
14442 count++;
14446 return count;
14449 // https://github.com/whatwg/html/issues/9143
14450 // We need to consider the precedence between active modal dialog, topmost auto
14451 // popover and fullscreen element once it's specified.
14452 void Document::HandleEscKey() {
14453 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14454 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14455 if (RefPtr popoverHTMLEl = nsGenericHTMLElement::FromNodeOrNull(element)) {
14456 if (element->IsAutoPopover() && element->IsPopoverOpen()) {
14457 popoverHTMLEl->HidePopover(IgnoreErrors());
14458 break;
14461 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14462 dialog->QueueCancelDialog();
14463 break;
14468 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
14469 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
14470 RefPtr<Promise> promise = exit->GetPromise();
14471 RestorePreviousFullscreenState(std::move(exit));
14472 return promise.forget();
14475 static void AskWindowToExitFullscreen(Document* aDoc) {
14476 if (XRE_GetProcessType() == GeckoProcessType_Content) {
14477 nsContentUtils::DispatchEventOnlyToChrome(
14478 aDoc, aDoc, u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
14479 Cancelable::eNo, /* DefaultAction */ nullptr);
14480 } else {
14481 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
14482 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
14487 class nsCallExitFullscreen : public Runnable {
14488 public:
14489 explicit nsCallExitFullscreen(Document* aDoc)
14490 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
14492 NS_IMETHOD Run() final {
14493 if (!mDoc) {
14494 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
14495 } else {
14496 AskWindowToExitFullscreen(mDoc);
14498 return NS_OK;
14501 private:
14502 nsCOMPtr<Document> mDoc;
14505 /* static */
14506 void Document::AsyncExitFullscreen(Document* aDoc) {
14507 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14508 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
14509 NS_DispatchToCurrentThread(exit.forget());
14512 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
14513 uint32_t count = 0;
14514 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
14515 auto subDoc = [&count](Document& aSubDoc) {
14516 if (aSubDoc.Fullscreen()) {
14517 count++;
14519 return CallState::Continue;
14521 aDoc.EnumerateSubDocuments(subDoc);
14522 return count;
14525 bool Document::IsFullscreenLeaf() {
14526 // A fullscreen leaf document is fullscreen, and has no fullscreen
14527 // subdocuments.
14529 // FIXME(emilio): This doesn't seem to account for fission iframes, is that
14530 // ok?
14531 return Fullscreen() && CountFullscreenSubDocuments(*this) == 0;
14534 static Document* GetFullscreenLeaf(Document& aDoc) {
14535 if (aDoc.IsFullscreenLeaf()) {
14536 return &aDoc;
14538 if (!aDoc.Fullscreen()) {
14539 return nullptr;
14541 Document* leaf = nullptr;
14542 auto recurse = [&leaf](Document& aSubDoc) {
14543 leaf = GetFullscreenLeaf(aSubDoc);
14544 return leaf ? CallState::Stop : CallState::Continue;
14546 aDoc.EnumerateSubDocuments(recurse);
14547 return leaf;
14550 static Document* GetFullscreenLeaf(Document* aDoc) {
14551 if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
14552 return leaf;
14554 // Otherwise we could be either in a non-fullscreen doc tree, or we're
14555 // below the fullscreen doc. Start the search from the root.
14556 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14557 return GetFullscreenLeaf(*root);
14560 static CallState ResetFullscreen(Document& aDocument) {
14561 if (Element* fsElement = aDocument.GetUnretargetedFullscreenElement()) {
14562 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
14563 "Should have at most 1 fullscreen subdocument.");
14564 aDocument.CleanupFullscreenState();
14565 NS_ASSERTION(!aDocument.Fullscreen(), "Should reset fullscreen");
14566 DispatchFullscreenChange(aDocument, fsElement);
14567 aDocument.EnumerateSubDocuments(ResetFullscreen);
14569 return CallState::Continue;
14572 // Since Document::ExitFullscreenInDocTree() could be called from
14573 // Element::UnbindFromTree() where it is not safe to synchronously run
14574 // script. This runnable is the script part of that function.
14575 class ExitFullscreenScriptRunnable : public Runnable {
14576 public:
14577 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
14578 : mozilla::Runnable("ExitFullscreenScriptRunnable"),
14579 mRoot(aRoot),
14580 mLeaf(aLeaf) {}
14582 NS_IMETHOD Run() override {
14583 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
14584 // document since we want this event to follow the same path that
14585 // MozDOMFullscreen:Entered was dispatched.
14586 nsContentUtils::DispatchEventOnlyToChrome(
14587 mLeaf, mLeaf, u"MozDOMFullscreen:Exited"_ns, CanBubble::eYes,
14588 Cancelable::eNo, /* DefaultAction */ nullptr);
14589 // Ensure the window exits fullscreen, as long as we don't have
14590 // pending fullscreen requests.
14591 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
14592 if (!mRoot->HasPendingFullscreenRequests()) {
14593 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
14594 false);
14597 return NS_OK;
14600 private:
14601 nsCOMPtr<Document> mRoot;
14602 nsCOMPtr<Document> mLeaf;
14605 /* static */
14606 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
14607 MOZ_ASSERT(aMaybeNotARootDoc);
14609 // Unlock the pointer
14610 PointerLockManager::Unlock();
14612 // Resolve all promises which waiting for exit fullscreen.
14613 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
14614 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14615 while (!iter.AtEnd()) {
14616 UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
14617 exit->MayResolvePromise();
14620 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
14621 if (!root || !root->Fullscreen()) {
14622 // If a document was detached before exiting from fullscreen, it is
14623 // possible that the root had left fullscreen state. In this case,
14624 // we would not get anything from the ResetFullscreen() call. Root's
14625 // not being a fullscreen doc also means the widget should have
14626 // exited fullscreen state. It means even if we do not return here,
14627 // we would actually do nothing below except crashing ourselves via
14628 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
14629 // document.
14630 return;
14633 // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
14634 // See ExitFullscreenScriptRunnable::Run for details. We have to
14635 // record it here because we don't have such information after we
14636 // reset the fullscreen state below.
14637 Document* fullscreenLeaf = GetFullscreenLeaf(root);
14639 // Walk the tree of fullscreen documents, and reset their fullscreen state.
14640 ResetFullscreen(*root);
14642 NS_ASSERTION(!root->Fullscreen(),
14643 "Fullscreen root should no longer be a fullscreen doc...");
14645 // Move the top-level window out of fullscreen mode.
14646 FullscreenRoots::Remove(root);
14648 nsContentUtils::AddScriptRunner(
14649 new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
14652 static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
14653 RefPtr<AsyncEventDispatcher> asyncDispatcher =
14654 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
14655 CanBubble::eYes, ChromeOnlyDispatch::eYes);
14656 asyncDispatcher->PostDOMEvent();
14659 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
14660 NS_ASSERTION(!Fullscreen() || !FullscreenRoots::IsEmpty(),
14661 "Should have at least 1 fullscreen root when fullscreen!");
14663 if (!GetWindow()) {
14664 aExit->MayRejectPromise("No active window");
14665 return;
14667 if (!Fullscreen() || FullscreenRoots::IsEmpty()) {
14668 aExit->MayRejectPromise("Not in fullscreen mode");
14669 return;
14672 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
14673 AutoTArray<Element*, 8> exitElements;
14675 Document* doc = fullScreenDoc;
14676 // Collect all subdocuments.
14677 for (; doc != this; doc = doc->GetInProcessParentDocument()) {
14678 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14679 MOZ_ASSERT(fsElement,
14680 "Parent document of "
14681 "a fullscreen document without fullscreen element?");
14682 exitElements.AppendElement(fsElement);
14684 MOZ_ASSERT(doc == this, "Must have reached this doc");
14685 // Collect all ancestor documents which we are going to change.
14686 for (; doc; doc = doc->GetInProcessParentDocument()) {
14687 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14688 MOZ_ASSERT(fsElement,
14689 "Ancestor of fullscreen document must also be in fullscreen");
14690 if (doc != this) {
14691 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
14692 if (iframe->FullscreenFlag()) {
14693 // If this is an iframe, and it explicitly requested
14694 // fullscreen, don't rollback it automatically.
14695 break;
14699 exitElements.AppendElement(fsElement);
14700 if (doc->CountFullscreenElements() > 1) {
14701 break;
14705 Document* lastDoc = exitElements.LastElement()->OwnerDoc();
14706 size_t fullscreenCount = lastDoc->CountFullscreenElements();
14707 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
14708 // If we are fully exiting fullscreen, don't touch anything here,
14709 // just wait for the window to get out from fullscreen first.
14710 PendingFullscreenChangeList::Add(std::move(aExit));
14711 AskWindowToExitFullscreen(this);
14712 return;
14715 // If fullscreen mode is updated the pointer should be unlocked
14716 PointerLockManager::Unlock();
14717 // All documents listed in the array except the last one are going to
14718 // completely exit from the fullscreen state.
14719 for (auto i : IntegerRange(exitElements.Length() - 1)) {
14720 exitElements[i]->OwnerDoc()->CleanupFullscreenState();
14722 // The last document will either rollback one fullscreen element, or
14723 // completely exit from the fullscreen state as well.
14724 Document* newFullscreenDoc;
14725 if (fullscreenCount > 1) {
14726 DebugOnly<bool> removedFullscreenElement = lastDoc->PopFullscreenElement();
14727 MOZ_ASSERT(removedFullscreenElement);
14728 newFullscreenDoc = lastDoc;
14729 } else {
14730 lastDoc->CleanupFullscreenState();
14731 newFullscreenDoc = lastDoc->GetInProcessParentDocument();
14733 // Dispatch the fullscreenchange event to all document listed. Note
14734 // that the loop order is reversed so that events are dispatched in
14735 // the tree order as indicated in the spec.
14736 for (Element* e : Reversed(exitElements)) {
14737 DispatchFullscreenChange(*e->OwnerDoc(), e);
14739 aExit->MayResolvePromise();
14741 MOZ_ASSERT(newFullscreenDoc,
14742 "If we were going to exit from fullscreen on "
14743 "all documents in this doctree, we should've asked the window to "
14744 "exit first instead of reaching here.");
14745 if (fullScreenDoc != newFullscreenDoc &&
14746 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
14747 // We've popped so enough off the stack that we've rolled back to
14748 // a fullscreen element in a parent document. If this document is
14749 // cross origin, dispatch an event to chrome so it knows to show
14750 // the warning UI.
14751 DispatchFullscreenNewOriginEvent(newFullscreenDoc);
14755 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
14756 if (nsPresContext* presContext = aDoc->GetPresContext()) {
14757 presContext->UpdateViewportScrollStylesOverride();
14761 static void NotifyFullScreenChangedForMediaElement(Element& aElement) {
14762 // When a media element enters the fullscreen, we would like to notify that
14763 // to the media controller in order to update its status.
14764 if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) {
14765 mediaElem->NotifyFullScreenChanged();
14769 void Document::CleanupFullscreenState() {
14770 while (PopFullscreenElement(UpdateViewport::No)) {
14771 // Remove the next one if appropriate
14774 UpdateViewportScrollbarOverrideForFullscreen(this);
14775 mFullscreenRoot = nullptr;
14777 // Restore the zoom level that was in place prior to entering fullscreen.
14778 if (PresShell* presShell = GetPresShell()) {
14779 if (presShell->GetMobileViewportManager()) {
14780 presShell->SetResolutionAndScaleTo(
14781 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
14786 bool Document::PopFullscreenElement(UpdateViewport aUpdateViewport) {
14787 Element* removedElement = TopLayerPop([](Element* element) -> bool {
14788 return element->State().HasState(ElementState::FULLSCREEN);
14791 if (!removedElement) {
14792 return false;
14795 MOZ_ASSERT(removedElement->State().HasState(ElementState::FULLSCREEN));
14796 removedElement->RemoveStates(ElementState::FULLSCREEN | ElementState::MODAL);
14797 NotifyFullScreenChangedForMediaElement(*removedElement);
14798 // Reset iframe fullscreen flag.
14799 if (auto* iframe = HTMLIFrameElement::FromNode(removedElement)) {
14800 iframe->SetFullscreenFlag(false);
14802 if (aUpdateViewport == UpdateViewport::Yes) {
14803 UpdateViewportScrollbarOverrideForFullscreen(this);
14805 return true;
14808 void Document::SetFullscreenElement(Element& aElement) {
14809 ElementState statesToAdd = ElementState::FULLSCREEN;
14810 if (!IsInChromeDocShell()) {
14811 // Don't make the document modal in chrome documents, since we don't want
14812 // the browser UI like the context menu / etc to be inert.
14813 statesToAdd |= ElementState::MODAL;
14815 aElement.AddStates(statesToAdd);
14816 TopLayerPush(aElement);
14817 NotifyFullScreenChangedForMediaElement(aElement);
14818 UpdateViewportScrollbarOverrideForFullscreen(this);
14821 void Document::TopLayerPush(Element& aElement) {
14822 const bool modal = aElement.State().HasState(ElementState::MODAL);
14824 TopLayerPop(aElement);
14825 mTopLayer.AppendElement(do_GetWeakReference(&aElement));
14826 NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
14828 if (modal) {
14829 aElement.AddStates(ElementState::TOPMOST_MODAL);
14831 bool foundExistingModalElement = false;
14832 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14833 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14834 if (element && element != &aElement &&
14835 element->State().HasState(ElementState::TOPMOST_MODAL)) {
14836 element->RemoveStates(ElementState::TOPMOST_MODAL);
14837 foundExistingModalElement = true;
14838 break;
14842 if (!foundExistingModalElement) {
14843 Element* root = GetRootElement();
14844 MOZ_RELEASE_ASSERT(root, "top layer element outside of document?");
14845 if (&aElement != root) {
14846 // Add inert to the root element so that the inertness is applied to the
14847 // entire document.
14848 root->AddStates(ElementState::INERT);
14854 void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
14855 aDialogElement.AddStates(ElementState::MODAL);
14856 TopLayerPush(aDialogElement);
14859 void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
14860 DebugOnly<Element*> removedElement = TopLayerPop(aDialogElement);
14861 MOZ_ASSERT(removedElement == &aDialogElement);
14862 aDialogElement.RemoveStates(ElementState::MODAL);
14865 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
14866 if (mTopLayer.IsEmpty()) {
14867 return nullptr;
14870 // Remove the topmost element that qualifies aPredicate; This
14871 // is required is because the top layer contains not only
14872 // fullscreen elements, but also dialog elements.
14873 Element* removedElement = nullptr;
14874 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
14875 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
14876 if (element && aPredicate(element)) {
14877 removedElement = element;
14878 mTopLayer.RemoveElementAt(i);
14879 break;
14883 // Pop from the stack null elements (references to elements which have
14884 // been GC'd since they were added to the stack) and elements which are
14885 // no longer in this document.
14887 // FIXME(emilio): If this loop does something, it'd violate the assertions
14888 // from GetTopLayerTop()... What gives?
14889 while (!mTopLayer.IsEmpty()) {
14890 Element* element = GetTopLayerTop();
14891 if (!element || element->GetComposedDoc() != this) {
14892 mTopLayer.RemoveLastElement();
14893 } else {
14894 // The top element of the stack is now an in-doc element. Return here.
14895 break;
14899 if (!removedElement) {
14900 return nullptr;
14903 const bool modal = removedElement->State().HasState(ElementState::MODAL);
14905 if (modal) {
14906 removedElement->RemoveStates(ElementState::TOPMOST_MODAL);
14907 bool foundExistingModalElement = false;
14908 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14909 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14910 if (element && element->State().HasState(ElementState::MODAL)) {
14911 element->AddStates(ElementState::TOPMOST_MODAL);
14912 foundExistingModalElement = true;
14913 break;
14916 // No more modal elements, make the document not inert anymore.
14917 if (!foundExistingModalElement) {
14918 Element* root = GetRootElement();
14919 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
14920 root->RemoveStates(ElementState::INERT);
14925 return removedElement;
14928 Element* Document::TopLayerPop(Element& aElement) {
14929 auto predictFunc = [&aElement](Element* element) {
14930 return element == &aElement;
14932 return TopLayerPop(predictFunc);
14935 void Document::GetWireframe(bool aIncludeNodes,
14936 Nullable<Wireframe>& aWireframe) {
14937 FlushPendingNotifications(FlushType::Layout);
14938 GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
14941 void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
14942 Nullable<Wireframe>& aWireframe) {
14943 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
14944 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
14946 PresShell* shell = GetPresShell();
14947 if (!shell) {
14948 return;
14951 nsPresContext* pc = shell->GetPresContext();
14952 if (!pc) {
14953 return;
14956 nsIFrame* rootFrame = shell->GetRootFrame();
14957 if (!rootFrame) {
14958 return;
14961 auto& wireframe = aWireframe.SetValue();
14962 wireframe.mCanvasBackground = shell->ComputeCanvasBackground().mViewportColor;
14964 FrameForPointOptions options;
14965 options.mBits += FrameForPointOption::IgnoreCrossDoc;
14966 options.mBits += FrameForPointOption::IgnorePaintSuppression;
14967 options.mBits += FrameForPointOption::OnlyVisible;
14969 AutoTArray<nsIFrame*, 32> frames;
14970 const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
14971 nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
14972 options);
14974 // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
14975 // something perhaps, but seems hard / like it'd involve at least some extra
14976 // copying around, since they don't outlive GetFramesForArea.
14977 auto& rects = wireframe.mRects.Construct();
14978 if (!rects.SetCapacity(frames.Length(), fallible)) {
14979 return;
14981 for (nsIFrame* frame : Reversed(frames)) {
14982 auto [rectColor,
14983 rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> {
14984 if (frame->IsTextFrame()) {
14985 return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
14986 WireframeRectType::Text};
14988 if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
14989 return {0, WireframeRectType::Image};
14991 if (frame->IsThemed()) {
14992 return {0, WireframeRectType::Background};
14994 bool drawImage = false;
14995 bool drawColor = false;
14996 if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) {
14997 const nscolor color = nsCSSRendering::DetermineBackgroundColor(
14998 pc, bgStyle, frame, drawImage, drawColor);
14999 if (drawImage &&
15000 !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
15001 return {color, WireframeRectType::Image};
15003 if (drawColor && !frame->IsCanvasFrame()) {
15004 // Canvas frame background already accounted for in mCanvasBackground.
15005 return {color, WireframeRectType::Background};
15008 return {0, WireframeRectType::Unknown};
15009 }();
15011 if (rectType == WireframeRectType::Unknown) {
15012 continue;
15015 const auto r =
15016 CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
15017 frame, frame->GetRectRelativeToSelf(), relativeTo));
15018 if ((uint32_t)r.Area() <
15019 StaticPrefs::browser_history_wireframeAreaThreshold()) {
15020 continue;
15023 // Can't really fail because SetCapacity succeeded.
15024 auto& taggedRect = *rects.AppendElement(fallible);
15026 if (aIncludeNodes) {
15027 if (nsIContent* c = frame->GetContent()) {
15028 taggedRect.mNode.Construct(c);
15031 taggedRect.mX = r.x;
15032 taggedRect.mY = r.y;
15033 taggedRect.mWidth = r.width;
15034 taggedRect.mHeight = r.height;
15035 taggedRect.mColor = rectColor;
15036 taggedRect.mType.Construct(rectType);
15040 Element* Document::GetTopLayerTop() {
15041 if (mTopLayer.IsEmpty()) {
15042 return nullptr;
15044 uint32_t last = mTopLayer.Length() - 1;
15045 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
15046 NS_ASSERTION(element, "Should have a top layer element!");
15047 NS_ASSERTION(element->IsInComposedDoc(),
15048 "Top layer element should be in doc");
15049 NS_ASSERTION(element->OwnerDoc() == this,
15050 "Top layer element should be in this doc");
15051 return element;
15054 Element* Document::GetUnretargetedFullscreenElement() const {
15055 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15056 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15057 // Per spec, the fullscreen element is the topmost element in the document’s
15058 // top layer whose fullscreen flag is set, if any, and null otherwise.
15059 if (element && element->State().HasState(ElementState::FULLSCREEN)) {
15060 return element;
15063 return nullptr;
15066 nsTArray<Element*> Document::GetTopLayer() const {
15067 nsTArray<Element*> elements;
15068 for (const nsWeakPtr& ptr : mTopLayer) {
15069 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
15070 elements.AppendElement(elem);
15073 return elements;
15076 bool Document::TopLayerContains(Element& aElement) const {
15077 if (mTopLayer.IsEmpty()) {
15078 return false;
15080 nsWeakPtr weakElement = do_GetWeakReference(&aElement);
15081 return mTopLayer.Contains(weakElement);
15084 void Document::HideAllPopoversUntil(nsINode& aEndpoint,
15085 bool aFocusPreviousElement,
15086 bool aFireEvents) {
15087 auto closeAllOpenPopovers = [&aFocusPreviousElement, &aFireEvents,
15088 this]() MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
15089 while (RefPtr<Element> topmost = GetTopmostAutoPopover()) {
15090 HidePopover(*topmost, aFocusPreviousElement, aFireEvents, IgnoreErrors());
15094 if (&aEndpoint == this) {
15095 closeAllOpenPopovers();
15096 return;
15099 // https://github.com/whatwg/html/pull/9198
15100 auto needRepeatingHide = [&]() {
15101 auto autoList = AutoPopoverList();
15102 return autoList.Contains(&aEndpoint) &&
15103 &aEndpoint != autoList.LastElement();
15106 MOZ_ASSERT((&aEndpoint)->IsElement() &&
15107 (&aEndpoint)->AsElement()->IsAutoPopover());
15108 bool repeatingHide = false;
15109 bool fireEvents = aFireEvents;
15110 do {
15111 RefPtr<const Element> lastToHide = nullptr;
15112 bool foundEndpoint = false;
15113 for (const Element* popover : AutoPopoverList()) {
15114 if (popover == &aEndpoint) {
15115 foundEndpoint = true;
15116 } else if (foundEndpoint) {
15117 lastToHide = popover;
15118 break;
15122 if (!foundEndpoint) {
15123 closeAllOpenPopovers();
15124 return;
15127 while (lastToHide && lastToHide->IsPopoverOpen()) {
15128 RefPtr<Element> topmost = GetTopmostAutoPopover();
15129 if (!topmost) {
15130 break;
15132 HidePopover(*topmost, aFocusPreviousElement, fireEvents, IgnoreErrors());
15135 repeatingHide = needRepeatingHide();
15136 if (repeatingHide) {
15137 fireEvents = false;
15139 } while (repeatingHide);
15142 MOZ_CAN_RUN_SCRIPT_BOUNDARY void
15143 Document::HideAllPopoversWithoutRunningScript() {
15144 return HideAllPopoversUntil(*this, false, false);
15147 void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement,
15148 bool aFireEvents, ErrorResult& aRv) {
15149 RefPtr<nsGenericHTMLElement> popoverHTMLEl =
15150 nsGenericHTMLElement::FromNode(aPopover);
15151 NS_ASSERTION(popoverHTMLEl, "Not a HTML element");
15153 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15154 nullptr, aRv)) {
15155 return;
15158 bool wasShowingOrHiding =
15159 popoverHTMLEl->GetPopoverData()->IsShowingOrHiding();
15160 popoverHTMLEl->GetPopoverData()->SetIsShowingOrHiding(true);
15161 const bool fireEvents = aFireEvents && !wasShowingOrHiding;
15162 auto cleanupHidingFlag = MakeScopeExit([&]() {
15163 if (auto* popoverData = popoverHTMLEl->GetPopoverData()) {
15164 popoverData->SetIsShowingOrHiding(wasShowingOrHiding);
15168 if (popoverHTMLEl->IsAutoPopover()) {
15169 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, fireEvents);
15170 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15171 nullptr, aRv)) {
15172 return;
15174 // TODO: we can't always guarantee:
15175 // The last item in document's auto popover list is popoverHTMLEl.
15176 // See, https://github.com/whatwg/html/issues/9197
15177 // If popoverHTMLEl is not on top, hide popovers again without firing
15178 // events.
15179 if (NS_WARN_IF(GetTopmostAutoPopover() != popoverHTMLEl)) {
15180 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false);
15181 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15182 nullptr, aRv)) {
15183 return;
15185 MOZ_ASSERT(GetTopmostAutoPopover() == popoverHTMLEl,
15186 "popoverHTMLEl should be on top of auto popover list");
15190 auto* data = popoverHTMLEl->GetPopoverData();
15191 MOZ_ASSERT(data, "Should have popover data");
15192 data->SetInvoker(nullptr);
15194 // Fire beforetoggle event and re-check popover validity.
15195 if (fireEvents) {
15196 // Intentionally ignore the return value here as only on open event for
15197 // beforetoggle the cancelable attribute is initialized to true.
15198 popoverHTMLEl->FireToggleEvent(PopoverVisibilityState::Showing,
15199 PopoverVisibilityState::Hidden,
15200 u"beforetoggle"_ns);
15201 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing,
15202 nullptr, aRv)) {
15203 return;
15207 RemovePopoverFromTopLayer(aPopover);
15209 popoverHTMLEl->PopoverPseudoStateUpdate(false, true);
15210 popoverHTMLEl->GetPopoverData()->SetPopoverVisibilityState(
15211 PopoverVisibilityState::Hidden);
15213 // Queue popover toggle event task.
15214 if (fireEvents) {
15215 popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing);
15218 if (aFocusPreviousElement) {
15219 popoverHTMLEl->FocusPreviousElementAfterHidingPopover();
15220 } else {
15221 popoverHTMLEl->ForgetPreviouslyFocusedElementAfterHidingPopover();
15225 nsTArray<Element*> Document::AutoPopoverList() const {
15226 nsTArray<Element*> elements;
15227 for (const nsWeakPtr& ptr : mTopLayer) {
15228 if (nsCOMPtr<Element> element = do_QueryReferent(ptr)) {
15229 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15230 elements.AppendElement(element);
15234 return elements;
15237 Element* Document::GetTopmostAutoPopover() const {
15238 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
15239 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
15240 if (element && element->IsAutoPopover() && element->IsPopoverOpen()) {
15241 return element;
15244 return nullptr;
15247 void Document::AddToAutoPopoverList(Element& aElement) {
15248 MOZ_ASSERT(aElement.IsAutoPopover());
15249 TopLayerPush(aElement);
15252 void Document::RemoveFromAutoPopoverList(Element& aElement) {
15253 MOZ_ASSERT(aElement.IsAutoPopover());
15254 TopLayerPop(aElement);
15257 void Document::AddPopoverToTopLayer(Element& aElement) {
15258 MOZ_ASSERT(aElement.GetPopoverData());
15259 TopLayerPush(aElement);
15262 void Document::RemovePopoverFromTopLayer(Element& aElement) {
15263 MOZ_ASSERT(aElement.GetPopoverData());
15264 TopLayerPop(aElement);
15267 // Returns true if aDoc browsing context is focused.
15268 bool IsInFocusedTab(Document* aDoc) {
15269 BrowsingContext* bc = aDoc->GetBrowsingContext();
15270 if (!bc) {
15271 return false;
15274 nsFocusManager* fm = nsFocusManager::GetFocusManager();
15275 if (!fm) {
15276 return false;
15279 if (XRE_IsParentProcess()) {
15280 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
15281 // by retaining the old code path for the parent process.
15282 nsIDocShell* docshell = aDoc->GetDocShell();
15283 if (!docshell) {
15284 return false;
15286 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15287 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15288 if (!rootItem) {
15289 return false;
15291 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
15292 if (!rootWin) {
15293 return false;
15296 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
15297 activeWindow = fm->GetActiveWindow();
15298 if (!activeWindow) {
15299 return false;
15302 return activeWindow == rootWin;
15305 return fm->GetActiveBrowsingContext() == bc->Top();
15308 // Returns true if aDoc browsing context is focused and is also active.
15309 bool IsInActiveTab(Document* aDoc) {
15310 if (!IsInFocusedTab(aDoc)) {
15311 return false;
15314 BrowsingContext* bc = aDoc->GetBrowsingContext();
15315 MOZ_ASSERT(bc, "With no BrowsingContext, we should have failed earlier.");
15316 return bc->IsActive();
15319 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
15320 // Ensure the frame element is the fullscreen element in this document.
15321 // If the frame element is already the fullscreen element in this document,
15322 // this has no effect.
15323 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
15324 RequestFullscreen(std::move(request), XRE_IsContentProcess());
15327 void Document::RemoteFrameFullscreenReverted() {
15328 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
15329 RestorePreviousFullscreenState(std::move(exit));
15332 static bool HasFullscreenSubDocument(Document& aDoc) {
15333 uint32_t count = CountFullscreenSubDocuments(aDoc);
15334 NS_ASSERTION(count <= 1,
15335 "Fullscreen docs should have at most 1 fullscreen child!");
15336 return count >= 1;
15339 // Returns nullptr if a request for Fullscreen API is currently enabled
15340 // in the given document. Returns a static string indicates the reason
15341 // why it is not enabled otherwise.
15342 const char* Document::GetFullscreenError(CallerType aCallerType) {
15343 if (!StaticPrefs::full_screen_api_enabled()) {
15344 return "FullscreenDeniedDisabled";
15347 if (aCallerType == CallerType::System) {
15348 // Chrome code can always use the fullscreen API, provided it's not
15349 // explicitly disabled.
15350 return nullptr;
15353 if (!IsVisible()) {
15354 return "FullscreenDeniedHidden";
15357 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
15358 return "FullscreenDeniedFeaturePolicy";
15361 // Ensure that all containing elements are <iframe> and have allowfullscreen
15362 // attribute set.
15363 BrowsingContext* bc = GetBrowsingContext();
15364 if (!bc || !bc->FullscreenAllowed()) {
15365 return "FullscreenDeniedContainerNotAllowed";
15368 return nullptr;
15371 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
15372 Element* elem = aRequest.Element();
15373 // Strictly speaking, this isn't part of the fullscreen element ready
15374 // check in the spec, but per steps in the spec, when an element which
15375 // is already the fullscreen element requests fullscreen, nothing
15376 // should change and no event should be dispatched, but we still need
15377 // to resolve the returned promise.
15378 Element* fullscreenElement = GetUnretargetedFullscreenElement();
15379 if (elem == fullscreenElement) {
15380 aRequest.MayResolvePromise();
15381 return false;
15383 if (!elem->IsInComposedDoc()) {
15384 aRequest.Reject("FullscreenDeniedNotInDocument");
15385 return false;
15387 if (elem->IsPopoverOpen()) {
15388 aRequest.Reject("FullscreenDeniedPopoverOpen");
15389 return false;
15391 if (elem->OwnerDoc() != this) {
15392 aRequest.Reject("FullscreenDeniedMovedDocument");
15393 return false;
15395 if (!GetWindow()) {
15396 aRequest.Reject("FullscreenDeniedLostWindow");
15397 return false;
15399 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
15400 aRequest.Reject(msg);
15401 return false;
15403 if (HasFullscreenSubDocument(*this)) {
15404 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
15405 return false;
15407 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
15408 aRequest.Reject("FullscreenDeniedHTMLDialog");
15409 return false;
15411 if (!nsContentUtils::IsChromeDoc(this) && !IsInFocusedTab(this)) {
15412 aRequest.Reject("FullscreenDeniedNotFocusedTab");
15413 return false;
15415 return true;
15418 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
15419 MOZ_ASSERT(XRE_IsParentProcess());
15420 nsIDocShell* docShell = aDoc->GetDocShell();
15421 if (!docShell) {
15422 return nullptr;
15424 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15425 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15426 return rootItem ? rootItem->GetWindow() : nullptr;
15429 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
15430 nsPIDOMWindowOuter* aRootWin) {
15431 MOZ_ASSERT(XRE_IsParentProcess());
15432 // If we are in the chrome process, and the window has not been in
15433 // fullscreen, we certainly need to make that fullscreen first.
15434 if (!aRootWin->GetFullScreen()) {
15435 return false;
15437 // The iterator not being at end indicates there is still some
15438 // pending fullscreen request relates to this document. We have to
15439 // push the request to the pending queue so requests are handled
15440 // in the correct order.
15441 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15442 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15443 if (!iter.AtEnd()) {
15444 return false;
15447 // Same thing for exits. If we have any pending, we have to push
15448 // to the pending queue.
15449 PendingFullscreenChangeList::Iterator<FullscreenExit> iterExit(
15450 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15451 if (!iterExit.AtEnd()) {
15452 return false;
15455 // We have to apply the fullscreen state directly in this case,
15456 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
15457 // if it is already in fullscreen. If we do not apply the state but
15458 // instead add it to the queue and wait for the window as normal,
15459 // we would get stuck.
15460 return true;
15463 static bool CheckFullscreenAllowedElementType(const Element* elem) {
15464 // Per spec only HTML, <svg>, and <math> should be allowed, but
15465 // we also need to allow XUL elements right now.
15466 return elem->IsHTMLElement() || elem->IsXULElement() ||
15467 elem->IsSVGElement(nsGkAtoms::svg) ||
15468 elem->IsMathMLElement(nsGkAtoms::math);
15471 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
15472 bool aApplyFullscreenDirectly) {
15473 if (XRE_IsContentProcess()) {
15474 RequestFullscreenInContentProcess(std::move(aRequest),
15475 aApplyFullscreenDirectly);
15476 } else {
15477 RequestFullscreenInParentProcess(std::move(aRequest),
15478 aApplyFullscreenDirectly);
15482 void Document::RequestFullscreenInContentProcess(
15483 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15484 MOZ_ASSERT(XRE_IsContentProcess());
15486 // If we are in the content process, we can apply the fullscreen
15487 // state directly only if we have been in DOM fullscreen, because
15488 // otherwise we always need to notify the chrome.
15489 if (aApplyFullscreenDirectly ||
15490 nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) {
15491 ApplyFullscreen(std::move(aRequest));
15492 return;
15495 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15496 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15497 return;
15500 // We don't need to check element ready before this point, because
15501 // if we called ApplyFullscreen, it would check that for us.
15502 if (!FullscreenElementReadyCheck(*aRequest)) {
15503 return;
15506 PendingFullscreenChangeList::Add(std::move(aRequest));
15507 // If we are not the top level process, dispatch an event to make
15508 // our parent process go fullscreen first.
15509 Dispatch(NS_NewRunnableFunction(
15510 "Document::RequestFullscreenInContentProcess", [self = RefPtr{this}] {
15511 if (!self->HasPendingFullscreenRequests()) {
15512 return;
15514 nsContentUtils::DispatchEventOnlyToChrome(
15515 self, self, u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
15516 Cancelable::eNo, /* DefaultAction */ nullptr);
15517 }));
15520 void Document::RequestFullscreenInParentProcess(
15521 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15522 MOZ_ASSERT(XRE_IsParentProcess());
15523 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
15524 if (!rootWin) {
15525 aRequest->MayRejectPromise("No active window");
15526 return;
15529 if (aApplyFullscreenDirectly ||
15530 ShouldApplyFullscreenDirectly(this, rootWin)) {
15531 ApplyFullscreen(std::move(aRequest));
15532 return;
15535 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15536 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15537 return;
15540 // See if we're waiting on an exit. If so, just make this one pending.
15541 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
15542 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15543 if (!iter.AtEnd()) {
15544 PendingFullscreenChangeList::Add(std::move(aRequest));
15545 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15546 return;
15549 // We don't need to check element ready before this point, because
15550 // if we called ApplyFullscreen, it would check that for us.
15551 if (!FullscreenElementReadyCheck(*aRequest)) {
15552 return;
15555 PendingFullscreenChangeList::Add(std::move(aRequest));
15556 // Make the window fullscreen.
15557 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15560 /* static */
15561 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
15562 bool handled = false;
15563 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15564 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15565 while (!iter.AtEnd()) {
15566 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15567 Document* doc = request->Document();
15568 if (doc->ApplyFullscreen(std::move(request))) {
15569 handled = true;
15572 return handled;
15575 /* static */
15576 void Document::ClearPendingFullscreenRequests(Document* aDoc) {
15577 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15578 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
15579 while (!iter.AtEnd()) {
15580 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15581 request->MayRejectPromise("Fullscreen request aborted");
15585 bool Document::HasPendingFullscreenRequests() {
15586 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15587 this, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15588 return !iter.AtEnd();
15591 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
15592 if (!FullscreenElementReadyCheck(*aRequest)) {
15593 return false;
15596 RefPtr<Document> doc = aRequest->Document();
15597 doc->HideAllPopoversWithoutRunningScript();
15599 // Stash a reference to any existing fullscreen doc, we'll use this later
15600 // to detect if the origin which is fullscreen has changed.
15601 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
15603 // Stores a list of documents which we must dispatch "fullscreenchange"
15604 // too. We're required by the spec to dispatch the events in root-to-leaf
15605 // order, but we traverse the doctree in a leaf-to-root order, so we save
15606 // references to the documents we must dispatch to so that we get the order
15607 // as specified.
15608 AutoTArray<Document*, 8> changed;
15610 // Remember the root document, so that if a fullscreen document is hidden
15611 // we can reset fullscreen state in the remaining visible fullscreen
15612 // documents.
15613 Document* fullScreenRootDoc =
15614 nsContentUtils::GetInProcessSubtreeRootDocument(this);
15616 // If a document is already in fullscreen, then unlock the mouse pointer
15617 // before setting a new document to fullscreen
15618 PointerLockManager::Unlock();
15620 // Set the fullscreen element. This sets the fullscreen style on the
15621 // element, and the fullscreen-ancestor styles on ancestors of the element
15622 // in this document.
15623 Element* elem = aRequest->Element();
15624 SetFullscreenElement(*elem);
15625 // Set the iframe fullscreen flag.
15626 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
15627 iframe->SetFullscreenFlag(true);
15629 changed.AppendElement(this);
15631 // Propagate up the document hierarchy, setting the fullscreen element as
15632 // the element's container in ancestor documents. This also sets the
15633 // appropriate css styles as well. Note we don't propagate down the
15634 // document hierarchy, the fullscreen element (or its container) is not
15635 // visible there. Stop when we reach the root document.
15636 Document* child = this;
15637 while (true) {
15638 child->SetFullscreenRoot(fullScreenRootDoc);
15640 // When entering fullscreen, reset the RCD's resolution to the intrinsic
15641 // resolution, otherwise the fullscreen content could be sized larger than
15642 // the screen (since fullscreen is implemented using position:fixed and
15643 // fixed elements are sized to the layout viewport).
15644 // This also ensures that things like video controls aren't zoomed in
15645 // when in fullscreen mode.
15646 if (PresShell* presShell = child->GetPresShell()) {
15647 if (RefPtr<MobileViewportManager> manager =
15648 presShell->GetMobileViewportManager()) {
15649 // Save the previous resolution so it can be restored.
15650 child->mSavedResolution = presShell->GetResolution();
15651 presShell->SetResolutionAndScaleTo(
15652 manager->ComputeIntrinsicResolution(),
15653 ResolutionChangeOrigin::MainThreadRestore);
15657 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
15658 "Fullscreen root should be set!");
15659 if (child == fullScreenRootDoc) {
15660 break;
15663 Element* element = child->GetEmbedderElement();
15664 if (!element) {
15665 // We've reached the root.No more changes need to be made
15666 // to the top layer stacks of documents further up the tree.
15667 break;
15670 Document* parent = child->GetInProcessParentDocument();
15671 parent->SetFullscreenElement(*element);
15672 changed.AppendElement(parent);
15673 child = parent;
15676 FullscreenRoots::Add(this);
15678 // If it is the first entry of the fullscreen, trigger an event so
15679 // that the UI can response to this change, e.g. hide chrome, or
15680 // notifying parent process to enter fullscreen. Note that chrome
15681 // code may also want to listen to MozDOMFullscreen:NewOrigin event
15682 // to pop up warning UI.
15683 if (!previousFullscreenDoc) {
15684 nsContentUtils::DispatchEventOnlyToChrome(
15685 this, elem, u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
15686 Cancelable::eNo, /* DefaultAction */ nullptr);
15689 // The origin which is fullscreen gets changed. Trigger an event so
15690 // that the chrome knows to pop up a warning UI. Note that
15691 // previousFullscreenDoc == nullptr upon first entry, we show the warning UI
15692 // directly as soon as chrome document goes into fullscreen state. Also note
15693 // that, in a multi-process browser, the code in content process is
15694 // responsible for sending message with the origin to its parent, and the
15695 // parent shouldn't rely on this event itself.
15696 if (aRequest->mShouldNotifyNewOrigin && previousFullscreenDoc &&
15697 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
15698 DispatchFullscreenNewOriginEvent(this);
15701 // Dispatch "fullscreenchange" events. Note that the loop order is
15702 // reversed so that events are dispatched in the tree order as
15703 // indicated in the spec.
15704 for (Document* d : Reversed(changed)) {
15705 DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement());
15707 aRequest->MayResolvePromise();
15708 return true;
15711 void Document::ClearOrientationPendingPromise() {
15712 mOrientationPendingPromise = nullptr;
15715 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
15716 if (mIsGoingAway) {
15717 return false;
15720 mOrientationPendingPromise = aPromise;
15721 return true;
15724 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
15725 dom::VisibilityState oldState = mVisibilityState;
15726 mVisibilityState = ComputeVisibilityState();
15727 if (oldState != mVisibilityState) {
15728 if (aDispatchEvent == DispatchVisibilityChange::Yes) {
15729 nsContentUtils::DispatchTrustedEvent(this, this, u"visibilitychange"_ns,
15730 CanBubble::eYes, Cancelable::eNo);
15732 NotifyActivityChanged();
15733 if (mVisibilityState == dom::VisibilityState::Visible) {
15734 MaybeActiveMediaComponents();
15737 bool visible = !Hidden();
15738 for (auto* listener : mWorkerListeners) {
15739 listener->OnVisible(visible);
15744 void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
15745 mWorkerListeners.Insert(aListener);
15746 aListener->OnVisible(!Hidden());
15749 void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
15750 mWorkerListeners.Remove(aListener);
15753 VisibilityState Document::ComputeVisibilityState() const {
15754 // We have to check a few pieces of information here:
15755 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
15756 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
15757 // want to use GetWindow here because it does weird groveling for windows
15758 // in some cases.
15759 // 3) Is our outer window background? If so, we're hidden.
15760 // Otherwise, we're visible.
15761 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
15762 mWindow->GetOuterWindow()->IsBackground()) {
15763 return dom::VisibilityState::Hidden;
15766 return dom::VisibilityState::Visible;
15769 void Document::PostVisibilityUpdateEvent() {
15770 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
15771 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
15772 DispatchVisibilityChange::Yes);
15773 Dispatch(event.forget());
15776 void Document::MaybeActiveMediaComponents() {
15777 auto* window = GetWindow();
15778 if (!window || !window->ShouldDelayMediaFromStart()) {
15779 return;
15781 window->ActivateMediaComponents();
15784 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
15785 nsINode::AddSizeOfExcludingThis(aWindowSizes,
15786 &aWindowSizes.mDOMSizes.mDOMOtherSize);
15788 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
15789 AddSizeOfNodeTree(*kid, aWindowSizes);
15792 // IMPORTANT: for our ComputedValues measurements, we want to measure
15793 // ComputedValues accessible from DOM elements before ComputedValues not
15794 // accessible from DOM elements (i.e. accessible only from the frame tree).
15796 // Therefore, the measurement of the Document superclass must happen after
15797 // the measurement of DOM nodes (above), because Document contains the
15798 // PresShell, which contains the frame tree.
15799 if (mPresShell) {
15800 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
15803 if (mStyleSet) {
15804 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
15807 aWindowSizes.mPropertyTablesSize +=
15808 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15810 if (EventListenerManager* elm = GetExistingListenerManager()) {
15811 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15814 if (mNodeInfoManager) {
15815 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
15818 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15819 mDOMMediaQueryLists.sizeOfExcludingThis(
15820 aWindowSizes.mState.mMallocSizeOf);
15822 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
15823 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15824 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15827 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
15829 for (auto& sheetArray : mAdditionalSheets) {
15830 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
15832 // Lumping in the loader with the style-sheets size is not ideal,
15833 // but most of the things in there are in fact stylesheets, so it
15834 // doesn't seem worthwhile to separate it out.
15835 // This can be null if we've already been unlinked.
15836 if (mCSSLoader) {
15837 aWindowSizes.mLayoutStyleSheetsSize +=
15838 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
15841 aWindowSizes.mDOMSizes.mDOMResizeObserverControllerSize +=
15842 mResizeObservers.ShallowSizeOfExcludingThis(
15843 aWindowSizes.mState.mMallocSizeOf);
15845 if (mAttributeStyles) {
15846 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15847 mAttributeStyles->DOMSizeOfIncludingThis(
15848 aWindowSizes.mState.mMallocSizeOf);
15851 if (mRadioGroupContainer) {
15852 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15853 mRadioGroupContainer->SizeOfIncludingThis(
15854 aWindowSizes.mState.mMallocSizeOf);
15857 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15858 mStyledLinks.ShallowSizeOfExcludingThis(
15859 aWindowSizes.mState.mMallocSizeOf);
15861 // Measurement of the following members may be added later if DMD finds it
15862 // is worthwhile:
15863 // - mMidasCommandManager
15864 // - many!
15867 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
15868 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15869 aWindowSizes.mState.mMallocSizeOf(this);
15870 DocAddSizeOfExcludingThis(aWindowSizes);
15873 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
15874 size_t* aNodeSize) const {
15875 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
15876 // nsDocuments can only appear at the top of the DOM tree, and we use the
15877 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
15878 // be called.
15879 MOZ_CRASH();
15882 /* static */
15883 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
15884 size_t nodeSize = 0;
15885 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
15887 // This is where we transfer the nodeSize obtained from
15888 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
15889 switch (aNode.NodeType()) {
15890 case nsINode::ELEMENT_NODE:
15891 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
15892 break;
15893 case nsINode::TEXT_NODE:
15894 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
15895 break;
15896 case nsINode::CDATA_SECTION_NODE:
15897 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
15898 break;
15899 case nsINode::COMMENT_NODE:
15900 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
15901 break;
15902 default:
15903 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
15904 break;
15907 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
15908 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15911 if (aNode.IsContent()) {
15912 nsTArray<nsIContent*> anonKids;
15913 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
15914 nsIContent::eAllChildren);
15915 for (nsIContent* anonKid : anonKids) {
15916 AddSizeOfNodeTree(*anonKid, aWindowSizes);
15919 if (auto* element = Element::FromNode(aNode)) {
15920 if (ShadowRoot* shadow = element->GetShadowRoot()) {
15921 AddSizeOfNodeTree(*shadow, aWindowSizes);
15926 // NOTE(emilio): If you feel smart and want to change this function to use
15927 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
15928 // sane way, and kids of <content> won't point to the parent, so we'd never
15929 // find the root node where we should stop at.
15930 for (nsIContent* kid = aNode.GetFirstChild(); kid;
15931 kid = kid->GetNextSibling()) {
15932 AddSizeOfNodeTree(*kid, aWindowSizes);
15936 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
15937 ErrorResult& rv) {
15938 nsCOMPtr<nsIScriptGlobalObject> global =
15939 do_QueryInterface(aGlobal.GetAsSupports());
15940 if (!global) {
15941 rv.Throw(NS_ERROR_UNEXPECTED);
15942 return nullptr;
15945 nsCOMPtr<nsIScriptObjectPrincipal> prin =
15946 do_QueryInterface(aGlobal.GetAsSupports());
15947 if (!prin) {
15948 rv.Throw(NS_ERROR_UNEXPECTED);
15949 return nullptr;
15952 nsCOMPtr<nsIURI> uri;
15953 NS_NewURI(getter_AddRefs(uri), "about:blank");
15954 if (!uri) {
15955 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
15956 return nullptr;
15959 nsCOMPtr<Document> doc;
15960 nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
15961 nullptr, uri, uri, prin->GetPrincipal(),
15962 true, global, DocumentFlavorPlain);
15963 if (NS_FAILED(res)) {
15964 rv.Throw(res);
15965 return nullptr;
15968 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
15970 return doc.forget();
15973 UniquePtr<XPathExpression> Document::CreateExpression(
15974 const nsAString& aExpression, XPathNSResolver* aResolver, ErrorResult& rv) {
15975 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
15978 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
15979 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
15982 already_AddRefed<XPathResult> Document::Evaluate(
15983 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
15984 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
15985 ErrorResult& rv) {
15986 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
15987 aType, aResult, rv);
15990 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
15991 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
15992 if (!item) {
15993 return nullptr;
15995 nsCOMPtr<nsIDocShellTreeOwner> owner;
15996 item->GetTreeOwner(getter_AddRefs(owner));
15997 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
15998 if (!appWin) {
15999 return nullptr;
16001 nsCOMPtr<nsIDocShell> appWinShell;
16002 appWin->GetDocShell(getter_AddRefs(appWinShell));
16003 if (!SameCOMIdentity(appWinShell, item)) {
16004 return nullptr;
16006 return appWin.forget();
16009 WindowContext* Document::GetTopLevelWindowContext() const {
16010 WindowContext* windowContext = GetWindowContext();
16011 return windowContext ? windowContext->TopWindowContext() : nullptr;
16014 Document* Document::GetTopLevelContentDocumentIfSameProcess() {
16015 Document* parent;
16017 if (!mLoadedAsData) {
16018 parent = this;
16019 } else {
16020 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
16021 if (!window) {
16022 return nullptr;
16025 parent = window->GetExtantDoc();
16026 if (!parent) {
16027 return nullptr;
16031 do {
16032 if (parent->IsTopLevelContentDocument()) {
16033 break;
16036 // If we ever have a non-content parent before we hit a toplevel content
16037 // parent, then we're never going to find one. Just bail.
16038 if (!parent->IsContentDocument()) {
16039 return nullptr;
16042 parent = parent->GetInProcessParentDocument();
16043 } while (parent);
16045 return parent;
16048 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
16049 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
16052 void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
16053 MOZ_ASSERT(IsBeingUsedAsImage());
16054 MOZ_ASSERT(aReferencingDocument);
16056 if (!aReferencingDocument->mShouldReportUseCounters) {
16057 // No need to propagate use counters to a document that itself won't report
16058 // use counters.
16059 return;
16062 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16063 ("PropagateImageUseCounters from %s to %s",
16064 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
16065 nsContentUtils::TruncatedURLForDisplay(
16066 aReferencingDocument->mDocumentURI)
16067 .get()));
16069 if (aReferencingDocument->IsBeingUsedAsImage()) {
16070 NS_WARNING(
16071 "Page use counters from nested image documents may not "
16072 "propagate to the top-level document (bug 1657805)");
16075 SetCssUseCounterBits();
16076 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
16077 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
16080 bool Document::HasScriptsBlockedBySandbox() const {
16081 return mSandboxFlags & SANDBOXED_SCRIPTS;
16084 // Some use-counter sanity-checking.
16085 static_assert(size_t(eUseCounter_EndCSSProperties) -
16086 size_t(eUseCounter_FirstCSSProperty) ==
16087 size_t(eCSSProperty_COUNT_with_aliases),
16088 "We should have the right amount of CSS property use counters");
16089 static_assert(size_t(eUseCounter_Count) -
16090 size_t(eUseCounter_FirstCountedUnknownProperty) ==
16091 size_t(CountedUnknownProperty::Count),
16092 "We should have the right amount of counted unknown properties"
16093 " use counters");
16094 static_assert(size_t(eUseCounter_Count) * 2 ==
16095 size_t(Telemetry::HistogramUseCounterCount),
16096 "There should be two histograms (document and page)"
16097 " for each use counter");
16099 #define ASSERT_CSS_COUNTER(id_, method_) \
16100 static_assert(size_t(eUseCounter_property_##method_) - \
16101 size_t(eUseCounter_FirstCSSProperty) == \
16102 size_t(id_), \
16103 "Order for CSS counters and CSS property id should match");
16104 #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
16105 #define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
16106 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
16107 #define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
16108 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
16109 #define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
16110 ASSERT_CSS_COUNTER(eCSSPropertyAlias_##aliasid_, method_)
16111 #include "mozilla/ServoCSSPropList.h"
16112 #undef CSS_PROP_ALIAS
16113 #undef CSS_PROP_SHORTHAND
16114 #undef CSS_PROP_LONGHAND
16115 #undef CSS_PROP_PUBLIC_OR_PRIVATE
16116 #undef ASSERT_CSS_COUNTER
16118 void Document::SetCssUseCounterBits() {
16119 if (StaticPrefs::layout_css_use_counters_enabled()) {
16120 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
16121 auto id = nsCSSPropertyID(i);
16122 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
16123 SetUseCounter(nsCSSProps::UseCounterFor(id));
16128 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
16129 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
16130 auto id = CountedUnknownProperty(i);
16131 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
16132 id)) {
16133 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
16139 void Document::InitUseCounters() {
16140 // We can be called more than once, e.g. when session history navigation shows
16141 // us a second time.
16142 if (mUseCountersInitialized) {
16143 return;
16145 mUseCountersInitialized = true;
16147 static_assert(Telemetry::HistogramUseCounterCount > 0);
16149 if (!ShouldIncludeInTelemetry()) {
16150 return;
16153 // Now we know for sure that we should report use counters from this document.
16154 mShouldReportUseCounters = true;
16156 WindowContext* top = GetWindowContextForPageUseCounters();
16157 if (!top) {
16158 // This is the case for SVG image documents. They are not displayed in a
16159 // window, but we still do want to record document use counters for them.
16161 // Page use counter propagation is handled in PropagateImageUseCounters,
16162 // so there is no need to use the cross-process machinery to send them.
16163 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16164 ("InitUseCounters for a non-displayed document [%s]",
16165 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16166 return;
16169 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16170 if (!wgc) {
16171 return;
16174 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16175 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
16176 " [from %s]",
16177 wgc->InnerWindowId(), top->Id(),
16178 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
16180 // Inform the parent process that we will send it page use counters later on.
16181 wgc->SendExpectPageUseCounters(top);
16182 mShouldSendPageUseCounters = true;
16185 // We keep separate counts for individual documents and top-level
16186 // pages to more accurately track how many web pages might break if
16187 // certain features were removed. Consider the case of a single
16188 // HTML document with several SVG images and/or iframes with
16189 // sub-documents of their own. If we maintained a single set of use
16190 // counters and all the sub-documents use a particular feature, then
16191 // telemetry would indicate that we would be breaking N documents if
16192 // that feature were removed. Whereas with a document/top-level
16193 // page split, we can see that N documents would be affected, but
16194 // only a single web page would be affected.
16196 // The difference between the values of these two histograms and the
16197 // related use counters below tell us how many pages did *not* use
16198 // the feature in question. For instance, if we see that a given
16199 // session has destroyed 30 content documents, but a particular use
16200 // counter shows only a count of 5, we can infer that the use
16201 // counter was *not* used in 25 of those 30 documents.
16203 // We do things this way, rather than accumulating a boolean flag
16204 // for each use counter, to avoid sending histograms for features
16205 // that don't get widely used. Doing things in this fashion means
16206 // smaller telemetry payloads and faster processing on the server
16207 // side.
16208 void Document::ReportDocumentUseCounters() {
16209 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
16210 return;
16213 mReportedDocumentUseCounters = true;
16215 // Note that a document is being destroyed. See the comment above for how
16216 // use counter histograms are interpreted relative to this measurement.
16217 // TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED is recorded in
16218 // WindowGlobalParent::FinishAccumulatingPageUseCounters.
16219 Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
16220 glean::use_counter::content_documents_destroyed.Add();
16222 // Ask all of our resource documents to report their own document use
16223 // counters.
16224 EnumerateExternalResources([](Document& aDoc) {
16225 aDoc.ReportDocumentUseCounters();
16226 return CallState::Continue;
16229 // Copy StyleUseCounters into our document use counters.
16230 SetCssUseCounterBits();
16232 Maybe<nsCString> urlForLogging;
16233 const bool dumpCounters = StaticPrefs::dom_use_counters_dump_document();
16234 if (dumpCounters) {
16235 urlForLogging.emplace(
16236 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()));
16239 // Report our per-document use counters.
16240 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
16241 auto uc = static_cast<UseCounter>(c);
16242 if (!mUseCounters[uc]) {
16243 continue;
16246 auto id = static_cast<Telemetry::HistogramID>(
16247 Telemetry::HistogramFirstUseCounter + uc * 2);
16248 if (dumpCounters) {
16249 printf_stderr("USE_COUNTER_DOCUMENT: %s - %s\n",
16250 Telemetry::GetHistogramName(id), urlForLogging->get());
16252 Telemetry::Accumulate(id, 1);
16253 IncrementUseCounter(uc, /* aIsPage = */ false);
16257 void Document::ReportLCP() {
16258 const nsDOMNavigationTiming* timing = GetNavigationTiming();
16260 if (!timing) {
16261 return;
16264 TimeStamp lcpTime = timing->GetLargestContentfulRenderTimeStamp();
16266 if (!lcpTime) {
16267 return;
16270 mozilla::glean::perf::largest_contentful_paint.AccumulateRawDuration(
16271 lcpTime - timing->GetNavigationStartTimeStamp());
16273 if (!GetChannel()) {
16274 return;
16277 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
16278 if (!timedChannel) {
16279 return;
16282 TimeStamp responseStart;
16283 timedChannel->GetResponseStart(&responseStart);
16285 if (!responseStart) {
16286 return;
16289 mozilla::glean::perf::largest_contentful_paint_from_response_start
16290 .AccumulateRawDuration(lcpTime - responseStart);
16292 if (profiler_thread_is_being_profiled_for_markers()) {
16293 MarkerInnerWindowId innerWindowID =
16294 MarkerInnerWindowIdFromDocShell(GetDocShell());
16295 GetNavigationTiming()->MaybeAddLCPProfilerMarker(innerWindowID);
16299 void Document::SendPageUseCounters() {
16300 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
16301 return;
16304 // Ask all of our resource documents to send their own document use
16305 // counters to the parent process to be counted as page use counters.
16306 EnumerateExternalResources([](Document& aDoc) {
16307 aDoc.SendPageUseCounters();
16308 return CallState::Continue;
16311 // Send our use counters to the parent process to accumulate them towards the
16312 // page use counters for the top-level document.
16314 // We take our own document use counters (those in mUseCounters) and any child
16315 // document use counters (those in mChildDocumentUseCounters) that have been
16316 // explicitly propagated up to us, which includes resource documents, static
16317 // clones, and SVG images.
16318 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
16319 if (!wgc) {
16320 MOZ_ASSERT_UNREACHABLE(
16321 "SendPageUseCounters should be called while we still have access "
16322 "to our WindowContext");
16323 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16324 (" > too late to send page use counters"));
16325 return;
16328 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
16329 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
16330 wgc->WindowContext()->Id(),
16331 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
16333 // Copy StyleUseCounters into our document use counters.
16334 SetCssUseCounterBits();
16336 UseCounters counters = mUseCounters | mChildDocumentUseCounters;
16337 wgc->SendAccumulatePageUseCounters(counters);
16340 bool Document::RecomputeResistFingerprinting() {
16341 mOverriddenFingerprintingSettings.reset();
16342 const bool previous = mShouldResistFingerprinting;
16344 RefPtr<BrowsingContext> opener =
16345 GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
16346 // If we have a parent or opener document, defer to it only when we have a
16347 // null principal (e.g. a sandboxed iframe or a data: uri) or when the
16348 // document's principal matches. This means we will defer about:blank,
16349 // about:srcdoc, blob and same-origin iframes/popups to the parent/opener,
16350 // but not cross-origin ones. Cross-origin iframes/popups may inherit a
16351 // CookieJarSettings.mShouldRFP = false bit however, which will be respected.
16352 auto shouldInheritFrom = [this](Document* aDoc) {
16353 return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
16354 this->NodePrincipal()->GetIsNullPrincipal());
16357 if (shouldInheritFrom(mParentDocument)) {
16358 MOZ_LOG(
16359 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16360 ("Inside RecomputeResistFingerprinting with URI %s and deferring "
16361 "to parent document %s",
16362 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16363 mParentDocument->GetDocumentURI()->GetSpecOrDefault().get()));
16364 mShouldResistFingerprinting = mParentDocument->ShouldResistFingerprinting(
16365 RFPTarget::IsAlwaysEnabledForPrecompute);
16366 mOverriddenFingerprintingSettings =
16367 mParentDocument->mOverriddenFingerprintingSettings;
16368 } else if (opener && shouldInheritFrom(opener->GetDocument())) {
16369 MOZ_LOG(
16370 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16371 ("Inside RecomputeResistFingerprinting with URI %s and deferring to "
16372 "opener document %s",
16373 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null",
16374 opener->GetDocument()->GetDocumentURI()->GetSpecOrDefault().get()));
16375 mShouldResistFingerprinting =
16376 opener->GetDocument()->ShouldResistFingerprinting(
16377 RFPTarget::IsAlwaysEnabledForPrecompute);
16378 mOverriddenFingerprintingSettings =
16379 opener->GetDocument()->mOverriddenFingerprintingSettings;
16380 } else if (nsContentUtils::IsChromeDoc(this)) {
16381 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16382 ("Inside RecomputeResistFingerprinting with a ChromeDoc"));
16384 mShouldResistFingerprinting = false;
16385 mOverriddenFingerprintingSettings.reset();
16386 } else if (mChannel) {
16387 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16388 ("Inside RecomputeResistFingerprinting with URI %s",
16389 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get()
16390 : "null"));
16391 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
16392 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16394 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16395 mOverriddenFingerprintingSettings =
16396 loadInfo->GetOverriddenFingerprintingSettings();
16397 } else {
16398 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16399 ("Inside RecomputeResistFingerprinting fallback case."));
16400 // We still preserve the behavior in the fallback case. But, it means there
16401 // might be some cases we haven't considered yet and we need to investigate
16402 // them.
16403 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting(
16404 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
16405 mOverriddenFingerprintingSettings.reset();
16408 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug,
16409 ("Finished RecomputeResistFingerprinting with result %x",
16410 mShouldResistFingerprinting));
16412 bool changed = previous != mShouldResistFingerprinting;
16413 if (changed) {
16414 if (auto win = nsGlobalWindowInner::Cast(GetInnerWindow())) {
16415 win->RefreshReduceTimerPrecisionCallerType();
16418 return changed;
16421 bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const {
16422 return mShouldResistFingerprinting &&
16423 nsRFPService::IsRFPEnabledFor(this->IsInPrivateBrowsing(), aTarget,
16424 mOverriddenFingerprintingSettings);
16427 void Document::RecordCanvasUsage(CanvasUsage& aUsage) {
16428 // Limit the number of recent canvas extraction uses that are tracked.
16429 const size_t kTrackedCanvasLimit = 8;
16430 // Timeout between different canvas extractions.
16431 const uint64_t kTimeoutUsec = 3000 * 1000;
16433 uint64_t now = PR_Now();
16434 if ((mCanvasUsage.Length() > kTrackedCanvasLimit) ||
16435 ((now - mLastCanvasUsage) > kTimeoutUsec)) {
16436 mCanvasUsage.ClearAndRetainStorage();
16439 mCanvasUsage.AppendElement(aUsage);
16440 mLastCanvasUsage = now;
16442 nsCString originNoSuffix;
16443 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
16444 return;
16447 nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsage, GetChannel(),
16448 originNoSuffix);
16451 void Document::RecordFontFingerprinting() {
16452 nsCString originNoSuffix;
16453 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) {
16454 return;
16457 nsRFPService::MaybeReportFontFingerprinter(GetChannel(), originNoSuffix);
16460 bool Document::IsInPrivateBrowsing() const { return mIsInPrivateBrowsing; }
16462 WindowContext* Document::GetWindowContextForPageUseCounters() const {
16463 if (mDisplayDocument) {
16464 // If we are a resource document, then go through it to find the
16465 // top-level document.
16466 return mDisplayDocument->GetWindowContextForPageUseCounters();
16469 if (mOriginalDocument) {
16470 // For static clones (print preview documents), contribute page use counters
16471 // towards the original document.
16472 return mOriginalDocument->GetWindowContextForPageUseCounters();
16475 WindowContext* wc = GetTopLevelWindowContext();
16476 if (!wc || !wc->GetBrowsingContext()->IsContent()) {
16477 return nullptr;
16480 return wc;
16483 void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
16484 if (mIntersectionObservers.IsEmpty()) {
16485 return;
16488 DOMHighResTimeStamp time = 0;
16489 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
16490 if (Performance* perf = win->GetPerformance()) {
16491 time = perf->TimeStampToDOMHighResForRendering(aNowTime);
16495 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16496 mIntersectionObservers);
16497 for (const auto& observer : observers) {
16498 if (observer) {
16499 observer->Update(*this, time);
16504 void Document::ScheduleIntersectionObserverNotification() {
16505 if (mIntersectionObservers.IsEmpty()) {
16506 return;
16508 MOZ_RELEASE_ASSERT(NS_IsMainThread());
16509 nsCOMPtr<nsIRunnable> notification =
16510 NewRunnableMethod("Document::NotifyIntersectionObservers", this,
16511 &Document::NotifyIntersectionObservers);
16512 Dispatch(notification.forget());
16515 void Document::NotifyIntersectionObservers() {
16516 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
16517 mIntersectionObservers);
16518 for (const auto& observer : observers) {
16519 if (observer) {
16520 // MOZ_KnownLive because the 'observers' array guarantees to keep it
16521 // alive.
16522 MOZ_KnownLive(observer)->Notify();
16527 DOMIntersectionObserver& Document::EnsureLazyLoadObserver() {
16528 if (!mLazyLoadObserver) {
16529 mLazyLoadObserver = DOMIntersectionObserver::CreateLazyLoadObserver(*this);
16531 return *mLazyLoadObserver;
16534 ResizeObserver& Document::EnsureLastRememberedSizeObserver() {
16535 if (!mLastRememberedSizeObserver) {
16536 mLastRememberedSizeObserver =
16537 ResizeObserver::CreateLastRememberedSizeObserver(*this);
16539 return *mLastRememberedSizeObserver;
16542 void Document::ObserveForLastRememberedSize(Element& aElement) {
16543 if (NS_WARN_IF(!IsActive())) {
16544 return;
16546 // Options are initialized with ResizeObserverBoxOptions::Content_box by
16547 // default, which is what we want.
16548 static ResizeObserverOptions options;
16549 EnsureLastRememberedSizeObserver().Observe(aElement, options);
16552 void Document::UnobserveForLastRememberedSize(Element& aElement) {
16553 if (mLastRememberedSizeObserver) {
16554 mLastRememberedSizeObserver->Unobserve(aElement);
16558 void Document::NotifyLayerManagerRecreated() {
16559 NotifyActivityChanged();
16560 EnumerateSubDocuments([](Document& aSubDoc) {
16561 aSubDoc.NotifyLayerManagerRecreated();
16562 return CallState::Continue;
16566 XPathEvaluator* Document::XPathEvaluator() {
16567 if (!mXPathEvaluator) {
16568 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
16570 return mXPathEvaluator.get();
16573 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
16574 return mCachedEncoder.forget();
16577 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
16578 mCachedEncoder = aEncoder;
16581 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
16583 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
16585 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
16586 mStateObjectContainer = scContainer;
16587 mCachedStateObject = JS::UndefinedValue();
16588 mCachedStateObjectValid = false;
16591 bool Document::ComputeDocumentLWTheme() const {
16592 if (!NodePrincipal()->IsSystemPrincipal()) {
16593 return false;
16596 Element* element = GetRootElement();
16597 return element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
16598 nsGkAtoms::_true, eCaseMatters);
16601 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
16602 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
16603 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
16604 ELEMENT_NODE);
16605 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
16607 nsCOMPtr<Element> element;
16608 DebugOnly<nsresult> rv =
16609 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
16610 mozilla::dom::NOT_FROM_PARSER);
16612 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
16613 return element.forget();
16616 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
16617 BrowsingContext* aContext) {
16618 aContext->PreOrderWalk([&](BrowsingContext* aBC) {
16619 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
16620 if (RefPtr<Document> doc = win->GetExtantDoc()) {
16621 SuppressDocument(doc);
16622 mDocuments.AppendElement(doc);
16628 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
16629 BrowsingContextGroup* aGroup) {
16630 for (const auto& bc : aGroup->Toplevels()) {
16631 SuppressBrowsingContext(bc);
16635 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
16636 SyncOperationBehavior aSyncBehavior)
16637 : mSyncBehavior(aSyncBehavior) {
16638 mMicroTaskLevel = 0;
16639 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16640 mMicroTaskLevel = ccjs->MicroTaskLevel();
16641 ccjs->SetMicroTaskLevel(0);
16643 if (aDoc) {
16644 mBrowsingContext = aDoc->GetBrowsingContext();
16645 if (InputTaskManager::CanSuspendInputEvent()) {
16646 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
16647 SuppressBrowsingContextGroup(bcg);
16649 } else if (mBrowsingContext) {
16650 SuppressBrowsingContext(mBrowsingContext->Top());
16652 if (mBrowsingContext &&
16653 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16654 InputTaskManager::CanSuspendInputEvent()) {
16655 mBrowsingContext->Group()->IncInputEventSuspensionLevel();
16660 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
16661 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16662 win->TimeoutManager().BeginSyncOperation();
16664 aDoc->SetIsInSyncOperation(true);
16667 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
16668 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16669 win->TimeoutManager().EndSyncOperation();
16671 aDoc->SetIsInSyncOperation(false);
16674 nsAutoSyncOperation::~nsAutoSyncOperation() {
16675 UnsuppressDocuments();
16676 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
16677 if (ccjs) {
16678 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
16680 if (mBrowsingContext &&
16681 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16682 InputTaskManager::CanSuspendInputEvent()) {
16683 mBrowsingContext->Group()->DecInputEventSuspensionLevel();
16687 void Document::SetIsInSyncOperation(bool aSync) {
16688 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16689 ccjs->UpdateMicroTaskSuppressionGeneration();
16692 if (aSync) {
16693 ++mInSyncOperationCount;
16694 } else {
16695 --mInSyncOperationCount;
16699 gfxUserFontSet* Document::GetUserFontSet() {
16700 if (!mFontFaceSet) {
16701 return nullptr;
16704 return mFontFaceSet->GetImpl();
16707 void Document::FlushUserFontSet() {
16708 if (!mFontFaceSetDirty) {
16709 return;
16712 mFontFaceSetDirty = false;
16714 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
16715 nsTArray<nsFontFaceRuleContainer> rules;
16716 RefPtr<PresShell> presShell = GetPresShell();
16717 if (presShell) {
16718 MOZ_ASSERT(mStyleSetFilled);
16719 EnsureStyleSet().AppendFontFaceRules(rules);
16722 if (!mFontFaceSet && !rules.IsEmpty()) {
16723 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16726 bool changed = false;
16727 if (mFontFaceSet) {
16728 changed = mFontFaceSet->UpdateRules(rules);
16731 // We need to enqueue a style change reflow (for later) to
16732 // reflect that we're modifying @font-face rules. (However,
16733 // without a reflow, nothing will happen to start any downloads
16734 // that are needed.)
16735 if (changed && presShell) {
16736 if (nsPresContext* presContext = presShell->GetPresContext()) {
16737 presContext->UserFontSetUpdated();
16743 void Document::MarkUserFontSetDirty() {
16744 if (mFontFaceSetDirty) {
16745 return;
16747 mFontFaceSetDirty = true;
16748 if (PresShell* presShell = GetPresShell()) {
16749 presShell->EnsureStyleFlush();
16753 FontFaceSet* Document::Fonts() {
16754 if (!mFontFaceSet) {
16755 mFontFaceSet = FontFaceSet::CreateForDocument(this);
16756 FlushUserFontSet();
16758 return mFontFaceSet;
16761 void Document::ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp) {
16762 MOZ_ASSERT(!aTimeStamp.IsNull());
16764 if (!mLastScrollLinkedEffectDetectionTime.IsNull() &&
16765 mLastScrollLinkedEffectDetectionTime >= aTimeStamp) {
16766 return;
16769 if (mLastScrollLinkedEffectDetectionTime.IsNull()) {
16770 // Report to console just once.
16771 nsContentUtils::ReportToConsole(
16772 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
16773 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
16776 mLastScrollLinkedEffectDetectionTime = aTimeStamp;
16779 bool Document::HasScrollLinkedEffect() const {
16780 if (nsPresContext* pc = GetPresContext()) {
16781 return mLastScrollLinkedEffectDetectionTime ==
16782 pc->RefreshDriver()->MostRecentRefresh();
16785 return false;
16788 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
16789 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16790 // Setting has user interction on a discarded browsing context has
16791 // no effect.
16792 Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
16796 bool Document::GetSHEntryHasUserInteraction() {
16797 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16798 return topWc->GetSHEntryHasUserInteraction();
16800 return false;
16803 void Document::SetUserHasInteracted() {
16804 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
16805 ("Document %p has been interacted by user.", this));
16807 // We maybe need to update the user-interaction permission.
16808 MaybeStoreUserInteractionAsPermission();
16810 // For purposes of reducing irrelevant session history entries on
16811 // the back button, we annotate entries with whether they had user
16812 // interaction. This is gated on its own flag on the WindowContext
16813 // (instead of mUserHasInteracted) to account for the fact that multiple
16814 // top-level SH entries can be associated with the same document.
16815 // Thus, whenever we create a new SH entry for this document,
16816 // this flag is reset.
16817 if (!GetSHEntryHasUserInteraction()) {
16818 nsIDocShell* docShell = GetDocShell();
16819 if (docShell) {
16820 nsCOMPtr<nsISHEntry> currentEntry;
16821 bool oshe;
16822 nsresult rv =
16823 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
16824 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
16825 currentEntry->SetHasUserInteraction(true);
16828 SetSHEntryHasUserInteraction(true);
16831 if (mUserHasInteracted) {
16832 return;
16835 mUserHasInteracted = true;
16837 if (mChannel) {
16838 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16839 loadInfo->SetDocumentHasUserInteracted(true);
16841 // Tell the parent process about user interaction
16842 if (auto* wgc = GetWindowGlobalChild()) {
16843 wgc->SendUpdateDocumentHasUserInteracted(true);
16846 MaybeAllowStorageForOpenerAfterUserInteraction();
16849 BrowsingContext* Document::GetBrowsingContext() const {
16850 return mDocumentContainer ? mDocumentContainer->GetBrowsingContext()
16851 : nullptr;
16854 void Document::NotifyUserGestureActivation(
16855 UserActivation::Modifiers
16856 aModifiers /* = UserActivation::Modifiers::None() */) {
16857 // https://html.spec.whatwg.org/multipage/interaction.html#activation-notification
16858 // 1. "Assert: document is fully active."
16859 RefPtr<BrowsingContext> currentBC = GetBrowsingContext();
16860 if (!currentBC) {
16861 return;
16864 RefPtr<WindowContext> currentWC = GetWindowContext();
16865 if (!currentWC) {
16866 return;
16869 // 2. "Let windows be « document's relevant global object"
16870 // Instead of assembling a list, we just call notify for wanted windows as we
16871 // find them
16872 currentWC->NotifyUserGestureActivation(aModifiers);
16874 // 3. "...windows with the active window of each of document's ancestor
16875 // navigables."
16876 for (WindowContext* wc = currentWC; wc; wc = wc->GetParentWindowContext()) {
16877 wc->NotifyUserGestureActivation(aModifiers);
16880 // 4. "windows with the active window of each of document's descendant
16881 // navigables, filtered to include only those navigables whose active
16882 // document's origin is same origin with document's origin"
16883 currentBC->PreOrderWalk([&](BrowsingContext* bc) {
16884 WindowContext* wc = bc->GetCurrentWindowContext();
16885 if (!wc) {
16886 return;
16889 // Check same-origin as current document
16890 WindowGlobalChild* wgc = wc->GetWindowGlobalChild();
16891 if (!wgc || !wgc->IsSameOriginWith(currentWC)) {
16892 return;
16895 wc->NotifyUserGestureActivation(aModifiers);
16899 bool Document::HasBeenUserGestureActivated() {
16900 RefPtr<WindowContext> wc = GetWindowContext();
16901 return wc && wc->HasBeenUserGestureActivated();
16904 DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
16905 if (RefPtr<WindowContext> wc = GetWindowContext()) {
16906 if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) {
16907 if (Performance* perf = innerWindow->GetPerformance()) {
16908 return perf->GetDOMTiming()->TimeStampToDOMHighRes(
16909 wc->GetUserGestureStart());
16914 NS_WARNING(
16915 "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
16916 return 0;
16919 void Document::ClearUserGestureActivation() {
16920 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16921 bc = bc->Top();
16922 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16923 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
16924 windowContext->NotifyResetUserGestureActivation();
16930 bool Document::HasValidTransientUserGestureActivation() const {
16931 RefPtr<WindowContext> wc = GetWindowContext();
16932 return wc && wc->HasValidTransientUserGestureActivation();
16935 bool Document::ConsumeTransientUserGestureActivation() {
16936 RefPtr<WindowContext> wc = GetWindowContext();
16937 return wc && wc->ConsumeTransientUserGestureActivation();
16940 bool Document::GetTransientUserGestureActivationModifiers(
16941 UserActivation::Modifiers* aModifiers) {
16942 RefPtr<WindowContext> wc = GetWindowContext();
16943 return wc && wc->GetTransientUserGestureActivationModifiers(aModifiers);
16946 void Document::SetDocTreeHadMedia() {
16947 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
16948 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
16949 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
16953 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
16954 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
16955 return;
16958 // This will probably change for project fission, but currently this document
16959 // and the opener are on the same process. In the future, we should make this
16960 // part async.
16961 nsPIDOMWindowInner* inner = GetInnerWindow();
16962 if (NS_WARN_IF(!inner)) {
16963 return;
16966 // We care about first-party tracking resources only.
16967 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
16968 return;
16971 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16972 if (NS_WARN_IF(!outer)) {
16973 return;
16976 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
16977 if (!openerBC) {
16978 // No opener.
16979 return;
16982 // We want to ensure the following check works for both fission mode and
16983 // non-fission mode:
16984 // "If the opener is not a 3rd party and if this window is not a 3rd party
16985 // with respect to the opener, we should not continue."
16987 // In non-fission mode, the opener and the opened window are in the same
16988 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
16989 // In fission mode, if this window is not a 3rd party with respect to the
16990 // opener, they must be in the same process, so we can still use
16991 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
16992 // party.
16993 if (openerBC->IsInProcess()) {
16994 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
16995 if (NS_WARN_IF(!outerOpener)) {
16996 return;
16999 nsCOMPtr<nsPIDOMWindowInner> openerInner =
17000 outerOpener->GetCurrentInnerWindow();
17001 if (NS_WARN_IF(!openerInner)) {
17002 return;
17005 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
17006 if (NS_WARN_IF(!openerDocument)) {
17007 return;
17010 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
17011 if (NS_WARN_IF(!openerURI)) {
17012 return;
17015 // If the opener is not a 3rd party and if this window is not
17016 // a 3rd party with respect to the opener, we should not continue.
17017 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
17018 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
17019 return;
17023 // We don't care when the asynchronous work finishes here.
17024 Unused << StorageAccessAPIHelper::AllowAccessForOnChildProcess(
17025 NodePrincipal(), openerBC,
17026 ContentBlockingNotifier::eOpenerAfterUserInteraction);
17029 namespace {
17031 // Documents can stay alive for days. We don't want to update the permission
17032 // value at any user-interaction, and, using a timer triggered any X seconds
17033 // should be good enough. 'X' is taken from
17034 // privacy.userInteraction.document.interval pref.
17035 // We also want to store the user-interaction before shutting down, and, for
17036 // this reason, this class implements nsIAsyncShutdownBlocker interface.
17037 class UserInteractionTimer final : public Runnable,
17038 public nsITimerCallback,
17039 public nsIAsyncShutdownBlocker {
17040 public:
17041 NS_DECL_ISUPPORTS_INHERITED
17043 explicit UserInteractionTimer(Document* aDocument)
17044 : Runnable("UserInteractionTimer"),
17045 mPrincipal(aDocument->NodePrincipal()),
17046 mDocument(aDocument) {
17047 static int32_t userInteractionTimerId = 0;
17048 // Blocker names must be unique. Let's create it now because when needed,
17049 // the document could be already gone.
17050 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
17051 ++userInteractionTimerId, aDocument);
17054 // Runnable interface
17056 NS_IMETHOD
17057 Run() override {
17058 uint32_t interval =
17059 StaticPrefs::privacy_userInteraction_document_interval();
17060 if (!interval) {
17061 return NS_OK;
17064 RefPtr<UserInteractionTimer> self = this;
17065 auto raii =
17066 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
17068 nsresult rv = NS_NewTimerWithCallback(
17069 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
17070 NS_ENSURE_SUCCESS(rv, NS_OK);
17072 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
17073 NS_ENSURE_TRUE(!!phase, NS_OK);
17075 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
17076 __LINE__, u"UserInteractionTimer shutdown"_ns);
17077 NS_ENSURE_SUCCESS(rv, NS_OK);
17079 raii.release();
17080 return NS_OK;
17083 // nsITimerCallback interface
17085 NS_IMETHOD
17086 Notify(nsITimer* aTimer) override {
17087 StoreUserInteraction();
17088 return NS_OK;
17091 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
17092 using nsINamed::GetName;
17093 #endif
17095 // nsIAsyncShutdownBlocker interface
17097 NS_IMETHOD
17098 GetName(nsAString& aName) override {
17099 aName = mBlockerName;
17100 return NS_OK;
17103 NS_IMETHOD
17104 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
17105 CancelTimerAndStoreUserInteraction();
17106 return NS_OK;
17109 NS_IMETHOD
17110 GetState(nsIPropertyBag**) override { return NS_OK; }
17112 private:
17113 ~UserInteractionTimer() = default;
17115 void StoreUserInteraction() {
17116 // Remove the shutting down blocker
17117 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
17118 if (phase) {
17119 phase->RemoveBlocker(this);
17122 // If the document is not gone, let's reset its timer flag.
17123 nsCOMPtr<Document> document(mDocument);
17124 if (document) {
17125 ContentBlockingUserInteraction::Observe(mPrincipal);
17126 document->ResetUserInteractionTimer();
17130 void CancelTimerAndStoreUserInteraction() {
17131 if (mTimer) {
17132 mTimer->Cancel();
17133 mTimer = nullptr;
17136 StoreUserInteraction();
17139 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
17140 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
17141 NS_ENSURE_TRUE(!!svc, nullptr);
17143 nsCOMPtr<nsIAsyncShutdownClient> phase;
17144 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
17145 NS_ENSURE_SUCCESS(rv, nullptr);
17147 return phase.forget();
17150 nsCOMPtr<nsIPrincipal> mPrincipal;
17151 WeakPtr<Document> mDocument;
17153 nsCOMPtr<nsITimer> mTimer;
17155 nsString mBlockerName;
17158 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
17159 nsIAsyncShutdownBlocker)
17161 } // namespace
17163 void Document::MaybeStoreUserInteractionAsPermission() {
17164 // We care about user-interaction stored only for top-level documents
17165 // and documents with access to the Storage Access API
17166 if (!IsTopLevelContentDocument()) {
17167 bool hasSA;
17168 nsresult rv = HasStorageAccessSync(hasSA);
17169 if (NS_FAILED(rv) || !hasSA) {
17170 return;
17174 if (!mUserHasInteracted) {
17175 // First interaction, let's store this info now.
17176 ContentBlockingUserInteraction::Observe(NodePrincipal());
17177 return;
17180 if (mHasUserInteractionTimerScheduled) {
17181 return;
17184 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
17185 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
17186 EventQueuePriority::Idle);
17187 if (NS_WARN_IF(NS_FAILED(rv))) {
17188 return;
17191 // This value will be reset by the timer.
17192 mHasUserInteractionTimerScheduled = true;
17195 void Document::ResetUserInteractionTimer() {
17196 mHasUserInteractionTimerScheduled = false;
17199 bool Document::IsExtensionPage() const {
17200 return BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
17203 void Document::AddResizeObserver(ResizeObserver& aObserver) {
17204 MOZ_ASSERT(!mResizeObservers.Contains(&aObserver));
17205 // Insert internal ResizeObservers before scripted ones, since they may have
17206 // observable side-effects and we don't want to expose the insertion time.
17207 if (aObserver.HasNativeCallback()) {
17208 mResizeObservers.InsertElementAt(0, &aObserver);
17209 } else {
17210 mResizeObservers.AppendElement(&aObserver);
17214 void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
17215 MOZ_ASSERT(mResizeObservers.Contains(&aObserver));
17216 mResizeObservers.RemoveElement(&aObserver);
17219 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
17220 if (!mPermissionDelegateHandler) {
17221 mPermissionDelegateHandler = MakeAndAddRef<PermissionDelegateHandler>(this);
17224 if (!mPermissionDelegateHandler->Initialize()) {
17225 mPermissionDelegateHandler = nullptr;
17228 return mPermissionDelegateHandler;
17231 void Document::ScheduleResizeObserversNotification() const {
17232 if (!mPresShell) {
17233 return;
17235 if (nsRefreshDriver* rd = mPresShell->GetRefreshDriver()) {
17236 rd->EnsureResizeObserverUpdateHappens();
17240 static void FlushLayoutForWholeBrowsingContextTree(Document& aDoc) {
17241 BrowsingContext* bc = aDoc.GetBrowsingContext();
17242 if (bc && bc->GetExtantDocument() == &aDoc) {
17243 RefPtr<BrowsingContext> top = bc->Top();
17244 top->PreOrderWalk([](BrowsingContext* aCur) {
17245 if (Document* doc = aCur->GetExtantDocument()) {
17246 doc->FlushPendingNotifications(FlushType::Layout);
17249 } else {
17250 // If there is no browsing context, or we're not the current document of the
17251 // browsing context, then we just flush this document itself.
17252 aDoc.FlushPendingNotifications(FlushType::Layout);
17256 void Document::DetermineProximityToViewportAndNotifyResizeObservers() {
17257 uint32_t shallowestTargetDepth = 0;
17258 bool initialResetOfScrolledIntoViewFlagsDone = false;
17259 while (true) {
17260 // Flush layout, so that any callback functions' style changes / resizes
17261 // get a chance to take effect. The callback functions may do changes in its
17262 // sub-documents or ancestors, so flushing layout for the whole browsing
17263 // context tree makes sure we don't miss anyone.
17264 FlushLayoutForWholeBrowsingContextTree(*this);
17265 if (PresShell* presShell = GetPresShell()) {
17266 auto result = presShell->DetermineProximityToViewport();
17267 if (result.mHadInitialDetermination) {
17268 continue;
17270 if (result.mAnyScrollIntoViewFlag) {
17271 // Not defined in the spec: It's possible that some elements with
17272 // content-visibility: auto were forced to be visible in order to
17273 // perform scrollIntoView() so clear their flags now and restart the
17274 // loop.
17275 // See https://github.com/w3c/csswg-drafts/issues/9337
17276 presShell->ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags();
17277 presShell->ScheduleContentRelevancyUpdate(
17278 ContentRelevancyReason::Visible);
17279 if (!initialResetOfScrolledIntoViewFlagsDone) {
17280 initialResetOfScrolledIntoViewFlagsDone = true;
17281 continue;
17286 // To avoid infinite resize loop, we only gather all active observations
17287 // that have the depth of observed target element more than current
17288 // shallowestTargetDepth.
17289 GatherAllActiveResizeObservations(shallowestTargetDepth);
17291 if (!HasAnyActiveResizeObservations()) {
17292 break;
17295 DebugOnly<uint32_t> oldShallowestTargetDepth = shallowestTargetDepth;
17296 shallowestTargetDepth = BroadcastAllActiveResizeObservations();
17297 NS_ASSERTION(oldShallowestTargetDepth < shallowestTargetDepth,
17298 "shallowestTargetDepth should be getting strictly deeper");
17301 if (HasAnySkippedResizeObservations()) {
17302 if (nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow()) {
17303 // Per spec, we deliver an error if the document has any skipped
17304 // observations. Also, we re-register via ScheduleNotification().
17305 RootedDictionary<ErrorEventInit> init(RootingCx());
17306 init.mMessage.AssignLiteral(
17307 "ResizeObserver loop completed with undelivered notifications.");
17308 init.mBubbles = false;
17309 init.mCancelable = false;
17311 nsEventStatus status = nsEventStatus_eIgnore;
17312 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
17313 MOZ_ASSERT(sgo);
17314 if (NS_WARN_IF(sgo->HandleScriptError(init, &status))) {
17315 status = nsEventStatus_eIgnore;
17317 } else {
17318 // We don't fire error events at any global for non-window JS on the main
17319 // thread.
17322 // We need to deliver pending notifications in next cycle.
17323 ScheduleResizeObserversNotification();
17327 void Document::GatherAllActiveResizeObservations(uint32_t aDepth) {
17328 for (ResizeObserver* observer : mResizeObservers) {
17329 observer->GatherActiveObservations(aDepth);
17333 uint32_t Document::BroadcastAllActiveResizeObservations() {
17334 uint32_t shallowestTargetDepth = std::numeric_limits<uint32_t>::max();
17336 // Copy the observers as this invokes the callbacks and could register and
17337 // unregister observers at will.
17338 const auto observers =
17339 ToTArray<nsTArray<RefPtr<ResizeObserver>>>(mResizeObservers);
17340 for (const auto& observer : observers) {
17341 // MOZ_KnownLive because 'observers' is guaranteed to keep it
17342 // alive.
17344 // This can go away once
17345 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
17346 uint32_t targetDepth =
17347 MOZ_KnownLive(observer)->BroadcastActiveObservations();
17348 if (targetDepth < shallowestTargetDepth) {
17349 shallowestTargetDepth = targetDepth;
17353 return shallowestTargetDepth;
17356 bool Document::HasAnySkippedResizeObservations() const {
17357 for (const auto& observer : mResizeObservers) {
17358 if (observer->HasSkippedObservations()) {
17359 return true;
17362 return false;
17365 bool Document::HasAnyActiveResizeObservations() const {
17366 for (const auto& observer : mResizeObservers) {
17367 if (observer->HasActiveObservations()) {
17368 return true;
17371 return false;
17374 void Document::ClearStaleServoData() {
17375 DocumentStyleRootIterator iter(this);
17376 while (Element* root = iter.GetNextStyleRoot()) {
17377 RestyleManager::ClearServoDataFromSubtree(root);
17381 Selection* Document::GetSelection(ErrorResult& aRv) {
17382 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
17383 if (!window) {
17384 return nullptr;
17387 if (!window->IsCurrentInnerWindow()) {
17388 return nullptr;
17391 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
17394 void Document::MakeBrowsingContextNonSynthetic() {
17395 if (BrowsingContext* bc = GetBrowsingContext()) {
17396 if (bc->GetSyntheticDocumentContainer()) {
17397 Unused << bc->SetSyntheticDocumentContainer(false);
17402 nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
17403 // Step 1: check if cookie permissions are available or denied to this
17404 // document's principal
17405 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17406 if (!inner) {
17407 aHasStorageAccess = false;
17408 return NS_OK;
17410 Maybe<bool> resultBecauseCookiesApproved =
17411 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17412 CookieJarSettings(), NodePrincipal());
17413 if (resultBecauseCookiesApproved.isSome()) {
17414 if (resultBecauseCookiesApproved.value()) {
17415 aHasStorageAccess = true;
17416 return NS_OK;
17417 } else {
17418 aHasStorageAccess = false;
17419 return NS_OK;
17423 // Step 2: Check if the browser settings determine whether or not this
17424 // document has access to its unpartitioned cookies.
17425 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17426 bool isOnThirdPartySkipList = false;
17427 if (mChannel) {
17428 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17429 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17430 nsILoadInfo::StoragePermissionAllowListed;
17432 bool isThirdPartyTracker =
17433 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17434 Maybe<bool> resultBecauseBrowserSettings =
17435 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17436 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
17437 isThirdPartyTracker);
17438 if (resultBecauseBrowserSettings.isSome()) {
17439 if (resultBecauseBrowserSettings.value()) {
17440 aHasStorageAccess = true;
17441 return NS_OK;
17442 } else {
17443 aHasStorageAccess = false;
17444 return NS_OK;
17448 // Step 3: Check if the location of this call (embedded, top level, same-site)
17449 // determines if cookies are permitted or not.
17450 Maybe<bool> resultBecauseCallContext =
17451 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17452 false);
17453 if (resultBecauseCallContext.isSome()) {
17454 if (resultBecauseCallContext.value()) {
17455 aHasStorageAccess = true;
17456 return NS_OK;
17457 } else {
17458 aHasStorageAccess = false;
17459 return NS_OK;
17463 // Step 4: Check if the permissions for this document determine if if has
17464 // access or is denied cookies.
17465 Maybe<bool> resultBecausePreviousPermission =
17466 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17467 this, false);
17468 if (resultBecausePreviousPermission.isSome()) {
17469 if (resultBecausePreviousPermission.value()) {
17470 aHasStorageAccess = true;
17471 return NS_OK;
17472 } else {
17473 aHasStorageAccess = false;
17474 return NS_OK;
17477 // If you get here, we default to not giving you permission.
17478 aHasStorageAccess = false;
17479 return NS_OK;
17482 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
17483 mozilla::ErrorResult& aRv) {
17484 nsIGlobalObject* global = GetScopeObject();
17485 if (!global) {
17486 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17487 return nullptr;
17490 RefPtr<Promise> promise =
17491 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
17492 if (aRv.Failed()) {
17493 return nullptr;
17496 if (!IsCurrentActiveDocument()) {
17497 promise->MaybeRejectWithInvalidStateError(
17498 "hasStorageAccess requires an active document");
17499 return promise.forget();
17502 bool hasStorageAccess;
17503 nsresult rv = HasStorageAccessSync(hasStorageAccess);
17504 if (NS_FAILED(rv)) {
17505 promise->MaybeRejectWithUndefined();
17506 } else {
17507 promise->MaybeResolve(hasStorageAccess);
17510 return promise.forget();
17513 RefPtr<Document::GetContentBlockingEventsPromise>
17514 Document::GetContentBlockingEvents() {
17515 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
17516 if (!wgc) {
17517 return nullptr;
17520 return wgc->SendGetContentBlockingEvents()->Then(
17521 GetCurrentSerialEventTarget(), __func__,
17522 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
17523 ResolveOrRejectValue& aValue) {
17524 if (aValue.IsResolve()) {
17525 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
17526 aValue.ResolveValue(), __func__);
17529 return Document::GetContentBlockingEventsPromise::CreateAndReject(
17530 false, __func__);
17534 StorageAccessAPIHelper::PerformPermissionGrant
17535 Document::CreatePermissionGrantPromise(
17536 nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal,
17537 bool aHasUserInteraction, bool aRequireUserInteraction,
17538 const Maybe<nsCString>& aTopLevelBaseDomain, bool aFrameOnly) {
17539 MOZ_ASSERT(aInnerWindow);
17540 MOZ_ASSERT(aPrincipal);
17541 RefPtr<Document> self(this);
17542 RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
17543 RefPtr<nsIPrincipal> principal(aPrincipal);
17545 return [inner, self, principal, aHasUserInteraction, aRequireUserInteraction,
17546 aTopLevelBaseDomain, aFrameOnly]() {
17547 RefPtr<StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::Private>
17548 p = new StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
17549 Private(__func__);
17551 RefPtr<PWindowGlobalChild::GetStorageAccessPermissionPromise> promise;
17552 // Test the permission
17553 MOZ_ASSERT(XRE_IsContentProcess());
17555 WindowGlobalChild* wgc = inner->GetWindowGlobalChild();
17556 MOZ_ASSERT(wgc);
17558 promise = wgc->SendGetStorageAccessPermission();
17559 MOZ_ASSERT(promise);
17560 promise->Then(
17561 GetCurrentSerialEventTarget(), __func__,
17562 [self, p, inner, principal, aHasUserInteraction,
17563 aRequireUserInteraction, aTopLevelBaseDomain,
17564 aFrameOnly](uint32_t aAction) {
17565 if (aAction == nsIPermissionManager::ALLOW_ACTION) {
17566 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17567 return;
17569 if (aAction == nsIPermissionManager::DENY_ACTION) {
17570 p->Reject(false, __func__);
17571 return;
17574 // We require user activation before conducting a permission request
17575 // See
17576 // https://privacycg.github.io/storage-access/#dom-document-requeststorageaccess
17577 // where we "If has transient activation is false: ..." immediately
17578 // before we "Let permissionState be the result of requesting
17579 // permission to use "storage-access"" from in parallel.
17580 if (!aHasUserInteraction && aRequireUserInteraction) {
17581 // Report an error to the console for this case
17582 nsContentUtils::ReportToConsole(
17583 nsIScriptError::errorFlag,
17584 nsLiteralCString("requestStorageAccess"), self,
17585 nsContentUtils::eDOM_PROPERTIES,
17586 "RequestStorageAccessUserGesture");
17587 p->Reject(false, __func__);
17588 return;
17591 // Create the user prompt
17592 RefPtr<StorageAccessPermissionRequest> sapr =
17593 StorageAccessPermissionRequest::Create(
17594 inner, principal, aTopLevelBaseDomain, aFrameOnly,
17595 // Allow
17596 [p] {
17597 Telemetry::AccumulateCategorical(
17598 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
17599 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
17601 // Block
17602 [p] {
17603 Telemetry::AccumulateCategorical(
17604 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
17605 p->Reject(false, __func__);
17608 using PromptResult = ContentPermissionRequestBase::PromptResult;
17609 PromptResult pr = sapr->CheckPromptPrefs();
17611 if (pr == PromptResult::Pending) {
17612 // We're about to show a prompt, record the request attempt
17613 Telemetry::AccumulateCategorical(
17614 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
17617 // Try to auto-grant the storage access so the user doesn't see the
17618 // prompt.
17619 self->AutomaticStorageAccessPermissionCanBeGranted(
17620 aHasUserInteraction)
17621 ->Then(
17622 GetCurrentSerialEventTarget(), __func__,
17623 // If the autogrant check didn't fail, call this function
17624 [p, pr, sapr,
17625 inner](const Document::
17626 AutomaticStorageAccessPermissionGrantPromise::
17627 ResolveOrRejectValue& aValue) -> void {
17628 // Make a copy because we can't modified copy-captured
17629 // lambda variables.
17630 PromptResult pr2 = pr;
17632 // If the user didn't already click "allow" and we can
17633 // autogrant, do that!
17634 bool storageAccessCanBeGrantedAutomatically =
17635 aValue.IsResolve() && aValue.ResolveValue();
17636 bool autoGrant = false;
17637 if (pr2 == PromptResult::Pending &&
17638 storageAccessCanBeGrantedAutomatically) {
17639 pr2 = PromptResult::Granted;
17640 autoGrant = true;
17642 Telemetry::AccumulateCategorical(
17643 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
17644 AllowAutomatically);
17647 // If we can complete the permission request, do so.
17648 if (pr2 != PromptResult::Pending) {
17649 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
17650 pr2 == PromptResult::Denied);
17651 if (pr2 == PromptResult::Granted) {
17652 StorageAccessAPIHelper::StorageAccessPromptChoices
17653 choice = StorageAccessAPIHelper::eAllow;
17654 if (autoGrant) {
17655 choice = StorageAccessAPIHelper::eAllowAutoGrant;
17657 if (!autoGrant) {
17658 p->Resolve(choice, __func__);
17659 } else {
17660 // We capture sapr here to prevent it from destructing
17661 // before the callbacks complete.
17662 sapr->MaybeDelayAutomaticGrants()->Then(
17663 GetCurrentSerialEventTarget(), __func__,
17664 [p, sapr, choice] {
17665 p->Resolve(choice, __func__);
17667 [p, sapr] { p->Reject(false, __func__); });
17669 return;
17671 p->Reject(false, __func__);
17672 return;
17675 // If we get here, the auto-decision failed and we need to
17676 // wait for the user prompt to complete.
17677 sapr->RequestDelayedTask(
17678 GetMainThreadSerialEventTarget(),
17679 ContentPermissionRequestBase::DelayedTaskType::Request);
17682 [p](mozilla::ipc::ResponseRejectReason aError) {
17683 p->Reject(false, __func__);
17684 return p;
17687 return p;
17691 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
17692 mozilla::ErrorResult& aRv) {
17693 nsIGlobalObject* global = GetScopeObject();
17694 if (!global) {
17695 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17696 return nullptr;
17699 RefPtr<Promise> promise = Promise::Create(global, aRv);
17700 if (aRv.Failed()) {
17701 return nullptr;
17704 if (!IsCurrentActiveDocument()) {
17705 promise->MaybeRejectWithInvalidStateError(
17706 "requestStorageAccess requires an active document");
17707 return promise.forget();
17710 // Get a pointer to the inner window- We need this for convenience sake
17711 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17712 if (!inner) {
17713 ConsumeTransientUserGestureActivation();
17714 promise->MaybeRejectWithNotAllowedError(
17715 "requestStorageAccess not allowed"_ns);
17716 return promise.forget();
17719 // Step 1: Check if the principal calling this has a permission that lets
17720 // them use cookies or forbids them from using cookies.
17721 // This is outside of the spec of the StorageAccess API, but makes the return
17722 // values to have proper semantics.
17723 Maybe<bool> resultBecauseCookiesApproved =
17724 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
17725 CookieJarSettings(), NodePrincipal());
17726 if (resultBecauseCookiesApproved.isSome()) {
17727 if (resultBecauseCookiesApproved.value()) {
17728 promise->MaybeResolveWithUndefined();
17729 return promise.forget();
17730 } else {
17731 ConsumeTransientUserGestureActivation();
17732 promise->MaybeRejectWithNotAllowedError(
17733 "requestStorageAccess not allowed"_ns);
17734 return promise.forget();
17738 // Step 2: Check if the browser settings always allow or deny cookies.
17739 // We should always return a resolved promise if the cookieBehavior is ACCEPT.
17740 // This is outside of the spec of the StorageAccess API, but makes the return
17741 // values to have proper semantics.
17742 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
17743 bool isOnThirdPartySkipList = false;
17744 if (mChannel) {
17745 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17746 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
17747 nsILoadInfo::StoragePermissionAllowListed;
17749 bool isThirdPartyTracker =
17750 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
17751 Maybe<bool> resultBecauseBrowserSettings =
17752 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17753 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList,
17754 isThirdPartyTracker);
17755 if (resultBecauseBrowserSettings.isSome()) {
17756 if (resultBecauseBrowserSettings.value()) {
17757 promise->MaybeResolveWithUndefined();
17758 return promise.forget();
17759 } else {
17760 ConsumeTransientUserGestureActivation();
17761 promise->MaybeRejectWithNotAllowedError(
17762 "requestStorageAccess not allowed"_ns);
17763 return promise.forget();
17767 // Step 3: Check if the Document calling requestStorageAccess has anything to
17768 // gain from storage access. It should be embedded, non-null, etc.
17769 Maybe<bool> resultBecauseCallContext =
17770 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
17771 true);
17772 if (resultBecauseCallContext.isSome()) {
17773 if (resultBecauseCallContext.value()) {
17774 promise->MaybeResolveWithUndefined();
17775 return promise.forget();
17776 } else {
17777 ConsumeTransientUserGestureActivation();
17778 promise->MaybeRejectWithNotAllowedError(
17779 "requestStorageAccess not allowed"_ns);
17780 return promise.forget();
17784 // Step 4: Check if we already allowed or denied storage access for this
17785 // document's storage key.
17786 Maybe<bool> resultBecausePreviousPermission =
17787 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17788 this, true);
17789 if (resultBecausePreviousPermission.isSome()) {
17790 if (resultBecausePreviousPermission.value()) {
17791 promise->MaybeResolveWithUndefined();
17792 return promise.forget();
17793 } else {
17794 ConsumeTransientUserGestureActivation();
17795 promise->MaybeRejectWithNotAllowedError(
17796 "requestStorageAccess not allowed"_ns);
17797 return promise.forget();
17801 // Get pointers to some objects that will be used in the async portion
17802 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17803 RefPtr<nsGlobalWindowOuter> outer =
17804 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17805 if (!outer) {
17806 ConsumeTransientUserGestureActivation();
17807 promise->MaybeRejectWithNotAllowedError(
17808 "requestStorageAccess not allowed"_ns);
17809 return promise.forget();
17811 RefPtr<Document> self(this);
17813 // Step 5. Start an async call to request storage access. This will either
17814 // perform an automatic decision or notify the user, then perform some follow
17815 // on work changing state to reflect the result of the API. If it resolves,
17816 // the request was granted. If it rejects it was denied.
17817 StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17818 this, inner, bc, NodePrincipal(),
17819 self->HasValidTransientUserGestureActivation(), true, true,
17820 ContentBlockingNotifier::eStorageAccessAPI, true)
17821 ->Then(
17822 GetCurrentSerialEventTarget(), __func__,
17823 [inner, promise] {
17824 inner->SaveStorageAccessPermissionGranted();
17825 promise->MaybeResolveWithUndefined();
17827 [self, promise] {
17828 self->ConsumeTransientUserGestureActivation();
17829 promise->MaybeRejectWithNotAllowedError(
17830 "requestStorageAccess not allowed"_ns);
17833 return promise.forget();
17836 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
17837 const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
17838 mozilla::ErrorResult& aRv) {
17839 nsIGlobalObject* global = GetScopeObject();
17840 if (!global) {
17841 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17842 return nullptr;
17844 RefPtr<Promise> promise = Promise::Create(global, aRv);
17845 if (aRv.Failed()) {
17846 return nullptr;
17849 // Step 0: Check that we have user activation before proceeding to prevent
17850 // rapid calls to the API to leak information.
17851 if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) {
17852 // Report an error to the console for this case
17853 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17854 nsLiteralCString("requestStorageAccess"),
17855 this, nsContentUtils::eDOM_PROPERTIES,
17856 "RequestStorageAccessUserGesture");
17857 ConsumeTransientUserGestureActivation();
17858 promise->MaybeRejectWithNotAllowedError(
17859 "requestStorageAccess not allowed"_ns);
17860 return promise.forget();
17863 // Step 1: Check if the provided URI is different-site to this Document
17864 nsCOMPtr<nsIURI> thirdPartyURI;
17865 nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
17866 if (NS_WARN_IF(NS_FAILED(rv))) {
17867 aRv.Throw(rv);
17868 return nullptr;
17870 bool isThirdPartyDocument;
17871 rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument);
17872 if (NS_WARN_IF(NS_FAILED(rv))) {
17873 aRv.Throw(rv);
17874 return nullptr;
17876 Maybe<bool> resultBecauseBrowserSettings =
17877 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17878 CookieJarSettings(), isThirdPartyDocument, false, true);
17879 if (resultBecauseBrowserSettings.isSome()) {
17880 if (resultBecauseBrowserSettings.value()) {
17881 promise->MaybeResolveWithUndefined();
17882 return promise.forget();
17884 ConsumeTransientUserGestureActivation();
17885 promise->MaybeRejectWithNotAllowedError(
17886 "requestStorageAccess not allowed"_ns);
17887 return promise.forget();
17890 // Step 2: Check that this Document is same-site to the top, and check that
17891 // we have user activation if we require it.
17892 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17893 CheckSameSiteCallingContextDecidesStorageAccessAPI(
17894 this, aRequireUserActivation);
17895 if (resultBecauseCallContext.isSome()) {
17896 if (resultBecauseCallContext.value()) {
17897 promise->MaybeResolveWithUndefined();
17898 return promise.forget();
17900 ConsumeTransientUserGestureActivation();
17901 promise->MaybeRejectWithNotAllowedError(
17902 "requestStorageAccess not allowed"_ns);
17903 return promise.forget();
17906 // Step 3: Get some useful variables that can be captured by the lambda for
17907 // the asynchronous portion
17908 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17909 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17910 if (!inner) {
17911 ConsumeTransientUserGestureActivation();
17912 promise->MaybeRejectWithNotAllowedError(
17913 "requestStorageAccess not allowed"_ns);
17914 return promise.forget();
17916 RefPtr<nsGlobalWindowOuter> outer =
17917 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17918 if (!outer) {
17919 ConsumeTransientUserGestureActivation();
17920 promise->MaybeRejectWithNotAllowedError(
17921 "requestStorageAccess not allowed"_ns);
17922 return promise.forget();
17924 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17925 thirdPartyURI, NodePrincipal()->OriginAttributesRef());
17926 if (!principal) {
17927 ConsumeTransientUserGestureActivation();
17928 promise->MaybeRejectWithNotAllowedError(
17929 "requestStorageAccess not allowed"_ns);
17930 return promise.forget();
17933 RefPtr<Document> self(this);
17934 bool hasUserActivation = HasValidTransientUserGestureActivation();
17936 // Consume user activation before entering the async part of this method.
17937 // This prevents usage of other transient activation-gated APIs.
17938 ConsumeTransientUserGestureActivation();
17940 // Step 4a: Start the async part of this function. Check the cookie
17941 // permission, but this can't be done in this process. We needs the cookie
17942 // permission of the URL as if it were embedded on this page, so we need to
17943 // make this check in the ContentParent.
17944 StorageAccessAPIHelper::
17945 AsyncCheckCookiesPermittedDecidesStorageAccessAPIOnChildProcess(
17946 GetBrowsingContext(), principal)
17947 ->Then(
17948 GetCurrentSerialEventTarget(), __func__,
17949 [inner, thirdPartyURI, bc, principal, hasUserActivation,
17950 aRequireUserActivation, self,
17951 promise](Maybe<bool> cookieResult) {
17952 // Handle the result of the cookie permission check that took
17953 // place in the ContentParent.
17954 if (cookieResult.isSome()) {
17955 if (cookieResult.value()) {
17956 return MozPromise<int, bool, true>::CreateAndResolve(
17957 true, __func__);
17959 return MozPromise<int, bool, true>::CreateAndReject(false,
17960 __func__);
17963 // Step 4b: Check for the existing storage access permission
17964 nsAutoCString type;
17965 bool ok = AntiTrackingUtils::CreateStoragePermissionKey(
17966 principal, type);
17967 if (!ok) {
17968 return MozPromise<int, bool, true>::CreateAndReject(false,
17969 __func__);
17971 if (AntiTrackingUtils::CheckStoragePermission(
17972 self->NodePrincipal(), type,
17973 nsContentUtils::IsInPrivateBrowsing(self), nullptr,
17974 0)) {
17975 return MozPromise<int, bool, true>::CreateAndResolve(
17976 true, __func__);
17979 // Step 4c: Try to request storage access, either automatically
17980 // or with a user-prompt. This is the part that is async in the
17981 // typical requestStorageAccess function.
17982 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
17983 self, inner, bc, principal, hasUserActivation,
17984 aRequireUserActivation, false,
17985 ContentBlockingNotifier::
17986 ePrivilegeStorageAccessForOriginAPI,
17987 true);
17989 // If the IPC rejects, we should reject our promise here which
17990 // will cause a rejection of the promise we already returned
17991 [promise]() {
17992 return MozPromise<int, bool, true>::CreateAndReject(false,
17993 __func__);
17995 ->Then(
17996 GetCurrentSerialEventTarget(), __func__,
17997 // If the previous handlers resolved, we should reinstate user
17998 // activation and resolve the promise we returned in Step 5.
17999 [self, inner, promise] {
18000 inner->SaveStorageAccessPermissionGranted();
18001 self->NotifyUserGestureActivation();
18002 promise->MaybeResolveWithUndefined();
18004 // If the previous handler rejected, we should reject the promise
18005 // returned by this function.
18006 [promise] {
18007 promise->MaybeRejectWithNotAllowedError(
18008 "requestStorageAccess not allowed"_ns);
18011 // Step 5: While the async stuff is happening, we should return the promise so
18012 // our caller can continue executing.
18013 return promise.forget();
18016 already_AddRefed<Promise> Document::RequestStorageAccessUnderSite(
18017 const nsAString& aSerializedSite, ErrorResult& aRv) {
18018 nsIGlobalObject* global = GetScopeObject();
18019 if (!global) {
18020 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
18021 return nullptr;
18023 RefPtr<Promise> promise = Promise::Create(global, aRv);
18024 if (aRv.Failed()) {
18025 return nullptr;
18028 // Check that we have user activation before proceeding to prevent
18029 // rapid calls to the API to leak information.
18030 if (!ConsumeTransientUserGestureActivation()) {
18031 // Report an error to the console for this case
18032 nsContentUtils::ReportToConsole(
18033 nsIScriptError::errorFlag, "requestStorageAccess"_ns, this,
18034 nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture");
18035 promise->MaybeRejectWithUndefined();
18036 return promise.forget();
18039 // Check if the provided URI is different-site to this Document
18040 nsCOMPtr<nsIURI> siteURI;
18041 nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite);
18042 if (NS_WARN_IF(NS_FAILED(rv))) {
18043 promise->MaybeRejectWithUndefined();
18044 return promise.forget();
18046 bool isCrossSiteArgument;
18047 rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument);
18048 if (NS_WARN_IF(NS_FAILED(rv))) {
18049 aRv.Throw(rv);
18050 return nullptr;
18052 if (!isCrossSiteArgument) {
18053 promise->MaybeRejectWithUndefined();
18054 return promise.forget();
18057 // Check if this party has broad cookie permissions.
18058 Maybe<bool> resultBecauseCookiesApproved =
18059 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
18060 CookieJarSettings(), NodePrincipal());
18061 if (resultBecauseCookiesApproved.isSome()) {
18062 if (resultBecauseCookiesApproved.value()) {
18063 promise->MaybeResolveWithUndefined();
18064 return promise.forget();
18066 promise->MaybeRejectWithUndefined();
18067 return promise.forget();
18070 // Check if browser settings preclude this document getting storage
18071 // access under the provided site
18072 Maybe<bool> resultBecauseBrowserSettings =
18073 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
18074 CookieJarSettings(), true, false, true);
18075 if (resultBecauseBrowserSettings.isSome()) {
18076 if (resultBecauseBrowserSettings.value()) {
18077 promise->MaybeResolveWithUndefined();
18078 return promise.forget();
18080 promise->MaybeRejectWithUndefined();
18081 return promise.forget();
18084 // Check that this Document is same-site to the top
18085 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
18086 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
18087 if (resultBecauseCallContext.isSome()) {
18088 if (resultBecauseCallContext.value()) {
18089 promise->MaybeResolveWithUndefined();
18090 return promise.forget();
18092 promise->MaybeRejectWithUndefined();
18093 return promise.forget();
18096 nsCOMPtr<nsIPrincipal> principal(NodePrincipal());
18098 // Test if the permission this is requesting is already set
18099 nsCOMPtr<nsIPrincipal> argumentPrincipal =
18100 BasePrincipal::CreateContentPrincipal(
18101 siteURI, NodePrincipal()->OriginAttributesRef());
18102 if (!argumentPrincipal) {
18103 ConsumeTransientUserGestureActivation();
18104 promise->MaybeRejectWithUndefined();
18105 return promise.forget();
18107 nsCString originNoSuffix;
18108 rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix);
18109 if (NS_WARN_IF(NS_FAILED(rv))) {
18110 promise->MaybeRejectWithUndefined();
18111 return promise.forget();
18114 ContentChild* cc = ContentChild::GetSingleton();
18115 MOZ_ASSERT(cc);
18116 RefPtr<Document> self(this);
18117 cc->SendTestStorageAccessPermission(argumentPrincipal, originNoSuffix)
18118 ->Then(
18119 GetCurrentSerialEventTarget(), __func__,
18120 [promise, siteURI,
18121 self](const ContentChild::TestStorageAccessPermissionPromise::
18122 ResolveValueType& aResult) {
18123 if (aResult) {
18124 return StorageAccessAPIHelper::
18125 StorageAccessPermissionGrantPromise::CreateAndResolve(
18126 StorageAccessAPIHelper::eAllow, __func__);
18128 // Get a grant for the storage access permission that will be set
18129 // when this is completed in the embedding context
18130 nsCString serializedSite;
18131 RefPtr<nsEffectiveTLDService> etld =
18132 nsEffectiveTLDService::GetInstance();
18133 if (!etld) {
18134 return StorageAccessAPIHelper::
18135 StorageAccessPermissionGrantPromise::CreateAndReject(
18136 false, __func__);
18138 nsresult rv = etld->GetSite(siteURI, serializedSite);
18139 if (NS_FAILED(rv)) {
18140 return StorageAccessAPIHelper::
18141 StorageAccessPermissionGrantPromise::CreateAndReject(
18142 false, __func__);
18144 return self->CreatePermissionGrantPromise(
18145 self->GetInnerWindow(), self->NodePrincipal(), true, true,
18146 Some(serializedSite), false)();
18148 [](const ContentChild::TestStorageAccessPermissionPromise::
18149 RejectValueType& aResult) {
18150 return StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
18151 CreateAndReject(false, __func__);
18153 ->Then(
18154 GetCurrentSerialEventTarget(), __func__,
18155 [promise, principal, siteURI](int result) {
18156 ContentChild* cc = ContentChild::GetSingleton();
18157 if (!cc) {
18158 // TODO(bug 1778561): Make this work in non-content processes.
18159 promise->MaybeRejectWithUndefined();
18160 return;
18162 // Set a permission in the parent process that this document wants
18163 // storage access under the argument's site, resolving our returned
18164 // promise on success
18165 cc->SendSetAllowStorageAccessRequestFlag(principal, siteURI)
18166 ->Then(
18167 GetCurrentSerialEventTarget(), __func__,
18168 [promise](bool success) {
18169 if (success) {
18170 promise->MaybeResolveWithUndefined();
18171 } else {
18172 promise->MaybeRejectWithUndefined();
18175 [promise](mozilla::ipc::ResponseRejectReason reason) {
18176 promise->MaybeRejectWithUndefined();
18179 [promise](bool result) { promise->MaybeRejectWithUndefined(); });
18181 // Return the promise that is resolved in the async handler above
18182 return promise.forget();
18185 already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite(
18186 const nsAString& aSerializedOrigin, ErrorResult& aRv) {
18187 nsIGlobalObject* global = GetScopeObject();
18188 if (!global) {
18189 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
18190 return nullptr;
18192 RefPtr<Promise> promise = Promise::Create(global, aRv);
18193 if (aRv.Failed()) {
18194 return nullptr;
18197 // Check that the provided URI is different-site to this Document
18198 nsCOMPtr<nsIURI> argumentURI;
18199 nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin);
18200 if (NS_WARN_IF(NS_FAILED(rv))) {
18201 promise->MaybeRejectWithUndefined();
18202 return promise.forget();
18204 bool isCrossSiteArgument;
18205 rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument);
18206 if (NS_WARN_IF(NS_FAILED(rv))) {
18207 aRv.Throw(rv);
18208 return nullptr;
18210 if (!isCrossSiteArgument) {
18211 promise->MaybeRejectWithUndefined();
18212 return promise.forget();
18215 // Check if browser settings preclude this document getting storage
18216 // access under the provided site
18217 Maybe<bool> resultBecauseBrowserSettings =
18218 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
18219 CookieJarSettings(), true, false, true);
18220 if (resultBecauseBrowserSettings.isSome()) {
18221 if (resultBecauseBrowserSettings.value()) {
18222 promise->MaybeResolveWithUndefined();
18223 return promise.forget();
18225 promise->MaybeRejectWithUndefined();
18226 return promise.forget();
18229 // Check that this Document is same-site to the top
18230 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
18231 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
18232 if (resultBecauseCallContext.isSome()) {
18233 if (resultBecauseCallContext.value()) {
18234 promise->MaybeResolveWithUndefined();
18235 return promise.forget();
18237 promise->MaybeRejectWithUndefined();
18238 return promise.forget();
18241 // Create principal of the embedded site requesting storage access
18242 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
18243 argumentURI, NodePrincipal()->OriginAttributesRef());
18244 if (!principal) {
18245 promise->MaybeRejectWithUndefined();
18246 return promise.forget();
18249 // Get versions of these objects that we can use in lambdas for callbacks
18250 RefPtr<Document> self(this);
18251 RefPtr<BrowsingContext> bc = GetBrowsingContext();
18252 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
18254 // Test that the permission was set by a call to RequestStorageAccessUnderSite
18255 // from a top level document that is same-site with the argument
18256 ContentChild* cc = ContentChild::GetSingleton();
18257 if (!cc) {
18258 // TODO(bug 1778561): Make this work in non-content processes.
18259 promise->MaybeRejectWithUndefined();
18260 return promise.forget();
18262 cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI)
18263 ->Then(
18264 GetCurrentSerialEventTarget(), __func__,
18265 [inner, bc, self, principal](bool success) {
18266 if (success) {
18267 // If that resolved with true, check that we don't already have a
18268 // permission that gives cookie access.
18269 return StorageAccessAPIHelper::
18270 AsyncCheckCookiesPermittedDecidesStorageAccessAPIOnChildProcess(
18271 bc, principal);
18273 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18274 NS_ERROR_FAILURE, __func__);
18276 [](mozilla::ipc::ResponseRejectReason reason) {
18277 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
18278 NS_ERROR_FAILURE, __func__);
18280 ->Then(
18281 GetCurrentSerialEventTarget(), __func__,
18282 [inner, bc, principal, self, promise](Maybe<bool> cookieResult) {
18283 // Handle the result of the cookie permission check that took place
18284 // in the ContentParent.
18285 if (cookieResult.isSome()) {
18286 if (cookieResult.value()) {
18287 return StorageAccessAPIHelper::
18288 StorageAccessPermissionGrantPromise::CreateAndResolve(
18289 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18291 return StorageAccessAPIHelper::
18292 StorageAccessPermissionGrantPromise::CreateAndReject(
18293 false, __func__);
18296 // Check for the existing storage access permission
18297 nsAutoCString type;
18298 bool ok =
18299 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
18300 if (!ok) {
18301 return StorageAccessAPIHelper::
18302 StorageAccessPermissionGrantPromise::CreateAndReject(
18303 false, __func__);
18305 if (AntiTrackingUtils::CheckStoragePermission(
18306 self->NodePrincipal(), type,
18307 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
18308 return StorageAccessAPIHelper::
18309 StorageAccessPermissionGrantPromise::CreateAndResolve(
18310 StorageAccessAPIHelper::eAllowAutoGrant, __func__);
18313 // Try to request storage access, ignoring the final checks.
18314 // We ignore the final checks because this is where the "grant"
18315 // either by prompt doorhanger or autogrant takes place. We already
18316 // gathered an equivalent grant in requestStorageAccessUnderSite.
18317 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper(
18318 self, inner, bc, principal, true, true, false,
18319 ContentBlockingNotifier::eStorageAccessAPI, false);
18321 // If the IPC rejects, we should reject our promise here which will
18322 // cause a rejection of the promise we already returned
18323 [promise]() {
18324 return MozPromise<int, bool, true>::CreateAndReject(false,
18325 __func__);
18327 ->Then(
18328 GetCurrentSerialEventTarget(), __func__,
18329 // If the previous handlers resolved, we should reinstate user
18330 // activation and resolve the promise we returned in Step 5.
18331 [self, inner, promise] {
18332 inner->SaveStorageAccessPermissionGranted();
18333 promise->MaybeResolveWithUndefined();
18335 // If the previous handler rejected, we should reject the promise
18336 // returned by this function.
18337 [promise] { promise->MaybeRejectWithUndefined(); });
18339 return promise.forget();
18342 nsTHashSet<RefPtr<WakeLockSentinel>>& Document::ActiveWakeLocks(
18343 WakeLockType aType) {
18344 return mActiveLocks.LookupOrInsert(aType);
18347 class UnlockAllWakeLockRunnable final : public Runnable {
18348 public:
18349 UnlockAllWakeLockRunnable(WakeLockType aType, Document* aDoc)
18350 : Runnable("UnlockAllWakeLocks"), mType(aType), mDoc(aDoc) {}
18352 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
18353 // bug 1535398.
18354 MOZ_CAN_RUN_SCRIPT_BOUNDARY
18355 NS_IMETHOD Run() override {
18356 // Move, as ReleaseWakeLock will try to remove from and possibly allow
18357 // scripts via onrelease to add to document.[[ActiveLocks]]["screen"]
18358 nsCOMPtr<Document> doc = mDoc;
18359 nsTHashSet<RefPtr<WakeLockSentinel>> locks =
18360 std::move(doc->ActiveWakeLocks(mType));
18361 for (const auto& lock : locks) {
18362 // ReleaseWakeLock runs script, which could release other locks
18363 if (!lock->Released()) {
18364 ReleaseWakeLock(doc, MOZ_KnownLive(lock), mType);
18367 return NS_OK;
18370 protected:
18371 ~UnlockAllWakeLockRunnable() = default;
18373 private:
18374 WakeLockType mType;
18375 nsCOMPtr<Document> mDoc;
18378 void Document::UnlockAllWakeLocks(WakeLockType aType) {
18379 // Perform unlock in a runnable to prevent UnlockAll being MOZ_CAN_RUN_SCRIPT
18380 RefPtr<UnlockAllWakeLockRunnable> runnable =
18381 MakeRefPtr<UnlockAllWakeLockRunnable>(aType, this);
18382 NS_DispatchToMainThread(runnable);
18385 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
18386 Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
18387 // requestStorageAccessForOrigin may not require user activation. If we don't
18388 // have user activation at this point we should always show the prompt.
18389 if (!hasUserActivation ||
18390 !StaticPrefs::privacy_antitracking_enableWebcompat()) {
18391 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18392 false, __func__);
18394 if (XRE_IsContentProcess()) {
18395 // In the content process, we need to ask the parent process to compute
18396 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
18397 // isn't accessible in the content process.
18398 ContentChild* cc = ContentChild::GetSingleton();
18399 MOZ_ASSERT(cc);
18401 return cc->SendAutomaticStorageAccessPermissionCanBeGranted(NodePrincipal())
18402 ->Then(GetCurrentSerialEventTarget(), __func__,
18403 [](const ContentChild::
18404 AutomaticStorageAccessPermissionCanBeGrantedPromise::
18405 ResolveOrRejectValue& aValue) {
18406 if (aValue.IsResolve()) {
18407 return AutomaticStorageAccessPermissionGrantPromise::
18408 CreateAndResolve(aValue.ResolveValue(), __func__);
18411 return AutomaticStorageAccessPermissionGrantPromise::
18412 CreateAndReject(false, __func__);
18416 if (XRE_IsParentProcess()) {
18417 // In the parent process, we can directly compute this.
18418 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
18419 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
18420 __func__);
18423 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
18424 false, __func__);
18427 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
18428 nsIPrincipal* aPrincipal) {
18429 if (!StaticPrefs::dom_storage_access_auto_grants()) {
18430 return false;
18433 if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
18434 return false;
18437 nsCOMPtr<nsIBrowserUsage> bu = do_ImportESModule(
18438 "resource:///modules/BrowserUsageTelemetry.sys.mjs", fallible);
18439 if (NS_WARN_IF(!bu)) {
18440 return false;
18443 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
18444 nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours(
18445 &uniqueDomainsVisitedInPast24Hours);
18446 if (NS_WARN_IF(NS_FAILED(rv))) {
18447 return false;
18450 Maybe<size_t> maybeOriginsThirdPartyHasAccessTo =
18451 AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal);
18452 if (maybeOriginsThirdPartyHasAccessTo.isNothing()) {
18453 return false;
18455 size_t originsThirdPartyHasAccessTo =
18456 maybeOriginsThirdPartyHasAccessTo.value();
18458 // one percent of the number of top-levels origins visited in the current
18459 // session (but not to exceed 24 hours), or the value of the
18460 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
18461 // higher.
18462 size_t maxConcurrentAutomaticGrants = std::max(
18463 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
18464 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
18467 return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
18470 void Document::RecordNavigationTiming(ReadyState aReadyState) {
18471 if (!XRE_IsContentProcess()) {
18472 return;
18474 if (!IsTopLevelContentDocument()) {
18475 return;
18477 // If we dont have the timing yet (mostly because the doc is still loading),
18478 // get it from docshell.
18479 RefPtr<nsDOMNavigationTiming> timing = mTiming;
18480 if (!timing) {
18481 if (!mDocumentContainer) {
18482 return;
18484 timing = mDocumentContainer->GetNavigationTiming();
18485 if (!timing) {
18486 return;
18489 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
18490 switch (aReadyState) {
18491 case READYSTATE_LOADING:
18492 if (!mDOMLoadingSet) {
18493 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
18494 startTime);
18495 mDOMLoadingSet = true;
18497 break;
18498 case READYSTATE_INTERACTIVE:
18499 if (!mDOMInteractiveSet) {
18500 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
18501 startTime);
18502 mDOMInteractiveSet = true;
18504 break;
18505 case READYSTATE_COMPLETE:
18506 if (!mDOMCompleteSet) {
18507 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
18508 startTime);
18509 mDOMCompleteSet = true;
18511 break;
18512 default:
18513 NS_WARNING("Unexpected ReadyState value");
18514 break;
18518 void Document::ReportShadowDOMUsage() {
18519 nsPIDOMWindowInner* inner = GetInnerWindow();
18520 if (NS_WARN_IF(!inner)) {
18521 return;
18524 WindowContext* wc = inner->GetWindowContext();
18525 if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
18526 return;
18529 WindowContext* topWc = wc->TopWindowContext();
18530 if (topWc->GetHasReportedShadowDOMUsage()) {
18531 return;
18534 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
18537 // static
18538 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
18539 return StaticPrefs::dom_storage_access_enabled() &&
18540 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
18543 bool Document::StorageAccessSandboxed() const {
18544 return Document::StorageAccessSandboxed(GetSandboxFlags());
18547 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
18548 if (mCachedTabSizeGeneration == 0 ||
18549 GetGeneration() != mCachedTabSizeGeneration) {
18550 return false;
18552 aSizes->mDom += mCachedTabSizes.mDom;
18553 aSizes->mStyle += mCachedTabSizes.mStyle;
18554 aSizes->mOther += mCachedTabSizes.mOther;
18555 return true;
18558 void Document::SetCachedSizes(nsTabSizes* aSizes) {
18559 mCachedTabSizes.mDom = aSizes->mDom;
18560 mCachedTabSizes.mStyle = aSizes->mStyle;
18561 mCachedTabSizes.mOther = aSizes->mOther;
18562 mCachedTabSizeGeneration = GetGeneration();
18565 nsAtom* Document::GetContentLanguageAsAtomForStyle() const {
18566 // Content-Language may be a comma-separated list of language codes,
18567 // in which case the HTML5 spec says to treat it as unknown
18568 if (mContentLanguage &&
18569 !nsDependentAtomString(mContentLanguage).Contains(char16_t(','))) {
18570 return GetContentLanguage();
18573 return nullptr;
18576 nsAtom* Document::GetLanguageForStyle() const {
18577 if (nsAtom* lang = GetContentLanguageAsAtomForStyle()) {
18578 return lang;
18580 return mLanguageFromCharset.get();
18583 void Document::GetContentLanguageForBindings(DOMString& aString) const {
18584 aString.SetKnownLiveAtom(mContentLanguage, DOMString::eTreatNullAsEmpty);
18587 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
18588 nsAtom* aLanguage, bool* aNeedsToCache) const {
18589 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
18590 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
18593 void Document::DoCacheAllKnownLangPrefs() {
18594 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
18595 RefPtr<nsAtom> lang = GetLanguageForStyle();
18596 StaticPresData* data = StaticPresData::Get();
18597 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
18598 data->GetFontPrefsForLang(nsGkAtoms::x_math);
18599 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
18600 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
18601 for (const auto& key : mLanguagesUsed) {
18602 data->GetFontPrefsForLang(key);
18604 mMayNeedFontPrefsUpdate = false;
18607 void Document::RecomputeLanguageFromCharset() {
18608 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
18609 RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
18610 if (language == nsGkAtoms::Unicode) {
18611 language = service->GetLocaleLanguage();
18614 if (language == mLanguageFromCharset) {
18615 return;
18618 mMayNeedFontPrefsUpdate = true;
18619 mLanguageFromCharset = std::move(language);
18622 nsICookieJarSettings* Document::CookieJarSettings() {
18623 // If we are here, this is probably a javascript: URL document. In any case,
18624 // we must have a nsCookieJarSettings. Let's create it.
18625 if (!mCookieJarSettings) {
18626 Document* inProcessParent = GetInProcessParentDocument();
18628 if (inProcessParent) {
18629 mCookieJarSettings = net::CookieJarSettings::Create(
18630 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
18631 mozilla::net::CookieJarSettings::Cast(
18632 inProcessParent->CookieJarSettings())
18633 ->GetPartitionKey(),
18634 inProcessParent->CookieJarSettings()->GetIsFirstPartyIsolated(),
18635 inProcessParent->CookieJarSettings()
18636 ->GetIsOnContentBlockingAllowList(),
18637 inProcessParent->CookieJarSettings()
18638 ->GetShouldResistFingerprinting());
18640 // Inherit the fingerprinting random key from the parent.
18641 nsTArray<uint8_t> randomKey;
18642 nsresult rv = inProcessParent->CookieJarSettings()
18643 ->GetFingerprintingRandomizationKey(randomKey);
18645 if (NS_SUCCEEDED(rv)) {
18646 net::CookieJarSettings::Cast(mCookieJarSettings)
18647 ->SetFingerprintingRandomizationKey(randomKey);
18649 } else {
18650 mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal());
18653 if (auto* wgc = GetWindowGlobalChild()) {
18654 net::CookieJarSettingsArgs csArgs;
18655 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
18656 // Update cookie settings in the parent process
18657 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
18658 NS_WARNING(
18659 "Failed to update document's cookie jar settings on the "
18660 "WindowGlobalParent");
18665 return mCookieJarSettings;
18668 bool Document::UsingStorageAccess() {
18669 if (WindowContext* wc = GetWindowContext()) {
18670 return wc->GetUsingStorageAccess();
18673 // If we don't yet have a window context, we have to use the decision
18674 // from the Document's Channel's LoadInfo directly.
18675 if (!mChannel) {
18676 return false;
18679 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18680 return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
18683 bool Document::HasStorageAccessPermissionGrantedByAllowList() {
18684 // We only care about if the document gets the storage permission via the
18685 // allow list here. So we don't check the storage access cache in the inner
18686 // window.
18688 if (!mChannel) {
18689 return false;
18692 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
18693 return loadInfo->GetStoragePermission() ==
18694 nsILoadInfo::StoragePermissionAllowListed;
18697 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
18698 if (!StaticPrefs::
18699 privacy_partition_always_partition_third_party_non_cookie_storage()) {
18700 return EffectiveCookiePrincipal();
18703 nsPIDOMWindowInner* inner = GetInnerWindow();
18704 if (!inner) {
18705 return NodePrincipal();
18708 // Return our cached storage principal if one exists.
18709 if (mActiveStoragePrincipal) {
18710 return mActiveStoragePrincipal;
18713 // Calling StorageAllowedForDocument will notify the ContentBlockLog. This
18714 // loads TrackingDBService.jsm, which in turn pulls in osfile.jsm, making us
18715 // fail // browser/base/content/test/performance/browser_startup.js. To avoid
18716 // that, we short-circuit the check here by allowing storage access to system
18717 // and addon principles, avoiding the test-failure.
18718 nsIPrincipal* principal = NodePrincipal();
18719 if (principal && (principal->IsSystemPrincipal() ||
18720 principal->GetIsAddonOrExpandedAddonPrincipal())) {
18721 return mActiveStoragePrincipal = NodePrincipal();
18724 auto cookieJarSettings = const_cast<Document*>(this)->CookieJarSettings();
18725 if (cookieJarSettings->GetIsOnContentBlockingAllowList()) {
18726 return mActiveStoragePrincipal = NodePrincipal();
18729 StorageAccess storageAccess = StorageAllowedForDocument(this);
18730 if (!ShouldPartitionStorage(storageAccess) ||
18731 !StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
18732 return mActiveStoragePrincipal = NodePrincipal();
18735 Unused << NS_WARN_IF(NS_FAILED(StoragePrincipalHelper::GetPrincipal(
18736 nsGlobalWindowInner::Cast(inner),
18737 StoragePrincipalHelper::eForeignPartitionedPrincipal,
18738 getter_AddRefs(mActiveStoragePrincipal))));
18739 return mActiveStoragePrincipal;
18742 nsIPrincipal* Document::EffectiveCookiePrincipal() const {
18743 nsPIDOMWindowInner* inner = GetInnerWindow();
18744 if (!inner) {
18745 return NodePrincipal();
18748 // Return our cached storage principal if one exists.
18749 if (mActiveCookiePrincipal) {
18750 return mActiveCookiePrincipal;
18753 // We use the lower-level ContentBlocking API here to ensure this
18754 // check doesn't send notifications.
18755 uint32_t rejectedReason = 0;
18756 if (ShouldAllowAccessFor(inner, GetDocumentURI(), &rejectedReason)) {
18757 return mActiveCookiePrincipal = NodePrincipal();
18760 // Let's use the storage principal only if we need to partition the cookie
18761 // jar. When the permission is granted, access will be different and the
18762 // normal principal will be used.
18763 if (ShouldPartitionStorage(rejectedReason) &&
18764 !StoragePartitioningEnabled(
18765 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
18766 return mActiveCookiePrincipal = NodePrincipal();
18769 return mActiveCookiePrincipal = mPartitionedPrincipal;
18772 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
18773 // If the document is sandboxed document or data: document, we should
18774 // get URI of the parent document.
18775 for (const Document* document = this;
18776 document && document->IsContentDocument();
18777 document = document->GetInProcessParentDocument()) {
18778 // The document URI may be about:blank even if it comes from actual web
18779 // site. Therefore, we need to check the URI of its principal.
18780 nsIPrincipal* principal = document->NodePrincipal();
18781 if (principal->GetIsNullPrincipal()) {
18782 continue;
18784 return principal;
18786 return nullptr;
18789 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
18790 mIsInitialDocumentInWindow = aIsInitialDocument;
18792 if (aIsInitialDocument && !mIsEverInitialDocumentInWindow) {
18793 mIsEverInitialDocumentInWindow = aIsInitialDocument;
18796 // Asynchronously tell the parent process that we are, or are no longer, the
18797 // initial document. This happens async.
18798 if (auto* wgc = GetWindowGlobalChild()) {
18799 wgc->SendSetIsInitialDocument(aIsInitialDocument);
18803 // static
18804 void Document::AddToplevelLoadingDocument(Document* aDoc) {
18805 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18806 // Currently we're interested in foreground documents only, so bail out early.
18807 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
18808 return;
18811 if (!sLoadingForegroundTopLevelContentDocument) {
18812 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
18813 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18814 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18815 if (idleScheduler) {
18816 idleScheduler->SendRunningPrioritizedOperation();
18819 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
18820 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
18824 // static
18825 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
18826 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
18827 if (sLoadingForegroundTopLevelContentDocument) {
18828 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
18829 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
18830 delete sLoadingForegroundTopLevelContentDocument;
18831 sLoadingForegroundTopLevelContentDocument = nullptr;
18833 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18834 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18835 if (idleScheduler) {
18836 idleScheduler->SendPrioritizedOperationDone();
18842 ColorScheme Document::DefaultColorScheme() const {
18843 return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
18846 ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
18847 if (ShouldResistFingerprinting(RFPTarget::CSSPrefersColorScheme) &&
18848 aIgnoreRFP == IgnoreRFP::No) {
18849 return ColorScheme::Light;
18852 if (nsPresContext* pc = GetPresContext()) {
18853 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
18854 return *scheme;
18858 return PreferenceSheet::PrefsFor(*this).mColorScheme;
18861 bool Document::HasRecentlyStartedForegroundLoads() {
18862 if (!sLoadingForegroundTopLevelContentDocument) {
18863 return false;
18866 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
18867 ++i) {
18868 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
18869 // A page loaded in foreground could be in background now.
18870 if (!doc->IsInBackgroundWindow()) {
18871 nsPIDOMWindowInner* win = doc->GetInnerWindow();
18872 if (win) {
18873 Performance* perf = win->GetPerformance();
18874 if (perf &&
18875 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
18876 return true;
18882 // Didn't find any loading foreground documents, just clear the array.
18883 delete sLoadingForegroundTopLevelContentDocument;
18884 sLoadingForegroundTopLevelContentDocument = nullptr;
18886 mozilla::ipc::IdleSchedulerChild* idleScheduler =
18887 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
18888 if (idleScheduler) {
18889 idleScheduler->SendPrioritizedOperationDone();
18891 return false;
18894 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
18895 nsFrameLoader* aStaticCloneOf) {
18896 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
18897 clone->mElement = aElement;
18898 clone->mStaticCloneOf = aStaticCloneOf;
18901 bool Document::ShouldAvoidNativeTheme() const {
18902 return StaticPrefs::widget_non_native_theme_enabled() &&
18903 (!IsInChromeDocShell() || XRE_IsContentProcess());
18906 bool Document::UseRegularPrincipal() const {
18907 return EffectiveStoragePrincipal() == NodePrincipal();
18910 bool Document::HasThirdPartyChannel() {
18911 nsCOMPtr<nsIChannel> channel = GetChannel();
18912 if (channel) {
18913 // We assume that the channel is a third-party by default.
18914 bool thirdParty = true;
18916 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
18917 components::ThirdPartyUtil::Service();
18918 if (!thirdPartyUtil) {
18919 return thirdParty;
18922 // Check that if the channel is a third-party to its parent.
18923 nsresult rv =
18924 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
18925 if (NS_FAILED(rv)) {
18926 // Assume third-party in case of failure
18927 thirdParty = true;
18930 return thirdParty;
18933 if (mParentDocument) {
18934 return mParentDocument->HasThirdPartyChannel();
18937 return false;
18940 bool Document::IsLikelyContentInaccessibleTopLevelAboutBlank() const {
18941 if (!mDocumentURI || !NS_IsAboutBlank(mDocumentURI)) {
18942 return false;
18944 // FIXME(emilio): This is not quite edge-case free. See bug 1860098.
18946 // For stuff in frames, that makes our per-document telemetry probes not
18947 // really reliable but doesn't affect the correctness of our page probes, so
18948 // it's not too terrible.
18949 BrowsingContext* bc = GetBrowsingContext();
18950 return bc && bc->IsTop() && !bc->HadOriginalOpener();
18953 bool Document::ShouldIncludeInTelemetry() const {
18954 if (!IsContentDocument() && !IsResourceDoc()) {
18955 return false;
18958 if (IsLikelyContentInaccessibleTopLevelAboutBlank()) {
18959 return false;
18962 nsIPrincipal* prin = NodePrincipal();
18963 // TODO(emilio): Should this use GetIsContentPrincipal() +
18964 // GetPrecursorPrincipal() instead (accounting for add-ons separately)?
18965 return !(prin->GetIsAddonOrExpandedAddonPrincipal() ||
18966 prin->IsSystemPrincipal() || prin->SchemeIs("about") ||
18967 prin->SchemeIs("chrome") || prin->SchemeIs("resource"));
18970 void Document::GetConnectedShadowRoots(
18971 nsTArray<RefPtr<ShadowRoot>>& aOut) const {
18972 AppendToArray(aOut, mComposedShadowRoots);
18975 void Document::AddMediaElementWithMSE() {
18976 if (mMediaElementWithMSECount++ == 0) {
18977 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18978 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18983 void Document::RemoveMediaElementWithMSE() {
18984 MOZ_ASSERT(mMediaElementWithMSECount > 0);
18985 if (--mMediaElementWithMSECount == 0) {
18986 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
18987 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
18992 void Document::UnregisterFromMemoryReportingForDataDocument() {
18993 if (!mAddedToMemoryReportingAsDataDocument) {
18994 return;
18996 mAddedToMemoryReportingAsDataDocument = false;
18997 nsIGlobalObject* global = GetScopeObject();
18998 if (global) {
18999 if (nsPIDOMWindowInner* win = global->GetAsInnerWindow()) {
19000 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
19001 this);
19005 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
19006 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
19007 mOOPChildrenLoading.AppendElement(aChild);
19008 if (mOOPChildrenLoading.Length() == 1) {
19009 // Let's block unload so that we're blocked from going into the BFCache
19010 // until the child has actually notified us that it has done loading.
19011 BlockOnload();
19015 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
19016 // aChild will not be in the list if nsDocLoader::Stop() was called, since
19017 // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
19018 // so we don't need to call DocLoaderIsEmpty in that case.
19019 if (mOOPChildrenLoading.RemoveElement(aChild)) {
19020 if (mOOPChildrenLoading.IsEmpty()) {
19021 UnblockOnload(false);
19023 RefPtr<nsDocLoader> docLoader(mDocumentContainer);
19024 if (docLoader) {
19025 docLoader->OOPChildrenLoadingIsEmpty();
19030 void Document::ClearOOPChildrenLoading() {
19031 nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
19032 mOOPChildrenLoading.SwapElements(oopChildrenLoading);
19033 if (!oopChildrenLoading.IsEmpty()) {
19034 UnblockOnload(false);
19038 bool Document::MayHaveDOMActivateListeners() const {
19039 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
19040 return inner->HasDOMActivateEventListeners();
19043 // If we can't get information from the window object, default to true.
19044 return true;
19047 HighlightRegistry& Document::HighlightRegistry() {
19048 if (!mHighlightRegistry) {
19049 mHighlightRegistry = MakeRefPtr<class HighlightRegistry>(this);
19051 return *mHighlightRegistry;
19054 RadioGroupContainer& Document::OwnedRadioGroupContainer() {
19055 if (!mRadioGroupContainer) {
19056 mRadioGroupContainer = MakeUnique<RadioGroupContainer>();
19058 return *mRadioGroupContainer;
19061 void Document::UpdateHiddenByContentVisibilityForAnimations() {
19062 for (AnimationTimeline* timeline : Timelines()) {
19063 timeline->UpdateHiddenByContentVisibility();
19067 void Document::SetAllowDeclarativeShadowRoots(
19068 bool aAllowDeclarativeShadowRoots) {
19069 mAllowDeclarativeShadowRoots = aAllowDeclarativeShadowRoots;
19072 bool Document::AllowsDeclarativeShadowRoots() const {
19073 return mAllowDeclarativeShadowRoots;
19076 /* static */
19077 already_AddRefed<Document> Document::ParseHTMLUnsafe(GlobalObject& aGlobal,
19078 const nsAString& aHTML) {
19079 nsCOMPtr<nsIURI> uri;
19080 NS_NewURI(getter_AddRefs(uri), "about:blank");
19081 if (!uri) {
19082 return nullptr;
19085 nsCOMPtr<Document> doc;
19086 nsresult rv =
19087 NS_NewHTMLDocument(getter_AddRefs(doc), aGlobal.GetSubjectPrincipal(),
19088 aGlobal.GetSubjectPrincipal());
19089 if (NS_WARN_IF(NS_FAILED(rv))) {
19090 return nullptr;
19093 doc->SetAllowDeclarativeShadowRoots(true);
19094 doc->SetDocumentURI(uri);
19095 rv = nsContentUtils::ParseDocumentHTML(aHTML, doc, false);
19096 if (NS_WARN_IF(NS_FAILED(rv))) {
19097 return nullptr;
19100 return doc.forget();
19103 } // namespace mozilla::dom